This commit is contained in:
2024-11-30 19:03:49 +08:00
commit 1e6763c160
3806 changed files with 737676 additions and 0 deletions

View File

@@ -0,0 +1,190 @@
/*
* 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.juli;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.TimeUnit;
import java.util.logging.LogRecord;
/**
* A {@link FileHandler} implementation that uses a queue of log entries.
*
* <p>Configuration properties are inherited from the {@link FileHandler}
* class. This class does not add its own configuration properties for the
* logging configuration, but relies on the following system properties
* instead:</p>
*
* <ul>
* <li><code>org.apache.juli.AsyncOverflowDropType</code>
* Default value: <code>1</code></li>
* <li><code>org.apache.juli.AsyncMaxRecordCount</code>
* Default value: <code>10000</code></li>
* <li><code>org.apache.juli.AsyncLoggerPollInterval</code>
* Default value: <code>1000</code></li>
* </ul>
*
* <p>See the System Properties page in the configuration reference of Tomcat.</p>
*/
public class AsyncFileHandler extends FileHandler {
public static final int OVERFLOW_DROP_LAST = 1;
public static final int OVERFLOW_DROP_FIRST = 2;
public static final int OVERFLOW_DROP_FLUSH = 3;
public static final int OVERFLOW_DROP_CURRENT = 4;
public static final int DEFAULT_OVERFLOW_DROP_TYPE = 1;
public static final int DEFAULT_MAX_RECORDS = 10000;
public static final int DEFAULT_LOGGER_SLEEP_TIME = 1000;
public static final int OVERFLOW_DROP_TYPE = Integer.parseInt(
System.getProperty("org.apache.juli.AsyncOverflowDropType",
Integer.toString(DEFAULT_OVERFLOW_DROP_TYPE)));
public static final int MAX_RECORDS = Integer.parseInt(
System.getProperty("org.apache.juli.AsyncMaxRecordCount",
Integer.toString(DEFAULT_MAX_RECORDS)));
public static final int LOGGER_SLEEP_TIME = Integer.parseInt(
System.getProperty("org.apache.juli.AsyncLoggerPollInterval",
Integer.toString(DEFAULT_LOGGER_SLEEP_TIME)));
protected static final LinkedBlockingDeque<LogEntry> queue =
new LinkedBlockingDeque<>(MAX_RECORDS);
protected static final LoggerThread logger = new LoggerThread();
static {
logger.start();
}
protected volatile boolean closed = false;
public AsyncFileHandler() {
this(null, null, null, DEFAULT_MAX_DAYS);
}
public AsyncFileHandler(String directory, String prefix, String suffix) {
this(directory, prefix, suffix, DEFAULT_MAX_DAYS);
}
public AsyncFileHandler(String directory, String prefix, String suffix, int maxDays) {
super(directory, prefix, suffix, maxDays);
open();
}
@Override
public void close() {
if (closed) {
return;
}
closed = true;
super.close();
}
@Override
protected void open() {
if (!closed) {
return;
}
closed = false;
super.open();
}
@Override
public void publish(LogRecord record) {
if (!isLoggable(record)) {
return;
}
// fill source entries, before we hand the record over to another
// thread with another class loader
record.getSourceMethodName();
LogEntry entry = new LogEntry(record, this);
boolean added = false;
try {
while (!added && !queue.offer(entry)) {
switch (OVERFLOW_DROP_TYPE) {
case OVERFLOW_DROP_LAST: {
//remove the last added element
queue.pollLast();
break;
}
case OVERFLOW_DROP_FIRST: {
//remove the first element in the queue
queue.pollFirst();
break;
}
case OVERFLOW_DROP_FLUSH: {
added = queue.offer(entry, 1000, TimeUnit.MILLISECONDS);
break;
}
case OVERFLOW_DROP_CURRENT: {
added = true;
break;
}
}//switch
}//while
} catch (InterruptedException x) {
// Allow thread to be interrupted and back out of the publish
// operation. No further action required.
}
}
protected void publishInternal(LogRecord record) {
super.publish(record);
}
protected static class LoggerThread extends Thread {
public LoggerThread() {
this.setDaemon(true);
this.setName("AsyncFileHandlerWriter-" + System.identityHashCode(this));
}
@Override
public void run() {
while (true) {
try {
LogEntry entry = queue.poll(LOGGER_SLEEP_TIME, TimeUnit.MILLISECONDS);
if (entry != null) {
entry.flush();
}
} catch (InterruptedException x) {
// Ignore the attempt to interrupt the thread.
} catch (Exception x) {
x.printStackTrace();
}
}
}
}
protected static class LogEntry {
private final LogRecord record;
private final AsyncFileHandler handler;
public LogEntry(LogRecord record, AsyncFileHandler handler) {
super();
this.record = record;
this.handler = handler;
}
public boolean flush() {
if (handler.closed) {
return false;
} else {
handler.publishInternal(record);
return true;
}
}
}
}

View File

