001/** 002 * Copyright (c) 2004-2011 QOS.ch 003 * All rights reserved. 004 * 005 * Permission is hereby granted, free of charge, to any person obtaining 006 * a copy of this software and associated documentation files (the 007 * "Software"), to deal in the Software without restriction, including 008 * without limitation the rights to use, copy, modify, merge, publish, 009 * distribute, sublicense, and/or sell copies of the Software, and to 010 * permit persons to whom the Software is furnished to do so, subject to 011 * the following conditions: 012 * 013 * The above copyright notice and this permission notice shall be 014 * included in all copies or substantial portions of the Software. 015 * 016 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 017 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 018 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 019 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 020 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 021 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 022 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 023 * 024 */ 025package org.slf4j.jul; 026 027import java.util.logging.Level; 028import java.util.logging.LogRecord; 029 030import org.slf4j.Logger; 031import org.slf4j.Marker; 032import org.slf4j.event.EventConstants; 033import org.slf4j.event.LoggingEvent; 034import org.slf4j.helpers.AbstractLogger; 035import org.slf4j.helpers.FormattingTuple; 036import org.slf4j.helpers.LegacyAbstractLogger; 037import org.slf4j.helpers.MessageFormatter; 038import org.slf4j.helpers.NormalizedParameters; 039import org.slf4j.helpers.SubstituteLogger; 040import org.slf4j.spi.DefaultLoggingEventBuilder; 041import org.slf4j.spi.LocationAwareLogger; 042 043/** 044 * A wrapper over {@link java.util.logging.Logger java.util.logging.Logger} in 045 * conformity with the {@link Logger} interface. Note that the logging levels 046 * mentioned in this class refer to those defined in the java.util.logging 047 * package. 048 * 049 * @author Ceki Gülcü 050 * @author Peter Royal 051 */ 052public final class JDK14LoggerAdapter extends LegacyAbstractLogger implements LocationAwareLogger { 053 054 private static final long serialVersionUID = -8053026990503422791L; 055 056 transient final java.util.logging.Logger logger; 057 058 static int NOT_FOUND = -1; 059 060 // WARN: JDK14LoggerAdapter constructor should have only package access so 061 // that only JDK14LoggerFactory be able to create one. 062 JDK14LoggerAdapter(java.util.logging.Logger logger) { 063 this.logger = logger; 064 this.name = logger.getName(); 065 } 066 067 /** 068 * Is this logger instance enabled for the FINEST level? 069 * 070 * @return True if this Logger is enabled for level FINEST, false otherwise. 071 */ 072 public boolean isTraceEnabled() { 073 return logger.isLoggable(Level.FINEST); 074 } 075 076 /** 077 * Is this logger instance enabled for the FINE level? 078 * 079 * @return True if this Logger is enabled for level FINE, false otherwise. 080 */ 081 public boolean isDebugEnabled() { 082 return logger.isLoggable(Level.FINE); 083 } 084 085 /** 086 * Is this logger instance enabled for the INFO level? 087 * 088 * @return True if this Logger is enabled for the INFO level, false otherwise. 089 */ 090 public boolean isInfoEnabled() { 091 return logger.isLoggable(Level.INFO); 092 } 093 094 /** 095 * Is this logger instance enabled for the WARNING level? 096 * 097 * @return True if this Logger is enabled for the WARNING level, false 098 * otherwise. 099 */ 100 public boolean isWarnEnabled() { 101 return logger.isLoggable(Level.WARNING); 102 } 103 104 /** 105 * Is this logger instance enabled for level SEVERE? 106 * 107 * @return True if this Logger is enabled for level SEVERE, false otherwise. 108 */ 109 public boolean isErrorEnabled() { 110 return logger.isLoggable(Level.SEVERE); 111 } 112 113 // /** 114 // * Log the message at the specified level with the specified throwable if any. 115 // * This method creates a LogRecord and fills in caller date before calling 116 // * this instance's JDK14 logger. 117 // * 118 // * See bug report #13 for more details. 119 // * 120 // * @param level 121 // * @param msg 122 // * @param t 123 // */ 124 // private void log(String callerFQCN, Level level, String msg, Throwable t) { 125 // // millis and thread are filled by the constructor 126 // LogRecord record = new LogRecord(level, msg); 127 // record.setLoggerName(getName()); 128 // record.setThrown(t); 129 // // Note: parameters in record are not set because SLF4J only 130 // // supports a single formatting style 131 // fillCallerData(callerFQCN, record); 132 // logger.log(record); 133 // } 134 135 /** 136 * Log the message at the specified level with the specified throwable if any. 137 * This method creates a LogRecord and fills in caller date before calling this 138 * instance's JDK14 logger. 139 */ 140 @Override 141 protected void handleNormalizedLoggingCall(org.slf4j.event.Level level, Marker marker, String msg, Object[] args, Throwable throwable) { 142 innerNormalizedLoggingCallHandler(getFullyQualifiedCallerName(), level, marker, msg, args, throwable); 143 } 144 145 private void innerNormalizedLoggingCallHandler(String fqcn, org.slf4j.event.Level level, Marker marker, String msg, Object[] args, Throwable throwable) { 146 // millis and thread are filled by the constructor 147 Level julLevel = slf4jLevelToJULLevel(level); 148 String formattedMessage = MessageFormatter.basicArrayFormat(msg, args); 149 LogRecord record = new LogRecord(julLevel, formattedMessage); 150 151 // https://jira.qos.ch/browse/SLF4J-13 152 record.setLoggerName(getName()); 153 record.setThrown(throwable); 154 // Note: parameters in record are not set because SLF4J only 155 // supports a single formatting style 156 // See also https://jira.qos.ch/browse/SLF4J-10 157 fillCallerData(fqcn, record); 158 logger.log(record); 159 } 160 161 @Override 162 protected String getFullyQualifiedCallerName() { 163 return SELF; 164 } 165 166 @Override 167 public void log(Marker marker, String callerFQCN, int slf4jLevelInt, String message, Object[] arguments, Throwable throwable) { 168 169 org.slf4j.event.Level slf4jLevel = org.slf4j.event.Level.intToLevel(slf4jLevelInt); 170 Level julLevel = slf4jLevelIntToJULLevel(slf4jLevelInt); 171 172 if (logger.isLoggable(julLevel)) { 173 NormalizedParameters np = NormalizedParameters.normalize(message, arguments, throwable); 174 innerNormalizedLoggingCallHandler(callerFQCN, slf4jLevel, marker, np.getMessage(), np.getArguments(), np.getThrowable()); 175 } 176 } 177 178 /** 179 * Fill in caller data if possible. 180 * 181 * @param record The record to update 182 */ 183 final private void fillCallerData(String callerFQCN, LogRecord record) { 184 StackTraceElement[] steArray = new Throwable().getStackTrace(); 185 186 int furthestIndex = findFurthestIndex(callerFQCN, steArray); 187 188 if (furthestIndex != NOT_FOUND) { 189 int found = furthestIndex+1; 190 StackTraceElement ste = steArray[found]; 191 // setting the class name has the side effect of setting 192 // the needToInferCaller variable to false. 193 record.setSourceClassName(ste.getClassName()); 194 record.setSourceMethodName(ste.getMethodName()); 195 } 196 } 197 198 // find the furthest index which matches any of the barrier classes 199 // We assume that the actual caller is at most MAX_SEARCH_DEPTH calls away 200 private int findFurthestIndex(String callerFQCN, StackTraceElement[] steArray) { 201 202 final int maxIndex = Math.min(MAX_SEARCH_DEPTH, steArray.length); 203 int furthestIndex = NOT_FOUND; 204 205 for (int i = 0; i < maxIndex; i++) { 206 final String className = steArray[i].getClassName(); 207 if (barrierMatch(callerFQCN, className)) { 208 furthestIndex = i; 209 } 210 } 211 return furthestIndex; 212 } 213 214 static final int MAX_SEARCH_DEPTH = 12; 215 static String SELF = JDK14LoggerAdapter.class.getName(); 216 217 static String SUPER = LegacyAbstractLogger.class.getName(); 218 static String SUPER_OF_SUPER = AbstractLogger.class.getName(); 219 static String SUBSTITUE = SubstituteLogger.class.getName(); 220 static String FLUENT = DefaultLoggingEventBuilder.class.getName(); 221 222 static String[] BARRIER_CLASSES = new String[] { SUPER_OF_SUPER, SUPER, SELF, SUBSTITUE, FLUENT }; 223 224 private boolean barrierMatch(String callerFQCN, String candidateClassName) { 225 if (candidateClassName.equals(callerFQCN)) 226 return true; 227 for (String barrierClassName : BARRIER_CLASSES) { 228 if (barrierClassName.equals(candidateClassName)) { 229 return true; 230 } 231 } 232 return false; 233 } 234 235 private static Level slf4jLevelIntToJULLevel(int levelInt) { 236 org.slf4j.event.Level slf4jLevel = org.slf4j.event.Level.intToLevel(levelInt); 237 return slf4jLevelToJULLevel(slf4jLevel); 238 } 239 240 private static Level slf4jLevelToJULLevel(org.slf4j.event.Level slf4jLevel) { 241 Level julLevel; 242 switch (slf4jLevel) { 243 case TRACE: 244 julLevel = Level.FINEST; 245 break; 246 case DEBUG: 247 julLevel = Level.FINE; 248 break; 249 case INFO: 250 julLevel = Level.INFO; 251 break; 252 case WARN: 253 julLevel = Level.WARNING; 254 break; 255 case ERROR: 256 julLevel = Level.SEVERE; 257 break; 258 default: 259 throw new IllegalStateException("Level " + slf4jLevel + " is not recognized."); 260 } 261 return julLevel; 262 } 263 264 /** 265 * @since 1.7.15 266 */ 267 public void log(LoggingEvent event) { 268 // assumes that the invocation is made from a substitute logger 269 // this assumption might change in the future with the advent of a fluent API 270 Level julLevel = slf4jLevelToJULLevel(event.getLevel()); 271 if (logger.isLoggable(julLevel)) { 272 LogRecord record = eventToRecord(event, julLevel); 273 logger.log(record); 274 } 275 } 276 277 private LogRecord eventToRecord(LoggingEvent event, Level julLevel) { 278 String format = event.getMessage(); 279 Object[] arguments = event.getArgumentArray(); 280 FormattingTuple ft = MessageFormatter.arrayFormat(format, arguments); 281 if (ft.getThrowable() != null && event.getThrowable() != null) { 282 throw new IllegalArgumentException("both last element in argument array and last argument are of type Throwable"); 283 } 284 285 Throwable t = event.getThrowable(); 286 if (ft.getThrowable() != null) { 287 t = ft.getThrowable(); 288 throw new IllegalStateException("fix above code"); 289 } 290 291 LogRecord record = new LogRecord(julLevel, ft.getMessage()); 292 record.setLoggerName(event.getLoggerName()); 293 record.setMillis(event.getTimeStamp()); 294 record.setSourceClassName(EventConstants.NA_SUBST); 295 record.setSourceMethodName(EventConstants.NA_SUBST); 296 297 record.setThrown(t); 298 return record; 299 } 300 301}