Source code: org/objectstyle/cayenne/access/QueryLogger.java
1 /* ====================================================================
2 *
3 * The ObjectStyle Group Software License, Version 1.0
4 *
5 * Copyright (c) 2002-2003 The ObjectStyle Group
6 * and individual authors of the software. All rights reserved.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 *
12 * 1. Redistributions of source code must retain the above copyright
13 * notice, this list of conditions and the following disclaimer.
14 *
15 * 2. Redistributions in binary form must reproduce the above copyright
16 * notice, this list of conditions and the following disclaimer in
17 * the documentation and/or other materials provided with the
18 * distribution.
19 *
20 * 3. The end-user documentation included with the redistribution, if
21 * any, must include the following acknowlegement:
22 * "This product includes software developed by the
23 * ObjectStyle Group (http://objectstyle.org/)."
24 * Alternately, this acknowlegement may appear in the software itself,
25 * if and wherever such third-party acknowlegements normally appear.
26 *
27 * 4. The names "ObjectStyle Group" and "Cayenne"
28 * must not be used to endorse or promote products derived
29 * from this software without prior written permission. For written
30 * permission, please contact andrus@objectstyle.org.
31 *
32 * 5. Products derived from this software may not be called "ObjectStyle"
33 * nor may "ObjectStyle" appear in their names without prior written
34 * permission of the ObjectStyle Group.
35 *
36 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
37 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
38 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
39 * DISCLAIMED. IN NO EVENT SHALL THE OBJECTSTYLE GROUP OR
40 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
41 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
42 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
43 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
44 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
45 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
46 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
47 * SUCH DAMAGE.
48 * ====================================================================
49 *
50 * This software consists of voluntary contributions made by many
51 * individuals on behalf of the ObjectStyle Group. For more
52 * information on the ObjectStyle Group, please see
53 * <http://objectstyle.org/>.
54 *
55 */
56 package org.objectstyle.cayenne.access;
57
58 import java.sql.SQLException;
59 import java.util.List;
60
61 import org.apache.log4j.Level;
62 import org.apache.log4j.Logger;
63 import org.objectstyle.cayenne.conn.DataSourceInfo;
64 import org.objectstyle.cayenne.query.Query;
65 import org.objectstyle.cayenne.util.Util;
66
67 /**
68 * A QueryLogger is intended to log special events during query executions.
69 * This includes generated SQL statements, result counts, connection events etc.
70 * It is a single consistent place for that kind of logging and should be used
71 * by all Cayenne classes that work with the database directly.
72 *
73 * <p>In many cases it is important to use this class as opposed to logging
74 * from the class that performs a particular operation, since QueryLogger
75 * will generate consistently formatted logs that are easy to analyze
76 * and turn on/off.</p>
77 *
78 * <p><i>For more information see <a href="../../../../../../userguide/index.html"
79 * target="_top">Cayenne User Guide.</a></i></p>
80 *
81 * @author Andrei Adamchik
82 */
83 public class QueryLogger {
84 private static Logger logObj = Logger.getLogger(QueryLogger.class);
85
86 public static final Level DEFAULT_LOG_LEVEL = Query.DEFAULT_LOG_LEVEL;
87 public static final int TRIM_VALUES_THRESHOLD = 300;
88
89 /**
90 * Utility method that appends SQL literal for the specified object to the buffer.
91 *
92 * <p>Note: this method is not intended to build SQL queries, rather this is used
93 * in logging routines only. In particular it will trim large values to avoid
94 * flooding the logs.</p>
95 *
96 * @param buf buffer to append value
97 * @param anObject object to be transformed to SQL literal.
98 */
99 public static void sqlLiteralForObject(StringBuffer buf, Object anObject) {
100 // 0. Null
101 if (anObject == null) {
102 buf.append("NULL");
103 }
104 // 1. String literal
105 else if (anObject instanceof String) {
106 buf.append('\'');
107 // lets escape quotes
108 String literal = (String) anObject;
109 if (literal.length() > TRIM_VALUES_THRESHOLD) {
110 literal = literal.substring(0, TRIM_VALUES_THRESHOLD) + "...";
111 }
112
113 int curPos = 0;
114 int endPos = 0;
115
116 while ((endPos = literal.indexOf('\'', curPos)) >= 0) {
117 buf.append(literal.substring(curPos, endPos + 1)).append('\'');
118 curPos = endPos + 1;
119 }
120
121 if (curPos < literal.length())
122 buf.append(literal.substring(curPos));
123
124 buf.append('\'');
125 }
126 // 2. Numeric literal
127 else if (anObject instanceof Number) {
128 // process numeric value (do something smart in the future)
129 buf.append(anObject);
130 }
131 // 3. Date
132 else if (anObject instanceof java.sql.Date) {
133 buf.append('\'').append(anObject).append('\'');
134 }
135 // 4. Date
136 else if (anObject instanceof java.sql.Time) {
137 buf.append('\'').append(anObject).append('\'');
138 }
139 // 5 Date
140 else if (anObject instanceof java.util.Date) {
141 long time = ((java.util.Date) anObject).getTime();
142 buf.append('\'').append(new java.sql.Timestamp(time)).append('\'');
143 }
144 // 6. byte[]
145 else if (anObject instanceof byte[]) {
146 buf.append("< ");
147 byte[] b = (byte[]) anObject;
148
149 int len = b.length;
150 boolean trimming = false;
151 if (len > TRIM_VALUES_THRESHOLD) {
152 len = TRIM_VALUES_THRESHOLD;
153 trimming = true;
154 }
155
156 for (int i = 0; i < len; i++) {
157 appendFormattedByte(buf, b[i]);
158 buf.append(' ');
159 }
160
161 if (trimming) {
162 buf.append("...");
163 }
164 buf.append('>');
165 } else if (anObject instanceof Boolean) {
166 buf.append('\'').append(anObject).append('\'');
167 } else {
168 // unknown
169 buf
170 .append("[")
171 .append(anObject.getClass().getName())
172 .append(": ")
173 .append(anObject)
174 .append("]");
175 }
176 }
177
178 /**
179 * Prints a byte value to a StringBuffer as a double digit hex value.
180 */
181 protected static void appendFormattedByte(
182 StringBuffer buf,
183 byte byteValue) {
184
185 String hexDecode = "0123456789ABCDEF";
186
187 buf.append(hexDecode.charAt(byteValue >>> 4));
188 buf.append(hexDecode.charAt(byteValue & 0x0F));
189 }
190
191 /**
192 * Returns current logging level.
193 */
194 public static Level getLoggingLevel() {
195 Level level = logObj.getLevel();
196 return (level != null) ? level : DEFAULT_LOG_LEVEL;
197 }
198
199 /**
200 * Sets logging level.
201 */
202 public static void setLoggingLevel(Level level) {
203 logObj.setLevel(level);
204 }
205
206 /**
207 * Logs database connection event using container data source.
208 */
209 public static void logConnect(Level logLevel, String dataSource) {
210 if (isLoggable(logLevel)) {
211 logObj.log(logLevel, "Connecting. JNDI path: " + dataSource);
212 }
213 }
214
215 public static void logConnect(
216 Level logLevel,
217 String url,
218 String userName,
219 String password) {
220 if (isLoggable(logLevel)) {
221 StringBuffer buf = new StringBuffer("Opening connection: ");
222
223 // append URL on the same line to make log somewhat grep-friendly
224 buf.append(url);
225 buf.append("\n\tLogin: ").append(userName);
226 buf.append("\n\tPassword: *******");
227
228 logObj.log(logLevel, buf.toString());
229 }
230 }
231
232 /**
233 * Logs database connection event.
234 */
235 public static void logPoolCreated(Level logLevel, DataSourceInfo dsi) {
236 if (isLoggable(logLevel)) {
237 StringBuffer buf = new StringBuffer("Created connection pool: ");
238
239 if (dsi != null) {
240 // append URL on the same line to make log somewhat grep-friendly
241 buf.append(dsi.getDataSourceUrl());
242
243 if (dsi.getAdapterClassName() != null) {
244 buf.append("\n\tCayenne DbAdapter: ").append(
245 dsi.getAdapterClassName());
246 }
247
248 buf.append("\n\tDriver class: ").append(dsi.getJdbcDriver());
249
250 if (dsi.getMinConnections() >= 0) {
251 buf.append("\n\tMin. connections in the pool: ").append(
252 dsi.getMinConnections());
253 }
254 if (dsi.getMaxConnections() >= 0) {
255 buf.append("\n\tMax. connections in the pool: ").append(
256 dsi.getMaxConnections());
257 }
258
259 buf.append("\n\tLogin: ").append(dsi.getUserName());
260 buf.append("\n\tPassword: *******");
261 } else {
262 buf.append(" pool information unavailable");
263 }
264
265 logObj.log(logLevel, buf.toString());
266 }
267 }
268
269 public static void logConnectSuccess(Level logLevel) {
270 logObj.log(logLevel, "+++ Connecting: SUCCESS.");
271 }
272
273 public static void logConnectFailure(Level logLevel, Throwable th) {
274 logObj.log(logLevel, "*** Connecting: FAILURE.", th);
275 }
276
277 public static void logQuery(Level logLevel, String queryStr, List params) {
278 logQuery(logLevel, queryStr, params, -1);
279 }
280
281 /**
282 * Log query content using Log4J Category with "INFO" priority.
283 *
284 * @param queryStr Query SQL string
285 * @param params optional list of query parameters that are used when
286 * executing query in prepared statement.
287 */
288 public static void logQuery(
289 Level logLevel,
290 String queryStr,
291 List params,
292 long time) {
293 if (isLoggable(logLevel)) {
294 StringBuffer buf = new StringBuffer(queryStr);
295 if (params != null && params.size() > 0) {
296 buf.append(" [bind: ");
297 sqlLiteralForObject(buf, params.get(0));
298
299 int len = params.size();
300 for (int i = 1; i < len; i++) {
301 buf.append(", ");
302 sqlLiteralForObject(buf, params.get(i));
303 }
304
305 buf.append(']');
306 }
307
308 // log preparation time only if it is something significant
309 if (time > 10) {
310 buf.append(" - prepared in ").append(time).append(" ms.");
311 }
312
313 logObj.log(logLevel, buf.toString());
314 }
315 }
316
317 public static void logQueryParameters(
318 Level logLevel,
319 String label,
320 List parameters) {
321
322 if (isLoggable(logLevel) && parameters.size() > 0) {
323 int len = parameters.size();
324 StringBuffer buf = new StringBuffer("[");
325 buf.append(label).append(": ");
326
327 sqlLiteralForObject(buf, parameters.get(0));
328
329 for (int i = 1; i < len; i++) {
330 buf.append(", ");
331 sqlLiteralForObject(buf, parameters.get(i));
332 }
333
334 buf.append(']');
335 logObj.log(logLevel, buf.toString());
336 }
337 }
338
339 public static void logSelectCount(Level logLevel, int count) {
340 logSelectCount(logLevel, count, -1);
341 }
342
343 public static void logSelectCount(Level logLevel, int count, long time) {
344 if (isLoggable(logLevel)) {
345 StringBuffer buf = new StringBuffer();
346
347 if (count == 1) {
348 buf.append("=== returned 1 row.");
349 } else {
350 buf.append("=== returned ").append(count).append(" rows.");
351 }
352
353 if (time >= 0) {
354 buf.append(" - took ").append(time).append(" ms.");
355 }
356
357 logObj.log(logLevel, buf.toString());
358 }
359 }
360
361 public static void logUpdateCount(Level logLevel, int count) {
362 if (isLoggable(logLevel)) {
363 String countStr =
364 (count == 1)
365 ? "=== updated 1 row."
366 : "=== updated " + count + " rows.";
367 logObj.log(logLevel, countStr);
368 }
369 }
370
371 public static void logCommitTransaction(Level logLevel) {
372 logObj.log(logLevel, "+++ transaction committed.");
373 }
374
375 public static void logRollbackTransaction(Level logLevel) {
376 logObj.log(logLevel, "*** transaction rolled back.");
377 }
378
379 public static void logQueryError(Level logLevel, Throwable th) {
380 if (th != null) {
381 th = Util.unwindException(th);
382 }
383
384 logObj.log(logLevel, "*** error.", th);
385
386 if (th instanceof SQLException) {
387 SQLException sqlException = ((SQLException) th).getNextException();
388 while (sqlException != null) {
389 logObj.log(logLevel, "*** nested SQL error.", sqlException);
390 sqlException = sqlException.getNextException();
391 }
392 }
393 }
394
395 public static void logQueryStart(Level logLevel, int count) {
396 if (isLoggable(logLevel)) {
397 String countStr =
398 (count == 1)
399 ? "--- will run 1 query."
400 : "--- will run " + count + " queries.";
401 logObj.log(logLevel, countStr);
402 }
403 }
404
405 public static boolean isLoggable(Level logLevel) {
406 return logObj.isEnabledFor(logLevel);
407 }
408
409 }