View Javadoc
1   /**
2    * Logback: the reliable, generic, fast and flexible logging framework.
3    * Copyright (C) 1999-2015, QOS.ch. All rights reserved.
4    *
5    * This program and the accompanying materials are dual-licensed under
6    * either the terms of the Eclipse Public License v1.0 as published by
7    * the Eclipse Foundation
8    *
9    *   or (per the licensee's choosing)
10   *
11   * under the terms of the GNU Lesser General Public License version 2.1
12   * as published by the Free Software Foundation.
13   */
14  package ch.qos.logback.classic.db;
15  
16  import static ch.qos.logback.core.db.DBHelper.closeStatement;
17  
18  import java.lang.reflect.Method;
19  import java.sql.Connection;
20  import java.sql.PreparedStatement;
21  import java.sql.SQLException;
22  import java.util.HashMap;
23  import java.util.Map;
24  import java.util.Set;
25  
26  import ch.qos.logback.classic.db.names.DBNameResolver;
27  import ch.qos.logback.classic.db.names.DefaultDBNameResolver;
28  import ch.qos.logback.classic.spi.*;
29  import ch.qos.logback.core.CoreConstants;
30  import ch.qos.logback.core.db.DBAppenderBase;
31  
32  /**
33   * The DBAppender inserts logging events into three database tables in a format
34   * independent of the Java programming language.
35   *
36   * For more information about this appender, please refer to the online manual
37   * at http://logback.qos.ch/manual/appenders.html#DBAppender
38   *
39   * @author Ceki Gülcü
40   * @author Ray DeCampo
41   * @author Sébastien Pennec
42   */
43  public class DBAppender extends DBAppenderBase<ILoggingEvent> {
44      protected String insertPropertiesSQL;
45      protected String insertExceptionSQL;
46      protected String insertSQL;
47      protected static final Method GET_GENERATED_KEYS_METHOD;
48  
49      private DBNameResolver dbNameResolver;
50  
51      static final int TIMESTMP_INDEX = 1;
52      static final int FORMATTED_MESSAGE_INDEX = 2;
53      static final int LOGGER_NAME_INDEX = 3;
54      static final int LEVEL_STRING_INDEX = 4;
55      static final int THREAD_NAME_INDEX = 5;
56      static final int REFERENCE_FLAG_INDEX = 6;
57      static final int ARG0_INDEX = 7;
58      static final int ARG1_INDEX = 8;
59      static final int ARG2_INDEX = 9;
60      static final int ARG3_INDEX = 10;
61      static final int CALLER_FILENAME_INDEX = 11;
62      static final int CALLER_CLASS_INDEX = 12;
63      static final int CALLER_METHOD_INDEX = 13;
64      static final int CALLER_LINE_INDEX = 14;
65      static final int EVENT_ID_INDEX = 15;
66  
67      static final StackTraceElement EMPTY_CALLER_DATA = CallerData.naInstance();
68  
69      static {
70          // PreparedStatement.getGeneratedKeys() method was added in JDK 1.4
71          Method getGeneratedKeysMethod;
72          try {
73              // the
74              getGeneratedKeysMethod = PreparedStatement.class.getMethod("getGeneratedKeys", (Class[]) null);
75          } catch (Exception ex) {
76              getGeneratedKeysMethod = null;
77          }
78          GET_GENERATED_KEYS_METHOD = getGeneratedKeysMethod;
79      }
80  
81      public void setDbNameResolver(DBNameResolver dbNameResolver) {
82          this.dbNameResolver = dbNameResolver;
83      }
84  
85      @Override
86      public void start() {
87          if (dbNameResolver == null)
88              dbNameResolver = new DefaultDBNameResolver();
89          insertExceptionSQL = SQLBuilder.buildInsertExceptionSQL(dbNameResolver);
90          insertPropertiesSQL = SQLBuilder.buildInsertPropertiesSQL(dbNameResolver);
91          insertSQL = SQLBuilder.buildInsertSQL(dbNameResolver);
92          super.start();
93      }
94  
95      @Override
96      protected void subAppend(ILoggingEvent event, Connection connection, PreparedStatement insertStatement) throws Throwable {
97  
98          bindLoggingEventWithInsertStatement(insertStatement, event);
99          bindLoggingEventArgumentsWithPreparedStatement(insertStatement, event.getArgumentArray());
100 
101         // This is expensive... should we do it every time?
102         bindCallerDataWithPreparedStatement(insertStatement, event.getCallerData());
103 
104         int updateCount = insertStatement.executeUpdate();
105         if (updateCount != 1) {
106             addWarn("Failed to insert loggingEvent");
107         }
108     }
109 
110     protected void secondarySubAppend(ILoggingEvent event, Connection connection, long eventId) throws Throwable {
111         Map<String, String> mergedMap = mergePropertyMaps(event);
112         insertProperties(mergedMap, connection, eventId);
113 
114         if (event.getThrowableProxy() != null) {
115             insertThrowable(event.getThrowableProxy(), connection, eventId);
116         }
117     }
118 
119     void bindLoggingEventWithInsertStatement(PreparedStatement stmt, ILoggingEvent event) throws SQLException {
120         stmt.setLong(TIMESTMP_INDEX, event.getTimeStamp());
121         stmt.setString(FORMATTED_MESSAGE_INDEX, event.getFormattedMessage());
122         stmt.setString(LOGGER_NAME_INDEX, event.getLoggerName());
123         stmt.setString(LEVEL_STRING_INDEX, event.getLevel().toString());
124         stmt.setString(THREAD_NAME_INDEX, event.getThreadName());
125         stmt.setShort(REFERENCE_FLAG_INDEX, DBHelper.computeReferenceMask(event));
126     }
127 
128     void bindLoggingEventArgumentsWithPreparedStatement(PreparedStatement stmt, Object[] argArray) throws SQLException {
129 
130         int arrayLen = argArray != null ? argArray.length : 0;
131 
132         for (int i = 0; i < arrayLen && i < 4; i++) {
133             stmt.setString(ARG0_INDEX + i, asStringTruncatedTo254(argArray[i]));
134         }
135         if (arrayLen < 4) {
136             for (int i = arrayLen; i < 4; i++) {
137                 stmt.setString(ARG0_INDEX + i, null);
138             }
139         }
140     }
141 
142     String asStringTruncatedTo254(Object o) {
143         String s = null;
144         if (o != null) {
145             s = o.toString();
146         }
147 
148         if (s == null) {
149             return null;
150         }
151         if (s.length() <= 254) {
152             return s;
153         } else {
154             return s.substring(0, 254);
155         }
156     }
157 
158     void bindCallerDataWithPreparedStatement(PreparedStatement stmt, StackTraceElement[] callerDataArray) throws SQLException {
159 
160         StackTraceElement caller = extractFirstCaller(callerDataArray);
161 
162         stmt.setString(CALLER_FILENAME_INDEX, caller.getFileName());
163         stmt.setString(CALLER_CLASS_INDEX, caller.getClassName());
164         stmt.setString(CALLER_METHOD_INDEX, caller.getMethodName());
165         stmt.setString(CALLER_LINE_INDEX, Integer.toString(caller.getLineNumber()));
166     }
167 
168     private StackTraceElement extractFirstCaller(StackTraceElement[] callerDataArray) {
169         StackTraceElement caller = EMPTY_CALLER_DATA;
170         if (hasAtLeastOneNonNullElement(callerDataArray))
171             caller = callerDataArray[0];
172         return caller;
173     }
174 
175     private boolean hasAtLeastOneNonNullElement(StackTraceElement[] callerDataArray) {
176         return callerDataArray != null && callerDataArray.length > 0 && callerDataArray[0] != null;
177     }
178 
179     Map<String, String> mergePropertyMaps(ILoggingEvent event) {
180         Map<String, String> mergedMap = new HashMap<String, String>();
181         // we add the context properties first, then the event properties, since
182         // we consider that event-specific properties should have priority over
183         // context-wide properties.
184         Map<String, String> loggerContextMap = event.getLoggerContextVO().getPropertyMap();
185         Map<String, String> mdcMap = event.getMDCPropertyMap();
186         if (loggerContextMap != null) {
187             mergedMap.putAll(loggerContextMap);
188         }
189         if (mdcMap != null) {
190             mergedMap.putAll(mdcMap);
191         }
192 
193         return mergedMap;
194     }
195 
196     @Override
197     protected Method getGeneratedKeysMethod() {
198         return GET_GENERATED_KEYS_METHOD;
199     }
200 
201     @Override
202     protected String getInsertSQL() {
203         return insertSQL;
204     }
205 
206     protected void insertProperties(Map<String, String> mergedMap, Connection connection, long eventId) throws SQLException {
207         Set<String> propertiesKeys = mergedMap.keySet();
208         if (propertiesKeys.size() > 0) {
209             PreparedStatement insertPropertiesStatement = null;
210             try {
211                 insertPropertiesStatement = connection.prepareStatement(insertPropertiesSQL);
212 
213                 for (String key : propertiesKeys) {
214                     String value = mergedMap.get(key);
215 
216                     insertPropertiesStatement.setLong(1, eventId);
217                     insertPropertiesStatement.setString(2, key);
218                     insertPropertiesStatement.setString(3, value);
219 
220                     if (cnxSupportsBatchUpdates) {
221                         insertPropertiesStatement.addBatch();
222                     } else {
223                         insertPropertiesStatement.execute();
224                     }
225                 }
226 
227                 if (cnxSupportsBatchUpdates) {
228                     insertPropertiesStatement.executeBatch();
229                 }
230             } finally {
231                 closeStatement(insertPropertiesStatement);
232             }
233         }
234     }
235 
236     /**
237      * Add an exception statement either as a batch or execute immediately if
238      * batch updates are not supported.
239      */
240     void updateExceptionStatement(PreparedStatement exceptionStatement, String txt, short i, long eventId) throws SQLException {
241         exceptionStatement.setLong(1, eventId);
242         exceptionStatement.setShort(2, i);
243         exceptionStatement.setString(3, txt);
244         if (cnxSupportsBatchUpdates) {
245             exceptionStatement.addBatch();
246         } else {
247             exceptionStatement.execute();
248         }
249     }
250 
251     short buildExceptionStatement(IThrowableProxy tp, short baseIndex, PreparedStatement insertExceptionStatement, long eventId) throws SQLException {
252 
253         StringBuilder buf = new StringBuilder();
254         ThrowableProxyUtil.subjoinFirstLine(buf, tp);
255         updateExceptionStatement(insertExceptionStatement, buf.toString(), baseIndex++, eventId);
256 
257         int commonFrames = tp.getCommonFrames();
258         StackTraceElementProxy[] stepArray = tp.getStackTraceElementProxyArray();
259         for (int i = 0; i < stepArray.length - commonFrames; i++) {
260             StringBuilder sb = new StringBuilder();
261             sb.append(CoreConstants.TAB);
262             ThrowableProxyUtil.subjoinSTEP(sb, stepArray[i]);
263             updateExceptionStatement(insertExceptionStatement, sb.toString(), baseIndex++, eventId);
264         }
265 
266         if (commonFrames > 0) {
267             StringBuilder sb = new StringBuilder();
268             sb.append(CoreConstants.TAB).append("... ").append(commonFrames).append(" common frames omitted");
269             updateExceptionStatement(insertExceptionStatement, sb.toString(), baseIndex++, eventId);
270         }
271 
272         return baseIndex;
273     }
274 
275     protected void insertThrowable(IThrowableProxy tp, Connection connection, long eventId) throws SQLException {
276 
277         PreparedStatement exceptionStatement = null;
278         try {
279             exceptionStatement = connection.prepareStatement(insertExceptionSQL);
280 
281             short baseIndex = 0;
282             while (tp != null) {
283                 baseIndex = buildExceptionStatement(tp, baseIndex, exceptionStatement, eventId);
284                 tp = tp.getCause();
285             }
286 
287             if (cnxSupportsBatchUpdates) {
288                 exceptionStatement.executeBatch();
289             }
290         } finally {
291             closeStatement(exceptionStatement);
292         }
293 
294     }
295 }