View Javadoc
1   /**
2    * Copyright (c) 2004-2011 QOS.ch
3    * All rights reserved.
4    *
5    * Permission is hereby granted, free  of charge, to any person obtaining
6    * a  copy  of this  software  and  associated  documentation files  (the
7    * "Software"), to  deal in  the Software without  restriction, including
8    * without limitation  the rights to  use, copy, modify,  merge, publish,
9    * distribute,  sublicense, and/or sell  copies of  the Software,  and to
10   * permit persons to whom the Software  is furnished to do so, subject to
11   * the following conditions:
12   *
13   * The  above  copyright  notice  and  this permission  notice  shall  be
14   * included in all copies or substantial portions of the Software.
15   *
16   * THE  SOFTWARE IS  PROVIDED  "AS  IS", WITHOUT  WARRANTY  OF ANY  KIND,
17   * EXPRESS OR  IMPLIED, INCLUDING  BUT NOT LIMITED  TO THE  WARRANTIES OF
18   * MERCHANTABILITY,    FITNESS    FOR    A   PARTICULAR    PURPOSE    AND
19   * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20   * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21   * OF CONTRACT, TORT OR OTHERWISE,  ARISING FROM, OUT OF OR IN CONNECTION
22   * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23   *
24   */
25  package org.slf4j.jul;
26  
27  import java.util.logging.Level;
28  import java.util.logging.LogRecord;
29  
30  import org.slf4j.Logger;
31  import org.slf4j.Marker;
32  import org.slf4j.event.EventConstants;
33  import org.slf4j.event.LoggingEvent;
34  import org.slf4j.helpers.AbstractLogger;
35  import org.slf4j.helpers.FormattingTuple;
36  import org.slf4j.helpers.LegacyAbstractLogger;
37  import org.slf4j.helpers.MessageFormatter;
38  import org.slf4j.helpers.NormalizedParameters;
39  import org.slf4j.helpers.SubstituteLogger;
40  import org.slf4j.spi.DefaultLoggingEventBuilder;
41  import org.slf4j.spi.LocationAwareLogger;
42  
43  /**
44   * A wrapper over {@link java.util.logging.Logger java.util.logging.Logger} in
45   * conformity with the {@link Logger} interface. Note that the logging levels
46   * mentioned in this class refer to those defined in the java.util.logging
47   * package.
48   * 
49   * @author Ceki Gülcü
50   * @author Peter Royal
51   */
52  public final class JDK14LoggerAdapter extends LegacyAbstractLogger implements LocationAwareLogger {
53  
54      private static final long serialVersionUID = -8053026990503422791L;
55  
56      transient final java.util.logging.Logger logger;
57  
58      // WARN: JDK14LoggerAdapter constructor should have only package access so
59      // that only JDK14LoggerFactory be able to create one.
60      JDK14LoggerAdapter(java.util.logging.Logger logger) {
61          this.logger = logger;
62          this.name = logger.getName();
63      }
64  
65      /**
66       * Is this logger instance enabled for the FINEST level?
67       * 
68       * @return True if this Logger is enabled for level FINEST, false otherwise.
69       */
70      public boolean isTraceEnabled() {
71          return logger.isLoggable(Level.FINEST);
72      }
73  
74      /**
75       * Is this logger instance enabled for the FINE level?
76       * 
77       * @return True if this Logger is enabled for level FINE, false otherwise.
78       */
79      public boolean isDebugEnabled() {
80          return logger.isLoggable(Level.FINE);
81      }
82  
83      /**
84       * Is this logger instance enabled for the INFO level?
85       * 
86       * @return True if this Logger is enabled for the INFO level, false otherwise.
87       */
88      public boolean isInfoEnabled() {
89          return logger.isLoggable(Level.INFO);
90      }
91  
92      /**
93       * Is this logger instance enabled for the WARNING level?
94       * 
95       * @return True if this Logger is enabled for the WARNING level, false
96       *         otherwise.
97       */
98      public boolean isWarnEnabled() {
99          return logger.isLoggable(Level.WARNING);
100     }
101 
102     /**
103      * Is this logger instance enabled for level SEVERE?
104      * 
105      * @return True if this Logger is enabled for level SEVERE, false otherwise.
106      */
107     public boolean isErrorEnabled() {
108         return logger.isLoggable(Level.SEVERE);
109     }
110 
111     // /**
112     // * Log the message at the specified level with the specified throwable if any.
113     // * This method creates a LogRecord and fills in caller date before calling
114     // * this instance's JDK14 logger.
115     // *
116     // * See bug report #13 for more details.
117     // *
118     // * @param level
119     // * @param msg
120     // * @param t
121     // */
122     // private void log(String callerFQCN, Level level, String msg, Throwable t) {
123     // // millis and thread are filled by the constructor
124     // LogRecord record = new LogRecord(level, msg);
125     // record.setLoggerName(getName());
126     // record.setThrown(t);
127     // // Note: parameters in record are not set because SLF4J only
128     // // supports a single formatting style
129     // fillCallerData(callerFQCN, record);
130     // logger.log(record);
131     // }
132 
133     /**
134      * Log the message at the specified level with the specified throwable if any.
135      * This method creates a LogRecord and fills in caller date before calling this
136      * instance's JDK14 logger.
137      */
138     @Override
139     protected void handleNormalizedLoggingCall(org.slf4j.event.Level level, Marker marker, String msg, Object[] args, Throwable throwable) {
140         innerNormalizedLoggingCallHandler(getFullyQualifiedCallerName(), level, marker, msg, args, throwable);
141     }
142 
143     private void innerNormalizedLoggingCallHandler(String fqcn, org.slf4j.event.Level level, Marker marker, String msg, Object[] args, Throwable throwable) {
144         // millis and thread are filled by the constructor
145         Level julLevel = slf4jLevelToJULLevel(level);
146         String formattedMessage = MessageFormatter.basicArrayFormat(msg, args);
147         LogRecord record = new LogRecord(julLevel, formattedMessage);
148 
149         // https://jira.qos.ch/browse/SLF4J-13
150         record.setLoggerName(getName());
151         record.setThrown(throwable);
152         // Note: parameters in record are not set because SLF4J only
153         // supports a single formatting style
154         // See also https://jira.qos.ch/browse/SLF4J-10
155         fillCallerData(fqcn, record);
156         logger.log(record);
157     }
158 
159     @Override
160     protected String getFullyQualifiedCallerName() {
161         return SELF;
162     }
163 
164     @Override
165     public void log(Marker marker, String callerFQCN, int slf4jLevelInt, String message, Object[] arguments, Throwable throwable) {
166 
167         org.slf4j.event.Level slf4jLevel = org.slf4j.event.Level.intToLevel(slf4jLevelInt);
168         Level julLevel = slf4jLevelIntToJULLevel(slf4jLevelInt);
169 
170         if (logger.isLoggable(julLevel)) {
171             NormalizedParameters np = NormalizedParameters.normalize(message, arguments, throwable);
172             innerNormalizedLoggingCallHandler(callerFQCN, slf4jLevel, marker, np.getMessage(), np.getArguments(), np.getThrowable());
173         }
174     }
175 
176     /**
177      * Fill in caller data if possible.
178      * 
179      * @param record The record to update
180      */
181     final private void fillCallerData(String callerFQCN, LogRecord record) {
182         StackTraceElement[] steArray = new Throwable().getStackTrace();
183 
184         int selfIndex = -1;
185         for (int i = 0; i < steArray.length; i++) {
186             final String className = steArray[i].getClassName();
187 
188             if (barrierMatch(callerFQCN, className)) {
189                 selfIndex = i;
190                 break;
191             }
192         }
193 
194         int found = -1;
195         for (int i = selfIndex + 1; i < steArray.length; i++) {
196             final String className = steArray[i].getClassName();
197             if (!(barrierMatch(callerFQCN, className))) {
198                 found = i;
199                 break;
200             }
201         }
202 
203         if (found != -1) {
204             StackTraceElement ste = steArray[found];
205             // setting the class name has the side effect of setting
206             // the needToInferCaller variable to false.
207             record.setSourceClassName(ste.getClassName());
208             record.setSourceMethodName(ste.getMethodName());
209         }
210     }
211 
212     static String SELF = JDK14LoggerAdapter.class.getName();
213 
214     static String SUPER = LegacyAbstractLogger.class.getName();
215     static String SUPER_OF_SUPER = AbstractLogger.class.getName();
216     static String SUBSTITUE = SubstituteLogger.class.getName();
217     static String FLUENT = DefaultLoggingEventBuilder.class.getName();
218 
219     static String[] BARRIER_CLASSES = new String[] { SUPER_OF_SUPER, SUPER, SELF, SUBSTITUE, FLUENT };
220 
221     private boolean barrierMatch(String callerFQCN, String candidateClassName) {
222         if (candidateClassName.equals(callerFQCN))
223             return true;
224         for (String barrierClassName : BARRIER_CLASSES) {
225             if (barrierClassName.equals(candidateClassName)) {
226                 return true;
227             }
228         }
229         return false;
230     }
231 
232     private static Level slf4jLevelIntToJULLevel(int levelInt) {
233         org.slf4j.event.Level slf4jLevel = org.slf4j.event.Level.intToLevel(levelInt);
234         return slf4jLevelToJULLevel(slf4jLevel);
235     }
236 
237     private static Level slf4jLevelToJULLevel(org.slf4j.event.Level slf4jLevel) {
238         Level julLevel;
239         switch (slf4jLevel) {
240         case TRACE:
241             julLevel = Level.FINEST;
242             break;
243         case DEBUG:
244             julLevel = Level.FINE;
245             break;
246         case INFO:
247             julLevel = Level.INFO;
248             break;
249         case WARN:
250             julLevel = Level.WARNING;
251             break;
252         case ERROR:
253             julLevel = Level.SEVERE;
254             break;
255         default:
256             throw new IllegalStateException("Level " + slf4jLevel + " is not recognized.");
257         }
258         return julLevel;
259     }
260 
261     /**
262      * @since 1.7.15
263      */
264     public void log(LoggingEvent event) {
265         // assumes that the invocation is made from a substitute logger
266         // this assumption might change in the future with the advent of a fluent API
267         Level julLevel = slf4jLevelToJULLevel(event.getLevel());
268         if (logger.isLoggable(julLevel)) {
269             LogRecord record = eventToRecord(event, julLevel);
270             logger.log(record);
271         }
272     }
273 
274     private LogRecord eventToRecord(LoggingEvent event, Level julLevel) {
275         String format = event.getMessage();
276         Object[] arguments = event.getArgumentArray();
277         FormattingTuple ft = MessageFormatter.arrayFormat(format, arguments);
278         if (ft.getThrowable() != null && event.getThrowable() != null) {
279             throw new IllegalArgumentException("both last element in argument array and last argument are of type Throwable");
280         }
281 
282         Throwable t = event.getThrowable();
283         if (ft.getThrowable() != null) {
284             t = ft.getThrowable();
285             throw new IllegalStateException("fix above code");
286         }
287 
288         LogRecord record = new LogRecord(julLevel, ft.getMessage());
289         record.setLoggerName(event.getLoggerName());
290         record.setMillis(event.getTimeStamp());
291         record.setSourceClassName(EventConstants.NA_SUBST);
292         record.setSourceMethodName(EventConstants.NA_SUBST);
293 
294         record.setThrown(t);
295         return record;
296     }
297 
298 }