blob: 09727e68a3228c0d9abdc4edc729349f0414f478 [file] [log] [blame]
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License 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 org.apache.cxf.common.logging;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.text.MessageFormat;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
import org.apache.cxf.common.i18n.BundleUtils;
import org.apache.cxf.common.util.StringUtils;
/**
* A container for static utility methods related to logging.
* By default, CXF logs to java.util.logging. An application can change this. To log to another system, the
* application must provide an object that extends {@link AbstractDelegatingLogger}, and advertise that class
* via one of the following mechanisms:
* <ul>
* <li>Create a file, in the classpath, named META-INF/cxf/org.apache.cxf.Logger.
* This file should contain the fully-qualified name
* of the class, with no comments, on a single line.</li>
* <li>Call {@link #setLoggerClass(Class)} with a Class<?> reference to the logger class.</li>
* </ul>
* CXF provides {@link Slf4jLogger} to use slf4j instead of java.util.logging.
*/
public final class LogUtils {
private static final String KEY = "org.apache.cxf.Logger";
private static final Object[] NO_PARAMETERS = new Object[0];
private static Class<?> loggerClass;
/**
* Prevents instantiation.
*/
private LogUtils() {
}
static {
JDKBugHacks.doHacks();
try {
String cname = null;
try {
cname = AccessController.doPrivileged(new PrivilegedAction<String>() {
public String run() {
return System.getProperty(KEY);
}
});
} catch (Throwable t) {
//ignore - likely security exception or similar that won't allow
//access to the system properties. We'll continue with other methods
}
if (StringUtils.isEmpty(cname)) {
InputStream ins = Thread.currentThread().getContextClassLoader()
.getResourceAsStream("META-INF/cxf/" + KEY);
if (ins == null) {
ins = ClassLoader.getSystemResourceAsStream("META-INF/cxf/" + KEY);
}
if (ins != null) {
try (BufferedReader din = new BufferedReader(new InputStreamReader(ins))) {
cname = din.readLine();
}
}
}
if (StringUtils.isEmpty(cname)) {
try {
// This Class.forName likely will barf in OSGi, but it's OK
// as we'll just use j.u.l and pax-logging will pick it up fine
// If we don't call this and there isn't a slf4j impl avail,
// you get warnings printed to stderr about NOPLoggers and such
Class.forName("org.slf4j.impl.StaticLoggerBinder");
Class<?> cls = Class.forName("org.slf4j.LoggerFactory");
Class<?> fcls = cls.getMethod("getILoggerFactory").invoke(null).getClass();
String clsName = fcls.getName();
if (clsName.contains("NOPLogger")) {
//no real slf4j implementation, use j.u.l
cname = null;
} else if (clsName.contains("JDK14")
|| clsName.contains("pax.logging")) {
//both of these we can use the appropriate j.u.l API's
//directly and have it work properly
cname = null;
} else {
// Either we cannot really detect where it's logging
// or we don't want to use a custom logger, so we'll
// go ahead and use the Slf4jLogger directly
cname = "org.apache.cxf.common.logging.Slf4jLogger";
}
} catch (Throwable t) {
//ignore - Slf4j not available
}
}
if (!StringUtils.isEmpty(cname)) {
try {
loggerClass = Class.forName(cname.trim(), true,
Thread.currentThread().getContextClassLoader());
} catch (Throwable ex) {
loggerClass = Class.forName(cname.trim());
}
getLogger(LogUtils.class).fine("Using " + loggerClass.getName() + " for logging.");
}
} catch (Throwable ex) {
//ignore - if we get here, some issue prevented the logger class from being loaded.
//maybe a ClassNotFound or NoClassDefFound or similar. Just use j.u.l
loggerClass = null;
}
}
/**
* Specify a logger class that inherits from {@link AbstractDelegatingLogger}.
* Enable users to use their own logger implementation.
*/
public static void setLoggerClass(Class<? extends AbstractDelegatingLogger> cls) {
loggerClass = cls;
}
/**
* Get a Logger with the associated default resource bundle for the class.
*
* @param cls the Class to contain the Logger
* @return an appropriate Logger
*/
public static Logger getLogger(Class<?> cls) {
return createLogger(cls, null, cls.getName());
}
/**
* Get a Logger with an associated resource bundle.
*
* @param cls the Class to contain the Logger
* @param resourcename the resource name
* @return an appropriate Logger
*/
public static Logger getLogger(Class<?> cls, String resourcename) {
return createLogger(cls, resourcename, cls.getName());
}
/**
* Get a Logger with an associated resource bundle.
*
* @param cls the Class to contain the Logger (to find resources)
* @param resourcename the resource name
* @param loggerName the full name for the logger
* @return an appropriate Logger
*/
public static Logger getLogger(Class<?> cls,
String resourcename,
String loggerName) {
return createLogger(cls, resourcename, loggerName);
}
/**
* Get a Logger with the associated default resource bundle for the class.
*
* @param cls the Class to contain the Logger
* @return an appropriate Logger
*/
public static Logger getL7dLogger(Class<?> cls) {
return createLogger(cls, null, cls.getName());
}
/**
* Get a Logger with an associated resource bundle.
*
* @param cls the Class to contain the Logger
* @param resourcename the resource name
* @return an appropriate Logger
*/
public static Logger getL7dLogger(Class<?> cls, String resourcename) {
return createLogger(cls, resourcename, cls.getName());
}
/**
* Get a Logger with an associated resource bundle.
*
* @param cls the Class to contain the Logger (to find resources)
* @param resourcename the resource name
* @param loggerName the full name for the logger
* @return an appropriate Logger
*/
public static Logger getL7dLogger(Class<?> cls,
String resourcename,
String loggerName) {
return createLogger(cls, resourcename, loggerName);
}
/**
* Create a logger
*/
protected static Logger createLogger(Class<?> cls,
String name,
String loggerName) {
ClassLoader orig = getContextClassLoader();
ClassLoader n = getClassLoader(cls);
if (n != null) {
setContextClassLoader(n);
}
String bundleName = name;
try {
ResourceBundle b = null;
if (bundleName == null) {
//grab the bundle prior to the call to Logger.getLogger(...) so the
//ResourceBundle can be loaded outside the big sync block that getLogger really is
bundleName = BundleUtils.getBundleName(cls);
try {
b = BundleUtils.getBundle(cls);
} catch (MissingResourceException rex) {
//ignore
}
} else {
bundleName = BundleUtils.getBundleName(cls, bundleName);
try {
b = BundleUtils.getBundle(cls, bundleName);
} catch (MissingResourceException rex) {
//ignore
}
}
if (b != null) {
b.getLocale();
}
if (loggerClass != null) {
try {
Constructor<?> cns = loggerClass.getConstructor(String.class, String.class);
if (name == null) {
try {
return (Logger) cns.newInstance(loggerName, bundleName);
} catch (InvocationTargetException ite) {
if (ite.getTargetException() instanceof MissingResourceException) {
return (Logger) cns.newInstance(loggerName, null);
}
throw ite;
}
}
try {
return (Logger) cns.newInstance(loggerName, bundleName);
} catch (InvocationTargetException ite) {
if (ite.getTargetException() instanceof MissingResourceException) {
throw (MissingResourceException)ite.getTargetException();
}
throw ite;
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
Logger logger;
try {
logger = Logger.getLogger(loggerName, bundleName); //NOPMD
} catch (IllegalArgumentException | MissingResourceException ex) {
//likely a mismatch on the bundle name, just return the default
logger = Logger.getLogger(loggerName); //NOPMD
}
return logger;
} finally {
if (n != orig) {
setContextClassLoader(orig);
}
}
}
private static void setContextClassLoader(final ClassLoader classLoader) {
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
AccessController.doPrivileged(new PrivilegedAction<Object>() {
public Object run() {
Thread.currentThread().setContextClassLoader(classLoader);
return null;
}
});
} else {
Thread.currentThread().setContextClassLoader(classLoader);
}
}
private static ClassLoader getContextClassLoader() {
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
return AccessController.doPrivileged(new PrivilegedAction<ClassLoader>() {
public ClassLoader run() {
return Thread.currentThread().getContextClassLoader();
}
});
}
return Thread.currentThread().getContextClassLoader();
}
private static ClassLoader getClassLoader(final Class<?> clazz) {
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
return AccessController.doPrivileged(new PrivilegedAction<ClassLoader>() {
public ClassLoader run() {
return clazz.getClassLoader();
}
});
}
return clazz.getClassLoader();
}
/**
* Allows both parameter substitution and a typed Throwable to be logged.
*
* @param logger the Logger the log to
* @param level the severity level
* @param message the log message
* @param throwable the Throwable to log
* @param parameter the parameter to substitute into message
*/
public static void log(Logger logger,
Level level,
String message,
Throwable throwable,
Object parameter) {
if (logger.isLoggable(level)) {
final String formattedMessage =
MessageFormat.format(localize(logger, message), parameter);
doLog(logger, level, formattedMessage, throwable);
}
}
/**
* Allows both parameter substitution and a typed Throwable to be logged.
*
* @param logger the Logger the log to
* @param level the severity level
* @param message the log message
* @param throwable the Throwable to log
* @param parameters the parameters to substitute into message
*/
public static void log(Logger logger,
Level level,
String message,
Throwable throwable,
Object... parameters) {
if (logger.isLoggable(level)) {
final String formattedMessage =
MessageFormat.format(localize(logger, message), parameters);
doLog(logger, level, formattedMessage, throwable);
}
}
/**
* Checks log level and logs
*
* @param logger the Logger the log to
* @param level the severity level
* @param message the log message
*/
public static void log(Logger logger,
Level level,
String message) {
log(logger, level, message, NO_PARAMETERS);
}
/**
* Checks log level and logs
*
* @param logger the Logger the log to
* @param level the severity level
* @param message the log message
* @param throwable the Throwable to log
*/
public static void log(Logger logger,
Level level,
String message,
Throwable throwable) {
log(logger, level, message, throwable, NO_PARAMETERS);
}
/**
* Checks log level and logs
*
* @param logger the Logger the log to
* @param level the severity level
* @param message the log message
* @param parameter the parameter to substitute into message
*/
public static void log(Logger logger,
Level level,
String message,
Object parameter) {
log(logger, level, message, new Object[] {parameter});
}
/**
* Checks log level and logs
*
* @param logger the Logger the log to
* @param level the severity level
* @param message the log message
* @param parameters the parameters to substitute into message
*/
public static void log(Logger logger,
Level level,
String message,
Object[] parameters) {
if (logger.isLoggable(level)) {
String msg = localize(logger, message);
try {
msg = MessageFormat.format(msg, parameters);
} catch (IllegalArgumentException ex) {
//ignore, log as is
}
doLog(logger, level, msg, null);
}
}
private static void doLog(Logger log, Level level, String msg, Throwable t) {
LogRecord record = new LogRecord(level, msg);
record.setLoggerName(log.getName());
record.setResourceBundleName(log.getResourceBundleName());
record.setResourceBundle(log.getResourceBundle());
if (t != null) {
record.setThrown(t);
}
//try to get the right class name/method name - just trace
//back the stack till we get out of this class
StackTraceElement[] stack = (new Throwable()).getStackTrace();
String cname = LogUtils.class.getName();
for (int x = 0; x < stack.length; x++) {
StackTraceElement frame = stack[x];
if (!frame.getClassName().equals(cname)) {
record.setSourceClassName(frame.getClassName());
record.setSourceMethodName(frame.getMethodName());
break;
}
}
log.log(record);
}
/**
* Retrieve localized message retrieved from a logger's resource
* bundle.
*
* @param logger the Logger
* @param message the message to be localized
*/
private static String localize(Logger logger, String message) {
ResourceBundle bundle = logger.getResourceBundle();
try {
return bundle != null ? bundle.getString(message) : message;
} catch (MissingResourceException ex) {
//string not in the bundle
return message;
}
}
}