Skip to content

Commit

Permalink
Add static loggers to system logger
Browse files Browse the repository at this point in the history
  • Loading branch information
agentgt committed Sep 14, 2024
1 parent abe6d93 commit fb6ec8c
Show file tree
Hide file tree
Showing 9 changed files with 312 additions and 60 deletions.
43 changes: 27 additions & 16 deletions core/src/main/java/io/jstach/rainbowgum/LogRouter.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

import org.eclipse.jdt.annotation.Nullable;

import io.jstach.rainbowgum.LogConfig.ChangePublisher.ChangeType;
import io.jstach.rainbowgum.LogProperty.Property;
import io.jstach.rainbowgum.LogPublisher.PublisherFactory;
import io.jstach.rainbowgum.LogRouter.RootRouter;
Expand Down Expand Up @@ -154,6 +155,13 @@ sealed interface RootRouter extends LogRouter permits InternalRootRouter {
*/
public void onChange(Consumer<? super RootRouter> router);

/**
* If a logger name configuration is changeable.
* @param loggerName logger name.
* @return true if config changes are allowed.
*/
public boolean isChangeable(String loggerName);

}

/**
Expand Down Expand Up @@ -567,7 +575,7 @@ static void setRouter(RootRouter router) {
GlobalLogRouter.INSTANCE.drain((InternalRootRouter) router);
}

static InternalRootRouter of(List<? extends Router> routes) {
static InternalRootRouter of(List<? extends Router> routes, LogConfig config) {

if (routes.isEmpty()) {
throw new IllegalArgumentException("atleast one route is required");
Expand Down Expand Up @@ -598,11 +606,14 @@ static InternalRootRouter of(List<? extends Router> routes) {

Router[] array = routes.toArray(new Router[] {});

RouteChangePublisher changePublisher = new RouteChangePublisher(
s -> config.changePublisher().allowedChanges(s).contains(ChangeType.LEVEL));
if (array.length == 1) {
var r = array[0];
return r.synchronous() ? new SingleSyncRootRouter(r) : new SingleAsyncRootRouter(r);
return r.synchronous() ? new SingleSyncRootRouter(r, changePublisher)
: new SingleAsyncRootRouter(r, changePublisher);
}
return new CompositeLogRouter(array, globalLevelResolver);
return new CompositeLogRouter(array, globalLevelResolver, changePublisher);
}

default boolean isEnabled(String loggerName, java.lang.System.Logger.Level level) {
Expand All @@ -621,10 +632,22 @@ default void onChange(Consumer<? super RootRouter> router) {

public RouteChangePublisher changePublisher();

@Override
default boolean isChangeable(String loggerName) {
return changePublisher().loggerChangeable.apply(loggerName);
}

final class RouteChangePublisher {

private final Collection<Consumer<? super RootRouter>> consumers = new CopyOnWriteArrayList<Consumer<? super RootRouter>>();

private final Function<String, Boolean> loggerChangeable;

RouteChangePublisher(Function<String, Boolean> loggerChangeable) {
super();
this.loggerChangeable = loggerChangeable;
}

void add(Consumer<? super RootRouter> consumer) {
consumers.add(consumer);
}
Expand All @@ -646,10 +669,6 @@ void transferTo(RouteChangePublisher changePublisher) {

record SingleSyncRootRouter(Router router, RouteChangePublisher changePublisher) implements InternalRootRouter {

public SingleSyncRootRouter(Router router) {
this(router, new RouteChangePublisher());
}

@Override
public void start(LogConfig config) {
router.start(config);
Expand All @@ -674,10 +693,6 @@ public Route route(String loggerName, Level level) {

record SingleAsyncRootRouter(Router router, RouteChangePublisher changePublisher) implements InternalRootRouter {

public SingleAsyncRootRouter(Router router) {
this(router, new RouteChangePublisher());
}

@Override
public void start(LogConfig config) {
router.start(config);
Expand All @@ -703,10 +718,6 @@ public Route route(String loggerName, Level level) {
record CompositeLogRouter(Router[] routers, LevelResolver levelResolver,
RouteChangePublisher changePublisher) implements InternalRootRouter, Route {

public CompositeLogRouter(Router[] routers, LevelResolver levelResolver) {
this(routers, levelResolver, new RouteChangePublisher());
}

@Override
public Route route(String loggerName, Level level) {
/*
Expand Down Expand Up @@ -767,7 +778,7 @@ final class QueueEventsRouter implements InternalRootRouter, Route {

private static final LevelResolver INFO_RESOLVER = StaticLevelResolver.of(Level.INFO);

private final RouteChangePublisher changePublisher = new RouteChangePublisher();
private final RouteChangePublisher changePublisher = new RouteChangePublisher(s -> true);

@Override
public LevelResolver levelResolver() {
Expand Down
25 changes: 20 additions & 5 deletions core/src/main/java/io/jstach/rainbowgum/RainbowGum.java
Original file line number Diff line number Diff line change
Expand Up @@ -305,7 +305,7 @@ private RainbowGum build(UUID instanceId) {
routes = routeNames.stream().map(n -> Router.builder(n, config).build()).toList();
}
}
var root = InternalRootRouter.of(routes);
var root = InternalRootRouter.of(routes, config);
config.changePublisher().subscribe(c -> {
root.changePublisher().publish(root);
});
Expand All @@ -332,6 +332,17 @@ public RainbowGum set() {
return gum;
}

/**
* Removes the currently set Rainbow Gum which will close it if one exists and
* will be replaced by the default provision process if {@link RainbowGum#of()} is
* called before {@link #set()}.
* @apiNote This is largely an internal detail for unit testing the provision
* process.
*/
public void unset() {
RainbowGumHolder.remove(null);
}

