// ================================================================================================= // Copyright 2013 Twitter, Inc. // ------------------------------------------------------------------------------------------------- // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this work except in compliance with the License. // You may obtain a copy of the License in the LICENSE file, or at: // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // ================================================================================================= package com.twitter.common.logging.julbridge; import java.text.MessageFormat; import java.util.MissingResourceException; import java.util.logging.Formatter; import java.util.logging.Handler; import java.util.logging.LogRecord; import javax.annotation.Nullable; import org.apache.log4j.Level; import org.apache.log4j.LogManager; import org.apache.log4j.Logger; import org.apache.log4j.spi.LocationInfo; import org.apache.log4j.spi.LoggerRepository; import org.apache.log4j.spi.LoggingEvent; import org.apache.log4j.spi.ThrowableInformation; import static com.google.common.base.Preconditions.checkNotNull; /** * JUL Handler to convert JUL {@link LogRecord} messages into Log4j's {@link LoggingEvent} messages, * and route them to a Log4J logger with the same name as the JUL logger. */ public class JULBridgeHandler extends Handler { private static final String UNKNOWN_LOGGERNAME = "unknown"; /** * Converts a JUL log record into a Log4J logging event. * * @param record the JUL log record to convert * @param logger the Log4J logger to use for the logging event * @param level the Log4J level to use for the logging event * @param useExtendedLocationInfo if false, do no try to get source file and line informations * @return a Log4J logging event */ static LoggingEvent toLoggingEvent(LogRecord record, Logger logger, Level level, boolean useExtendedLocationInfo) { LocationInfo locationInfo = useExtendedLocationInfo ? new LocationInfo(new Throwable(), record.getSourceClassName()) : new LocationInfo("?", record.getSourceClassName(), record.getSourceMethodName(), "?"); // Getting thread name from thread id? complicated... String threadName = String.valueOf(record.getThreadID()); ThrowableInformation throwableInformation = record.getThrown() == null ? null : new ThrowableInformation(record.getThrown()); return new LoggingEvent( record.getSourceClassName(), logger, record.getMillis(), level, formatMessage(record), threadName, throwableInformation, null /* ndc */, locationInfo, null /* properties */); } /** * Formats a log record message in a way similar to {@link Formatter#formatMessage(LogRecord)}. * * If the record contains a resource bundle, a lookup is done to find a localized version. * * If the record contains parameters, the message is formatted using * {@link MessageFormat#format(String, Object...)} * * @param record the log record used to format the message * @return a formatted string */ static String formatMessage(LogRecord record) { String message = record.getMessage(); // Look for a resource bundle java.util.ResourceBundle catalog = record.getResourceBundle(); if (catalog != null) { try { message = catalog.getString(record.getMessage()); } catch (MissingResourceException e) { // Not found? Fallback to original message string message = record.getMessage(); } } Object parameters[] = record.getParameters(); if (parameters == null || parameters.length == 0) { // No parameters? just return the message string return message; } // Try formatting try { return MessageFormat.format(message, parameters); } catch (IllegalArgumentException e) { return message; } } private final LoggerRepository loggerRepository; private final boolean useExtendedLocationInfo; /** * Creates a new JUL handler. Equivalent to calling {@link #JULBridgeHandler(boolean)} passing * <code>false</code> as argument. */ public JULBridgeHandler() { this(LogManager.getLoggerRepository(), false); } /** * Creates a new JUL handler. * Equivalent to calling {@link #JULBridgeHandler(LoggerRepository, boolean)} passing * <code>LogManager.getLoggerRepository()</code> and <code>useExtendedLocationInfo</code> as * arguments. * * @param useExtendedLocationInfo if true, try to add source filename and line info to log message */ public JULBridgeHandler(boolean useExtendedLocationInfo) { this(LogManager.getLoggerRepository(), useExtendedLocationInfo); } /** * Creates a new JUL handler. * * @param loggerRepository Log4j logger repository where to get loggers from * @param useExtendedLocationInfo if true, try to add source filename and line info to log message * @throws NullPointerException if loggerRepository is null */ public JULBridgeHandler(LoggerRepository loggerRepository, boolean useExtendedLocationInfo) { this.loggerRepository = checkNotNull(loggerRepository); this.useExtendedLocationInfo = useExtendedLocationInfo; } /** * Gets a Log4J Logger with the same name as the logger name stored in the log record. * * @param record a JUL log record * @return a Log4J logger with the same name, or name {@value #UNKNOWN_LOGGERNAME} if no name is * present in the record. */ Logger getLogger(LogRecord record) { String loggerName = record.getLoggerName(); if (loggerName == null) { loggerName = UNKNOWN_LOGGERNAME; } return loggerRepository.getLogger(loggerName); } /** * Publishes the log record to a Log4J logger of the same name. * * Before formatting the message, level is converted and message is discarded if Log4j logger is * not enabled for that level. * * @param record the record to publish */ @Override public void publish(@Nullable LogRecord record) { // Ignore silently null records if (record == null) { return; } Logger log4jLogger = getLogger(record); Level log4jLevel = JULBridgeLevelConverter.toLog4jLevel(record.getLevel()); if (log4jLogger.isEnabledFor(log4jLevel)) { LoggingEvent event = toLoggingEvent(record, log4jLogger, log4jLevel, useExtendedLocationInfo); log4jLogger.callAppenders(event); } } @Override public void flush() {} @Override public void close() {} }