@@ -0,0 +1,798 @@
/*
* 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.juli;
import java.io.File;
import java.io.FileInputStream;
import java.io.FilePermission;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLClassLoader;
import java.security.AccessControlException;
import java.security.AccessController;
import java.security.Permission;
import java.security.PrivilegedAction;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Properties;
import java.util.StringTokenizer;
import java.util.WeakHashMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.LogManager;
import java.util.logging.Logger;
/**
* Per classloader LogManager implementation.
*
* For light debugging, set the system property
* <code>org.apache.juli.ClassLoaderLogManager.debug=true</code>.
* Short configuration information will be sent to <code>System.err</code>.
*/
public class ClassLoaderLogManager extends LogManager {
private static final boolean isJava9;
private static ThreadLocal<Boolean> addingLocalRootLogger = new ThreadLocal<Boolean>() {
@Override
protected Boolean initialValue() {
return Boolean.FALSE;
}
};
public static final String DEBUG_PROPERTY =
ClassLoaderLogManager.class.getName() + ".debug";
static {
Class<?> c = null;
try {
c = Class.forName("java.lang.Runtime$Version");
} catch (ClassNotFoundException e) {
// Must be Java 8
}
isJava9 = c != null;
}
private final class Cleaner extends Thread {
@Override
public void run() {
if (useShutdownHook) {
shutdown();
}
}
}
// ------------------------------------------------------------Constructors
public ClassLoaderLogManager() {
super();
try {
Runtime.getRuntime().addShutdownHook(new Cleaner());
} catch (IllegalStateException ise) {
// We are probably already being shutdown. Ignore this error.
}
}
// -------------------------------------------------------------- Variables
/**
* Map containing the classloader information, keyed per classloader. A
* weak hashmap is used to ensure no classloader reference is leaked from
* application redeployment.
*/
protected final Map<ClassLoader, ClassLoaderLogInfo> classLoaderLoggers =
new WeakHashMap<>(); // Guarded by this
/**
* This prefix is used to allow using prefixes for the properties names
* of handlers and their subcomponents.
*/
protected final ThreadLocal<String> prefix = new ThreadLocal<>();
/**
* Determines if the shutdown hook is used to perform any necessary
* clean-up such as flushing buffered handlers on JVM shutdown. Defaults to
* <code>true</code> but may be set to false if another component ensures
* that {@link #shutdown()} is called.
*/
protected volatile boolean useShutdownHook = true;
// ------------------------------------------------------------- Properties
public boolean isUseShutdownHook() {
return useShutdownHook;
}
public void setUseShutdownHook(boolean useShutdownHook) {
this.useShutdownHook = useShutdownHook;
}
// --------------------------------------------------------- Public Methods
/**
* Add the specified logger to the classloader local configuration.
*
* @param logger The logger to be added
*/
@Override
public synchronized boolean addLogger(final Logger logger) {
final String loggerName = logger.getName();
ClassLoader classLoader =
Thread.currentThread().getContextClassLoader();
ClassLoaderLogInfo info = getClassLoaderInfo(classLoader);
if (info.loggers.containsKey(loggerName)) {
return false;
}
info.loggers.put(loggerName, logger);
// Apply initial level for new logger
final String levelString = getProperty(loggerName + ".level");
if (levelString != null) {
try {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
@Override
public Void run() {
logger.setLevel(Level.parse(levelString.trim()));
return null;
}
});
} catch (IllegalArgumentException e) {
// Leave level set to null
}
}
// Always instantiate parent loggers so that
// we can control log categories even during runtime
int dotIndex = loggerName.lastIndexOf('.');
if (dotIndex >= 0) {
final String parentName = loggerName.substring(0, dotIndex);
Logger.getLogger(parentName);
}
// Find associated node
LogNode node = info.rootNode.findNode(loggerName);
node.logger = logger;
// Set parent logger
Logger parentLogger = node.findParentLogger();
if (parentLogger != null) {
doSetParentLogger(logger, parentLogger);
}
// Tell children we are their new parent
node.setParentLogger(logger);
// Add associated handlers, if any are defined using the .handlers property.
// In this case, handlers of the parent logger(s) will not be used
String handlers = getProperty(loggerName + ".handlers");
if (handlers != null) {
logger.setUseParentHandlers(false);
StringTokenizer tok = new StringTokenizer(handlers, ",");
while (tok.hasMoreTokens()) {
String handlerName = (tok.nextToken().trim());
Handler handler = null;
ClassLoader current = classLoader;
while (current != null) {
info = classLoaderLoggers.get(current);
if (info != null) {
handler = info.handlers.get(handlerName);
if (handler != null) {
break;
}
}
current = current.getParent();
}
if (handler != null) {
logger.addHandler(handler);
}
}
}
// Parse useParentHandlers to set if the logger should delegate to its parent.
// Unlike java.util.logging, the default is to not delegate if a list of handlers
// has been specified for the logger.
String useParentHandlersString = getProperty(loggerName + ".useParentHandlers");
if (Boolean.parseBoolean(useParentHandlersString)) {
logger.setUseParentHandlers(true);
}
return true;
}
/**
* Get the logger associated with the specified name inside
* the classloader local configuration. If this returns null,
* and the call originated for Logger.getLogger, a new
* logger with the specified name will be instantiated and
* added using addLogger.
*
* @param name The name of the logger to retrieve
*/
@Override
public synchronized Logger getLogger(final String name) {
ClassLoader classLoader = Thread.currentThread()
.getContextClassLoader();
return getClassLoaderInfo(classLoader).loggers.get(name);
}
/**
* Get an enumeration of the logger names currently defined in the
* classloader local configuration.
*/
@Override
public synchronized Enumeration<String> getLoggerNames() {
ClassLoader classLoader = Thread.currentThread()
.getContextClassLoader();
return Collections.enumeration(getClassLoaderInfo(classLoader).loggers.keySet());
}
/**
* Get the value of the specified property in the classloader local
* configuration.
*
* @param name The property name
*/
@Override
public String getProperty(String name) {
// Use a ThreadLocal to work around
// https://bugs.openjdk.java.net/browse/JDK-8195096
if (".handlers".equals(name) && !addingLocalRootLogger.get().booleanValue()) {
return null;
}
String prefix = this.prefix.get();
String result = null;
// If a prefix is defined look for a prefixed property first
if (prefix != null) {
result = findProperty(prefix + name);
}
// If there is no prefix or no property match with the prefix try just
// the name
if (result == null) {
result = findProperty(name);
}
// Simple property replacement (mostly for folder names)
if (result != null) {
result = replace(result);
}
return result;
}
private synchronized String findProperty(String name) {
ClassLoader classLoader = Thread.currentThread()
.getContextClassLoader();
ClassLoaderLogInfo info = getClassLoaderInfo(classLoader);
String result = info.props.getProperty(name);
// If the property was not found, and the current classloader had no
// configuration (property list is empty), look for the parent classloader
// properties.
if ((result == null) && (info.props.isEmpty())) {
ClassLoader current = classLoader.getParent();
while (current != null) {
info = classLoaderLoggers.get(current);
if (info != null) {
result = info.props.getProperty(name);
if ((result != null) || (!info.props.isEmpty())) {
break;
}
}
current = current.getParent();
}
if (result == null) {
result = super.getProperty(name);
}
}
return result;
}
@Override
public void readConfiguration()
throws IOException, SecurityException {
checkAccess();
readConfiguration(Thread.currentThread().getContextClassLoader());
}
@Override
public void readConfiguration(InputStream is)
throws IOException, SecurityException {
checkAccess();
reset();
readConfiguration(is, Thread.currentThread().getContextClassLoader());
}
@Override
public void reset() throws SecurityException {
Thread thread = Thread.currentThread();
if (thread.getClass().getName().startsWith(
"java.util.logging.LogManager$")) {
// Ignore the call from java.util.logging.LogManager.Cleaner,
// because we have our own shutdown hook
return;
}
ClassLoader classLoader = thread.getContextClassLoader();
ClassLoaderLogInfo clLogInfo = getClassLoaderInfo(classLoader);
resetLoggers(clLogInfo);
// Do not call super.reset(). It should be a NO-OP as all loggers should
// have been registered via this manager. Very rarely a
// ConcurrentModificationException has been seen in the unit tests when
// calling super.reset() and that exception could cause the stop of a
// web application to fail.
}
/**
* Shuts down the logging system.
*/
public synchronized void shutdown() {
// The JVM is being shutdown. Make sure all loggers for all class
// loaders are shutdown
for (ClassLoaderLogInfo clLogInfo : classLoaderLoggers.values()) {
resetLoggers(clLogInfo);
}
}
// -------------------------------------------------------- Private Methods
private void resetLoggers(ClassLoaderLogInfo clLogInfo) {
// This differs from LogManager#resetLogger() in that we close not all
// handlers of all loggers, but only those that are present in our
// ClassLoaderLogInfo#handlers list. That is because our #addLogger(..)
// method can use handlers from the parent class loaders, and closing
// handlers that the current class loader does not own would be not
// good.
synchronized (clLogInfo) {
for (Logger logger : clLogInfo.loggers.values()) {
Handler[] handlers = logger.getHandlers();
for (Handler handler : handlers) {
logger.removeHandler(handler);
}
}
for (Handler handler : clLogInfo.handlers.values()) {
try {
handler.close();
} catch (Exception e) {
// Ignore
}
}
clLogInfo.handlers.clear();
}
}
// ------------------------------------------------------ Protected Methods
/**
* Retrieve the configuration associated with the specified classloader. If
* it does not exist, it will be created.
*
* @param classLoader The classloader for which we will retrieve or build the
* configuration
* @return the log configuration
*/
protected synchronized ClassLoaderLogInfo getClassLoaderInfo(ClassLoader classLoader) {
if (classLoader == null) {
classLoader = ClassLoader.getSystemClassLoader();
}
ClassLoaderLogInfo info = classLoaderLoggers.get(classLoader);
if (info == null) {
final ClassLoader classLoaderParam = classLoader;
AccessController.doPrivileged(new PrivilegedAction<Void>() {
@Override
public Void run() {
try {
readConfiguration(classLoaderParam);
} catch (IOException e) {
// Ignore
}
return null;
}
});
info = classLoaderLoggers.get(classLoader);
}
return info;
}
/**
* Read configuration for the specified classloader.
*
* @param classLoader The classloader
* @throws IOException Error reading configuration
*/
protected synchronized void readConfiguration(ClassLoader classLoader)
throws IOException {
InputStream is = null;
// Special case for URL classloaders which are used in containers:
// only look in the local repositories to avoid redefining loggers 20 times
try {
if (classLoader instanceof WebappProperties) {
if (((WebappProperties) classLoader).hasLoggingConfig()) {
is = classLoader.getResourceAsStream("logging.properties");
}
} else if (classLoader instanceof URLClassLoader) {
URL logConfig = ((URLClassLoader)classLoader).findResource("logging.properties");
if(null != logConfig) {
if(Boolean.getBoolean(DEBUG_PROPERTY))
System.err.println(getClass().getName()
+ ".readConfiguration(): "
+ "Found logging.properties at "
+ logConfig);
is = classLoader.getResourceAsStream("logging.properties");
} else {
if(Boolean.getBoolean(DEBUG_PROPERTY))
System.err.println(getClass().getName()
+ ".readConfiguration(): "
+ "Found no logging.properties");
}
}
} catch (AccessControlException ace) {
// No permission to configure logging in context
// Log and carry on
ClassLoaderLogInfo info = classLoaderLoggers.get(ClassLoader.getSystemClassLoader());
if (info != null) {
Logger log = info.loggers.get("");
if (log != null) {
Permission perm = ace.getPermission();
if (perm instanceof FilePermission && perm.getActions().equals("read")) {
log.warning("Reading " + perm.getName() + " is not permitted. See \"per context logging\" in the default catalina.policy file.");
}
else {
log.warning("Reading logging.properties is not permitted in some context. See \"per context logging\" in the default catalina.policy file.");
log.warning("Original error was: " + ace.getMessage());
}
}
}
}
if ((is == null) && (classLoader == ClassLoader.getSystemClassLoader())) {
String configFileStr = System.getProperty("java.util.logging.config.file");
if (configFileStr != null) {
try {
is = new FileInputStream(replace(configFileStr));
} catch (IOException e) {
System.err.println("Configuration error");
e.printStackTrace();
}
}
// Try the default JVM configuration
if (is == null) {
File defaultFile = new File(new File(System.getProperty("java.home"),
isJava9 ? "conf" : "lib"),
"logging.properties");
try {
is = new FileInputStream(defaultFile);
} catch (IOException e) {
System.err.println("Configuration error");
e.printStackTrace();
}
}
}
Logger localRootLogger = new RootLogger();
if (is == null) {
// Retrieve the root logger of the parent classloader instead
ClassLoader current = classLoader.getParent();
ClassLoaderLogInfo info = null;
while (current != null && info == null) {
info = getClassLoaderInfo(current);
current = current.getParent();
}
if (info != null) {
localRootLogger.setParent(info.rootNode.logger);
}
}
ClassLoaderLogInfo info =
new ClassLoaderLogInfo(new LogNode(null, localRootLogger));
classLoaderLoggers.put(classLoader, info);
if (is != null) {
readConfiguration(is, classLoader);
}
try {
// Use a ThreadLocal to work around
// https://bugs.openjdk.java.net/browse/JDK-8195096
addingLocalRootLogger.set(Boolean.TRUE);
addLogger(localRootLogger);
} finally {
addingLocalRootLogger.set(Boolean.FALSE);
}
}
/**
* Load specified configuration.
*
* @param is InputStream to the properties file
* @param classLoader for which the configuration will be loaded
* @throws IOException If something wrong happens during loading
*/
protected synchronized void readConfiguration(InputStream is, ClassLoader classLoader)
throws IOException {
ClassLoaderLogInfo info = classLoaderLoggers.get(classLoader);
try {
info.props.load(is);
} catch (IOException e) {
// Report error
System.err.println("Configuration error");
e.printStackTrace();
} finally {
try {
is.close();
} catch (IOException ioe) {
// Ignore
}
}
// Create handlers for the root logger of this classloader
String rootHandlers = info.props.getProperty(".handlers");
String handlers = info.props.getProperty("handlers");
Logger localRootLogger = info.rootNode.logger;
if (handlers != null) {
StringTokenizer tok = new StringTokenizer(handlers, ",");
while (tok.hasMoreTokens()) {
String handlerName = (tok.nextToken().trim());
String handlerClassName = handlerName;
String prefix = "";
if (handlerClassName.length() <= 0) {
continue;
}
// Parse and remove a prefix (prefix start with a digit, such as
// "10WebappFooHandler.")
if (Character.isDigit(handlerClassName.charAt(0))) {
int pos = handlerClassName.indexOf('.');
if (pos >= 0) {
prefix = handlerClassName.substring(0, pos + 1);
handlerClassName = handlerClassName.substring(pos + 1);
}
}
try {
this.prefix.set(prefix);
Handler handler = (Handler) classLoader.loadClass(
handlerClassName).getConstructor().newInstance();
// The specification strongly implies all configuration should be done
// during the creation of the handler object.
// This includes setting level, filter, formatter and encoding.
this.prefix.set(null);
info.handlers.put(handlerName, handler);
if (rootHandlers == null) {
localRootLogger.addHandler(handler);
}
} catch (Exception e) {
// Report error
System.err.println("Handler error");
e.printStackTrace();
}
}
}
}
/**
* Set parent child relationship between the two specified loggers.
*
* @param logger The logger
* @param parent The parent logger
*/
protected static void doSetParentLogger(final Logger logger,
final Logger parent) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
@Override
public Void run() {
logger.setParent(parent);
return null;
}
});
}
/**
* System property replacement in the given string.
*
* @param str The original string
* @return the modified string
*/
protected String replace(String str) {
String result = str;
int pos_start = str.indexOf("${");
if (pos_start >= 0) {
StringBuilder builder = new StringBuilder();
int pos_end = -1;
while (pos_start >= 0) {
builder.append(str, pos_end + 1, pos_start);
pos_end = str.indexOf('}', pos_start + 2);
if (pos_end < 0) {
pos_end = pos_start - 1;
break;
}
String propName = str.substring(pos_start + 2, pos_end);
String replacement = replaceWebApplicationProperties(propName);
if (replacement == null) {
replacement = propName.length() > 0 ? System.getProperty(propName) : null;
}
if (replacement != null) {
builder.append(replacement);
} else {
builder.append(str, pos_start, pos_end + 1);
}
pos_start = str.indexOf("${", pos_end + 1);
}
builder.append(str, pos_end + 1, str.length());
result = builder.toString();
}
return result;
}
private String replaceWebApplicationProperties(String propName) {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
if (cl instanceof WebappProperties) {
WebappProperties wProps = (WebappProperties) cl;
if ("classloader.webappName".equals(propName)) {
return wProps.getWebappName();
} else if ("classloader.hostName".equals(propName)) {
return wProps.getHostName();
} else if ("classloader.serviceName".equals(propName)) {
return wProps.getServiceName();
} else {
return null;
}
} else {
return null;
}
}
// ---------------------------------------------------- LogNode Inner Class
protected static final class LogNode {
Logger logger;
final Map<String, LogNode> children = new HashMap<>();
final LogNode parent;
LogNode(final LogNode parent, final Logger logger) {
this.parent = parent;
this.logger = logger;
}
LogNode(final LogNode parent) {
this(parent, null);
}
LogNode findNode(String name) {
LogNode currentNode = this;
if (logger.getName().equals(name)) {
return this;
}
while (name != null) {
final int dotIndex = name.indexOf('.');
final String nextName;
if (dotIndex < 0) {
nextName = name;
name = null;
} else {
nextName = name.substring(0, dotIndex);
name = name.substring(dotIndex + 1);
}
LogNode childNode = currentNode.children.get(nextName);
if (childNode == null) {
childNode = new LogNode(currentNode);
currentNode.children.put(nextName, childNode);
}
currentNode = childNode;
}
return currentNode;
}
Logger findParentLogger() {
Logger logger = null;
LogNode node = parent;
while (node != null && logger == null) {
logger = node.logger;
node = node.parent;
}
return logger;
}
void setParentLogger(final Logger parent) {
for (final Iterator<LogNode> iter =
children.values().iterator(); iter.hasNext();) {
final LogNode childNode = iter.next();
if (childNode.logger == null) {
childNode.setParentLogger(parent);
} else {
doSetParentLogger(childNode.logger, parent);
}
}
}
}
// -------------------------------------------- ClassLoaderInfo Inner Class
protected static final class ClassLoaderLogInfo {
final LogNode rootNode;
final Map<String, Logger> loggers = new ConcurrentHashMap<>();
final Map<String, Handler> handlers = new HashMap<>();
final Properties props = new Properties();
ClassLoaderLogInfo(final LogNode rootNode) {
this.rootNode = rootNode;
}
}
// ------------------------------------------------- RootLogger Inner Class
/**
* This class is needed to instantiate the root of each per classloader
* hierarchy.
*/
protected static class RootLogger extends Logger {
public RootLogger() {
super("", null);
}
}
}

