1 package org.lsst.ccs.utilities.logging;
2
3 import org.lsst.ccs.utilities.beanutils.Optional;
4 import org.lsst.ccs.utilities.tracers.Tracer;
5
6 import java.util.concurrent.Executors;
7 import java.util.concurrent.ScheduledExecutorService;
8 import java.util.concurrent.ThreadFactory;
9 import java.util.concurrent.TimeUnit;
10 import java.util.logging.Level;
11 import java.util.logging.LogManager;
12 import java.util.logging.LogRecord;
13
14 /**
15 * Just a wrapper class around java.util.Logger to add specific logging methods.
16 * The instances are not cached (so better have only one per package)
17 * <BR/>
18 * Most methods allow "multi-dimensional" logging (log the same data to various
19 * loggers).
20 * <BR/>
21 * Many of those are intended to replace log4j invocations by just changing the
22 * <TT>import</TT> directives. The mapping from log4J Levels is operated this
23 * way
24 * <PRE>
25 * finest -> finest
26 * trace -> finer
27 * debug-> fine
28 * info -> info
29 * warn -> warning
30 * error -> severe
31 * fatal -> severe
32 * </PRE>
33 * <BR/>
34 * Logging methods that return a boolean can be used through <TT>assert</TT>
35 * calls (they always return true).
36 * <p>
37 * Do not use this <TT>Logger</TT> for admin purpose (setting a level, a Filter,
38 * a Formatter or a Handler): use the corresponding
39 * <TT>java.util.logging.Logger</TT> instead.
40 *
41 * @author bamade
42 */
43 // Date: 29/05/13
44 public class Logger {
45
46 static {
47 assert Tracer.version("$Rev$", Logger.class, "org-lsst-ccs-utilities");
48 // Forces Logger Initialization
49 // See https://jira.slac.stanford.edu/browse/LSSTCCS-215
50 /*
51 * this is to ensure that the first time we use our Logger
52 * then static initialization of codes of LogManagement and thus LogPropertiesLoader
53 * are load-time initialized.
54 * (though this is probably the case but depends on JVM load strategy)
55 */
56 LogManagement.isConfigInitialized();
57 }
58
59 /**
60 * the real JUL logger for which we delegate log4J lookalike methods
61 * and all our logs.
62 */
63 private java.util.logging.Logger julDelegate;
64
65 /**
66 * scheduler for delayed logs
67 */
68 protected ScheduledExecutorService executor = Executors.newScheduledThreadPool(1,
69 new ThreadFactory() {
70 @Override
71 public Thread newThread(Runnable r) {
72 Thread res = new Thread(r, "LoggerDelayedExecutor");
73 res.setDaemon(true);
74 return res;
75 }
76 });
77
78 /**
79 * private constructor: we need a JUL delegate to deliver our own Logger.
80 * see factory methods to obtain a Logger.
81 * @param delegate
82 */
83 private Logger(java.util.logging.Logger delegate) {
84 this.julDelegate = delegate;
85 Optional<LogManager> optManager = LogPropertiesLoader.getSystemLoaderLogManager();
86 if (optManager.isPresent()) {
87 LogManager manager = optManager.get();
88 manager.addLogger(delegate);
89 }
90 }
91
92 /**
93 * factory method to obtain a <TT>Logger</TT> proxy.
94 *
95 * @param name usually a package name (top of hierarchy is "" empty
96 * String)
97 * @return
98 */
99 public static Logger getLogger(String name) {
100 java.util.logging.Logger realLogger = java.util.logging.Logger.getLogger(name);
101 return new Logger(realLogger);
102 }
103
104 /**
105 * factory method to obtain a <TT>Logger</TT> proxy.
106 *
107 * @param name usually a package name (top of hierarchy is "" empty
108 * String)
109 * @param resourceBundleName
110 * @return
111 */
112 public static Logger getLogger(String name, String resourceBundleName) {
113 java.util.logging.Logger realLogger = java.util.logging.Logger.getLogger(name, resourceBundleName);
114 return new Logger(realLogger);
115 }
116
117 /**
118 * @return the name of the Logger
119 */
120 public String getName() {
121 return julDelegate.getName();
122 }
123
124 /**
125 * @return the level of the associated JUL logger
126 */
127 public Level getLevel() {
128 return julDelegate.getLevel();
129 }
130
131 /**
132 * @return the parent of the associated JUL logger
133 */
134 protected java.util.logging.Logger getParent() {
135 return julDelegate.getParent();
136 }
137
138 /**
139 * creates a simple log record. This factory manipulates the caller
140 * information (className, method Name) by getting rid of any
141 * information coming from a package that contains "logging" (or
142 * "log4j").
143 * <BR/>
144 * note that the LogRecord is automatically marked with a sequenceNumber
145 * that can be used by <TT>Handlers</TT>
146 * that want to detect duplicate publications.
147 *
148 * @param level JUL Level
149 * @param message (avoid null values)
150 * @return a simple LogRecord
151 */
152 public LogRecord createLogRecord(Level level, String message) {
153 LogRecord res = new LogRecord(level, message);
154 //now modifies the method and class calls
155 //Throwable throwable = new Throwable();
156 //StackTraceElement[] stackTraceElements = throwable.getStackTrace();
157 StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace();
158 for (StackTraceElement stackTraceElement : stackTraceElements) {
159 String className = stackTraceElement.getClassName();
160 if (!className.startsWith("org.lsst.ccs.utilities.logging")) {
161 if (!className.startsWith("org.apache.log4j") && !className.contains(".logging.")) {
162 String methodName = stackTraceElement.getMethodName();
163 if (!"getStackTrace".equals(methodName)) {
164 res.setSourceClassName(className);
165 res.setSourceMethodName(methodName);
166 break;
167 }
168 }
169 }
170 }
171
172 return res;
173 }
174
175 /* LOG4J compatible calls
176 finest -> finest
177 trace -> finer
178 debug-> fine
179 info -> info
180 warn -> warning
181 error -> severe
182 fatal -> severe
183 isDebugEnabled
184 */
185
186 /**
187 * utility method to log a message with a Level
188 *
189 * @param level JUL level
190 * @param message (avoid null values)
191 * @param concerns a list of additional JUL loggers name (such as
192 * "INIT", "CONFIG" ,...)
193 * @return true
194 */
195 protected boolean logMessage(Level level, Object message, String... concerns) {
196 String msg = String.valueOf(message);
197 LogRecord record = createLogRecord(level, msg);
198 this.log(record, concerns);
199 return true;
200 }
201
202 /**
203 * logs a message at FINEST level
204 *
205 * @param message
206 * @param concerns a list of additional JUL loggers name (such as
207 * "INIT", "CONFIG" ,...)
208 * @return true
209 */
210 public boolean finest(Object message, String... concerns) {
211 return logMessage(Level.FINEST, message, concerns);
212 }
213
214 /**
215 * logs a message at FINER level
216 *
217 * @param message
218 * @param concerns a list of additional JUL loggers name (such as
219 * "INIT", "CONFIG" ,...)
220 * @return true
221 */
222 public boolean trace(Object message, String... concerns) {
223 return logMessage(Level.FINER, message, concerns);
224 }
225
226 /**
227 * logs a message at FINER level
228 *
229 * @param message
230 * @param concerns a list of additional JUL loggers name (such as
231 * "INIT", "CONFIG" ,...)
232 * @return true
233 */
234 public boolean finer(Object message, String... concerns) {
235 return logMessage(Level.FINER, message, concerns);
236 }
237
238 /**
239 * logs a message at FINE Level
240 *
241 * @param message
242 * @param concerns a list of additional JUL loggers name (such as
243 * "INIT", "CONFIG" ,...)
244 * @return true
245 */
246 public boolean debug(Object message, String... concerns) {
247 return logMessage(Level.FINE, message, concerns);
248 }
249
250 /**
251 * logs a message at FINE Level
252 *
253 * @param message
254 * @param concerns a list of additional JUL loggers name (such as
255 * "INIT", "CONFIG" ,...)
256 * @return true
257 */
258 public boolean fine(Object message, String... concerns) {
259 return logMessage(Level.FINE, message, concerns);
260 }
261
262 /**
263 * logs a message at INFO Level
264 *
265 * @param message
266 * @param concerns a list of additional JUL loggers name (such as
267 * "INIT", "CONFIG" ,...)
268 * @return true
269 */
270 public boolean info(Object message, String... concerns) {
271 return logMessage(Level.INFO, message, concerns);
272 }
273
274 /**
275 * logs a message at WARNING Level
276 *
277 * @param message
278 * @param concerns a list of additional JUL loggers name (such as
279 * "INIT", "CONFIG" ,...)
280 * @return true
281 */
282 public boolean warn(Object message, String... concerns) {
283 return logMessage(Level.WARNING, message, concerns);
284 }
285
286 /**
287 * logs a message at WARNING Level
288 *
289 * @param message
290 * @param concerns a list of additional JUL loggers name (such as
291 * "INIT", "CONFIG" ,...)
292 * @return true
293 */
294 public boolean warning(Object message, String... concerns) {
295 return logMessage(Level.WARNING, message, concerns);
296 }
297
298 /**
299 * logs a message at SEVERE Level (use preferably the error method that
300 * uses a <TT>Throwable</TT> parameter)
301 *
302 * @param message
303 * @param concerns a list of additional JUL loggers name (such as
304 * "INIT", "CONFIG" ,...)
305 */
306 public void error(Object message, String... concerns) {
307 logMessage(Level.SEVERE, message, concerns);
308 }
309
310 /**
311 * logs a message at SEVERE Level (use preferably the fatal method that
312 * uses a <TT>Throwable</TT> parameter)
313 *
314 * @param message
315 * @param concerns a list of additional JUL loggers name (such as
316 * "INIT", "CONFIG" ,...)
317 */
318 public void fatal(Object message, String... concerns) {
319 logMessage(Level.SEVERE, message, concerns);
320 }
321
322 /**
323 * logs a message at SEVERE Level (use preferably the severe method that
324 * uses a <TT>Throwable</TT> parameter)
325 *
326 * @param message
327 * @param concerns a list of additional JUL loggers name (such as
328 * "INIT", "CONFIG" ,...)
329 */
330 public void severe(Object message, String... concerns) {
331 logMessage(Level.SEVERE, message, concerns);
332 }
333
334 /**
335 * tells if the FINE level is activated for the corresponding JUL
336 * Logger. (to be used if a subsequent logging call is costly)
337 *
338 * @return
339 */
340 public boolean isDebugEnabled() {
341 return julDelegate.isLoggable(Level.FINE);
342 }
343
344 /**
345 * tells if the INFO level is activated for the corresponding JUL
346 * Logger. (to be used if a subsequent logging call is costly)
347 *
348 * @return
349 */
350 public boolean isInfoEnabled() {
351 return julDelegate.isLoggable(Level.INFO);
352 }
353
354 /**
355 * utility method to forward a <TT>Throwable</TT> and a message to the
356 * loggers.
357 *
358 * @param level JUL level
359 * @param message
360 * @param throwable
361 * @param concerns a list of additional JUL loggers name (such as
362 * "INIT", "CONFIG" ,...)
363 */
364 protected void logSimpleThrowable(Level level, Object message, Throwable throwable, String... concerns) {
365 String msg = String.valueOf(message);
366 LogRecord record = createLogRecord(level, msg);
367 record.setThrown(throwable);
368 this.log(record, concerns);
369 }
370
371 /**
372 * logs a message at SEVERE Level
373 *
374 * @param message
375 * @param throwable
376 * @param concerns a list of additional JUL loggers name (such as
377 * "INIT", "CONFIG" ,...)
378 */
379 public void fatal(Object message, Throwable throwable, String... concerns) {
380 logSimpleThrowable(Level.SEVERE, message, throwable, concerns);
381 }
382
383 /**
384 * logs a message at SEVERE Level
385 *
386 * @param message
387 * @param throwable
388 * @param concerns a list of additional JUL loggers name (such as
389 * "INIT", "CONFIG" ,...)
390 */
391 public void severe(Object message, Throwable throwable, String... concerns) {
392 logSimpleThrowable(Level.SEVERE, message, throwable, concerns);
393 }
394
395 /**
396 * logs a message at SEVERE Level
397 *
398 * @param message
399 * @param throwable
400 * @param concerns a list of additional JUL loggers name (such as
401 * "INIT", "CONFIG" ,...)
402 */
403 public void error(Object message, Throwable throwable, String... concerns) {
404 logSimpleThrowable(Level.SEVERE, message, throwable, concerns);
405 }
406
407 /**
408 * logs a message at WARNING Level
409 *
410 * @param message
411 * @param throwable
412 * @param concerns a list of additional JUL loggers name (such as
413 * "INIT", "CONFIG" ,...)
414 */
415 public void warn(Object message, Throwable throwable, String... concerns) {
416 logSimpleThrowable(Level.WARNING, message, throwable, concerns);
417 }
418
419 /**
420 * logs a message at WARNING Level
421 *
422 * @param message
423 * @param throwable
424 * @param concerns a list of additional JUL loggers name (such as
425 * "INIT", "CONFIG" ,...)
426 */
427 public void warning(Object message, Throwable throwable, String... concerns) {
428 logSimpleThrowable(Level.WARNING, message, throwable, concerns);
429 }
430
431 /**
432 * invokes the <TT>throwing</TT> method on the corresponding JUL logger.
433 *
434 * @param sourceClass
435 * @param sourceMethod
436 * @param throwable
437 */
438 public void throwing(String sourceClass, String sourceMethod, Throwable throwable) {
439 julDelegate.throwing(sourceClass, sourceMethod, throwable);
440 }
441
442 /**
443 * logs a message at INFO Level
444 *
445 * @param message
446 * @param throwable
447 * @param concerns a list of additional JUL loggers name (such as
448 * "INIT", "CONFIG" ,...)
449 */
450 public void info(Object message, Throwable throwable, String... concerns) {
451 logSimpleThrowable(Level.INFO, message, throwable, concerns);
452 }
453
454 /**
455 * logs a message at FINE Level
456 *
457 * @param message
458 * @param throwable
459 * @param concerns a list of additional JUL loggers name (such as
460 * "INIT", "CONFIG" ,...)
461 */
462 public void debug(Object message, Throwable throwable, String... concerns) {
463 logSimpleThrowable(Level.FINE, message, throwable, concerns);
464 }
465
466 //END LOG4J
467 // BEGIN JUL-LIKE calls
468
469 /**
470 * tells if the corresponding JUL level is activated for the
471 * corresponding JUL Logger. (to be used if a subsequent logging call is
472 * costly)
473 *
474 * @return
475 */
476 public boolean isLoggable(Level level) {
477 return julDelegate.isLoggable(level);
478 }
479
480 /**
481 * utility method to send a <TT>LogRecord</TT> to the corresponding JUL
482 * logger and to a list of other loggers.
483 *
484 * @param record (should be created with the <TT>createLogRecord</TT>
485 * factory :otherwise stack information will be wrong)
486 * @param concerns a list of additional JUL loggers name (such as
487 * "INIT", "CONFIG" ,...)
488 */
489 public void log(LogRecord record, String... concerns) {
490 record.setLoggerName(julDelegate.getName());
491 julDelegate.log(record);
492 for (String concern : concerns) {
493 java.util.logging.Logger logger = java.util.logging.Logger.getLogger(concern);
494 record.setLoggerName(concern);
495 logger.log(record);
496 }
497 }
498 /*
499 public static Logger[] emptyArg = new Logger[0] ;
500 public void log(LogRecord record, Logger... otherLoggers) {
501 record.setLoggerName(julDelegate.getName());
502 julDelegate.log(record);
503 for(Logger logger: otherLoggers) {
504 logger.log(record, emptyArg) ;
505 }
506 }
507 */
508
509 /**
510 * same method as <TT>log(LogRecord, String... concerns</TT>
511 * but executed in a different Thread.
512 * <BR/>
513 * this might be interesting when the Thread that logs should be
514 * different from the Thread that handles the LogRecord
515 *
516 * @param delay in millis
517 * @param record
518 * @param concerns
519 */
520 public void decoupledLog(long delay, final LogRecord record, final String... concerns) {
521 executor.schedule(new Runnable() {
522 public void run() {
523 log(record, concerns);
524 }
525 }, delay, TimeUnit.MILLISECONDS);
526
527 }
528 /* too ambiguous
529 public void log(Level level, String message, String... concerns) {
530
531 }
532 */
533
534 /**
535 * does the same as the equivalent standard JUL method but forwards also
536 * to other Loggers.
537 *
538 * @param level JUL level
539 * @param message
540 * @param argument
541 * @param concerns a list of additional JUL loggers name (such as
542 * "INIT", "CONFIG" ,...)
543 * @return true
544 */
545 public boolean log(Level level, String message, Object argument, String... concerns) {
546 LogRecord record = createLogRecord(level, message);
547 record.setParameters(new Object[]{argument});
548 this.log(record, concerns);
549 return true;
550 }
551
552 /**
553 * same method as <TT>public boolean log(Level level, String message,
554 * Object argument, String... concerns)</TT>
555 * but executed in a different Thread.
556 * <BR/>
557 * this might be interesting when the Thread that logs should be
558 * different from the Thread that handles the LogRecord
559 *
560 * @param delay in Milliseconds
561 * @param level
562 * @param message
563 * @param argument
564 * @param concerns
565 * @return
566 */
567 public boolean decoupledLog(long delay, Level level, String message, Object argument, String... concerns) {
568 LogRecord record = createLogRecord(level, message);
569 record.setParameters(new Object[]{argument});
570 this.decoupledLog(delay, record, concerns);
571 return true;
572 }
573
574 /**
575 * does the same as the equivalent standard JUL method but forwards also
576 * to other Loggers.
577 *
578 * @param level JUL level
579 * @param message
580 * @param arguments
581 * @param concerns a list of additional JUL loggers name (such as
582 * "INIT", "CONFIG" ,...)
583 * @return true
584 */
585 public boolean log(Level level, String message, Object[] arguments, String... concerns) {
586 LogRecord record = createLogRecord(level, message);
587 record.setParameters(arguments);
588 this.log(record, concerns);
589 return true;
590 }
591
592 /**
593 * does the same as the corresponding <TT>log</TT> method but executed
594 * in a different Thread.
595 * <BR/>
596 * this might be interesting when the Thread that logs should be
597 * different from the Thread that handles the LogRecord
598 *
599 * @param delay in millis
600 * @param level
601 * @param message
602 * @param arguments
603 * @param concerns
604 * @return
605 */
606 public boolean decoupledLog(long delay, Level level, String message, Object[] arguments, String... concerns) {
607 LogRecord record = createLogRecord(level, message);
608 record.setParameters(arguments);
609 this.decoupledLog(delay, record, concerns);
610 return true;
611 }
612
613 /**
614 * does the same as the equivalent standard JUL method but forwards also
615 * to other Loggers.
616 *
617 * @param level JUL level
618 * @param message
619 * @param throwable
620 * @param concerns a list of additional JUL loggers name (such as
621 * "INIT", "CONFIG" ,...)
622 * @return true
623 */
624 public void log(Level level, String message, Throwable throwable, String... concerns) {
625 LogRecord record = createLogRecord(level, message);
626 record.setThrown(throwable);
627 this.log(record, concerns);
628 }
629
630 /**
631 * does the same as the corresponding <TT>log</TT> method but executed
632 * in a different Thread.
633 * <BR/>
634 * this might be interesting when the Thread that logs should be
635 * different from the Thread that handles the LogRecord
636 *
637 * @param delay in millis
638 * @param level
639 * @param message
640 * @param throwable
641 * @param concerns
642 */
643 public void decoupledLog(long delay, Level level, String message, Throwable throwable, String... concerns) {
644 LogRecord record = createLogRecord(level, message);
645 record.setThrown(throwable);
646 this.decoupledLog(delay, record, concerns);
647 }
648
649
650 /*
651 **historic code : think about getting rid of it!
652 */
653 @Deprecated
654 public static void configure() {
655 //DONE in static block : check if static code had been run (otherwise?)
656 //LogManagement.isConfigInitialized();
657 }
658 }