/**
* For returning an optional for the LogProvider contract.
* @return optional that always has a rainbow gum.
Expand Down Expand Up @@ -415,17 +426,21 @@ static RainbowGum get() {

}

static boolean remove(RainbowGum gum) {
static boolean remove(@Nullable RainbowGum gum) {
lock.writeLock().lock();
try {
var original = rainbowGum;
if (original != gum) {
return false;
if (gum != null) {
if (original != gum) {
return false;
}
}
/*
* Reset the global router
*/
LogRouter.global().close();
if (original != null) {
LogRouter.global().close();
}
rainbowGum = null;
supplier = RainbowGumServiceProvider::provide;
return true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.ServiceLoader;
Expand Down Expand Up @@ -223,7 +224,7 @@ public non-sealed interface RainbowGumEagerLoad extends RainbowGumServiceProvide
* @return true if something is marked as eager.
*/
public static boolean exists() {
var sl = ServiceLoaderCache.SERVICE_LOADER_CACHE.findServiceLoader(defaultClassLoader());
var sl = ServiceLoaderCache.SERVICE_LOADER_CACHE.findServiceLoader();
return sl.stream()
.filter(p -> RainbowGumEagerLoad.class.isAssignableFrom(p.type()))
.findFirst()
Expand Down Expand Up @@ -292,25 +293,26 @@ public static RainbowGum provide() {

private static ServiceLoader<RainbowGumServiceProvider> cachedServiceLoader(@Nullable ClassLoader classLoader) {
if (classLoader == null) {
classLoader = defaultClassLoader();
return ServiceLoaderCache.SERVICE_LOADER_CACHE.findServiceLoader();
}
return ServiceLoaderCache.SERVICE_LOADER_CACHE.serviceLoader(classLoader);
}

private static ClassLoader defaultClassLoader() {
// TODO perhaps we should use RainbowGum.class classloader
return ClassLoader.getSystemClassLoader();
}

}

@SuppressWarnings("ImmutableEnumChecker")
enum ServiceLoaderCache {

SERVICE_LOADER_CACHE;

private WeakHashMap<ClassLoader, ServiceLoader<RainbowGumServiceProvider>> serviceLoaderCache = new WeakHashMap<>();
private final Map<ClassLoader, ServiceLoader<RainbowGumServiceProvider>> serviceLoaderCache = new WeakHashMap<>();

private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
static ClassLoader defaultClassLoader() {
// TODO perhaps we should use RainbowGum.class classloader
return ClassLoader.getSystemClassLoader();
}

ServiceLoader<RainbowGumServiceProvider> serviceLoader(ClassLoader classLoader) {
lock.writeLock().lock();
Expand All @@ -325,27 +327,36 @@ ServiceLoader<RainbowGumServiceProvider> serviceLoader(ClassLoader classLoader)
}
}

ServiceLoader<RainbowGumServiceProvider> findServiceLoader(ClassLoader fallback) {
lock.readLock().lock();
try {
@Nullable
ServiceLoader<RainbowGumServiceProvider> loader = null;
@Nullable
ClassLoader[] classLoaders = new @Nullable ClassLoader[] { Thread.currentThread().getContextClassLoader() };
for (var cl : classLoaders) {
if (cl != null) {
loader = serviceLoader(cl);
private @Nullable ServiceLoader<RainbowGumServiceProvider> _findServiceLoader(
@Nullable ClassLoader[] classLoaders) {
for (var cl : classLoaders) {
if (cl != null) {
var sl = serviceLoaderCache.get(cl);
if (sl != null) {
return sl;
}
}
if (loader == null) {
return serviceLoader(fallback);
}
return loader;
}
return null;
}

ServiceLoader<RainbowGumServiceProvider> findServiceLoader() {
@Nullable
ServiceLoader<RainbowGumServiceProvider> loader = null;
@Nullable
ClassLoader[] classLoaders = new @Nullable ClassLoader[] { Thread.currentThread().getContextClassLoader(),
ServiceLoaderCache.class.getClassLoader(), defaultClassLoader() };
lock.readLock().lock();
try {
loader = _findServiceLoader(classLoaders);
}
finally {
lock.readLock().unlock();
}
if (loader != null) {
return loader;
}
return serviceLoader(defaultClassLoader());
}

}
3 changes: 2 additions & 1 deletion core/src/test/java/io/jstach/rainbowgum/RouterTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ void testCompositeRouter() throws Exception {
var publisher2 = new TestSyncPublisher();
var router2 = new SimpleRouter("2", publisher2, resolver2);

var root = InternalRootRouter.of(List.of(router1, router2));
var config = LogConfig.builder().build();
var root = InternalRootRouter.of(List.of(router1, router2), config);

var route = root.route("stuff", Level.DEBUG);

Expand Down
9 changes: 7 additions & 2 deletions doc/overview.html
Original file line number Diff line number Diff line change
Expand Up @@ -950,13 +950,19 @@ <h3 id="system_logger">java.lang.System.Logger and java.util.logging</h3>

<p>
The integration will make sure that neither the System.Logger or java.util.logging initialize
Rainbow Gum too early by queueing the events. When a Rainbow Gum initializes and
Rainbow Gum too early by queueing the events if some other facade is detected like SLF4J. When a Rainbow Gum initializes and
{@linkplain io.jstach.rainbowgum.RainbowGum#set(Supplier) set as global} the events will be replayed.
If the events level are equal to {@link java.lang.System.Logger.Level#ERROR} and a normal Rainbow Gum has not been bound
the messages will be printed to <code>System.err</code>.
The idea is something catastrophic has happened that will probably cause Rainbow Gum to never load and thus never replay the events
and you will not be able to figure out what happened otherwise.
</p>
<p>
The exception of the above is if {@linkplain io.jstach.rainbowgum.spi.RainbowGumServiceProvider.RainbowGumEagerLoad no other facade is detected}
the module will initialize Rainbow Gum. The idea is that your application only uses the JDK logging facilities
(the Rainbow Gum SLF4J implementation is not found in the module/classpath). Thus only having the dependencies
<code>rainbowgum-core</code>, and <code>rainbowgum-jdk</code> will work as normal.
</p>
<p>
SLF4J does
<a href="https://www.slf4j.org/manual.html#jep264">provide an adapter/bridge for the System.Logger (org.slf4j:slf4j-jdk-platform-logging)</a>
Expand All @@ -969,7 +975,6 @@ <h3 id="system_logger">java.lang.System.Logger and java.util.logging</h3>
(the SLF4J adapter will initialize Rainbow Gum on System.Logger usage if using {@link io.jstach.rainbowgum.slf4j/ })
</li>
</ul>
In the future Rainbow Gum may include a System.Logger provider module that does eager initialization.
<p>
<em>
<strong>NOTE:</strong>
Expand Down
9 changes: 7 additions & 2 deletions rainbowgum-jdk/src/main/java/module-info.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,19 @@
* <p>
* The integration will make sure that neither the System.Logger or
* java.util.logging will initialize Rainbow Gum too early by queueing the
* events. When a Rainbow Gum initializes and
* events if a
* {@link io.jstach.rainbowgum.spi.RainbowGumServiceProvider.RainbowGumEagerLoad}
* implementation is found (SLF4J facade implements this to indicate it will load Rainbow Gum).
* When a Rainbow Gum initializes and
* {@linkplain io.jstach.rainbowgum.RainbowGum#set(Supplier) set as global} the
* events will be replayed. If the events level are equal to
* {@link java.lang.System.Logger.Level#ERROR} and a normal Rainbow Gum has not
* been bound the messages will be printed to <code>System.err</code>. The idea
* is something catastrophic has happened that will probably cause Rainbow Gum
* to never load and thus never replay the events and you will not be able to
* figure out what happened.
* figure out what happened. If no
* {@link io.jstach.rainbowgum.spi.RainbowGumServiceProvider.RainbowGumEagerLoad}
* is found the SystemLogger will initialize Rainbow Gum.
* </p>
* <p>
* SLF4J does <a href="https://www.slf4j.org/manual.html#jep264">provide an
Expand Down
Loading

0 comments on commit fb6ec8c

Please sign in to comment.