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