1 /*
2 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3 *
4 * Copyright (c) 2012-2017 Oracle and/or its affiliates. All rights reserved.
5 *
6 * The contents of this file are subject to the terms of either the GNU
7 * General Public License Version 2 only ("GPL") or the Common Development
8 * and Distribution License("CDDL") (collectively, the "License"). You
9 * may not use this file except in compliance with the License. You can
10 * obtain a copy of the License at
11 * https://oss.oracle.com/licenses/CDDL+GPL-1.1
12 * or LICENSE.txt. See the License for the specific
13 * language governing permissions and limitations under the License.
14 *
15 * When distributing the software, include this License Header Notice in each
16 * file and include the License file at LICENSE.txt.
17 *
18 * GPL Classpath Exception:
19 * Oracle designates this particular file as subject to the "Classpath"
20 * exception as provided by Oracle in the GPL Version 2 section of the License
21 * file that accompanied this code.
22 *
23 * Modifications:
24 * If applicable, add the following below the License Header, with the fields
25 * enclosed by brackets [] replaced by your own identifying information:
26 * "Portions Copyright [year] [name of copyright owner]"
27 *
28 * Contributor(s):
29 * If you wish your version of this file to be governed by only the CDDL or
30 * only the GPL Version 2, indicate your decision by adding "[Contributor]
31 * elects to include this software in this distribution under the [CDDL or GPL
32 * Version 2] license." If you don't indicate a single choice of license, a
33 * recipient has the option to distribute your version of this file under
34 * either the CDDL, the GPL Version 2 or to extend the choice of license to
35 * its licensees as provided above. However, if you add GPL Version 2 code
36 * and therefore, elected the GPL Version 2 license, then the option applies
37 * only if the new code is made subject to such option by the copyright
38 * holder.
39 */
40
41 package com.sun.mail.util;
42
43 import java.io.PrintStream;
44 import java.text.MessageFormat;
45 import java.util.logging.Level;
46 import java.util.logging.Logger;
47 import javax.mail.Session;
48
49 /**
50 * A simplified logger used by JavaMail to handle logging to a
51 * PrintStream and logging through a java.util.logging.Logger.
52 * If debug is set, messages are written to the PrintStream and
53 * prefixed by the specified prefix (which is not included in
54 * Logger messages).
55 * Messages are logged by the Logger based on the configuration
56 * of the logging system.
57 */
58
59 /*
60 * It would be so much simpler to just subclass Logger and override
61 * the log(LogRecord) method, as the javadocs suggest, but that doesn't
62 * work because Logger makes the decision about whether to log the message
63 * or not before calling the log(LogRecord) method. Instead, we just
64 * provide the few log methods we need here.
65 */
66
67 public final class MailLogger {
68 /**
69 * For log messages.
70 */
71 private final Logger logger;
72 /**
73 * For debug output.
74 */
75 private final String prefix;
76 /**
77 * Produce debug output?
78 */
79 private final boolean debug;
80 /**
81 * Stream for debug output.
82 */
83 private final PrintStream out;
84
85 /**
86 * Construct a new MailLogger using the specified Logger name,
87 * debug prefix (e.g., "DEBUG"), debug flag, and PrintStream.
88 *
89 * @param name the Logger name
90 * @param prefix the prefix for debug output, or null for none
91 * @param debug if true, write to PrintStream
92 * @param out the PrintStream to write to
93 */
94 public MailLogger(String name, String prefix, boolean debug,
95 PrintStream out) {
96 logger = Logger.getLogger(name);
97 this.prefix = prefix;
98 this.debug = debug;
99 this.out = out != null ? out : System.out;
100 }
101
102 /**
103 * Construct a new MailLogger using the specified class' package
104 * name as the Logger name,
105 * debug prefix (e.g., "DEBUG"), debug flag, and PrintStream.
106 *
107 * @param clazz the Logger name is the package name of this class
108 * @param prefix the prefix for debug output, or null for none
109 * @param debug if true, write to PrintStream
110 * @param out the PrintStream to write to
111 */
112 public MailLogger(Class<?> clazz, String prefix, boolean debug,
113 PrintStream out) {
114 String name = packageOf(clazz);
115 logger = Logger.getLogger(name);
116 this.prefix = prefix;
117 this.debug = debug;
118 this.out = out != null ? out : System.out;
119 }
120
121 /**
122 * Construct a new MailLogger using the specified class' package
123 * name combined with the specified subname as the Logger name,
124 * debug prefix (e.g., "DEBUG"), debug flag, and PrintStream.
125 *
126 * @param clazz the Logger name is the package name of this class
127 * @param subname the Logger name relative to this Logger name
128 * @param prefix the prefix for debug output, or null for none
129 * @param debug if true, write to PrintStream
130 * @param out the PrintStream to write to
131 */
132 public MailLogger(Class<?> clazz, String subname, String prefix, boolean debug,
133 PrintStream out) {
134 String name = packageOf(clazz) + "." + subname;
135 logger = Logger.getLogger(name);
136 this.prefix = prefix;
137 this.debug = debug;
138 this.out = out != null ? out : System.out;
139 }
140
141 /**
142 * Construct a new MailLogger using the specified Logger name and
143 * debug prefix (e.g., "DEBUG"). Get the debug flag and PrintStream
144 * from the Session.
145 *
146 * @param name the Logger name
147 * @param prefix the prefix for debug output, or null for none
148 * @param session where to get the debug flag and PrintStream
149 */
150 public MailLogger(String name, String prefix, Session session) {
151 this(name, prefix, session.getDebug(), session.getDebugOut());
152 }
153
154 /**
155 * Construct a new MailLogger using the specified class' package
156 * name as the Logger name and the specified
157 * debug prefix (e.g., "DEBUG"). Get the debug flag and PrintStream
158 * from the Session.
159 *
160 * @param clazz the Logger name is the package name of this class
161 * @param prefix the prefix for debug output, or null for none
162 * @param session where to get the debug flag and PrintStream
163 */
164 public MailLogger(Class<?> clazz, String prefix, Session session) {
165 this(clazz, prefix, session.getDebug(), session.getDebugOut());
166 }
167
168 /**
169 * Create a MailLogger that uses a Logger with the specified name
170 * and prefix. The new MailLogger uses the same debug flag and
171 * PrintStream as this MailLogger.
172 *
173 * @param name the Logger name
174 * @param prefix the prefix for debug output, or null for none
175 * @return a MailLogger for the given name and prefix.
176 */
177 public MailLogger getLogger(String name, String prefix) {
178 return new MailLogger(name, prefix, debug, out);
179 }
180
181 /**
182 * Create a MailLogger using the specified class' package
183 * name as the Logger name and the specified prefix.
184 * The new MailLogger uses the same debug flag and
185 * PrintStream as this MailLogger.
186 *
187 * @param clazz the Logger name is the package name of this class
188 * @param prefix the prefix for debug output, or null for none
189 * @return a MailLogger for the given name and prefix.
190 */
191 public MailLogger getLogger(Class<?> clazz, String prefix) {
192 return new MailLogger(clazz, prefix, debug, out);
193 }
194
195 /**
196 * Create a MailLogger that uses a Logger whose name is composed
197 * of this MailLogger's name plus the specified sub-name, separated
198 * by a dot. The new MailLogger uses the new prefix for debug output.
199 * This is used primarily by the protocol trace code that wants a
200 * different prefix (none).
201 *
202 * @param subname the Logger name relative to this Logger name
203 * @param prefix the prefix for debug output, or null for none
204 * @return a MailLogger for the given name and prefix.
205 */
206 public MailLogger getSubLogger(String subname, String prefix) {
207 return new MailLogger(logger.getName() + "." + subname, prefix,
208 debug, out);
209 }
210
211 /**
212 * Create a MailLogger that uses a Logger whose name is composed
213 * of this MailLogger's name plus the specified sub-name, separated
214 * by a dot. The new MailLogger uses the new prefix for debug output.
215 * This is used primarily by the protocol trace code that wants a
216 * different prefix (none).
217 *
218 * @param subname the Logger name relative to this Logger name
219 * @param prefix the prefix for debug output, or null for none
220 * @param debug the debug flag for the sub-logger
221 * @return a MailLogger for the given name and prefix.
222 */
223 public MailLogger getSubLogger(String subname, String prefix,
224 boolean debug) {
225 return new MailLogger(logger.getName() + "." + subname, prefix,
226 debug, out);
227 }
228
229 /**
230 * Log the message at the specified level.
231 * @param level the log level.
232 * @param msg the message.
233 */
234 public void log(Level level, String msg) {
235 ifDebugOut(msg);
236 if (logger.isLoggable(level)) {
237 final StackTraceElement frame = inferCaller();
238 logger.logp(level, frame.getClassName(), frame.getMethodName(), msg);
239 }
240 }
241
242 /**
243 * Log the message at the specified level.
244 * @param level the log level.
245 * @param msg the message.
246 * @param param1 the additional parameter.
247 */
248 public void log(Level level, String msg, Object param1) {
249 if (debug) {
250 msg = MessageFormat.format(msg, new Object[] { param1 });
251 debugOut(msg);
252 }
253
254 if (logger.isLoggable(level)) {
255 final StackTraceElement frame = inferCaller();
256 logger.logp(level, frame.getClassName(), frame.getMethodName(), msg, param1);
257 }
258 }
259
260 /**
261 * Log the message at the specified level.
262 * @param level the log level.
263 * @param msg the message.
264 * @param params the message parameters.
265 */
266 public void log(Level level, String msg, Object... params) {
267 if (debug) {
268 msg = MessageFormat.format(msg, params);
269 debugOut(msg);
270 }
271
272 if (logger.isLoggable(level)) {
273 final StackTraceElement frame = inferCaller();
274 logger.logp(level, frame.getClassName(), frame.getMethodName(), msg, params);
275 }
276 }
277
278 /**
279 * Log the message at the specified level using a format string.
280 * @param level the log level.
281 * @param msg the message format string.
282 * @param params the message parameters.
283 *
284 * @since JavaMail 1.5.4
285 */
286 public void logf(Level level, String msg, Object... params) {
287 msg = String.format(msg, params);
288 ifDebugOut(msg);
289 logger.log(level, msg);
290 }
291
292 /**
293 * Log the message at the specified level.
294 * @param level the log level.
295 * @param msg the message.
296 * @param thrown the throwable to log.
297 */
298 public void log(Level level, String msg, Throwable thrown) {
299 if (debug) {
300 if (thrown != null) {
301 debugOut(msg + ", THROW: ");
302 thrown.printStackTrace(out);
303 } else {
304 debugOut(msg);
305 }
306 }
307
308 if (logger.isLoggable(level)) {
309 final StackTraceElement frame = inferCaller();
310 logger.logp(level, frame.getClassName(), frame.getMethodName(), msg, thrown);
311 }
312 }
313
314 /**
315 * Log a message at the CONFIG level.
316 * @param msg the message.
317 */
318 public void config(String msg) {
319 log(Level.CONFIG, msg);
320 }
321
322 /**
323 * Log a message at the FINE level.
324 * @param msg the message.
325 */
326 public void fine(String msg) {
327 log(Level.FINE, msg);
328 }
329
330 /**
331 * Log a message at the FINER level.
332 * @param msg the message.
333 */
334 public void finer(String msg) {
335 log(Level.FINER, msg);
336 }
337
338 /**
339 * Log a message at the FINEST level.
340 * @param msg the message.
341 */
342 public void finest(String msg) {
343 log(Level.FINEST, msg);
344 }
345
346 /**
347 * If "debug" is set, or our embedded Logger is loggable at the
348 * given level, return true.
349 * @param level the log level.
350 * @return true if loggable.
351 */
352 public boolean isLoggable(Level level) {
353 return debug || logger.isLoggable(level);
354 }
355
356 /**
357 * Common code to conditionally log debug statements.
358 * @param msg the message to log.
359 */
360 private void ifDebugOut(String msg) {
361 if (debug)
362 debugOut(msg);
363 }
364
365 /**
366 * Common formatting for debug output.
367 * @param msg the message to log.
368 */
369 private void debugOut(String msg) {
370 if (prefix != null)
371 out.println(prefix + ": " + msg);
372 else
373 out.println(msg);
374 }
375
376 /**
377 * Return the package name of the class.
378 * Sometimes there will be no Package object for the class,
379 * e.g., if the class loader hasn't created one (see Class.getPackage()).
380 * @param clazz the class source.
381 * @return the package name or an empty string.
382 */
383 private String packageOf(Class<?> clazz) {
384 Package p = clazz.getPackage();
385 if (p != null)
386 return p.getName(); // hopefully the common case
387 String cname = clazz.getName();
388 int i = cname.lastIndexOf('.');
389 if (i > 0)
390 return cname.substring(0, i);
391 // no package name, now what?
392 return "";
393 }
394
395 /**
396 * A disadvantage of not being able to use Logger directly in JavaMail
397 * code is that the "source class" information that Logger guesses will
398 * always refer to this class instead of our caller. This method
399 * duplicates what Logger does to try to find *our* caller, so that
400 * Logger doesn't have to do it (and get the wrong answer), and because
401 * our caller is what's wanted.
402 * @return StackTraceElement that logged the message. Treat as read-only.
403 */
404 private StackTraceElement inferCaller() {
405 // Get the stack trace.
406 StackTraceElement stack[] = (new Throwable()).getStackTrace();
407 // First, search back to a method in the Logger class.
408 int ix = 0;
409 while (ix < stack.length) {
410 StackTraceElement frame = stack[ix];
411 String cname = frame.getClassName();
412 if (isLoggerImplFrame(cname)) {
413 break;
414 }
415 ix++;
416 }
417 // Now search for the first frame before the "Logger" class.
418 while (ix < stack.length) {
419 StackTraceElement frame = stack[ix];
420 String cname = frame.getClassName();
421 if (!isLoggerImplFrame(cname)) {
422 // We've found the relevant frame.
423 return frame;
424 }
425 ix++;
426 }
427 // We haven't found a suitable frame, so just punt. This is
428 // OK as we are only committed to making a "best effort" here.
429 return new StackTraceElement(MailLogger.class.getName(), "log",
430 MailLogger.class.getName(), -1);
431 }
432
433 /**
434 * Frames to ignore as part of the MailLogger to JUL bridge.
435 * @param cname the class name.
436 * @return true if the class name is part of the MailLogger bridge.
437 */
438 private boolean isLoggerImplFrame(String cname) {
439 return MailLogger.class.getName().equals(cname);
440 }
441 }
442