View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
4   //  ------------------------------------------------------------------------
5   //  All rights reserved. This program and the accompanying materials
6   //  are made available under the terms of the Eclipse Public License v1.0
7   //  and Apache License v2.0 which accompanies this distribution.
8   //
9   //      The Eclipse Public License is available at
10  //      http://www.eclipse.org/legal/epl-v10.html
11  //
12  //      The Apache License v2.0 is available at
13  //      http://www.opensource.org/licenses/apache2.0.php
14  //
15  //  You may elect to redistribute this code under either of these licenses.
16  //  ========================================================================
17  //
18  
19  package org.eclipse.jetty.util.log;
20  
21  import java.io.PrintStream;
22  import java.security.AccessControlException;
23  import java.util.Properties;
24  
25  import org.eclipse.jetty.util.DateCache;
26  import org.eclipse.jetty.util.annotation.ManagedAttribute;
27  import org.eclipse.jetty.util.annotation.ManagedObject;
28  
29  /**
30   * StdErr Logging implementation. 
31   * <p>
32   * A Jetty {@link Logger} that sends all logs to STDERR ({@link System#err}) with basic formatting.
33   * <p>
34   * Supports named loggers, and properties based configuration. 
35   * <p>
36   * Configuration Properties:
37   * <dl>
38   *   <dt>${name|hierarchy}.LEVEL=(ALL|DEBUG|INFO|WARN|OFF)</dt>
39   *   <dd>
40   *   Sets the level that the Logger should log at.<br/>
41   *   Names can be a package name, or a fully qualified class name.<br/>
42   *   Default: INFO<br/>
43   *   <br/>
44   *   Examples:
45   *   <dl>
46   *   <dt>org.eclipse.jetty.LEVEL=WARN</dt>
47   *   <dd>indicates that all of the jetty specific classes, in any package that 
48   *   starts with <code>org.eclipse.jetty</code> should log at level WARN.</dd>
49   *   <dt>org.eclipse.jetty.io.ChannelEndPoint.LEVEL=ALL</dt>
50   *   <dd>indicates that the specific class, ChannelEndPoint, should log all
51   *   logging events that it can generate, including DEBUG, INFO, WARN (and even special
52   *   internally ignored exception cases).</dd>
53   *   </dl>  
54   *   </dd>
55   *   
56   *   <dt>${name}.SOURCE=(true|false)</dt>
57   *   <dd>
58   *   Logger specific, attempt to print the java source file name and line number
59   *   where the logging event originated from.<br/>
60   *   Name must be a fully qualified class name (package name hierarchy is not supported
61   *   by this configurable)<br/>
62   *   Warning: this is a slow operation and will have an impact on performance!<br/>
63   *   Default: false
64   *   </dd>
65   *   
66   *   <dt>${name}.STACKS=(true|false)</dt>
67   *   <dd>
68   *   Logger specific, control the display of stacktraces.<br/>
69   *   Name must be a fully qualified class name (package name hierarchy is not supported
70   *   by this configurable)<br/>
71   *   Default: true
72   *   </dd>
73   *   
74   *   <dt>org.eclipse.jetty.util.log.stderr.SOURCE=(true|false)</dt>
75   *   <dd>Special Global Configuration, attempt to print the java source file name and line number
76   *   where the logging event originated from.<br/>
77   *   Default: false
78   *   </dd>
79   *   
80   *   <dt>org.eclipse.jetty.util.log.stderr.LONG=(true|false)</dt>
81   *   <dd>Special Global Configuration, when true, output logging events to STDERR using
82   *   long form, fully qualified class names.  when false, use abbreviated package names<br/>
83   *   Default: false
84   *   </dd>
85   * </dl>
86   */
87  @ManagedObject("Jetty StdErr Logging Implementation")
88  public class StdErrLog extends AbstractLogger
89  {
90      private static final String EOL = System.getProperty("line.separator");
91      private static DateCache _dateCache;
92      private static final Properties __props = new Properties();
93  
94      private final static boolean __source = Boolean.parseBoolean(Log.__props.getProperty("org.eclipse.jetty.util.log.SOURCE",
95              Log.__props.getProperty("org.eclipse.jetty.util.log.stderr.SOURCE","false")));
96      private final static boolean __long = Boolean.parseBoolean(Log.__props.getProperty("org.eclipse.jetty.util.log.stderr.LONG","false"));
97  
98      static
99      {
100         __props.putAll(Log.__props);
101 
102         String deprecatedProperties[] =
103         { "DEBUG", "org.eclipse.jetty.util.log.DEBUG", "org.eclipse.jetty.util.log.stderr.DEBUG" };
104 
105         // Toss a message to users about deprecated system properties
106         for (String deprecatedProp : deprecatedProperties)
107         {
108             if (System.getProperty(deprecatedProp) != null)
109             {
110                 System.err.printf("System Property [%s] has been deprecated! (Use org.eclipse.jetty.LEVEL=DEBUG instead)%n",deprecatedProp);
111             }
112         }
113 
114         try
115         {
116             _dateCache = new DateCache("yyyy-MM-dd HH:mm:ss");
117         }
118         catch (Exception x)
119         {
120             x.printStackTrace(System.err);
121         }
122     }
123 
124     public static final int LEVEL_ALL = 0;
125     public static final int LEVEL_DEBUG = 1;
126     public static final int LEVEL_INFO = 2;
127     public static final int LEVEL_WARN = 3;
128     public static final int LEVEL_OFF = 10;
129 
130     private int _level = LEVEL_INFO;
131     // Level that this Logger was configured as (remembered in special case of .setDebugEnabled())
132     private int _configuredLevel;
133     private PrintStream _stderr = null;
134     private boolean _source = __source;
135     // Print the long form names, otherwise use abbreviated
136     private boolean _printLongNames = __long;
137     // The full log name, as provided by the system.
138     private final String _name;
139     // The abbreviated log name (used by default, unless _long is specified)
140     private final String _abbrevname;
141     private boolean _hideStacks = false;
142 
143     /**
144      * Obtain a StdErrLog reference for the specified class, a convenience method used most often during testing to allow for control over a specific logger.
145      * <p>
146      * Must be actively using StdErrLog as the Logger implementation.
147      * 
148      * @param clazz
149      *            the Class reference for the logger to use.
150      * @return the StdErrLog logger
151      * @throws RuntimeException
152      *             if StdErrLog is not the active Logger implementation.
153      */
154     public static StdErrLog getLogger(Class<?> clazz)
155     {
156         Logger log = Log.getLogger(clazz);
157         if (log instanceof StdErrLog)
158         {
159             return (StdErrLog)log;
160         }
161         throw new RuntimeException("Logger for " + clazz + " is not of type StdErrLog");
162     }
163 
164     /**
165      * Construct an anonymous StdErrLog (no name).
166      * <p>
167      * NOTE: Discouraged usage!
168      */
169     public StdErrLog()
170     {
171         this(null);
172     }
173 
174     /**
175      * Construct a named StdErrLog using the {@link Log} defined properties
176      * 
177      * @param name
178      *            the name of the logger
179      */
180     public StdErrLog(String name)
181     {
182         this(name,__props);
183     }
184 
185     /**
186      * Construct a named Logger using the provided properties to configure logger.
187      * 
188      * @param name
189      *            the name of the logger
190      * @param props
191      *            the configuration properties
192      */
193     public StdErrLog(String name, Properties props)
194     {
195         if (props!=null && props!=__props)
196             __props.putAll(props);
197         this._name = name == null?"":name;
198         this._abbrevname = condensePackageString(this._name);
199         this._level = getLoggingLevel(props,this._name);
200         this._configuredLevel = this._level;
201 
202         try
203         {
204             String source = getLoggingProperty(props,_name,"SOURCE");
205             _source = source==null?__source:Boolean.parseBoolean(source);
206         }
207         catch (AccessControlException ace)
208         {
209             _source = __source;
210         }
211 
212         try
213         {
214             // allow stacktrace display to be controlled by properties as well
215             String stacks = getLoggingProperty(props,_name,"STACKS");
216             _hideStacks = stacks==null?false:!Boolean.parseBoolean(stacks);
217         }
218         catch (AccessControlException ignore)
219         {
220             /* ignore */
221         }        
222     }
223 
224     /**
225      * Get the Logging Level for the provided log name. Using the FQCN first, then each package segment from longest to
226      * shortest.
227      *
228      * @param props
229      *            the properties to check
230      * @param name
231      *            the name to get log for
232      * @return the logging level
233      */
234     public static int getLoggingLevel(Properties props, final String name)
235     {
236         // Calculate the level this named logger should operate under.
237         // Checking with FQCN first, then each package segment from longest to shortest.
238         String nameSegment = name;
239 
240         while ((nameSegment != null) && (nameSegment.length() > 0))
241         {
242             String levelStr = props.getProperty(nameSegment + ".LEVEL");
243             // System.err.printf("[StdErrLog.CONFIG] Checking for property [%s.LEVEL] = %s%n",nameSegment,levelStr);
244             int level = getLevelId(nameSegment + ".LEVEL",levelStr);
245             if (level != (-1))
246             {
247                 return level;
248             }
249 
250             // Trim and try again.
251             int idx = nameSegment.lastIndexOf('.');
252             if (idx >= 0)
253             {
254                 nameSegment = nameSegment.substring(0,idx);
255             }
256             else
257             {
258                 nameSegment = null;
259             }
260         }
261 
262         // Default Logging Level
263         return getLevelId("log.LEVEL",props.getProperty("log.LEVEL","INFO"));
264     }
265     
266     public static String getLoggingProperty(Properties props, String name, String property)
267     {
268         // Calculate the level this named logger should operate under.
269         // Checking with FQCN first, then each package segment from longest to shortest.
270         String nameSegment = name;
271 
272         while ((nameSegment != null) && (nameSegment.length() > 0))
273         {
274             String s = props.getProperty(nameSegment+"."+property);
275             if (s!=null)
276                 return s;
277 
278             // Trim and try again.
279             int idx = nameSegment.lastIndexOf('.');
280             nameSegment = (idx >= 0)?nameSegment.substring(0,idx):null;
281         }
282 
283         return null;
284     }
285 
286     protected static int getLevelId(String levelSegment, String levelName)
287     {
288         if (levelName == null)
289         {
290             return -1;
291         }
292         String levelStr = levelName.trim();
293         if ("ALL".equalsIgnoreCase(levelStr))
294         {
295             return LEVEL_ALL;
296         }
297         else if ("DEBUG".equalsIgnoreCase(levelStr))
298         {
299             return LEVEL_DEBUG;
300         }
301         else if ("INFO".equalsIgnoreCase(levelStr))
302         {
303             return LEVEL_INFO;
304         }
305         else if ("WARN".equalsIgnoreCase(levelStr))
306         {
307             return LEVEL_WARN;
308         }
309         else if ("OFF".equalsIgnoreCase(levelStr))
310         {
311             return LEVEL_OFF;
312         }
313 
314         System.err.println("Unknown StdErrLog level [" + levelSegment + "]=[" + levelStr + "], expecting only [ALL, DEBUG, INFO, WARN, OFF] as values.");
315         return -1;
316     }
317 
318     /**
319      * Condenses a classname by stripping down the package name to just the first character of each package name
320      * segment.Configured
321      * <p>
322      *
323      * <pre>
324      * Examples:
325      * "org.eclipse.jetty.test.FooTest"           = "oejt.FooTest"
326      * "org.eclipse.jetty.server.logging.LogTest" = "orjsl.LogTest"
327      * </pre>
328      *
329      * @param classname
330      *            the fully qualified class name
331      * @return the condensed name
332      */
333     protected static String condensePackageString(String classname)
334     {
335         String parts[] = classname.split("\\.");
336         StringBuilder dense = new StringBuilder();
337         for (int i = 0; i < (parts.length - 1); i++)
338         {
339             dense.append(parts[i].charAt(0));
340         }
341         if (dense.length() > 0)
342         {
343             dense.append('.');
344         }
345         dense.append(parts[parts.length - 1]);
346         return dense.toString();
347     }
348 
349     public String getName()
350     {
351         return _name;
352     }
353 
354     public void setPrintLongNames(boolean printLongNames)
355     {
356         this._printLongNames = printLongNames;
357     }
358 
359     public boolean isPrintLongNames()
360     {
361         return this._printLongNames;
362     }
363 
364     public boolean isHideStacks()
365     {
366         return _hideStacks;
367     }
368 
369     public void setHideStacks(boolean hideStacks)
370     {
371         _hideStacks = hideStacks;
372     }
373 
374     /* ------------------------------------------------------------ */
375     /**
376      * Is the source of a log, logged
377      *
378      * @return true if the class, method, file and line number of a log is logged.
379      */
380     public boolean isSource()
381     {
382         return _source;
383     }
384 
385     /* ------------------------------------------------------------ */
386     /**
387      * Set if a log source is logged.
388      *
389      * @param source
390      *            true if the class, method, file and line number of a log is logged.
391      */
392     public void setSource(boolean source)
393     {
394         _source = source;
395     }
396 
397     public void warn(String msg, Object... args)
398     {
399         if (_level <= LEVEL_WARN)
400         {
401             StringBuilder buffer = new StringBuilder(64);
402             format(buffer,":WARN:",msg,args);
403             (_stderr==null?System.err:_stderr).println(buffer);
404         }
405     }
406 
407     public void warn(Throwable thrown)
408     {
409         warn("",thrown);
410     }
411 
412     public void warn(String msg, Throwable thrown)
413     {
414         if (_level <= LEVEL_WARN)
415         {
416             StringBuilder buffer = new StringBuilder(64);
417             format(buffer,":WARN:",msg,thrown);
418             (_stderr==null?System.err:_stderr).println(buffer);
419         }
420     }
421 
422     public void info(String msg, Object... args)
423     {
424         if (_level <= LEVEL_INFO)
425         {
426             StringBuilder buffer = new StringBuilder(64);
427             format(buffer,":INFO:",msg,args);
428             (_stderr==null?System.err:_stderr).println(buffer);
429         }
430     }
431 
432     public void info(Throwable thrown)
433     {
434         info("",thrown);
435     }
436 
437     public void info(String msg, Throwable thrown)
438     {
439         if (_level <= LEVEL_INFO)
440         {
441             StringBuilder buffer = new StringBuilder(64);
442             format(buffer,":INFO:",msg,thrown);
443             (_stderr==null?System.err:_stderr).println(buffer);
444         }
445     }
446 
447     @ManagedAttribute("is debug enabled for root logger Log.LOG")
448     public boolean isDebugEnabled()
449     {
450         return (_level <= LEVEL_DEBUG);
451     }
452 
453     /**
454      * Legacy interface where a programmatic configuration of the logger level
455      * is done as a wholesale approach.
456      */
457     @Override
458     public void setDebugEnabled(boolean enabled)
459     {
460         if (enabled)
461         {
462             this._level = LEVEL_DEBUG;
463 
464             for (Logger log : Log.getLoggers().values())
465             {                
466                 if (log.getName().startsWith(getName()) && log instanceof StdErrLog)
467                     ((StdErrLog)log).setLevel(LEVEL_DEBUG);
468             }
469         }
470         else
471         {
472             this._level = this._configuredLevel;
473 
474             for (Logger log : Log.getLoggers().values())
475             {
476                 if (log.getName().startsWith(getName()) && log instanceof StdErrLog)
477                     ((StdErrLog)log).setLevel(((StdErrLog)log)._configuredLevel);
478             }
479         }
480     }
481 
482     public int getLevel()
483     {
484         return _level;
485     }
486 
487     /**
488      * Set the level for this logger.
489      * <p>
490      * Available values ({@link StdErrLog#LEVEL_ALL}, {@link StdErrLog#LEVEL_DEBUG}, {@link StdErrLog#LEVEL_INFO},
491      * {@link StdErrLog#LEVEL_WARN})
492      *
493      * @param level
494      *            the level to set the logger to
495      */
496     public void setLevel(int level)
497     {
498         this._level = level;
499     }
500 
501     public void setStdErrStream(PrintStream stream)
502     {
503         this._stderr = stream==System.err?null:stream;
504     }
505 
506     public void debug(String msg, Object... args)
507     {
508         if (_level <= LEVEL_DEBUG)
509         {
510             StringBuilder buffer = new StringBuilder(64);
511             format(buffer,":DBUG:",msg,args);
512             (_stderr==null?System.err:_stderr).println(buffer);
513         }
514     }
515 
516     public void debug(Throwable thrown)
517     {
518         debug("",thrown);
519     }
520 
521     public void debug(String msg, Throwable thrown)
522     {
523         if (_level <= LEVEL_DEBUG)
524         {
525             StringBuilder buffer = new StringBuilder(64);
526             format(buffer,":DBUG:",msg,thrown);
527             (_stderr==null?System.err:_stderr).println(buffer);
528         }
529     }
530 
531     private void format(StringBuilder buffer, String level, String msg, Object... args)
532     {
533         long now = System.currentTimeMillis();
534         int ms=(int)(now%1000);
535         String d = _dateCache.now();
536         tag(buffer,d,ms,level);
537         format(buffer,msg,args);
538     }
539 
540     private void format(StringBuilder buffer, String level, String msg, Throwable thrown)
541     {
542         format(buffer,level,msg);
543         if (isHideStacks())
544         {
545             format(buffer,": "+String.valueOf(thrown));
546         }
547         else
548         {
549             format(buffer,thrown);
550         }
551     }
552 
553     private void tag(StringBuilder buffer, String d, int ms, String tag)
554     {
555         buffer.setLength(0);
556         buffer.append(d);
557         if (ms > 99)
558         {
559             buffer.append('.');
560         }
561         else if (ms > 9)
562         {
563             buffer.append(".0");
564         }
565         else
566         {
567             buffer.append(".00");
568         }
569         buffer.append(ms).append(tag);
570         if (_printLongNames)
571         {
572             buffer.append(_name);
573         }
574         else
575         {
576             buffer.append(_abbrevname);
577         }
578         buffer.append(':');
579         buffer.append(Thread.currentThread().getName()).append(": ");
580         if (_source)
581         {
582             Throwable source = new Throwable();
583             StackTraceElement[] frames = source.getStackTrace();
584             for (int i = 0; i < frames.length; i++)
585             {
586                 final StackTraceElement frame = frames[i];
587                 String clazz = frame.getClassName();
588                 if (clazz.equals(StdErrLog.class.getName()) || clazz.equals(Log.class.getName()))
589                 {
590                     continue;
591                 }
592                 if (!_printLongNames && clazz.startsWith("org.eclipse.jetty."))
593                 {
594                     buffer.append(condensePackageString(clazz));
595                 }
596                 else
597                 {
598                     buffer.append(clazz);
599                 }
600                 buffer.append('#').append(frame.getMethodName());
601                 if (frame.getFileName() != null)
602                 {
603                     buffer.append('(').append(frame.getFileName()).append(':').append(frame.getLineNumber()).append(')');
604                 }
605                 buffer.append(':');
606                 break;
607             }
608         }
609     }
610 
611     private void format(StringBuilder builder, String msg, Object... args)
612     {
613         if (msg == null)
614         {
615             msg = "";
616             for (int i = 0; i < args.length; i++)
617             {
618                 msg += "{} ";
619             }
620         }
621         String braces = "{}";
622         int start = 0;
623         for (Object arg : args)
624         {
625             int bracesIndex = msg.indexOf(braces,start);
626             if (bracesIndex < 0)
627             {
628                 escape(builder,msg.substring(start));
629                 builder.append(" ");
630                 builder.append(arg);
631                 start = msg.length();
632             }
633             else
634             {
635                 escape(builder,msg.substring(start,bracesIndex));
636                 builder.append(String.valueOf(arg));
637                 start = bracesIndex + braces.length();
638             }
639         }
640         escape(builder,msg.substring(start));
641     }
642 
643     private void escape(StringBuilder builder, String string)
644     {
645         for (int i = 0; i < string.length(); ++i)
646         {
647             char c = string.charAt(i);
648             if (Character.isISOControl(c))
649             {
650                 if (c == '\n')
651                 {
652                     builder.append('|');
653                 }
654                 else if (c == '\r')
655                 {
656                     builder.append('<');
657                 }
658                 else
659                 {
660                     builder.append('?');
661                 }
662             }
663             else
664             {
665                 builder.append(c);
666             }
667         }
668     }
669 
670     private void format(StringBuilder buffer, Throwable thrown)
671     {
672         if (thrown == null)
673         {
674             buffer.append("null");
675         }
676         else
677         {
678             buffer.append(EOL);
679             format(buffer,thrown.toString());
680             StackTraceElement[] elements = thrown.getStackTrace();
681             for (int i = 0; elements != null && i < elements.length; i++)
682             {
683                 buffer.append(EOL).append("\tat ");
684                 format(buffer,elements[i].toString());
685             }
686 
687             Throwable cause = thrown.getCause();
688             if (cause != null && cause != thrown)
689             {
690                 buffer.append(EOL).append("Caused by: ");
691                 format(buffer,cause);
692             }
693         }
694     }
695 
696 
697     /**
698      * Create a Child Logger of this Logger.
699      */
700     @Override
701     protected Logger newLogger(String fullname)
702     {
703         StdErrLog logger = new StdErrLog(fullname);
704         // Preserve configuration for new loggers configuration
705         logger.setPrintLongNames(_printLongNames);
706         logger._stderr = this._stderr;
707 
708         // Force the child to have any programmatic configuration
709         if (_level!=_configuredLevel)
710             logger._level=_level;
711 
712         return logger;
713     }
714 
715     @Override
716     public String toString()
717     {
718         StringBuilder s = new StringBuilder();
719         s.append("StdErrLog:");
720         s.append(_name);
721         s.append(":LEVEL=");
722         switch (_level)
723         {
724             case LEVEL_ALL:
725                 s.append("ALL");
726                 break;
727             case LEVEL_DEBUG:
728                 s.append("DEBUG");
729                 break;
730             case LEVEL_INFO:
731                 s.append("INFO");
732                 break;
733             case LEVEL_WARN:
734                 s.append("WARN");
735                 break;
736             default:
737                 s.append("?");
738                 break;
739         }
740         return s.toString();
741     }
742 
743     public static void setProperties(Properties props)
744     {
745         __props.clear();
746         __props.putAll(props);
747     }
748 
749     public void ignore(Throwable ignored)
750     {
751         if (_level <= LEVEL_ALL)
752         {
753             StringBuilder buffer = new StringBuilder(64);
754             format(buffer,":IGNORED:","",ignored);
755             (_stderr==null?System.err:_stderr).println(buffer);
756         }
757     }
758 }