View Javadoc

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