View File

@@ -0,0 +1,192 @@
/*
* 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.juli;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;
/**
* <p>Cache structure for SimpleDateFormat formatted timestamps based on
* seconds.</p>
*
* <p>Millisecond formatting using S is not supported. You should add the
* millisecond information after getting back the second formatting.</p>
*
* <p>The cache consists of entries for a consecutive range of
* seconds. The length of the range is configurable. It is
* implemented based on a cyclic buffer. New entries shift the range.</p>
*
* <p>The cache is not threadsafe. It can be used without synchronization
* via thread local instances, or with synchronization as a global cache.</p>
*
* <p>The cache can be created with a parent cache to build a cache hierarchy.
* Access to the parent cache is threadsafe.</p>
*/
public class DateFormatCache {
private static final String msecPattern = "#";
/* Timestamp format */
private final String format;
/* Number of cached entries */
private final int cacheSize;
private final Cache cache;
/**
* Replace the millisecond formatting character 'S' by
* some dummy characters in order to make the resulting
* formatted time stamps cacheable. Our consumer might
* choose to replace the dummy chars with the actual
* milliseconds because that's relatively cheap.
*/
private String tidyFormat(String format) {
boolean escape = false;
StringBuilder result = new StringBuilder();
int len = format.length();
char x;
for (int i = 0; i < len; i++) {
x = format.charAt(i);
if (escape || x != 'S') {
result.append(x);
} else {
result.append(msecPattern);
}
if (x == '\'') {
escape = !escape;
}
}
return result.toString();
}
public DateFormatCache(int size, String format, DateFormatCache parent) {
cacheSize = size;
this.format = tidyFormat(format);
Cache parentCache = null;
if (parent != null) {
synchronized(parent) {
parentCache = parent.cache;
}
}
cache = new Cache(parentCache);
}
public String getFormat(long time) {
return cache.getFormat(time);
}
public String getTimeFormat() {
return format;
}
private class Cache {
/* Second formatted in most recent invocation */
private long previousSeconds = Long.MIN_VALUE;
/* Formatted timestamp generated in most recent invocation */
private String previousFormat = "";
/* First second contained in cache */
private long first = Long.MIN_VALUE;
/* Last second contained in cache */
private long last = Long.MIN_VALUE;
/* Index of "first" in the cyclic cache */
private int offset = 0;
/* Helper object to be able to call SimpleDateFormat.format(). */
private final Date currentDate = new Date();
private String cache[];
private SimpleDateFormat formatter;
private Cache parent = null;
private Cache(Cache parent) {
cache = new String[cacheSize];
formatter = new SimpleDateFormat(format, Locale.US);
formatter.setTimeZone(TimeZone.getDefault());
this.parent = parent;
}
private String getFormat(long time) {
long seconds = time / 1000;
/* First step: if we have seen this timestamp
during the previous call, return the previous value. */
if (seconds == previousSeconds) {
return previousFormat;
}
/* Second step: Try to locate in cache */
previousSeconds = seconds;
int index = (offset + (int)(seconds - first)) % cacheSize;
if (index < 0) {
index += cacheSize;
}
if (seconds >= first && seconds <= last) {
if (cache[index] != null) {
/* Found, so remember for next call and return.*/
previousFormat = cache[index];
return previousFormat;
}
/* Third step: not found in cache, adjust cache and add item */
} else if (seconds >= last + cacheSize || seconds <= first - cacheSize) {
first = seconds;
last = first + cacheSize - 1;
index = 0;
offset = 0;
for (int i = 1; i < cacheSize; i++) {
cache[i] = null;
}
} else if (seconds > last) {
for (int i = 1; i < seconds - last; i++) {
cache[(index + cacheSize - i) % cacheSize] = null;
}
first = seconds - (cacheSize - 1);
last = seconds;
offset = (index + 1) % cacheSize;
} else if (seconds < first) {
for (int i = 1; i < first - seconds; i++) {
cache[(index + i) % cacheSize] = null;
}
first = seconds;
last = seconds + (cacheSize - 1);
offset = index;
}
/* Last step: format new timestamp either using
* parent cache or locally. */
if (parent != null) {
synchronized(parent) {
previousFormat = parent.getFormat(time);
}
} else {
currentDate.setTime(time);
previousFormat = formatter.format(currentDate);
}
cache[index] = previousFormat;
return previousFormat;
}
}
}

