View Javadoc

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