diff --git a/pom.xml b/pom.xml
index 03c301df..6ed25de5 100644
--- a/pom.xml
+++ b/pom.xml
@@ -49,7 +49,7 @@
1.0
3.0.10
1.0.4
- 1.5.2.Final
+ 1.6.2.Final
1.2.0.Final
4.12
diff --git a/src/main/java/org/jboss/logmanager/CallerClassLoaderLogContextSelector.java b/src/main/java/org/jboss/logmanager/CallerClassLoaderLogContextSelector.java
index b1eb7584..5216f9bd 100644
--- a/src/main/java/org/jboss/logmanager/CallerClassLoaderLogContextSelector.java
+++ b/src/main/java/org/jboss/logmanager/CallerClassLoaderLogContextSelector.java
@@ -86,22 +86,6 @@ public CallerClassLoaderLogContextSelector(final boolean checkParentClassLoaders
this(LogContext.DEFAULT_LOG_CONTEXT_SELECTOR, checkParentClassLoaders);
}
- private static final class Gateway extends SecurityManager {
- protected Class[] getClassContext() {
- return super.getClassContext();
- }
- }
-
- private static final Gateway GATEWAY;
-
- static {
- GATEWAY = AccessController.doPrivileged(new PrivilegedAction() {
- public Gateway run() {
- return new Gateway();
- }
- });
- }
-
private final LogContextSelector defaultSelector;
private final ConcurrentMap contextMap = new CopyOnWriteMap();
@@ -110,32 +94,20 @@ public Gateway run() {
private final PrivilegedAction logContextAction = new PrivilegedAction() {
public LogContext run() {
- for (Class> caller : GATEWAY.getClassContext()) {
- final ClassLoader classLoader = caller.getClassLoader();
- // If the class loader is a log API class loader or null (bootstrap class loader), keep checking
- if (classLoader == null || logApiClassLoaders.contains(classLoader)) {
- continue;
- }
- final LogContext result = check(classLoader);
- if (result != null) {
- return result;
- }
- break;
- }
- return defaultSelector.getLogContext();
+ final Class> callingClass = JDKSpecific.findCallingClass(logApiClassLoaders);
+ return callingClass == null ? defaultSelector.getLogContext() : check(callingClass.getClassLoader());
}
private LogContext check(final ClassLoader classLoader) {
- if (classLoader != null && !logApiClassLoaders.contains(classLoader)) {
- final LogContext context = contextMap.get(classLoader);
- if (context != null) {
- return context;
- }
- if (checkParentClassLoaders) {
- return check(classLoader.getParent());
- }
+ final LogContext context = contextMap.get(classLoader);
+ if (context != null) {
+ return context;
}
- return null;
+ final ClassLoader parent = classLoader.getParent();
+ if (parent != null && checkParentClassLoaders && ! logApiClassLoaders.contains(parent)) {
+ return check(parent);
+ }
+ return defaultSelector.getLogContext();
}
};
diff --git a/src/main/java/org/jboss/logmanager/ClassLoaderLogContextSelector.java b/src/main/java/org/jboss/logmanager/ClassLoaderLogContextSelector.java
index e2929018..2a1682bf 100644
--- a/src/main/java/org/jboss/logmanager/ClassLoaderLogContextSelector.java
+++ b/src/main/java/org/jboss/logmanager/ClassLoaderLogContextSelector.java
@@ -81,22 +81,6 @@ public ClassLoaderLogContextSelector(final boolean checkParentClassLoaders) {
this(LogContext.DEFAULT_LOG_CONTEXT_SELECTOR, checkParentClassLoaders);
}
- private static final class Gateway extends SecurityManager {
- protected Class[] getClassContext() {
- return super.getClassContext();
- }
- }
-
- private static final Gateway GATEWAY;
-
- static {
- GATEWAY = AccessController.doPrivileged(new PrivilegedAction() {
- public Gateway run() {
- return new Gateway();
- }
- });
- }
-
private final LogContextSelector defaultSelector;
private final ConcurrentMap contextMap = new CopyOnWriteMap();
@@ -105,27 +89,20 @@ public Gateway run() {
private final PrivilegedAction logContextAction = new PrivilegedAction() {
public LogContext run() {
- for (Class> caller : GATEWAY.getClassContext()) {
- final ClassLoader classLoader = caller.getClassLoader();
- final LogContext result = check(classLoader);
- if (result != null) {
- return result;
- }
- }
- return defaultSelector.getLogContext();
+ final Class> callingClass = JDKSpecific.findCallingClass(logApiClassLoaders);
+ return callingClass == null ? defaultSelector.getLogContext() : check(callingClass.getClassLoader());
}
private LogContext check(final ClassLoader classLoader) {
- if (classLoader != null && !logApiClassLoaders.contains(classLoader)) {
- final LogContext context = contextMap.get(classLoader);
- if (context != null) {
- return context;
- }
- if (checkParentClassLoaders) {
- return check(classLoader.getParent());
- }
+ final LogContext context = contextMap.get(classLoader);
+ if (context != null) {
+ return context;
}
- return null;
+ final ClassLoader parent = classLoader.getParent();
+ if (parent != null && checkParentClassLoaders && ! logApiClassLoaders.contains(parent)) {
+ return check(parent);
+ }
+ return defaultSelector.getLogContext();
}
};
diff --git a/src/main/java/org/jboss/logmanager/ExtLogRecord.java b/src/main/java/org/jboss/logmanager/ExtLogRecord.java
index fc37a2ab..60f121de 100644
--- a/src/main/java/org/jboss/logmanager/ExtLogRecord.java
+++ b/src/main/java/org/jboss/logmanager/ExtLogRecord.java
@@ -154,6 +154,8 @@ public static ExtLogRecord wrap(LogRecord rec) {
private String hostName;
private String processName;
private long processId = -1;
+ private String sourceModuleName;
+ private String sourceModuleVersion;
private void writeObject(ObjectOutputStream oos) throws IOException {
copyAll();
@@ -174,6 +176,8 @@ private void readObject(ObjectInputStream ois) throws IOException, ClassNotFound
hostName = (String) fields.get("hostName", null);
processName = (String) fields.get("processName", null);
processId = fields.get("processId", -1L);
+ sourceModuleName = (String) fields.get("sourceModuleName", null);
+ sourceModuleVersion = (String) fields.get("sourceModuleVersion", null);
}
/**
@@ -326,30 +330,16 @@ private void calculateCaller() {
return;
}
calculateCaller = false;
- final StackTraceElement[] stack = new Throwable().getStackTrace();
- boolean found = false;
- for (StackTraceElement element : stack) {
- final String className = element.getClassName();
- if (found) {
- if (! loggerClassName.equals(className)) {
- setSourceClassName(className);
- setSourceMethodName(element.getMethodName());
- setSourceLineNumber(element.getLineNumber());
- setSourceFileName(element.getFileName());
- return;
- }
- } else {
- found = loggerClassName.equals(className);
- }
- }
- setUnknownCaller();
+ JDKSpecific.calculateCaller(this);
}
- private void setUnknownCaller() {
- setSourceClassName("");
- setSourceMethodName("");
+ void setUnknownCaller() {
+ setSourceClassName(null);
+ setSourceMethodName(null);
setSourceLineNumber(-1);
- setSourceFileName("");
+ setSourceFileName(null);
+ setSourceModuleName(null);
+ setSourceModuleVersion(null);
}
/**
@@ -426,6 +416,46 @@ public void setSourceMethodName(final String sourceMethodName) {
super.setSourceMethodName(sourceMethodName);
}
+ /**
+ * Get the name of the module that initiated the logging request, if known.
+ *
+ * @return the name of the module that initiated the logging request
+ */
+ public String getSourceModuleName() {
+ calculateCaller();
+ return sourceModuleName;
+ }
+
+ /**
+ * Set the source module name of this record.
+ *
+ * @param sourceModuleName the source module name
+ */
+ public void setSourceModuleName(final String sourceModuleName) {
+ calculateCaller = false;
+ this.sourceModuleName = sourceModuleName;
+ }
+
+ /**
+ * Get the version of the module that initiated the logging request, if known.
+ *
+ * @return the version of the module that initiated the logging request
+ */
+ public String getSourceModuleVersion() {
+ calculateCaller();
+ return sourceModuleVersion;
+ }
+
+ /**
+ * Set the source module version of this record.
+ *
+ * @param sourceModuleVersion the source module version
+ */
+ public void setSourceModuleVersion(final String sourceModuleVersion) {
+ calculateCaller = false;
+ this.sourceModuleVersion = sourceModuleVersion;
+ }
+
/**
* Get the fully formatted log record, with resources resolved and parameters applied.
*
diff --git a/src/main/java/org/jboss/logmanager/JDKSpecific.java b/src/main/java/org/jboss/logmanager/JDKSpecific.java
new file mode 100644
index 00000000..7e7e7e7c
--- /dev/null
+++ b/src/main/java/org/jboss/logmanager/JDKSpecific.java
@@ -0,0 +1,120 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ *
+ * Copyright 2017 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed 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.jboss.logmanager;
+
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.Set;
+
+import org.jboss.modules.Module;
+import org.jboss.modules.Version;
+
+/**
+ * @author David M. Lloyd
+ */
+final class JDKSpecific {
+ private JDKSpecific() {}
+
+ private static final Gateway GATEWAY;
+ private static final boolean JBOSS_MODULES;
+
+ static {
+ GATEWAY = AccessController.doPrivileged(new PrivilegedAction() {
+ public Gateway run() {
+ return new Gateway();
+ }
+ });
+ boolean jbossModules = false;
+ try {
+ Module.getStartTime();
+ jbossModules = true;
+ } catch (Throwable ignored) {}
+ JBOSS_MODULES = jbossModules;
+ }
+
+ static final class Gateway extends SecurityManager {
+ protected Class>[] getClassContext() {
+ return super.getClassContext();
+ }
+ }
+
+ static Class> findCallingClass(Set rejectClassLoaders) {
+ for (Class> caller : GATEWAY.getClassContext()) {
+ final ClassLoader classLoader = caller.getClassLoader();
+ if (classLoader != null && ! rejectClassLoaders.contains(classLoader)) {
+ return caller;
+ }
+ }
+ return null;
+ }
+
+ static void calculateCaller(ExtLogRecord logRecord) {
+ final String loggerClassName = logRecord.getLoggerClassName();
+ final StackTraceElement[] stackTrace = new Throwable().getStackTrace();
+ final Class>[] classes = GATEWAY.getClassContext();
+ // The stack trace may be missing classes, but the class context is not, so if we find a mismatch, we skip the class context items.
+ int i = 1, j = 0;
+ Class> clazz = classes[i++];
+ StackTraceElement element = stackTrace[j++];
+ boolean found = false;
+ for (;;) {
+ if (clazz.getName().equals(element.getClassName())) {
+ if (clazz.getName().equals(loggerClassName)) {
+ // next entry could be the one we want!
+ found = true;
+ } else {
+ if (found) {
+ logRecord.setSourceClassName(element.getClassName());
+ logRecord.setSourceMethodName(element.getMethodName());
+ logRecord.setSourceFileName(element.getFileName());
+ logRecord.setSourceLineNumber(element.getLineNumber());
+ if (JBOSS_MODULES) {
+ calculateModule(logRecord, clazz);
+ }
+ return;
+ }
+ }
+ if (j == classes.length) {
+ logRecord.setUnknownCaller();
+ return;
+ }
+ element = stackTrace[j ++];
+ }
+ if (i == classes.length) {
+ logRecord.setUnknownCaller();
+ return;
+ }
+ clazz = classes[i ++];
+ }
+ }
+
+ private static void calculateModule(final ExtLogRecord logRecord, final Class> clazz) {
+ final Module module = Module.forClass(clazz);
+ if (module != null) {
+ logRecord.setSourceModuleName(module.getName());
+ final Version version = module.getVersion();
+ if (version != null) {
+ logRecord.setSourceModuleVersion(version.toString());
+ } else {
+ logRecord.setSourceModuleVersion(null);
+ }
+ }
+ }
+}
diff --git a/src/main/java/org/jboss/logmanager/LogLevelInitTask.java b/src/main/java/org/jboss/logmanager/LogLevelInitTask.java
new file mode 100644
index 00000000..f0d43422
--- /dev/null
+++ b/src/main/java/org/jboss/logmanager/LogLevelInitTask.java
@@ -0,0 +1,390 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2017 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed 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.jboss.logmanager;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.security.PrivilegedAction;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Map;
+
+/**
+ */
+class LogLevelInitTask implements PrivilegedAction {
+ LogLevelInitTask() {
+ }
+
+ @SuppressWarnings({ "unchecked" })
+ public Void run() {
+ /* This mysterious-looking hack is designed to trick JDK logging into not leaking classloaders and
+ so forth when adding levels, by simply shutting down the craptastic level name "registry" that it keeps.
+ */
+ final Class lc = java.util.logging.Level.class;
+ try {
+ synchronized (lc) {
+ final Field knownField = lc.getDeclaredField("known");
+ knownField.setAccessible(true);
+ final List old = (List) knownField.get(null);
+ if (! (old instanceof ReadOnlyArrayList)) {
+ knownField.set(null, new ReadOnlyArrayList(Arrays.asList(
+ Level.TRACE,
+ Level.DEBUG,
+ Level.INFO,
+ Level.WARN,
+ Level.ERROR,
+ Level.FATAL,
+ java.util.logging.Level.ALL,
+ java.util.logging.Level.FINEST,
+ java.util.logging.Level.FINER,
+ java.util.logging.Level.FINE,
+ java.util.logging.Level.INFO,
+ java.util.logging.Level.CONFIG,
+ java.util.logging.Level.WARNING,
+ java.util.logging.Level.SEVERE,
+ java.util.logging.Level.OFF
+ )));
+ }
+ }
+ } catch (Throwable e) {
+ // ignore; just don't install
+ }
+
+ // OpenJDK uses a KnownLevel inner class with two static maps
+ try {
+ final Class> knownLevelClass = Class.forName("java.util.logging.Level$KnownLevel");
+ synchronized (knownLevelClass) {
+ final Constructor> constructor = knownLevelClass.getDeclaredConstructor(java.util.logging.Level.class);
+ constructor.setAccessible(true);
+ boolean doBuild = false;
+ boolean setNameToLevel = false;
+ boolean setIntToLevel = false;
+ // namesToLevels
+ final Field nameToLevels = knownLevelClass.getDeclaredField("nameToLevels");
+ nameToLevels.setAccessible(true);
+ // Current
+ final Map oldNameToLevels = (Map) nameToLevels.get(null);
+ if (! (oldNameToLevels instanceof ReadOnlyHashMap)) {
+ doBuild = true;
+ setNameToLevel = true;
+ }
+
+ final Field intToLevels = knownLevelClass.getDeclaredField("intToLevels");
+ intToLevels.setAccessible(true);
+ final Map oldIntToLevels = (Map) intToLevels.get(null);
+ if (! (oldIntToLevels instanceof ReadOnlyHashMap)) {
+ doBuild = true;
+ setIntToLevel = true;
+ }
+
+ if (doBuild) {
+ final KnownLevelBuilder builder = new KnownLevelBuilder(constructor)
+ .add(Level.TRACE)
+ .add(Level.DEBUG)
+ .add(Level.INFO)
+ .add(Level.WARN)
+ .add(Level.ERROR)
+ .add(Level.FATAL)
+ .add(java.util.logging.Level.ALL)
+ .add(java.util.logging.Level.FINEST)
+ .add(java.util.logging.Level.FINER)
+ .add(java.util.logging.Level.FINE)
+ .add(java.util.logging.Level.INFO)
+ .add(java.util.logging.Level.CONFIG)
+ .add(java.util.logging.Level.WARNING)
+ .add(java.util.logging.Level.SEVERE)
+ .add(java.util.logging.Level.OFF);
+
+ if (setNameToLevel) {
+ nameToLevels.set(null, builder.toNameMap());
+ }
+ if (setIntToLevel) {
+ intToLevels.set(null, builder.toIntMap());
+ }
+ }
+ }
+ } catch (Throwable e) {
+ // ignore
+ }
+
+ /* Next hack: the default Sun JMX implementation has a horribly inefficient log implementation which
+ kills performance if a custom logmanager is used. We'll just blot that out.
+ */
+ try {
+ final Class> traceManagerClass = Class.forName("com.sun.jmx.trace.Trace");
+ final Field outField = traceManagerClass.getDeclaredField("out");
+ outField.setAccessible(true);
+ outField.set(null, null);
+ } catch (Throwable e) {
+ // ignore; just skip it
+ }
+ /* Next hack: Replace the crappy MXBean on the system logmanager, if it's there.
+ */
+ final Class lmc = java.util.logging.LogManager.class;
+ try {
+ synchronized (lmc) {
+ final Field loggingMXBean = lmc.getDeclaredField("loggingMXBean");
+ loggingMXBean.setAccessible(true);
+ loggingMXBean.set(null, LogContext.getSystemLogContext().getLoggingMXBean());
+ }
+ } catch (Throwable e) {
+ // ignore; just skip it
+ }
+ return null;
+ }
+
+ static final class ReadOnlyArrayList extends ArrayList {
+
+ private static final long serialVersionUID = -6048215349511680936L;
+
+ ReadOnlyArrayList(final Collection extends T> c) {
+ super(c);
+ }
+
+ static ReadOnlyArrayList of(final Collection extends T> c) {
+ return new ReadOnlyArrayList(c);
+ }
+
+ public T set(final int index, final T element) {
+ // ignore
+ return null;
+ }
+
+ public T remove(final int index) {
+ // ignore
+ return null;
+ }
+
+ public boolean remove(final Object o) {
+ // ignore
+ return false;
+ }
+
+ public void clear() {
+ // ignore
+ }
+
+ protected void removeRange(final int fromIndex, final int toIndex) {
+ // ignore
+ }
+
+ public Iterator iterator() {
+ final Iterator superIter = super.iterator();
+ return new Iterator() {
+ public boolean hasNext() {
+ return superIter.hasNext();
+ }
+
+ public T next() {
+ return superIter.next();
+ }
+
+ public void remove() {
+ // ignore
+ }
+ };
+ }
+
+ public ListIterator listIterator(final int index) {
+ final ListIterator superIter = super.listIterator(index);
+ return new ListIterator() {
+ public boolean hasNext() {
+ return superIter.hasNext();
+ }
+
+ public T next() {
+ return superIter.next();
+ }
+
+ public boolean hasPrevious() {
+ return superIter.hasPrevious();
+ }
+
+ public T previous() {
+ return superIter.previous();
+ }
+
+ public int nextIndex() {
+ return superIter.nextIndex();
+ }
+
+ public int previousIndex() {
+ return superIter.previousIndex();
+ }
+
+ public void remove() {
+ // ignore
+ }
+
+ public void set(final T o) {
+ // ignore
+ }
+
+ public void add(final T o) {
+ // ignore
+ }
+ };
+ }
+
+ public boolean removeAll(final Collection> c) {
+ // ignore
+ return false;
+ }
+
+ public boolean retainAll(final Collection> c) {
+ // ignore
+ return false;
+ }
+ }
+
+ static final class ReadOnlyHashMap extends HashMap {
+
+ private static final long serialVersionUID = -6048215349511680936L;
+
+ ReadOnlyHashMap(final int size) {
+ super(size);
+ }
+
+ static ReadOnlyHashMap of(final List> entries) {
+ final ReadOnlyHashMap result = new ReadOnlyHashMap(entries.size());
+ for (ReadOnlyMapEntry entry : entries) {
+ result.add(entry.getKey(), entry.getValue());
+ }
+ return result;
+ }
+
+ private void add(final K key, final V value) {
+ super.put(key, value);
+ }
+
+ @Override
+ public V put(final K key, final V value) {
+ // ignore
+ return null;
+ }
+
+ @Override
+ public void putAll(final Map extends K, ? extends V> m) {
+ // ignore
+ }
+
+ @Override
+ public V remove(final Object key) {
+ // ignore
+ return null;
+ }
+
+ @Override
+ public void clear() {
+ // ignore
+ }
+
+ @Override
+ public Collection values() {
+ return new ReadOnlyArrayList(super.values());
+ }
+ }
+
+ static final class ReadOnlyMapEntry implements Map.Entry {
+
+ private final K key;
+ private final V value;
+
+ private ReadOnlyMapEntry(final K key, final V value) {
+ this.key = key;
+ this.value = value;
+ }
+
+ static ReadOnlyMapEntry of(final K key, final V value) {
+ return new ReadOnlyMapEntry(key, value);
+ }
+
+ @Override
+ public K getKey() {
+ return key;
+ }
+
+ @Override
+ public V getValue() {
+ return value;
+ }
+
+ @Override
+ public V setValue(final V value) {
+ // ignore
+ return null;
+ }
+ }
+
+ static class KnownLevelBuilder {
+ private final Map> nameMap;
+ private final Map> intMap;
+ private final Constructor> constructor;
+
+ KnownLevelBuilder(final Constructor> constructor) {
+ nameMap = new HashMap>();
+ intMap = new HashMap>();
+ this.constructor = constructor;
+ }
+
+ public KnownLevelBuilder add(final java.util.logging.Level level) throws IllegalAccessException, InvocationTargetException, InstantiationException {
+ final String name = level.getName();
+ final Object knownLevel = constructor.newInstance(level);
+ List