View File

@@ -0,0 +1,597 @@
/*
* 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.juli;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.sql.Timestamp;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.logging.ErrorManager;
import java.util.logging.Filter;
import java.util.logging.Formatter;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.LogManager;
import java.util.logging.LogRecord;
import java.util.regex.Pattern;
/**
* Implementation of <b>Handler</b> that appends log messages to a file
* named {prefix}{date}{suffix} in a configured directory.
*
* <p>The following configuration properties are available:</p>
*
* <ul>
* <li><code>directory</code> - The directory where to create the log file.
* If the path is not absolute, it is relative to the current working
* directory of the application. The Apache Tomcat configuration files usually
* specify an absolute path for this property,
* <code>${catalina.base}/logs</code>
* Default value: <code>logs</code></li>
* <li><code>rotatable</code> - If <code>true</code>, the log file will be
* rotated on the first write past midnight and the filename will be
* <code>{prefix}{date}{suffix}</code>, where date is yyyy-MM-dd. If <code>false</code>,
* the file will not be rotated and the filename will be <code>{prefix}{suffix}</code>.
* Default value: <code>true</code></li>
* <li><code>prefix</code> - The leading part of the log file name.
* Default value: <code>juli.</code></li>
* <li><code>suffix</code> - The trailing part of the log file name. Default value: <code>.log</code></li>
* <li><code>bufferSize</code> - Configures buffering. The value of <code>0</code>
* uses system default buffering (typically an 8K buffer will be used). A
* value of <code>&lt;0</code> forces a writer flush upon each log write. A
* value <code>&gt;0</code> uses a BufferedOutputStream with the defined
* value but note that the system default buffering will also be
* applied. Default value: <code>-1</code></li>
* <li><code>encoding</code> - Character set used by the log file. Default value:
* empty string, which means to use the system default character set.</li>
* <li><code>level</code> - The level threshold for this Handler. See the
* <code>java.util.logging.Level</code> class for the possible levels.
* Default value: <code>ALL</code></li>
* <li><code>filter</code> - The <code>java.util.logging.Filter</code>
* implementation class name for this Handler. Default value: unset</li>
* <li><code>formatter</code> - The <code>java.util.logging.Formatter</code>
* implementation class name for this Handler. Default value:
* <code>java.util.logging.SimpleFormatter</code></li>
* <li><code>maxDays</code> - The maximum number of days to keep the log
* files. If the specified value is <code>&lt;=0</code> then the log files
* will be kept on the file system forever, otherwise they will be kept the
* specified maximum days. Default value: <code>-1</code>.</li>
* </ul>
*/
public class FileHandler extends Handler {
public static final int DEFAULT_MAX_DAYS = -1;
private static final ExecutorService DELETE_FILES_SERVICE =
Executors.newSingleThreadExecutor(new ThreadFactory() {
private static final String NAME_PREFIX = "FileHandlerLogFilesCleaner-";
private final boolean isSecurityEnabled;
private final ThreadGroup group;
private final AtomicInteger threadNumber = new AtomicInteger(1);
{
SecurityManager s = System.getSecurityManager();
if (s == null) {
this.isSecurityEnabled = false;
this.group = Thread.currentThread().getThreadGroup();
} else {
this.isSecurityEnabled = true;
this.group = s.getThreadGroup();
}
}
@Override
public Thread newThread(Runnable r) {
final ClassLoader loader = Thread.currentThread().getContextClassLoader();
try {
// Threads should not be created by the webapp classloader
if (isSecurityEnabled) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
@Override
public Void run() {
Thread.currentThread()
.setContextClassLoader(getClass().getClassLoader());
return null;
}
});
} else {
Thread.currentThread()
.setContextClassLoader(getClass().getClassLoader());
}
Thread t = new Thread(group, r,
NAME_PREFIX + threadNumber.getAndIncrement());
t.setDaemon(true);
return t;
} finally {
if (isSecurityEnabled) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
@Override
public Void run() {
Thread.currentThread().setContextClassLoader(loader);
return null;
}
});
} else {
Thread.currentThread().setContextClassLoader(loader);
}
}
}
});
// ------------------------------------------------------------ Constructor
public FileHandler() {
this(null, null, null, DEFAULT_MAX_DAYS);
}
public FileHandler(String directory, String prefix, String suffix) {
this(directory, prefix, suffix, DEFAULT_MAX_DAYS);
}
public FileHandler(String directory, String prefix, String suffix, int maxDays) {
this.directory = directory;
this.prefix = prefix;
this.suffix = suffix;
this.maxDays = maxDays;
configure();
openWriter();
clean();
}
// ----------------------------------------------------- Instance Variables
/**
* The as-of date for the currently open log file, or a zero-length
* string if there is no open log file.
*/
private volatile String date = "";
/**
* The directory in which log files are created.
*/
private String directory = null;
/**
* The prefix that is added to log file filenames.
*/
private String prefix = null;
/**
* The suffix that is added to log file filenames.
*/
private String suffix = null;
/**
* Determines whether the log file is rotatable
*/
private boolean rotatable = true;
/**
* Maximum number of days to keep the log files
*/
private int maxDays = DEFAULT_MAX_DAYS;
/**
* The PrintWriter to which we are currently logging, if any.
*/
private volatile PrintWriter writer = null;
/**
* Lock used to control access to the writer.
*/
protected final ReadWriteLock writerLock = new ReentrantReadWriteLock();
/**
* Log buffer size.
*/
private int bufferSize = -1;
/**
* Represents a file name pattern of type {prefix}{date}{suffix}.
* The date is YYYY-MM-DD
*/
private Pattern pattern;
// --------------------------------------------------------- Public Methods
/**
* Format and publish a <code>LogRecord</code>.
*
* @param record description of the log event
*/
@Override
public void publish(LogRecord record) {
if (!isLoggable(record)) {
return;
}
// Construct the timestamp we will use, if requested
Timestamp ts = new Timestamp(System.currentTimeMillis());
String tsDate = ts.toString().substring(0, 10);
writerLock.readLock().lock();
try {
// If the date has changed, switch log files
if (rotatable && !date.equals(tsDate)) {
// Upgrade to writeLock before we switch
writerLock.readLock().unlock();
writerLock.writeLock().lock();
try {
// Make sure another thread hasn't already done this
if (!date.equals(tsDate)) {
closeWriter();
date = tsDate;
openWriter();
clean();
}
} finally {
// Downgrade to read-lock. This ensures the writer remains valid
// until the log message is written
writerLock.readLock().lock();
writerLock.writeLock().unlock();
}
}
String result = null;
try {
result = getFormatter().format(record);
} catch (Exception e) {
reportError(null, e, ErrorManager.FORMAT_FAILURE);
return;
}
try {
if (writer != null) {
writer.write(result);
if (bufferSize < 0) {
writer.flush();
}
} else {
reportError("FileHandler is closed or not yet initialized, unable to log ["
+ result + "]", null, ErrorManager.WRITE_FAILURE);
}
} catch (Exception e) {
reportError(null, e, ErrorManager.WRITE_FAILURE);
}
} finally {
writerLock.readLock().unlock();
}
}
// -------------------------------------------------------- Private Methods
/**
* Close the currently open log file (if any).
*/
@Override
public void close() {
closeWriter();
}
protected void closeWriter() {
writerLock.writeLock().lock();
try {
if (writer == null) {
return;
}
writer.write(getFormatter().getTail(this));
writer.flush();
writer.close();
writer = null;
date = "";
} catch (Exception e) {
reportError(null, e, ErrorManager.CLOSE_FAILURE);
} finally {
writerLock.writeLock().unlock();
}
}
/**
* Flush the writer.
*/
@Override
public void flush() {
writerLock.readLock().lock();
try {
if (writer == null) {
return;
}
writer.flush();
} catch (Exception e) {
reportError(null, e, ErrorManager.FLUSH_FAILURE);
} finally {
writerLock.readLock().unlock();
}
}
/**
* Configure from <code>LogManager</code> properties.
*/
private void configure() {
Timestamp ts = new Timestamp(System.currentTimeMillis());
date = ts.toString().substring(0, 10);
String className = this.getClass().getName(); //allow classes to override
ClassLoader cl = Thread.currentThread().getContextClassLoader();
// Retrieve configuration of logging file name
rotatable = Boolean.parseBoolean(getProperty(className + ".rotatable", "true"));
if (directory == null) {
directory = getProperty(className + ".directory", "logs");
}
if (prefix == null) {
prefix = getProperty(className + ".prefix", "juli.");
}
if (suffix == null) {
suffix = getProperty(className + ".suffix", ".log");
}
// https://bz.apache.org/bugzilla/show_bug.cgi?id=61232
boolean shouldCheckForRedundantSeparator = !rotatable && !prefix.isEmpty()
&& !suffix.isEmpty();
// assuming separator is just one char, if there are use cases with
// more, the notion of separator might be introduced
if (shouldCheckForRedundantSeparator
&& (prefix.charAt(prefix.length() - 1) == suffix.charAt(0))) {
suffix = suffix.substring(1);
}
pattern = Pattern.compile("^(" + Pattern.quote(prefix) + ")\\d{4}-\\d{1,2}-\\d{1,2}("
+ Pattern.quote(suffix) + ")$");
String sMaxDays = getProperty(className + ".maxDays", String.valueOf(DEFAULT_MAX_DAYS));
if (maxDays <= 0) {
try {
maxDays = Integer.parseInt(sMaxDays);
} catch (NumberFormatException ignore) {
// no-op
}
}
String sBufferSize = getProperty(className + ".bufferSize", String.valueOf(bufferSize));
try {
bufferSize = Integer.parseInt(sBufferSize);
} catch (NumberFormatException ignore) {
//no op
}
// Get encoding for the logging file
String encoding = getProperty(className + ".encoding", null);
if (encoding != null && encoding.length() > 0) {
try {
setEncoding(encoding);
} catch (UnsupportedEncodingException ex) {
// Ignore
}
}
// Get logging level for the handler
setLevel(Level.parse(getProperty(className + ".level", "" + Level.ALL)));
// Get filter configuration
String filterName = getProperty(className + ".filter", null);
if (filterName != null) {
try {
setFilter((Filter) cl.loadClass(filterName).getConstructor().newInstance());
} catch (Exception e) {
// Ignore
}
}
// Set formatter
String formatterName = getProperty(className + ".formatter", null);
if (formatterName != null) {
try {
setFormatter((Formatter) cl.loadClass(
formatterName).getConstructor().newInstance());
} catch (Exception e) {
// Ignore and fallback to defaults
setFormatter(new OneLineFormatter());
}
} else {
setFormatter(new OneLineFormatter());
}
// Set error manager
setErrorManager(new ErrorManager());
}
private String getProperty(String name, String defaultValue) {
String value = LogManager.getLogManager().getProperty(name);
if (value == null) {
value = defaultValue;
} else {
value = value.trim();
}
return value;
}
/**
* Open the new log file for the date specified by <code>date</code>.
*/
protected void open() {
openWriter();
}
protected void openWriter() {
// Create the directory if necessary
File dir = new File(directory);
if (!dir.mkdirs() && !dir.isDirectory()) {
reportError("Unable to create [" + dir + "]", null, ErrorManager.OPEN_FAILURE);
writer = null;
return;
}
// Open the current log file
writerLock.writeLock().lock();
FileOutputStream fos = null;
OutputStream os = null;
try {
File pathname = new File(dir.getAbsoluteFile(), prefix
+ (rotatable ? date : "") + suffix);
File parent = pathname.getParentFile();
if (!parent.mkdirs() && !parent.isDirectory()) {
reportError("Unable to create [" + parent + "]", null, ErrorManager.OPEN_FAILURE);
writer = null;
return;
}
String encoding = getEncoding();
fos = new FileOutputStream(pathname, true);
os = bufferSize > 0 ? new BufferedOutputStream(fos, bufferSize) : fos;
writer = new PrintWriter(
(encoding != null) ? new OutputStreamWriter(os, encoding)
: new OutputStreamWriter(os), false);
writer.write(getFormatter().getHead(this));
} catch (Exception e) {
reportError(null, e, ErrorManager.OPEN_FAILURE);
writer = null;
if (fos != null) {
try {
fos.close();
} catch (IOException e1) {
// Ignore
}
}
if (os != null) {
try {
os.close();
} catch (IOException e1) {
// Ignore
}
}
} finally {
writerLock.writeLock().unlock();
}
}
private void clean() {
if (maxDays <= 0) {
return;
}
DELETE_FILES_SERVICE.submit(new Runnable() {
@Override
public void run() {
try (DirectoryStream<Path> files = streamFilesForDelete()) {
for (Path file : files) {
Files.delete(file);
}
} catch (IOException e) {
reportError("Unable to delete log files older than [" + maxDays + "] days",
null, ErrorManager.GENERIC_FAILURE);
}
}
});
}
private DirectoryStream<Path> streamFilesForDelete() throws IOException {
final Date maxDaysOffset = getMaxDaysOffset();
final SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd");
return Files.newDirectoryStream(new File(directory).toPath(),
new DirectoryStream.Filter<Path>() {
@Override
public boolean accept(Path path) throws IOException {
boolean result = false;
String date = obtainDateFromPath(path);
if (date != null) {
try {
Date dateFromFile = formatter.parse(date);
result = dateFromFile.before(maxDaysOffset);
} catch (ParseException e) {
// no-op
}
}
return result;
}
});
}
private String obtainDateFromPath(Path path) {
Path fileName = path.getFileName();
if (fileName == null) {
return null;
}
String date = fileName.toString();
if (pattern.matcher(date).matches()) {
date = date.substring(prefix.length());
return date.substring(0, date.length() - suffix.length());
} else {
return null;
}
}
private Date getMaxDaysOffset() {
Calendar cal = Calendar.getInstance();
cal.set(Calendar.HOUR_OF_DAY, 0);
cal.set(Calendar.MINUTE, 0);
cal.set(Calendar.SECOND, 0);
cal.set(Calendar.MILLISECOND, 0);
cal.add(Calendar.DATE, -maxDays);
return cal.getTime();
}
}

