From 229297e47ba738dec4e636d62f370e39bea91337 Mon Sep 17 00:00:00 2001 From: "David M. Lloyd" Date: Wed, 21 Nov 2018 00:00:37 -0600 Subject: [PATCH 1/9] [LOGMGR-220] Use common reference type instead of custom classes --- .../java/org/jboss/logmanager/LogContext.java | 64 +++++++------------ 1 file changed, 22 insertions(+), 42 deletions(-) diff --git a/core/src/main/java/org/jboss/logmanager/LogContext.java b/core/src/main/java/org/jboss/logmanager/LogContext.java index b3905046..4019705a 100644 --- a/core/src/main/java/org/jboss/logmanager/LogContext.java +++ b/core/src/main/java/org/jboss/logmanager/LogContext.java @@ -19,7 +19,9 @@ package org.jboss.logmanager; -import java.lang.ref.WeakReference; +import org.wildfly.common.ref.Reference; +import org.wildfly.common.ref.References; + import java.security.Permission; import java.util.Collection; import java.util.Enumeration; @@ -60,17 +62,17 @@ public final class LogContext implements AutoCloseable { * before the class init is complete. */ private static final class LazyHolder { - private static final HashMap INITIAL_LEVEL_MAP; + private static final HashMap> INITIAL_LEVEL_MAP; private LazyHolder() { } - private static void addStrong(Map map, Level level) { - map.put(level.getName().toUpperCase(), new StrongLevelRef(level)); + private static void addStrong(Map> map, Level level) { + map.put(level.getName().toUpperCase(), References.create(Reference.Type.STRONG, level, null)); } static { - final HashMap map = new HashMap(); + final HashMap> map = new HashMap>(); addStrong(map, Level.OFF); addStrong(map, Level.ALL); addStrong(map, Level.SEVERE); @@ -92,7 +94,7 @@ private static void addStrong(Map map, Level level) { } } - private final AtomicReference> levelMapReference; + private final AtomicReference>> levelMapReference; // Guarded by treeLock private final Set closeHandlers; @@ -103,7 +105,7 @@ private static void addStrong(Map map, Level level) { LogContext(final boolean strong) { this.strong = strong; - levelMapReference = new AtomicReference>(LazyHolder.INITIAL_LEVEL_MAP); + levelMapReference = new AtomicReference>>(LazyHolder.INITIAL_LEVEL_MAP); rootLogger = new LoggerNode(this); loggerNames = new ConcurrentSkipListMap(); closeHandlers = new LinkedHashSet<>(); @@ -189,8 +191,8 @@ public LoggingMXBean getLoggingMXBean() { */ public Level getLevelForName(String name) throws IllegalArgumentException { if (name != null) { - final Map map = levelMapReference.get(); - final LogContext.LevelRef levelRef = map.get(name); + final Map> map = levelMapReference.get(); + final Reference levelRef = map.get(name); if (levelRef != null) { final Level level = levelRef.get(); if (level != null) { @@ -214,16 +216,16 @@ public void registerLevel(Level level) { sm.checkPermission(CONTROL_PERMISSION); } for (;;) { - final Map oldLevelMap = levelMapReference.get(); - final Map newLevelMap = new HashMap(oldLevelMap.size()); - for (Map.Entry entry : oldLevelMap.entrySet()) { + final Map> oldLevelMap = levelMapReference.get(); + final Map> newLevelMap = new HashMap<>(oldLevelMap.size()); + for (Map.Entry> entry : oldLevelMap.entrySet()) { final String name = entry.getKey(); - final LogContext.LevelRef levelRef = entry.getValue(); + final Reference levelRef = entry.getValue(); if (levelRef.get() != null) { newLevelMap.put(name, levelRef); } } - newLevelMap.put(level.getName(), new WeakLevelRef(level)); + newLevelMap.put(level.getName(), References.create(Reference.Type.WEAK, level, null)); if (levelMapReference.compareAndSet(oldLevelMap, newLevelMap)) { return; } @@ -242,22 +244,22 @@ public void unregisterLevel(Level level) { sm.checkPermission(CONTROL_PERMISSION); } for (;;) { - final Map oldLevelMap = levelMapReference.get(); - final LevelRef oldRef = oldLevelMap.get(level.getName()); + final Map> oldLevelMap = levelMapReference.get(); + final Reference oldRef = oldLevelMap.get(level.getName()); if (oldRef == null || oldRef.get() != level) { // not registered, or the registration expired naturally return; } - final Map newLevelMap = new HashMap(oldLevelMap.size()); - for (Map.Entry entry : oldLevelMap.entrySet()) { + final Map> newLevelMap = new HashMap<>(oldLevelMap.size()); + for (Map.Entry> entry : oldLevelMap.entrySet()) { final String name = entry.getKey(); - final LevelRef levelRef = entry.getValue(); + final Reference levelRef = entry.getValue(); final Level oldLevel = levelRef.get(); if (oldLevel != null && oldLevel != level) { newLevelMap.put(name, levelRef); } } - newLevelMap.put(level.getName(), new WeakLevelRef(level)); + newLevelMap.put(level.getName(), References.create(Reference.Type.WEAK, level, null)); if (levelMapReference.compareAndSet(oldLevelMap, newLevelMap)) { return; } @@ -453,26 +455,4 @@ private void recursivelyClose(final LoggerNode loggerNode) { } loggerNode.close(); } - - private interface LevelRef { - Level get(); - } - - private static final class WeakLevelRef extends WeakReference implements LevelRef { - private WeakLevelRef(final Level level) { - super(level); - } - } - - private static final class StrongLevelRef implements LevelRef { - private final Level level; - - private StrongLevelRef(final Level level) { - this.level = level; - } - - public Level get() { - return level; - } - } } From b1c0dc3d157040ef14ef092ff555a7d62e27c191 Mon Sep 17 00:00:00 2001 From: "David M. Lloyd" Date: Wed, 21 Nov 2018 00:02:13 -0600 Subject: [PATCH 2/9] [LOGMGR-221] Allow levels to be registered strongly --- .../main/java/org/jboss/logmanager/LogContext.java | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/org/jboss/logmanager/LogContext.java b/core/src/main/java/org/jboss/logmanager/LogContext.java index 4019705a..a85b23c9 100644 --- a/core/src/main/java/org/jboss/logmanager/LogContext.java +++ b/core/src/main/java/org/jboss/logmanager/LogContext.java @@ -211,6 +211,17 @@ public Level getLevelForName(String name) throws IllegalArgumentException { * @param level the level to register */ public void registerLevel(Level level) { + registerLevel(level, false); + } + + /** + * Register a level instance with this log context. The level can then be looked up by name. Any previous level + * registration for the given level's name will be overwritten. + * + * @param level the level to register + * @param strong {@code true} to strongly reference the level, or {@code false} to weakly reference it + */ + public void registerLevel(Level level, boolean strong) { final SecurityManager sm = System.getSecurityManager(); if (sm != null) { sm.checkPermission(CONTROL_PERMISSION); @@ -225,7 +236,7 @@ public void registerLevel(Level level) { newLevelMap.put(name, levelRef); } } - newLevelMap.put(level.getName(), References.create(Reference.Type.WEAK, level, null)); + newLevelMap.put(level.getName(), References.create(strong ? Reference.Type.STRONG : Reference.Type.WEAK, level, null)); if (levelMapReference.compareAndSet(oldLevelMap, newLevelMap)) { return; } From 97848047af3b89884e6e8e405d0b3b9596426d39 Mon Sep 17 00:00:00 2001 From: "David M. Lloyd" Date: Wed, 21 Nov 2018 00:25:04 -0600 Subject: [PATCH 3/9] [LOGMGR-153] Eliminate finalizers --- .../java/org/jboss/logmanager/LogContext.java | 58 +------------- .../java/org/jboss/logmanager/Logger.java | 9 --- .../java/org/jboss/logmanager/LoggerNode.java | 79 +++++++++++++------ 3 files changed, 58 insertions(+), 88 deletions(-) diff --git a/core/src/main/java/org/jboss/logmanager/LogContext.java b/core/src/main/java/org/jboss/logmanager/LogContext.java index a85b23c9..768cacc9 100644 --- a/core/src/main/java/org/jboss/logmanager/LogContext.java +++ b/core/src/main/java/org/jboss/logmanager/LogContext.java @@ -26,15 +26,10 @@ import java.util.Collection; import java.util.Enumeration; import java.util.HashMap; -import java.util.Iterator; import java.util.LinkedHashSet; import java.util.Map; -import java.util.Map.Entry; -import java.util.NoSuchElementException; import java.util.Set; import java.util.concurrent.ConcurrentMap; -import java.util.concurrent.ConcurrentSkipListMap; -import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import java.util.logging.Level; import java.util.logging.LoggingMXBean; @@ -55,7 +50,6 @@ public final class LogContext implements AutoCloseable { @SuppressWarnings({ "ThisEscapedInObjectConstruction" }) private final LoggingMXBean mxBean = new LoggingMXBeanImpl(this); private final boolean strong; - private final ConcurrentSkipListMap loggerNames; /** * This lazy holder class is required to prevent a problem due to a LogContext instance being constructed @@ -107,7 +101,6 @@ private static void addStrong(Map> map, Level lev this.strong = strong; levelMapReference = new AtomicReference>>(LazyHolder.INITIAL_LEVEL_MAP); rootLogger = new LoggerNode(this); - loggerNames = new ConcurrentSkipListMap(); closeHandlers = new LinkedHashSet<>(); } @@ -332,8 +325,6 @@ public void close() throws Exception { for (AutoCloseable handler : closeHandlers) { handler.close(); } - // Finally clear any logger names - loggerNames.clear(); } } @@ -347,37 +338,7 @@ public void close() throws Exception { * @see java.util.logging.LogManager#getLoggerNames() */ public Enumeration getLoggerNames() { - final Iterator> iter = loggerNames.entrySet().iterator(); - return new Enumeration() { - String next = null; - @Override - public boolean hasMoreElements() { - while (next == null) { - if (iter.hasNext()) { - final Entry entry = iter.next(); - if (entry.getValue().get() > 0) { - next = entry.getKey(); - return true; - } - } else { - return false; - } - } - return next != null; - } - - @Override - public String nextElement() { - if (!hasMoreElements()) { - throw new NoSuchElementException(); - } - try { - return next; - } finally { - next = null; - } - } - }; + return rootLogger.getLoggerNames(); } /** @@ -423,23 +384,6 @@ public void setCloseHandlers(final Collection closeHandlers) { } } - protected void incrementRef(final String name) { - AtomicInteger counter = loggerNames.get(name); - if (counter == null) { - final AtomicInteger appearing = loggerNames.putIfAbsent(name, counter = new AtomicInteger()); - if (appearing != null) { - counter = appearing; - } - } - counter.incrementAndGet(); - } - - protected void decrementRef(final String name) { - AtomicInteger counter = loggerNames.get(name); - assert (counter != null && counter.get() > 0); - counter.decrementAndGet(); - } - private static SecurityException accessDenied() { return new SecurityException("Log context modification access denied"); } diff --git a/core/src/main/java/org/jboss/logmanager/Logger.java b/core/src/main/java/org/jboss/logmanager/Logger.java index bce4c387..6bff4be3 100644 --- a/core/src/main/java/org/jboss/logmanager/Logger.java +++ b/core/src/main/java/org/jboss/logmanager/Logger.java @@ -878,13 +878,4 @@ public AttachmentKey() { public String toString() { return "Logger '" + getName() + "' in context " + loggerNode.getContext(); } - - @Override - protected void finalize() throws Throwable { - try { - loggerNode.decrementRef(); - } finally { - super.finalize(); - } - } } diff --git a/core/src/main/java/org/jboss/logmanager/LoggerNode.java b/core/src/main/java/org/jboss/logmanager/LoggerNode.java index 1360b27c..d6cedfb4 100644 --- a/core/src/main/java/org/jboss/logmanager/LoggerNode.java +++ b/core/src/main/java/org/jboss/logmanager/LoggerNode.java @@ -19,12 +19,20 @@ package org.jboss.logmanager; +import org.wildfly.common.ref.PhantomReference; +import org.wildfly.common.ref.Reaper; +import org.wildfly.common.ref.Reference; + import java.security.AccessController; import java.security.PrivilegedAction; import java.util.Collection; import java.util.Collections; +import java.util.Enumeration; import java.util.HashMap; +import java.util.Iterator; import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; import java.util.logging.Filter; @@ -36,6 +44,13 @@ */ final class LoggerNode implements AutoCloseable { + private static final Reaper REAPER = new Reaper() { + @Override + public void reap(Reference reference) { + reference.getAttachment().activeLoggers.remove(reference); + } + }; + /** * The log context. */ @@ -76,6 +91,11 @@ final class LoggerNode implements AutoCloseable { */ private volatile boolean useParentFilter = false; + /** + * The set of phantom references to active loggers. + */ + private final Set> activeLoggers = ConcurrentHashMap.newKeySet(); + /** * The attachments map. */ @@ -221,24 +241,17 @@ Logger createLogger() { return AccessController.doPrivileged(new PrivilegedAction() { public Logger run() { final Logger logger = new Logger(LoggerNode.this, fullName); - context.incrementRef(fullName); + activeLoggers.add(new PhantomReference(logger, LoggerNode.this, REAPER)); return logger; } }); } else { final Logger logger = new Logger(this, fullName); - context.incrementRef(fullName); + activeLoggers.add(new PhantomReference(logger, LoggerNode.this, REAPER)); return logger; } } - /** - * Removes one from the reference count. - */ - void decrementRef() { - context.decrementRef(fullName); - } - /** * Get the children of this logger. * @@ -487,19 +500,41 @@ private static boolean isLoggable(final LoggerNode loggerNode, final ExtLogRecor return !(filter != null && !filter.isLoggable(record)) && (!loggerNode.useParentFilter || isLoggable(loggerNode.getParent(), record)); } - // GC + Enumeration getLoggerNames() { + return new Enumeration() { + final Iterator children = getChildren().iterator(); + String next = activeLoggers.isEmpty() ? null : fullName; + Enumeration sub; + + @Override + public boolean hasMoreElements() { + while (next == null) { + while (sub == null) { + if (children.hasNext()) { + final LoggerNode child = children.next(); + sub = child.getLoggerNames(); + } else { + return false; + } + } + if (sub.hasMoreElements()) { + next = sub.nextElement(); + return true; + } else { + sub = null; + } + } + return true; + } - /** - * Perform finalization actions. This amounts to clearing out the loglevel so that all children are updated - * with the parent's effective loglevel. As such, a lock is acquired from this method which might cause delays in - * garbage collection. - */ - protected void finalize() throws Throwable { - try { - // clear out level so that it spams out to all children - setLevel(null); - } finally { - super.finalize(); - } + @Override + public String nextElement() { + try { + return next; + } finally { + next = null; + } + } + }; } } From 8ccf983e24bcf20ba2283719d26dc759d0595616 Mon Sep 17 00:00:00 2001 From: "David M. Lloyd" Date: Wed, 21 Nov 2018 00:32:20 -0600 Subject: [PATCH 4/9] [LOGMGR-222] Logger creation does not need privileges with a null resource bundle name --- .../java/org/jboss/logmanager/LoggerNode.java | 19 +++---------------- 1 file changed, 3 insertions(+), 16 deletions(-) diff --git a/core/src/main/java/org/jboss/logmanager/LoggerNode.java b/core/src/main/java/org/jboss/logmanager/LoggerNode.java index d6cedfb4..3affcb5e 100644 --- a/core/src/main/java/org/jboss/logmanager/LoggerNode.java +++ b/core/src/main/java/org/jboss/logmanager/LoggerNode.java @@ -23,8 +23,6 @@ import org.wildfly.common.ref.Reaper; import org.wildfly.common.ref.Reference; -import java.security.AccessController; -import java.security.PrivilegedAction; import java.util.Collection; import java.util.Collections; import java.util.Enumeration; @@ -236,20 +234,9 @@ LoggerNode getIfExists(final String name) { } Logger createLogger() { - final SecurityManager sm = System.getSecurityManager(); - if (sm != null) { - return AccessController.doPrivileged(new PrivilegedAction() { - public Logger run() { - final Logger logger = new Logger(LoggerNode.this, fullName); - activeLoggers.add(new PhantomReference(logger, LoggerNode.this, REAPER)); - return logger; - } - }); - } else { - final Logger logger = new Logger(this, fullName); - activeLoggers.add(new PhantomReference(logger, LoggerNode.this, REAPER)); - return logger; - } + final Logger logger = new Logger(this, fullName); + activeLoggers.add(new PhantomReference(logger, LoggerNode.this, REAPER)); + return logger; } /** From 0d4eca61d495c55276b29a17e32b400b320962be Mon Sep 17 00:00:00 2001 From: "David M. Lloyd" Date: Wed, 21 Nov 2018 00:44:05 -0600 Subject: [PATCH 5/9] [LOGMGR-223] No longer require the log manager to be installed to use a log context --- .../main/java/org/jboss/logmanager/Logger.java | 17 +++++------------ .../org/jboss/logmanager/SerializedLogger.java | 2 +- 2 files changed, 6 insertions(+), 13 deletions(-) diff --git a/core/src/main/java/org/jboss/logmanager/Logger.java b/core/src/main/java/org/jboss/logmanager/Logger.java index 6bff4be3..4e9ffa3c 100644 --- a/core/src/main/java/org/jboss/logmanager/Logger.java +++ b/core/src/main/java/org/jboss/logmanager/Logger.java @@ -22,6 +22,7 @@ import java.io.ObjectStreamException; import java.io.Serializable; import java.util.Arrays; +import java.util.Locale; import java.util.ResourceBundle; import java.util.logging.Filter; import java.util.logging.Handler; @@ -50,12 +51,7 @@ public final class Logger extends java.util.logging.Logger implements Serializab * @return the logger */ public static Logger getLogger(final String name) { - try { - // call through j.u.l.Logger so that primordial configuration is set up - return (Logger) java.util.logging.Logger.getLogger(name); - } catch (ClassCastException e) { - throw new IllegalStateException("The LogManager was not properly installed (you must set the \"java.util.logging.manager\" system property to \"" + LogManager.class.getName() + "\")"); - } + return LogContext.getLogContext().getLogger(name); } /** @@ -66,12 +62,9 @@ public static Logger getLogger(final String name) { * @return the logger */ public static Logger getLogger(final String name, final String bundleName) { - try { - // call through j.u.l.Logger so that primordial configuration is set up - return (Logger) java.util.logging.Logger.getLogger(name, bundleName); - } catch (ClassCastException e) { - throw new IllegalStateException("The LogManager was not properly installed (you must set the \"java.util.logging.manager\" system property to \"" + LogManager.class.getName() + "\")"); - } + final Logger logger = LogContext.getLogContext().getLogger(name); + logger.setResourceBundle(ResourceBundle.getBundle(bundleName, Locale.getDefault(), Logger.class.getClassLoader())); + return logger; } /** diff --git a/core/src/main/java/org/jboss/logmanager/SerializedLogger.java b/core/src/main/java/org/jboss/logmanager/SerializedLogger.java index 21bd8149..aa040bf3 100644 --- a/core/src/main/java/org/jboss/logmanager/SerializedLogger.java +++ b/core/src/main/java/org/jboss/logmanager/SerializedLogger.java @@ -46,6 +46,6 @@ public SerializedLogger(final String name) { * @see Serialization spec, 3.7 */ public Object readResolve() { - return java.util.logging.Logger.getLogger(name); + return Logger.getLogger(name); } } From 15d2e29f26a2d4d56472a61593a6227af64d5768 Mon Sep 17 00:00:00 2001 From: "David M. Lloyd" Date: Wed, 21 Nov 2018 01:09:37 -0600 Subject: [PATCH 6/9] [LOGMGR-224] Keep our own resource bundles to avoid complex JDK process --- .../java/org/jboss/logmanager/Logger.java | 74 +++++++++++++++---- 1 file changed, 59 insertions(+), 15 deletions(-) diff --git a/core/src/main/java/org/jboss/logmanager/Logger.java b/core/src/main/java/org/jboss/logmanager/Logger.java index 4e9ffa3c..b80183f1 100644 --- a/core/src/main/java/org/jboss/logmanager/Logger.java +++ b/core/src/main/java/org/jboss/logmanager/Logger.java @@ -19,11 +19,14 @@ package org.jboss.logmanager; +import org.wildfly.common.Assert; + import java.io.ObjectStreamException; import java.io.Serializable; import java.util.Arrays; import java.util.Locale; import java.util.ResourceBundle; +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; import java.util.logging.Filter; import java.util.logging.Handler; import java.util.logging.Level; @@ -42,6 +45,16 @@ public final class Logger extends java.util.logging.Logger implements Serializab */ private final LoggerNode loggerNode; + /** + * The resource bundle for this logger. + */ + private volatile ResourceBundle resourceBundle; + + /** + * Atomic updater for {@link #resourceBundle}. + */ + private static final AtomicReferenceFieldUpdater resourceBundleUpdater = AtomicReferenceFieldUpdater.newUpdater(Logger.class, ResourceBundle.class, "resourceBundle"); + private static final String LOGGER_CLASS_NAME = Logger.class.getName(); /** @@ -63,7 +76,7 @@ public static Logger getLogger(final String name) { */ public static Logger getLogger(final String name, final String bundleName) { final Logger logger = LogContext.getLogContext().getLogger(name); - logger.setResourceBundle(ResourceBundle.getBundle(bundleName, Locale.getDefault(), Logger.class.getClassLoader())); + logger.resourceBundle = ResourceBundle.getBundle(bundleName, Locale.getDefault(), Logger.class.getClassLoader()); return logger; } @@ -814,20 +827,9 @@ public void log(final String fqcn, final Level level, final String message, fina */ public void logRaw(final ExtLogRecord record) { record.setLoggerName(getName()); - String bundleName = null; - ResourceBundle bundle = null; - // todo: new parents never have resource bundles; this could cause an issue - bundleName = getResourceBundleName(); - bundle = getResourceBundle(); -// for (Logger current = this; current != null; current = current.getParent()) { -// bundleName = current.getResourceBundleName(); -// if (bundleName != null) { -// bundle = current.getResourceBundle(); -// break; -// } -// } - if (bundleName != null && bundle != null) { - record.setResourceBundleName(bundleName); + final ResourceBundle bundle = getResourceBundle(); + if (bundle != null) { + record.setResourceBundleName(bundle.getBaseBundleName()); record.setResourceBundle(bundle); } try { @@ -843,6 +845,48 @@ public void logRaw(final ExtLogRecord record) { loggerNode.publish(record); } + /** + * Set the resource bundle for this logger. Unlike {@link java.util.logging.Logger#setResourceBundle(ResourceBundle)}, + * there is no parent search performed for resource bundles by this implementation. + * + * @param resourceBundle the resource bundle (must not be {@code null}) + */ + @Override + public void setResourceBundle(ResourceBundle resourceBundle) { + Assert.checkNotNullParam("resourceBundle", resourceBundle); + LogContext.checkAccess(); + ResourceBundle old; + do { + old = this.resourceBundle; + if (old != null && ! old.getBaseBundleName().equals(resourceBundle.getBaseBundleName())) { + throw new IllegalArgumentException("Bundle base name does not match existing bundle"); + } + } while (! resourceBundleUpdater.compareAndSet(this, old, resourceBundle)); + } + + /** + * Get the resource bundle for this logger. Unlike {@link java.util.logging.Logger#getResourceBundle()}, + * there is no parent search performed for resource bundles by this implementation. + * + * @return the resource bundle, or {@code null} if none is configured for this logger + */ + @Override + public ResourceBundle getResourceBundle() { + return resourceBundle; + } + + /** + * Get the resource bundle name for this logger. Unlike {@link java.util.logging.Logger#getResourceBundleName()}, + * there is no parent search performed for resource bundles by this implementation. + * + * @return the resource bundle, or {@code null} if none is configured for this logger + */ + @Override + public String getResourceBundleName() { + final ResourceBundle resourceBundle = getResourceBundle(); + return resourceBundle == null ? null : resourceBundle.getBaseBundleName(); + } + /** * Do the logging with no level checks (they've already been done). Creates an extended log record if the * provided record is not one. From f54c446a4ffaa0fa8be665d8a55a5b01e60747ae Mon Sep 17 00:00:00 2001 From: "David M. Lloyd" Date: Mon, 26 Nov 2018 13:26:42 -0600 Subject: [PATCH 7/9] [LOGMGR-225] Add attachment API to LogContext --- .../java/org/jboss/logmanager/LogContext.java | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/core/src/main/java/org/jboss/logmanager/LogContext.java b/core/src/main/java/org/jboss/logmanager/LogContext.java index 768cacc9..d1c8273e 100644 --- a/core/src/main/java/org/jboss/logmanager/LogContext.java +++ b/core/src/main/java/org/jboss/logmanager/LogContext.java @@ -130,6 +130,69 @@ public static LogContext create() { return create(false); } + // Attachment mgmt + + /** + * Get the attachment value for a given key, or {@code null} if there is no such attachment. + * Log context attachments are placed on the root logger and can also be accessed there. + * + * @param key the key + * @param the attachment value type + * @return the attachment, or {@code null} if there is none for this key + */ + @SuppressWarnings({ "unchecked" }) + public V getAttachment(Logger.AttachmentKey key) { + return rootLogger.getAttachment(key); + } + + /** + * Attach an object to this log context under a given key. + * A strong reference is maintained to the key and value for as long as this log context exists. + * Log context attachments are placed on the root logger and can also be accessed there. + * + * @param key the attachment key + * @param value the attachment value + * @param the attachment value type + * @return the old attachment, if there was one + * @throws SecurityException if a security manager exists and if the caller does not have {@code LoggingPermission(control)} + */ + public V attach(Logger.AttachmentKey key, V value) throws SecurityException { + checkAccess(); + return rootLogger.attach(key, value); + } + + /** + * Attach an object to this log context under a given key, if such an attachment does not already exist. + * A strong reference is maintained to the key and value for as long as this log context exists. + * Log context attachments are placed on the root logger and can also be accessed there. + * + * @param key the attachment key + * @param value the attachment value + * @param the attachment value type + * @return the current attachment, if there is one, or {@code null} if the value was successfully attached + * @throws SecurityException if a security manager exists and if the caller does not have {@code LoggingPermission(control)} + */ + @SuppressWarnings({ "unchecked" }) + public V attachIfAbsent(Logger.AttachmentKey key, V value) throws SecurityException { + checkAccess(); + return rootLogger.attachIfAbsent(key, value); + } + + /** + * Remove an attachment. + * Log context attachments are placed on the root logger and can also be accessed there. + * + * @param key the attachment key + * @param the attachment value type + * @return the old value, or {@code null} if there was none + * @throws SecurityException if a security manager exists and if the caller does not have {@code LoggingPermission(control)} + */ + @SuppressWarnings({ "unchecked" }) + public V detach(Logger.AttachmentKey key) throws SecurityException { + checkAccess(); + return rootLogger.detach(key); + } + /** * Get a logger with the given name from this logging context. * From 4c93f0ef98ce57a008a4a79d355b40469ab2e624 Mon Sep 17 00:00:00 2001 From: "David M. Lloyd" Date: Mon, 26 Nov 2018 13:50:22 -0600 Subject: [PATCH 8/9] [LOGMGR-212] Introduce minimal configuration API with locator for system context --- .../logmanager/ConfigurationLocator.java | 36 ----------- .../DefaultConfigurationLocator.java | 48 -------------- .../logmanager/LogContextConfigurator.java | 46 ++++++++++++++ .../java/org/jboss/logmanager/LogManager.java | 63 +++++++++++++------ 4 files changed, 91 insertions(+), 102 deletions(-) delete mode 100644 core/src/main/java/org/jboss/logmanager/ConfigurationLocator.java delete mode 100644 core/src/main/java/org/jboss/logmanager/DefaultConfigurationLocator.java create mode 100644 core/src/main/java/org/jboss/logmanager/LogContextConfigurator.java diff --git a/core/src/main/java/org/jboss/logmanager/ConfigurationLocator.java b/core/src/main/java/org/jboss/logmanager/ConfigurationLocator.java deleted file mode 100644 index 6730d98c..00000000 --- a/core/src/main/java/org/jboss/logmanager/ConfigurationLocator.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * JBoss, Home of Professional Open Source. - * - * Copyright 2014 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.io.InputStream; -import java.io.IOException; - -/** - * A locator for logger configuration. - */ -public interface ConfigurationLocator { - - /** - * Find the configuration file. - * - * @return the configuration file input stream - */ - InputStream findConfiguration() throws IOException; -} diff --git a/core/src/main/java/org/jboss/logmanager/DefaultConfigurationLocator.java b/core/src/main/java/org/jboss/logmanager/DefaultConfigurationLocator.java deleted file mode 100644 index b5ce9e23..00000000 --- a/core/src/main/java/org/jboss/logmanager/DefaultConfigurationLocator.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * JBoss, Home of Professional Open Source. - * - * Copyright 2014 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.io.InputStream; -import java.io.IOException; -import java.net.URL; - -/** - * A configuration locator which looks for a {@code logging.properties} file in the class path, allowing the location - * to be overridden via a URL specified in the {@code logging.configuration} system property. - */ -public final class DefaultConfigurationLocator implements ConfigurationLocator { - - /** {@inheritDoc} */ - public InputStream findConfiguration() throws IOException { - final String propLoc = System.getProperty("logging.configuration"); - if (propLoc != null) try { - return new URL(propLoc).openStream(); - } catch (IOException e) { - StandardOutputStreams.printError("Unable to read the logging configuration from '%s' (%s)%n", propLoc, e); - } - final ClassLoader tccl = Thread.currentThread().getContextClassLoader(); - if (tccl != null) try { - final InputStream stream = tccl.getResourceAsStream("logging.properties"); - if (stream != null) return stream; - } catch (Exception e) { - } - return getClass().getResourceAsStream("logging.properties"); - } -} diff --git a/core/src/main/java/org/jboss/logmanager/LogContextConfigurator.java b/core/src/main/java/org/jboss/logmanager/LogContextConfigurator.java new file mode 100644 index 00000000..317d5480 --- /dev/null +++ b/core/src/main/java/org/jboss/logmanager/LogContextConfigurator.java @@ -0,0 +1,46 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2018 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.io.InputStream; + +/** + * A configurator for a log context. A log context configurator should set up all the log categories, + * handlers, formatters, filters, attachments, and other constructs as specified by the configuration. + */ +public interface LogContextConfigurator { + /** + * Configure the given log context according to this configurator's policy. If a configuration stream was + * provided, that is passed in to this method to be used or ignored. The stream should remain open after + * this method is called. + * + * @param logContext the log context to configure (not {@code null}) + * @param inputStream the input stream that was requested to be used, or {@code null} if none was provided + */ + void configure(LogContext logContext, InputStream inputStream); + + /** + * A constant representing an empty configuration. The configurator does nothing. + */ + LogContextConfigurator EMPTY = new LogContextConfigurator() { + @Override + public void configure(LogContext logContext, InputStream inputStream) { + } + }; +} diff --git a/core/src/main/java/org/jboss/logmanager/LogManager.java b/core/src/main/java/org/jboss/logmanager/LogManager.java index 880da826..6e5f2264 100644 --- a/core/src/main/java/org/jboss/logmanager/LogManager.java +++ b/core/src/main/java/org/jboss/logmanager/LogManager.java @@ -19,12 +19,20 @@ package org.jboss.logmanager; +import org.wildfly.common.Assert; + import java.beans.PropertyChangeListener; import java.io.IOException; import java.io.InputStream; import java.security.AccessController; import java.security.PrivilegedAction; +import java.util.ArrayList; import java.util.Enumeration; +import java.util.Iterator; +import java.util.List; +import java.util.ServiceConfigurationError; +import java.util.ServiceLoader; +import java.util.concurrent.atomic.AtomicReference; import java.util.function.BiFunction; import java.util.function.Function; import java.util.logging.Filter; @@ -81,39 +89,58 @@ public LogManager() { // Configuration + private final AtomicReference configuratorRef = new AtomicReference<>(); + /** - * Do nothing. Log contexts are configured on construction. + * Configure the system log context initially. */ public void readConfiguration() { - // on operation + doConfigure(null); } /** - * Do nothing. Log contexts are configured on construction. + * Configure the system log context initially withe given input stream. * * @param inputStream ignored */ public void readConfiguration(InputStream inputStream) { - // on operation + Assert.checkNotNullParam("inputStream", inputStream); + doConfigure(inputStream); } - static T construct(Class type, String className) throws IOException { - try { - Class clazz = null; - try { - final ClassLoader tccl = Thread.currentThread().getContextClassLoader(); - if (tccl != null) { - clazz = Class.forName(className, true, tccl); + private void doConfigure(InputStream inputStream) { + final AtomicReference configuratorRef = this.configuratorRef; + LogContextConfigurator configurator = configuratorRef.get(); + if (configurator == null) { + synchronized (configuratorRef) { + configurator = configuratorRef.get(); + if (configurator == null) { + final ServiceLoader serviceLoader = ServiceLoader.load(LogContextConfigurator.class); + final Iterator iterator = serviceLoader.iterator(); + List problems = null; + for (;;) try { + if (! iterator.hasNext()) break; + configurator = iterator.next(); + break; + } catch (Throwable t) { + if (problems == null) problems = new ArrayList<>(4); + problems.add(t); + } + if (configurator == null) { + if (problems == null) { + configuratorRef.set(configurator = LogContextConfigurator.EMPTY); + } else { + final ServiceConfigurationError e = new ServiceConfigurationError("Failed to configure log configurator service"); + for (Throwable problem : problems) { + e.addSuppressed(problem); + } + throw e; + } + } } - } catch (ClassNotFoundException ignore) { } - if (clazz == null) clazz = Class.forName(className, true, LogManager.class.getClassLoader()); - return type.cast(clazz.getConstructor().newInstance()); - } catch (Exception e) { - final IOException ioe = new IOException("Unable to load configuration class " + className); - ioe.initCause(e); - throw ioe; } + configurator.configure(LogContext.getSystemLogContext(), inputStream); } /** From eb596af8ad4654f35f216031acf28c99bf823a85 Mon Sep 17 00:00:00 2001 From: "David M. Lloyd" Date: Thu, 29 Nov 2018 09:06:58 -0600 Subject: [PATCH 9/9] [LOGMGR-226] Atomic addHandler/replay op for QueueHandler --- .../logmanager/handlers/QueueHandler.java | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/core/src/main/java/org/jboss/logmanager/handlers/QueueHandler.java b/core/src/main/java/org/jboss/logmanager/handlers/QueueHandler.java index 54d9f947..4e1cc230 100644 --- a/core/src/main/java/org/jboss/logmanager/handlers/QueueHandler.java +++ b/core/src/main/java/org/jboss/logmanager/handlers/QueueHandler.java @@ -23,6 +23,7 @@ import java.util.Deque; import org.jboss.logmanager.ExtHandler; import org.jboss.logmanager.ExtLogRecord; +import org.wildfly.common.Assert; import java.util.logging.ErrorManager; import java.util.logging.Formatter; @@ -119,6 +120,33 @@ public void setLimit(final int limit) { } } + @Override + public void addHandler(Handler handler) throws SecurityException { + addHandler(handler, false); + } + + /** + * Add the given handler, optionally atomically replaying the queue, allowing the delegate handler to receive + * all queued messages as well as all subsequent messages with no loss or reorder in between. + * + * @param handler the handler to add (must not be {@code null}) + * @param replay {@code true} to replay the prior messages, or {@code false} to add the handler without replaying + * @throws SecurityException if the handler was not allowed to be added + */ + public void addHandler(Handler handler, boolean replay) throws SecurityException { + Assert.checkNotNullParam("handler", handler); + if (replay) { + synchronized (buffer) { + super.addHandler(handler); + for (ExtLogRecord record : buffer) { + handler.publish(record); + } + } + } else { + super.addHandler(handler); + } + } + /** * Get a copy of the queue as it is at an exact moment in time. *