View Javadoc

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     /**
500      * same method as <TT>log(LogRecord, String... concerns</TT>
501      * but executed in a different Thread.
502      * <BR/>
503      * this might be interesting when the Thread that logs should be
504      * different from the Thread that handles the LogRecord
505      *
506      * @param delay    in millis
507      * @param record
508      * @param concerns
509      */
510     public void decoupledLog(long delay, final LogRecord record, final String... concerns) {
511         executor.schedule(new Runnable() {
512             public void run() {
513                 log(record, concerns);
514             }
515         }, delay, TimeUnit.MILLISECONDS);
516 
517     }
518     /* too ambiguous
519      public void log(Level level, String message,  String... concerns) {
520 
521      }
522      */
523 
524     /**
525      * does the same as the equivalent standard JUL method but forwards also
526      * to other Loggers.
527      *
528      * @param level    JUL level
529      * @param message
530      * @param argument
531      * @param concerns a list of additional JUL loggers name (such as
532      *                 "INIT", "CONFIG" ,...)
533      * @return true
534      */
535     public boolean log(Level level, String message, Object argument, String... concerns) {
536         LogRecord record = createLogRecord(level, message);
537         record.setParameters(new Object[]{argument});
538         this.log(record, concerns);
539         return true;
540     }
541 
542     /**
543      * same method as <TT>public boolean log(Level level, String message,
544      * Object argument, String... concerns)</TT>
545      * but executed in a different Thread.
546      * <BR/>
547      * this might be interesting when the Thread that logs should be
548      * different from the Thread that handles the LogRecord
549      *
550      * @param delay    in Milliseconds
551      * @param level
552      * @param message
553      * @param argument
554      * @param concerns
555      * @return
556      */
557     public boolean decoupledLog(long delay, Level level, String message, Object argument, String... concerns) {
558         LogRecord record = createLogRecord(level, message);
559         record.setParameters(new Object[]{argument});
560         this.decoupledLog(delay, record, concerns);
561         return true;
562     }
563 
564     /**
565      * does the same as the equivalent standard JUL method but forwards also
566      * to other Loggers.
567      *
568      * @param level     JUL level
569      * @param message
570      * @param arguments
571      * @param concerns  a list of additional JUL loggers name (such as
572      *                  "INIT", "CONFIG" ,...)
573      * @return true
574      */
575     public boolean log(Level level, String message, Object[] arguments, String... concerns) {
576         LogRecord record = createLogRecord(level, message);
577         record.setParameters(arguments);
578         this.log(record, concerns);
579         return true;
580     }
581 
582     /**
583      * does the same as the corresponding <TT>log</TT> method but executed
584      * in a different Thread.
585      * <BR/>
586      * this might be interesting when the Thread that logs should be
587      * different from the Thread that handles the LogRecord
588      *
589      * @param delay     in millis
590      * @param level
591      * @param message
592      * @param arguments
593      * @param concerns
594      * @return
595      */
596     public boolean decoupledLog(long delay, Level level, String message, Object[] arguments, String... concerns) {
597         LogRecord record = createLogRecord(level, message);
598         record.setParameters(arguments);
599         this.decoupledLog(delay, record, concerns);
600         return true;
601     }
602 
603     /**
604      * does the same as the equivalent standard JUL method but forwards also
605      * to other Loggers.
606      *
607      * @param level     JUL level
608      * @param message
609      * @param throwable
610      * @param concerns  a list of additional JUL loggers name (such as
611      *                  "INIT", "CONFIG" ,...)
612      * @return true
613      */
614     public void log(Level level, String message, Throwable throwable, String... concerns) {
615         LogRecord record = createLogRecord(level, message);
616         record.setThrown(throwable);
617         this.log(record, concerns);
618     }
619 
620     /**
621      * does the same as the corresponding <TT>log</TT> method but executed
622      * in a different Thread.
623      * <BR/>
624      * this might be interesting when the Thread that logs should be
625      * different from the Thread that handles the LogRecord
626      *
627      * @param delay     in millis
628      * @param level
629      * @param message
630      * @param throwable
631      * @param concerns
632      */
633     public void decoupledLog(long delay, Level level, String message, Throwable throwable, String... concerns) {
634         LogRecord record = createLogRecord(level, message);
635         record.setThrown(throwable);
636         this.decoupledLog(delay, record, concerns);
637     }
638 
639 
640     /*
641     **historic code : think about getting rid of it!
642      */
643     @Deprecated
644     public static void configure() {
645         //DONE in static block : check if static code had been run (otherwise?)
646         //LogManagement.isConfigInitialized();
647     }
648 }