View File

@@ -0,0 +1,107 @@
/*
* 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.juli;
import java.util.logging.Formatter;
import java.util.logging.LogRecord;
/**
* A more compact formatter.
*
* Equivalent log4j config:
* <pre>
* log4j.rootCategory=WARN, A1
* log4j.appender.A1=org.apache.log4j.ConsoleAppender
* log4j.appender.A1.layout=org.apache.log4j.PatternLayout
* log4j.appender.A1.Target=System.err
* log4j.appender.A1.layout.ConversionPattern=%r %-15.15c{2} %-1.1p %m %n
* </pre>
*
* Example:
* 1130122891846 Http11BaseProtocol I Initializing Coyote HTTP/1.1 on http-8800
*
*
* @author Costin Manolache
*/
public class JdkLoggerFormatter extends Formatter {
// values from JDK Level
public static final int LOG_LEVEL_TRACE = 400;
public static final int LOG_LEVEL_DEBUG = 500;
public static final int LOG_LEVEL_INFO = 800;
public static final int LOG_LEVEL_WARN = 900;
public static final int LOG_LEVEL_ERROR = 1000;
public static final int LOG_LEVEL_FATAL = 1000;
@Override
public String format(LogRecord record) {
Throwable t=record.getThrown();
int level=record.getLevel().intValue();
String name=record.getLoggerName();
long time=record.getMillis();
String message=formatMessage(record);
if( name.indexOf('.') >= 0 )
name = name.substring(name.lastIndexOf('.') + 1);
// Use a string buffer for better performance
StringBuilder buf = new StringBuilder();
buf.append(time);
// pad to 8 to make it more readable
for( int i=0; i<8-buf.length(); i++ ) { buf.append(" "); }
// Append a readable representation of the log level.
switch(level) {
case LOG_LEVEL_TRACE: buf.append(" T "); break;
case LOG_LEVEL_DEBUG: buf.append(" D "); break;
case LOG_LEVEL_INFO: buf.append(" I "); break;
case LOG_LEVEL_WARN: buf.append(" W "); break;
case LOG_LEVEL_ERROR: buf.append(" E "); break;
//case : buf.append(" F "); break;
default: buf.append(" ");
}
// Append the name of the log instance if so configured
buf.append(name);
buf.append(" ");
// pad to 20 chars
for( int i=0; i<8-buf.length(); i++ ) { buf.append(" "); }
// Append the message
buf.append(message);
// Append stack trace if not null
if(t != null) {
buf.append(System.lineSeparator());
java.io.StringWriter sw= new java.io.StringWriter(1024);
java.io.PrintWriter pw= new java.io.PrintWriter(sw);
t.printStackTrace(pw);
pw.close();
buf.append(sw.toString());
}
buf.append(System.lineSeparator());
// Print to the appropriate destination
return buf.toString();
}
}

