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 c) { + super(c); + } + + static ReadOnlyArrayList of(final Collection 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 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 nl = nameMap.get(name); + if (nl == null) { + nl = new ArrayList(); + nameMap.put(name, nl); + } + nl.add(constructor.newInstance(knownLevel)); + + final int intValue = level.intValue(); + List il = intMap.get(intValue); + if (il == null) { + il = new ArrayList(); + intMap.put(intValue, il); + } + il.add(knownLevel); + return this; + } + + public ReadOnlyHashMap> toNameMap() { + final List>> list = + new ArrayList>>(nameMap.size()); + for (String key : nameMap.keySet()) { + list.add(ReadOnlyMapEntry.of(key, ReadOnlyArrayList.of(nameMap.get(key)))); + } + return ReadOnlyHashMap.of(list); + } + + public ReadOnlyHashMap> toIntMap() { + final List>> list = + new ArrayList>>(intMap.size()); + for (Integer key : intMap.keySet()) { + list.add(ReadOnlyMapEntry.of(key, ReadOnlyArrayList.of(intMap.get(key)))); + } + return ReadOnlyHashMap.of(list); + } + } +} diff --git a/src/main/java/org/jboss/logmanager/LogManager.java b/src/main/java/org/jboss/logmanager/LogManager.java index d99bc542..cae49950 100644 --- a/src/main/java/org/jboss/logmanager/LogManager.java +++ b/src/main/java/org/jboss/logmanager/LogManager.java @@ -22,21 +22,10 @@ import java.beans.PropertyChangeListener; import java.io.IOException; import java.io.InputStream; -import java.lang.reflect.Constructor; -import java.lang.reflect.Field; -import java.lang.reflect.InvocationTargetException; import java.security.AccessController; import java.security.PrivilegedAction; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; import java.util.Enumeration; -import java.util.HashMap; import java.util.Iterator; -import java.util.List; -import java.util.ListIterator; -import java.util.Map; -import java.util.Map.Entry; import java.util.ServiceLoader; import java.util.concurrent.atomic.AtomicBoolean; import java.util.logging.Filter; @@ -88,359 +77,7 @@ private static class LocalFilterHolder { * well. */ public LogManager() { - AccessController.doPrivileged(new PrivilegedAction() { - @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; - } - }); - } - - private static final class ReadOnlyArrayList extends ArrayList { - - private static final long serialVersionUID = -6048215349511680936L; - - private ReadOnlyArrayList(final Collection c) { - super(c); - } - - static ReadOnlyArrayList of(final Collection 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; - } - } - - private 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 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()); - } - } - - private static final class ReadOnlyMapEntry implements 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; - } - } - - private static class KnownLevelBuilder { - private final Map> nameMap; - private final Map> intMap; - private final Constructor constructor; - - private 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 nl = nameMap.get(name); - if (nl == null) { - nl = new ArrayList(); - nameMap.put(name, nl); - } - nl.add(constructor.newInstance(knownLevel)); - - final int intValue = level.intValue(); - List il = intMap.get(intValue); - if (il == null) { - il = new ArrayList(); - intMap.put(intValue, il); - } - il.add(knownLevel); - return this; - } - - public ReadOnlyHashMap> toNameMap() { - final List>> list = - new ArrayList>>(nameMap.size()); - for (String key : nameMap.keySet()) { - list.add(ReadOnlyMapEntry.of(key, ReadOnlyArrayList.of(nameMap.get(key)))); - } - return ReadOnlyHashMap.of(list); - } - - public ReadOnlyHashMap> toIntMap() { - final List>> list = - new ArrayList>>(intMap.size()); - for (Integer key : intMap.keySet()) { - list.add(ReadOnlyMapEntry.of(key, ReadOnlyArrayList.of(intMap.get(key)))); - } - return ReadOnlyHashMap.of(list); - } + AccessController.doPrivileged(new LogLevelInitTask()); } // Configuration diff --git a/src/main/java/org/jboss/logmanager/formatters/FormatStringParser.java b/src/main/java/org/jboss/logmanager/formatters/FormatStringParser.java index f994a958..823afeb0 100644 --- a/src/main/java/org/jboss/logmanager/formatters/FormatStringParser.java +++ b/src/main/java/org/jboss/logmanager/formatters/FormatStringParser.java @@ -94,6 +94,10 @@ public static FormatStep[] getSteps(final String formatString, ColorMap colors) stepList.add(Formatters.dateFormatStep(timeZone, argument, leftJustify, minimumWidth, truncateBeginning, maximumWidth)); break; } + case 'D': { + stepList.add(Formatters.moduleNameFormatStep(leftJustify, minimumWidth, truncateBeginning, maximumWidth, argument)); + break; + } case 'e': { stepList.add(Formatters.exceptionFormatStep(leftJustify, minimumWidth, truncateBeginning, maximumWidth, argument, false)); break; @@ -173,6 +177,10 @@ public static FormatStep[] getSteps(final String formatString, ColorMap colors) stepList.add(Formatters.threadFormatStep(argument, leftJustify, minimumWidth, truncateBeginning, maximumWidth)); break; } + case 'v': { + stepList.add(Formatters.moduleVersionFormatStep(leftJustify, minimumWidth, maximumWidth, argument)); + break; + } case 'x': { final int count = argument == null ? 0 : Integer.parseInt(argument); stepList.add(Formatters.ndcFormatStep(leftJustify, minimumWidth, truncateBeginning, maximumWidth, count)); diff --git a/src/main/java/org/jboss/logmanager/formatters/Formatters.java b/src/main/java/org/jboss/logmanager/formatters/Formatters.java index 56926254..11aa0695 100644 --- a/src/main/java/org/jboss/logmanager/formatters/Formatters.java +++ b/src/main/java/org/jboss/logmanager/formatters/Formatters.java @@ -343,6 +343,71 @@ public boolean isCallerInformationRequired() { }; } + /** + * Create a format step which emits the source module name with the given justification rules (NOTE: call stack + * introspection introduces a significant performance penalty). + * + * @param leftJustify {@code true} to left justify, {@code false} to right justify + * @param minimumWidth the minimum field width, or 0 for none + * @param maximumWidth the maximum field width (must be greater than {@code minimumFieldWidth}), or 0 for none + * @param precision the argument used for the class name, may be {@code null} or contain dots to format the class name + * @return the format step + */ + public static FormatStep moduleNameFormatStep(final boolean leftJustify, final int minimumWidth, final int maximumWidth, final String precision) { + return moduleNameFormatStep(leftJustify, minimumWidth, DEFAULT_TRUNCATE_BEGINNING, maximumWidth, precision); + } + + /** + * Create a format step which emits the source module name with the given justification rules (NOTE: call stack + * introspection introduces a significant performance penalty). + * + * @param leftJustify {@code true} to left justify, {@code false} to right justify + * @param minimumWidth the minimum field width, or 0 for none + * @param truncateBeginning {@code true} to truncate the beginning, otherwise {@code false} to truncate the end + * @param maximumWidth the maximum field width (must be greater than {@code minimumFieldWidth}), or 0 for none + * @param precision the argument used for the class name, may be {@code null} or contain dots to format the + * class name + * + * @return the format step + */ + public static FormatStep moduleNameFormatStep(final boolean leftJustify, final int minimumWidth, final boolean truncateBeginning, final int maximumWidth, final String precision) { + return new SegmentedFormatStep(leftJustify, minimumWidth, truncateBeginning, maximumWidth, precision) { + public String getSegmentedSubject(final ExtLogRecord record) { + return record.getSourceModuleName(); + } + + @Override + public boolean isCallerInformationRequired() { + return true; + } + }; + } + + /** + * Create a format step which emits the source module version with the given justification rules (NOTE: call stack + * introspection introduces a significant performance penalty). + * + * @param leftJustify {@code true} to left justify, {@code false} to right justify + * @param minimumWidth the minimum field width, or 0 for none + * @param maximumWidth the maximum field width (must be greater than {@code minimumFieldWidth}), or 0 for none + * @param precision the argument used for the class name, may be {@code null} or contain dots to format the + * class name + * + * @return the format step + */ + public static FormatStep moduleVersionFormatStep(final boolean leftJustify, final int minimumWidth, final int maximumWidth, final String precision) { + return new SegmentedFormatStep(leftJustify, minimumWidth, DEFAULT_TRUNCATE_BEGINNING, maximumWidth, precision) { + public String getSegmentedSubject(final ExtLogRecord record) { + return record.getSourceModuleVersion(); + } + + @Override + public boolean isCallerInformationRequired() { + return true; + } + }; + } + /** * Create a format step which emits the date of the log record with the given justification rules. * diff --git a/src/main/java/org/jboss/logmanager/formatters/JsonFormatter.java b/src/main/java/org/jboss/logmanager/formatters/JsonFormatter.java index 67a7b334..6d52d39e 100644 --- a/src/main/java/org/jboss/logmanager/formatters/JsonFormatter.java +++ b/src/main/java/org/jboss/logmanager/formatters/JsonFormatter.java @@ -42,6 +42,8 @@ *
  • {@link org.jboss.logmanager.ExtLogRecord#getSourceFileName() source file name}
  • *
  • {@link org.jboss.logmanager.ExtLogRecord#getSourceMethodName() source method name}
  • *
  • {@link org.jboss.logmanager.ExtLogRecord#getSourceLineNumber() source line number}
  • + *
  • {@link org.jboss.logmanager.ExtLogRecord#getSourceModuleName() source module name}
  • + *
  • {@link org.jboss.logmanager.ExtLogRecord#getSourceModuleVersion() source module version}
  • * * * @author James R. Perkins diff --git a/src/main/java/org/jboss/logmanager/formatters/StructuredFormatter.java b/src/main/java/org/jboss/logmanager/formatters/StructuredFormatter.java index 0369f6b2..c87ed7fa 100644 --- a/src/main/java/org/jboss/logmanager/formatters/StructuredFormatter.java +++ b/src/main/java/org/jboss/logmanager/formatters/StructuredFormatter.java @@ -80,6 +80,8 @@ public enum Key { SOURCE_FILE_NAME("sourceFileName"), SOURCE_LINE_NUMBER("sourceLineNumber"), SOURCE_METHOD_NAME("sourceMethodName"), + SOURCE_MODULE_NAME("sourceModuleName"), + SOURCE_MODULE_VERSION("sourceModuleVersion"), STACK_TRACE("stackTrace"), THREAD_ID("threadId"), THREAD_NAME("threadName"), @@ -255,7 +257,9 @@ public final synchronized String format(final ExtLogRecord record) { generator.add(getKey(Key.SOURCE_CLASS_NAME), record.getSourceClassName()) .add(getKey(Key.SOURCE_FILE_NAME), record.getSourceFileName()) .add(getKey(Key.SOURCE_METHOD_NAME), record.getSourceMethodName()) - .add(getKey(Key.SOURCE_LINE_NUMBER), record.getSourceLineNumber()); + .add(getKey(Key.SOURCE_LINE_NUMBER), record.getSourceLineNumber()) + .add(getKey(Key.SOURCE_MODULE_NAME), record.getSourceModuleName()) + .add(getKey(Key.SOURCE_MODULE_VERSION), record.getSourceModuleVersion()); } if (isNotNullOrEmpty(metaData)) { diff --git a/src/main/resources/xml-formatter.xsd b/src/main/resources/xml-formatter.xsd index 67973f08..211ab0ea 100644 --- a/src/main/resources/xml-formatter.xsd +++ b/src/main/resources/xml-formatter.xsd @@ -171,6 +171,20 @@ + + + + The name of the module that (allegedly) issued the logging request. + + + + + + + The version of the module that (allegedly) issued the logging request. + + +