View File

@@ -0,0 +1,253 @@
/*
* 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.juli;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.Writer;
import java.lang.management.ManagementFactory;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.logging.Formatter;
import java.util.logging.LogManager;
import java.util.logging.LogRecord;
/**
* Provides same information as default log format but on a single line to make
* it easier to grep the logs. The only exception is stacktraces which are
* always preceded by whitespace to make it simple to skip them.
*/
/*
* Date processing based on AccessLogValve.
*/
public class OneLineFormatter extends Formatter {
private static final String UNKNOWN_THREAD_NAME = "Unknown thread with ID ";
private static final Object threadMxBeanLock = new Object();
private static volatile ThreadMXBean threadMxBean = null;
private static final int THREAD_NAME_CACHE_SIZE = 10000;
private static ThreadLocal<ThreadNameCache> threadNameCache = new ThreadLocal<ThreadNameCache>() {
@Override
protected ThreadNameCache initialValue() {
return new ThreadNameCache(THREAD_NAME_CACHE_SIZE);
}
};
/* Timestamp format */
private static final String DEFAULT_TIME_FORMAT = "dd-MMM-yyyy HH:mm:ss";
/**
* The size of our global date format cache
*/
private static final int globalCacheSize = 30;
/**
* The size of our thread local date format cache
*/
private static final int localCacheSize = 5;
/**
* Thread local date format cache.
*/
private ThreadLocal<DateFormatCache> localDateCache;
public OneLineFormatter() {
String timeFormat = LogManager.getLogManager().getProperty(
OneLineFormatter.class.getName() + ".timeFormat");
if (timeFormat == null) {
timeFormat = DEFAULT_TIME_FORMAT;
}
setTimeFormat(timeFormat);
}
/**
* Specify the time format to use for time stamps in log messages.
*
* @param timeFormat The format to use using the
* {@link java.text.SimpleDateFormat} syntax
*/
public void setTimeFormat(final String timeFormat) {
final DateFormatCache globalDateCache =
new DateFormatCache(globalCacheSize, timeFormat, null);
localDateCache = new ThreadLocal<DateFormatCache>() {
@Override
protected DateFormatCache initialValue() {
return new DateFormatCache(localCacheSize, timeFormat, globalDateCache);
}
};
}
/**
* Obtain the format currently being used for time stamps in log messages.
*
* @return The current format in {@link java.text.SimpleDateFormat} syntax
*/
public String getTimeFormat() {
return localDateCache.get().getTimeFormat();
}
@Override
public String format(LogRecord record) {
StringBuilder sb = new StringBuilder();
// Timestamp
addTimestamp(sb, record.getMillis());
// Severity
sb.append(' ');
sb.append(record.getLevel().getLocalizedName());
// Thread
sb.append(' ');
sb.append('[');
if (Thread.currentThread() instanceof AsyncFileHandler.LoggerThread) {
// If using the async handler can't get the thread name from the
// current thread.
sb.append(getThreadName(record.getThreadID()));
} else {
sb.append(Thread.currentThread().getName());
}
sb.append(']');
// Source
sb.append(' ');
sb.append(record.getSourceClassName());
sb.append('.');
sb.append(record.getSourceMethodName());
// Message
sb.append(' ');
sb.append(formatMessage(record));
// New line for next record
sb.append(System.lineSeparator());
// Stack trace
if (record.getThrown() != null) {
StringWriter sw = new StringWriter();
PrintWriter pw = new IndentingPrintWriter(sw);
record.getThrown().printStackTrace(pw);
pw.close();
sb.append(sw.getBuffer());
}
return sb.toString();
}
protected void addTimestamp(StringBuilder buf, long timestamp) {
buf.append(localDateCache.get().getFormat(timestamp));
long frac = timestamp % 1000;
buf.append('.');
if (frac < 100) {
if (frac < 10) {
buf.append('0');
buf.append('0');
} else {
buf.append('0');
}
}
buf.append(frac);
}
/**
* LogRecord has threadID but no thread name.
* LogRecord uses an int for thread ID but thread IDs are longs.
* If the real thread ID > (Integer.MAXVALUE / 2) LogRecord uses it's own
* ID in an effort to avoid clashes due to overflow.
* <p>
* Words fail me to describe what I think of the design decision to use an
* int in LogRecord for a long value and the resulting mess that follows.
*/
private static String getThreadName(int logRecordThreadId) {
Map<Integer,String> cache = threadNameCache.get();
String result = null;
if (logRecordThreadId > (Integer.MAX_VALUE / 2)) {
result = cache.get(Integer.valueOf(logRecordThreadId));
}
if (result != null) {
return result;
}
if (logRecordThreadId > Integer.MAX_VALUE / 2) {
result = UNKNOWN_THREAD_NAME + logRecordThreadId;
} else {
// Double checked locking OK as threadMxBean is volatile
if (threadMxBean == null) {
synchronized (threadMxBeanLock) {
if (threadMxBean == null) {
threadMxBean = ManagementFactory.getThreadMXBean();
}
}
}
ThreadInfo threadInfo =
threadMxBean.getThreadInfo(logRecordThreadId);
if (threadInfo == null) {
return Long.toString(logRecordThreadId);
}
result = threadInfo.getThreadName();
}
cache.put(Integer.valueOf(logRecordThreadId), result);
return result;
}
private static class ThreadNameCache extends LinkedHashMap<Integer,String> {
private static final long serialVersionUID = 1L;
private final int cacheSize;
public ThreadNameCache(int cacheSize) {
this.cacheSize = cacheSize;
}
@Override
protected boolean removeEldestEntry(Entry<Integer, String> eldest) {
return (size() > cacheSize);
}
}
/*
* Minimal implementation to indent the printing of stack traces. This
* implementation depends on Throwable using WrappedPrintWriter.
*/
private static class IndentingPrintWriter extends PrintWriter {
public IndentingPrintWriter(Writer out) {
super(out);
}
@Override
public void println(Object x) {
super.print('\t');
super.println(x);
}
}
}

View File

@@ -0,0 +1,43 @@
/*
* 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.juli;
import java.util.logging.Formatter;
import java.util.logging.LogRecord;
/**
* Outputs the just the log message with no additional elements. Stack traces
* are not logged. Log messages are separated by
* <code>System.lineSeparator()</code>. This is intended for use
* by access logs and the like that need complete control over the output
* format.
*/
public class VerbatimFormatter extends Formatter {
@Override
public String format(LogRecord record) {
// Timestamp
StringBuilder sb = new StringBuilder(record.getMessage());
// New line for next record
sb.append(System.lineSeparator());
return sb.toString();
}
}

View File

@@ -0,0 +1,65 @@
/*
* 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.juli;
/**
* An interface intended for use by class loaders associated with a web
* application that enables them to provide additional information to JULI about
* the web application with which they are associated. For any web application
* the combination of {@link #getWebappName()}, {@link #getHostName()} and
* {@link #getServiceName()} must be unique.
*/
public interface WebappProperties {
/**
* Returns a name for the logging system to use for the web application, if
* any, associated with the class loader.
*
* @return The name to use for the web application or null if none is
* available.
*/
String getWebappName();
/**
* Returns a name for the logging system to use for the Host where the
* web application, if any, associated with the class loader is deployed.
*
* @return The name to use for the Host where the web application is
* deployed or null if none is available.
*/
String getHostName();
/**
* Returns a name for the logging system to use for the Service where the
* Host, if any, associated with the class loader is deployed.
*
* @return The name to use for the Service where the Host is deployed or
* null if none is available.
*/
String getServiceName();
/**
* Enables JULI to determine if the web application includes a local
* configuration without JULI having to look for the file which it may not
* have permission to do when running under a SecurityManager.
*
* @return {@code true} if the web application includes a logging
* configuration at the standard location of
* /WEB-INF/classes/logging.properties.
*/
boolean hasLoggingConfig();
}

View File

@@ -0,0 +1,185 @@
/*
* 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.juli.logging;
import java.util.logging.ConsoleHandler;
import java.util.logging.Formatter;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Hard-coded java.util.logging commons-logging implementation.
*/
class DirectJDKLog implements Log {
// no reason to hide this - but good reasons to not hide
public final Logger logger;
// Alternate config reader and console format
private static final String SIMPLE_FMT="java.util.logging.SimpleFormatter";
private static final String FORMATTER="org.apache.juli.formatter";
static {
if (System.getProperty("java.util.logging.config.class") == null &&
System.getProperty("java.util.logging.config.file") == null) {
// default configuration - it sucks. Let's override at least the
// formatter for the console
try {
Formatter fmt= (Formatter) Class.forName(System.getProperty(
FORMATTER, SIMPLE_FMT)).getConstructor().newInstance();
// it is also possible that the user modified jre/lib/logging.properties -
// but that's really stupid in most cases
Logger root=Logger.getLogger("");
for (Handler handler : root.getHandlers()) {
// I only care about console - that's what's used in default config anyway
if (handler instanceof ConsoleHandler) {
handler.setFormatter(fmt);
}
}
} catch (Throwable t) {
// maybe it wasn't included - the ugly default will be used.
}
}
}
public DirectJDKLog(String name ) {
logger=Logger.getLogger(name);
}
@Override
public final boolean isErrorEnabled() {
return logger.isLoggable(Level.SEVERE);
}
@Override
public final boolean isWarnEnabled() {
return logger.isLoggable(Level.WARNING);
}
@Override
public final boolean isInfoEnabled() {
return logger.isLoggable(Level.INFO);
}
@Override
public final boolean isDebugEnabled() {
return logger.isLoggable(Level.FINE);
}
@Override
public final boolean isFatalEnabled() {
return logger.isLoggable(Level.SEVERE);
}
@Override
public final boolean isTraceEnabled() {
return logger.isLoggable(Level.FINER);
}
@Override
public final void debug(Object message) {
log(Level.FINE, String.valueOf(message), null);
}
@Override
public final void debug(Object message, Throwable t) {
log(Level.FINE, String.valueOf(message), t);
}
@Override
public final void trace(Object message) {
log(Level.FINER, String.valueOf(message), null);
}
@Override
public final void trace(Object message, Throwable t) {
log(Level.FINER, String.valueOf(message), t);
}
@Override
public final void info(Object message) {
log(Level.INFO, String.valueOf(message), null);
}
@Override
public final void info(Object message, Throwable t) {
log(Level.INFO, String.valueOf(message), t);
}
@Override
public final void warn(Object message) {
log(Level.WARNING, String.valueOf(message), null);
}
@Override
public final void warn(Object message, Throwable t) {
log(Level.WARNING, String.valueOf(message), t);
}
@Override
public final void error(Object message) {
log(Level.SEVERE, String.valueOf(message), null);
}
@Override
public final void error(Object message, Throwable t) {
log(Level.SEVERE, String.valueOf(message), t);
}
@Override
public final void fatal(Object message) {
log(Level.SEVERE, String.valueOf(message), null);
}
@Override
public final void fatal(Object message, Throwable t) {
log(Level.SEVERE, String.valueOf(message), t);
}
// from commons logging. This would be my number one reason why java.util.logging
// is bad - design by committee can be really bad ! The impact on performance of
// using java.util.logging - and the ugliness if you need to wrap it - is far
// worse than the unfriendly and uncommon default format for logs.
private void log(Level level, String msg, Throwable ex) {
if (logger.isLoggable(level)) {
// Hack (?) to get the stack trace.
Throwable dummyException=new Throwable();
StackTraceElement locations[]=dummyException.getStackTrace();
// Caller will be the third element
String cname = "unknown";
String method = "unknown";
if (locations != null && locations.length >2) {
StackTraceElement caller = locations[2];
cname = caller.getClassName();
method = caller.getMethodName();
}
if (ex==null) {
logger.logp(level, cname, method, msg);
} else {
logger.logp(level, cname, method, msg, ex);
}
}
}
static Log getInstance(String name) {
return new DirectJDKLog( name );
}
}

View File

@@ -0,0 +1,251 @@
/*
* 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.juli.logging;
/**
* <p>A simple logging interface abstracting logging APIs. In order to be
* instantiated successfully by {@link LogFactory}, classes that implement
* this interface must have a constructor that takes a single String
* parameter representing the "name" of this Log.</p>
*
* <p> The six logging levels used by <code>Log</code> are (in order):</p>
* <ol>
* <li>trace (the least serious)</li>
* <li>debug</li>
* <li>info</li>
* <li>warn</li>
* <li>error</li>
* <li>fatal (the most serious)</li>
* </ol>
* <p>The mapping of these log levels to the concepts used by the underlying
* logging system is implementation dependent.
* The implementation should ensure, though, that this ordering behaves
* as expected.</p>
*
* <p>Performance is often a logging concern.
* By examining the appropriate property,
* a component can avoid expensive operations (producing information
* to be logged).</p>
*
* <p> For example,
* <code>
* if (log.isDebugEnabled()) {
* ... do something expensive ...
* log.debug(theResult);
* }
* </code>
* </p>
*
* <p>Configuration of the underlying logging system will generally be done
* external to the Logging APIs, through whatever mechanism is supported by
* that system.</p>
*
* @author <a href="mailto:sanders@apache.org">Scott Sanders</a>
* @author Rod Waldhoff
*/
public interface Log {
// ----------------------------------------------------- Logging Properties
/**
* <p> Is debug logging currently enabled? </p>
*
* <p> Call this method to prevent having to perform expensive operations
* (for example, <code>String</code> concatenation)
* when the log level is more than debug. </p>
*
* @return <code>true</code> if debug level logging is enabled, otherwise
* <code>false</code>
*/
public boolean isDebugEnabled();
/**
* <p> Is error logging currently enabled? </p>
*
* <p> Call this method to prevent having to perform expensive operations
* (for example, <code>String</code> concatenation)
* when the log level is more than error. </p>
*
* @return <code>true</code> if error level logging is enabled, otherwise
* <code>false</code>
*/
public boolean isErrorEnabled();
/**
* <p> Is fatal logging currently enabled? </p>
*
* <p> Call this method to prevent having to perform expensive operations
* (for example, <code>String</code> concatenation)
* when the log level is more than fatal. </p>
*
* @return <code>true</code> if fatal level logging is enabled, otherwise
* <code>false</code>
*/
public boolean isFatalEnabled();
/**
* <p> Is info logging currently enabled? </p>
*
* <p> Call this method to prevent having to perform expensive operations
* (for example, <code>String</code> concatenation)
* when the log level is more than info. </p>
*
* @return <code>true</code> if info level logging is enabled, otherwise
* <code>false</code>
*/
public boolean isInfoEnabled();
/**
* <p> Is trace logging currently enabled? </p>
*
* <p> Call this method to prevent having to perform expensive operations
* (for example, <code>String</code> concatenation)
* when the log level is more than trace. </p>
*
* @return <code>true</code> if trace level logging is enabled, otherwise
* <code>false</code>
*/
public boolean isTraceEnabled();
/**
* <p> Is warn logging currently enabled? </p>
*
* <p> Call this method to prevent having to perform expensive operations
* (for example, <code>String</code> concatenation)
* when the log level is more than warn. </p>
*
* @return <code>true</code> if warn level logging is enabled, otherwise
* <code>false</code>
*/
public boolean isWarnEnabled();
// -------------------------------------------------------- Logging Methods
/**
* <p> Log a message with trace log level. </p>
*
* @param message log this message
*/
public void trace(Object message);
/**
* <p> Log an error with trace log level. </p>
*
* @param message log this message
* @param t log this cause
*/
public void trace(Object message, Throwable t);
/**
* <p> Log a message with debug log level. </p>
*
* @param message log this message
*/
public void debug(Object message);
/**
* <p> Log an error with debug log level. </p>
*
* @param message log this message
* @param t log this cause
*/
public void debug(Object message, Throwable t);
/**
* <p> Log a message with info log level. </p>
*
* @param message log this message
*/
public void info(Object message);
/**
* <p> Log an error with info log level. </p>
*
* @param message log this message
* @param t log this cause
*/
public void info(Object message, Throwable t);
/**
* <p> Log a message with warn log level. </p>
*
* @param message log this message
*/
public void warn(Object message);
/**
* <p> Log an error with warn log level. </p>
*
* @param message log this message
* @param t log this cause
*/
public void warn(Object message, Throwable t);
/**
* <p> Log a message with error log level. </p>
*
* @param message log this message
*/
public void error(Object message);
/**
* <p> Log an error with error log level. </p>
*
* @param message log this message
* @param t log this cause
*/
public void error(Object message, Throwable t);
/**
* <p> Log a message with fatal log level. </p>
*
* @param message log this message
*/
public void fatal(Object message);
/**
* <p> Log an error with fatal log level. </p>
*
* @param message log this message
* @param t log this cause
*/
public void fatal(Object message, Throwable t);
}

View File

@@ -0,0 +1,72 @@
/*
* 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.juli.logging;
/**
* <p>An exception that is thrown only if a suitable <code>LogFactory</code>
* or <code>Log</code> instance cannot be created by the corresponding
* factory methods.</p>
*
* @author Craig R. McClanahan
*/
public class LogConfigurationException extends RuntimeException {
private static final long serialVersionUID = 1L;
/**
* Construct a new exception with <code>null</code> as its detail message.
*/
public LogConfigurationException() {
super();
}
/**
* Construct a new exception with the specified detail message.
*
* @param message The detail message
*/
public LogConfigurationException(String message) {
super(message);
}
/**
* Construct a new exception with the specified cause and a derived
* detail message.
*
* @param cause The underlying cause
*/
public LogConfigurationException(Throwable cause) {
super(cause);
}
/**
* Construct a new exception with the specified detail message and cause.
*
* @param message The detail message
* @param cause The underlying cause
*/
public LogConfigurationException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@@ -0,0 +1,248 @@
/*
* 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.juli.logging;
import java.lang.reflect.Constructor;
import java.nio.file.FileSystems;
import java.util.ServiceLoader;
import java.util.logging.LogManager;
/**
* This is a modified LogFactory that uses a simple {@link ServiceLoader} based
* discovery mechanism with a default of using JDK based logging. An
* implementation that uses the full Commons Logging discovery mechanism is
* available as part of the Tomcat extras download.
*
* Why? It is an attempt to strike a balance between simpler code (no discovery)
* and providing flexibility - particularly for those projects that embed Tomcat
* or some of Tomcat's components - is an alternative logging
* implementation is desired.
*
* Note that this implementation is not just a wrapper around JDK logging (like
* the original commons-logging impl). It adds 2 features - a simpler
* configuration (which is in fact a subset of log4j.properties) and a
* formatter that is less ugly.
*
* The removal of 'abstract' preserves binary backward compatibility. It is
* possible to preserve the abstract - and introduce another (hardcoded) factory
* - but I see no benefit.
*
* Since this class is not intended to be extended - all protected methods are
* removed. This can be changed - but again, there is little value in keeping
* dead code. Just take a quick look at the removed code ( and it's complexity).
*
* --------------
*
* Original comment:
* <p>Factory for creating {@link Log} instances, with discovery and
* configuration features similar to that employed by standard Java APIs
* such as JAXP.</p>
*
* <p><strong>IMPLEMENTATION NOTE</strong> - This implementation is heavily
* based on the SAXParserFactory and DocumentBuilderFactory implementations
* (corresponding to the JAXP pluggability APIs) found in Apache Xerces.</p>
*
*
* @author Craig R. McClanahan
* @author Costin Manolache
* @author Richard A. Sitze
*/
public class LogFactory {
private static final LogFactory singleton = new LogFactory();
private final Constructor<? extends Log> discoveredLogConstructor;
/**
* Private constructor that is not available for public use.
*/
private LogFactory() {
/*
* Work-around known a JRE bug.
* https://bugs.openjdk.java.net/browse/JDK-8194653
*
* Pre-load the default file system. No performance impact as we need to
* load the default file system anyway. Just do it earlier to avoid the
* potential deadlock.
*
* This can be removed once the oldest JRE supported by Tomcat includes
* a fix.
*/
FileSystems.getDefault();
// Look via a ServiceLoader for a Log implementation that has a
// constructor taking the String name.
ServiceLoader<Log> logLoader = ServiceLoader.load(Log.class);
Constructor<? extends Log> m=null;
for (Log log: logLoader) {
Class<? extends Log> c=log.getClass();
try {
m=c.getConstructor(String.class);
break;
}
catch (NoSuchMethodException | SecurityException e) {
throw new Error(e);
}
}
discoveredLogConstructor=m;
}
// --------------------------------------------------------- Public Methods
// only those 2 methods need to change to use a different direct logger.
/**
* <p>Construct (if necessary) and return a <code>Log</code> instance,
* using the factory's current set of configuration attributes.</p>
*
* <p><strong>NOTE</strong> - Depending upon the implementation of
* the <code>LogFactory</code> you are using, the <code>Log</code>
* instance you are returned may or may not be local to the current
* application, and may or may not be returned again on a subsequent
* call with the same name argument.</p>
*
* @param name Logical name of the <code>Log</code> instance to be
* returned (the meaning of this name is only known to the underlying
* logging implementation that is being wrapped)
*
* @return A log instance with the requested name
*
* @exception LogConfigurationException if a suitable <code>Log</code>
* instance cannot be returned
*/
public Log getInstance(String name) throws LogConfigurationException {
if (discoveredLogConstructor == null) {
return DirectJDKLog.getInstance(name);
}
try {
return discoveredLogConstructor.newInstance(name);
} catch (ReflectiveOperationException | IllegalArgumentException e) {
throw new LogConfigurationException(e);
}
}
/**
* Convenience method to derive a name from the specified class and
* call <code>getInstance(String)</code> with it.
*
* @param clazz Class for which a suitable Log name will be derived
*
* @return A log instance with a name of clazz.getName()
*
* @exception LogConfigurationException if a suitable <code>Log</code>
* instance cannot be returned
*/
public Log getInstance(Class<?> clazz) throws LogConfigurationException {
return getInstance( clazz.getName());
}
// ------------------------------------------------------- Static Variables
// --------------------------------------------------------- Static Methods
/**
* <p>Construct (if necessary) and return a <code>LogFactory</code>
* instance, using the following ordered lookup procedure to determine
* the name of the implementation class to be loaded.</p>
* <ul>
* <li>The <code>org.apache.commons.logging.LogFactory</code> system
* property.</li>
* <li>The JDK 1.3 Service Discovery mechanism</li>
* <li>Use the properties file <code>commons-logging.properties</code>
* file, if found in the class path of this class. The configuration
* file is in standard <code>java.util.Properties</code> format and
* contains the fully qualified name of the implementation class
* with the key being the system property defined above.</li>
* <li>Fall back to a default implementation class
* (<code>org.apache.commons.logging.impl.LogFactoryImpl</code>).</li>
* </ul>
*
* <p><em>NOTE</em> - If the properties file method of identifying the
* <code>LogFactory</code> implementation class is utilized, all of the
* properties defined in this file will be set as configuration attributes
* on the corresponding <code>LogFactory</code> instance.</p>
*
* @return The singleton LogFactory instance
*
* @exception LogConfigurationException if the implementation class is not
* available or cannot be instantiated.
*/
public static LogFactory getFactory() throws LogConfigurationException {
return singleton;
}
/**
* Convenience method to return a named logger, without the application
* having to care about factories.
*
* @param clazz Class from which a log name will be derived
*
* @return A log instance with a name of clazz.getName()
*
* @exception LogConfigurationException if a suitable <code>Log</code>
* instance cannot be returned
*/
public static Log getLog(Class<?> clazz)
throws LogConfigurationException {
return getFactory().getInstance(clazz);
}
/**
* Convenience method to return a named logger, without the application
* having to care about factories.
*
* @param name Logical name of the <code>Log</code> instance to be
* returned (the meaning of this name is only known to the underlying
* logging implementation that is being wrapped)
*
* @return A log instance with the requested name
*
* @exception LogConfigurationException if a suitable <code>Log</code>
* instance cannot be returned
*/
public static Log getLog(String name)
throws LogConfigurationException {
return getFactory().getInstance(name);
}
/**
* Release any internal references to previously created {@link LogFactory}
* instances that have been associated with the specified class loader
* (if any), after calling the instance method <code>release()</code> on
* each of them.
*
* @param classLoader ClassLoader for which to release the LogFactory
*/
public static void release(ClassLoader classLoader) {
// JULI's log manager looks at the current classLoader so there is no
// need to use the passed in classLoader, the default implementation
// does not so calling reset in that case will break things
if (!LogManager.getLogManager().getClass().getName().equals(
"java.util.logging.LogManager")) {
LogManager.getLogManager().reset();
}
}
}

View File

@@ -0,0 +1,37 @@
<!--
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.
-->
<body>
<h2>Overview</h2>
<p>This implementation of commons-logging uses a commons-logging.jar
specific to a particular logging framework, instead of discovery. This takes
out the guessing, is simpler, faster and more robust. Just like you chose a
logging implementation, you should also use a matching commons-logging - for
example you download log4j.jar and commons-logging-log4j.jar, or use jdk
logging and use commons-logging-jdk.jar.</p>
<p>A similar packaging is used by Eclipse SWT - they provide a common widget API,
but each platform uses a different implementation jar - instead of using a complex
discovery/plugin mechanism.
</p>
<p>This package generates commons-logging-jdk14.jar - i.e. the java.util implementation
of commons-logging api.</p>
</body>