New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Adapter for Platform Logging (JEP 264) #232
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done reviewing myself. 😁 One more question: Where should this be documented? Should I put something together? (Oops, two questions.)
<version>2.0.0-alpha0</version> | ||
</parent> | ||
|
||
<artifactId>slf4j-jdk-platform-logging</artifactId> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure what the best name would be for the artifact, module, package, and classes. 😊 At the moment they're a little all over the place using the terms jdk
, platform
, and system
. I think a single term in all places would be better.
var loggerFinders = new ArrayList<System.LoggerFinder>(); | ||
// this fails when test is executed from the module path | ||
// because the module declaration does not declare | ||
// `uses System.LoggerFinder` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
At the moment, only the class path integration is tested. Module path integration would fail here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I observe the following error:
[ERROR] org.slf4j.jdk.SLF4JSystemLoggerTest.loggerLogsMessage Time elapsed: 0.003 s <<< ERROR!
java.util.ServiceConfigurationError: java.lang.System$LoggerFinder: module org.slf4j.jdk does not declare uses
at java.base/java.util.ServiceLoader.fail(ServiceLoader.java:589)
at java.base/java.util.ServiceLoader.checkCaller(ServiceLoader.java:575)
at java.base/java.util.ServiceLoader.(ServiceLoader.java:504)
at java.base/java.util.ServiceLoader.load(ServiceLoader.java:1687)
at org.slf4j.jdk@2.0.0-alpha4-SNAPSHOT/org.slf4j.jdk.SLF4JSystemLoggerTest.getSLF4JSystemLoggerFinder(SLF4JSystemLoggerTest.java:63)
at org.slf4j.jdk@2.0.0-alpha4-SNAPSHOT/org.slf4j.jdk.SLF4JSystemLoggerTest.getSLF4JSystemLogger(SLF4JSystemLoggerTest.java:83)
at org.slf4j.jdk@2.0.0-alpha4-SNAPSHOT/org.slf4j.jdk.SLF4JSystemLoggerTest.loggerLogsMessage(SLF4JSystemLoggerTest.java:49)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:78)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:567)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
...
Bundle-SymbolicName: slf4j.ext | ||
Bundle-Name: slf4j-jdk-platform-logging | ||
Bundle-Vendor: SLF4J.ORG | ||
Bundle-RequiredExecutionEnvironment: JavaSE-1.8 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- is that the correct
Bundle-RequiredExecutionEnvironment
or should it be Java 9? - no
Export-Package
, right? - oops, forgot
Import-Package
INTERNAL_LOGGER.error( | ||
"SLF4J internal error: unknown log level {} passed to `isLoggable` (likely by the JDK).", level); | ||
return true; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was unsure how to such handle error cases. They're highly unlikely to occur (JDK API should be very stable), but if they do, IMHO SLF4J should not crash, misbehave as little as possible, but let the user know that there's a problem (hence the error logger). See below for two more such cases.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To avoid error cases from future JDK changes, you could compare to Level.getSeverity()
.
Basic idea is that you don't want to map all j.u.l. levels, you want to start from the available SLF4J levels and determine into which bracket the passed-in j.u.l. level fits. (FINER/FINEST etc. have different severities, despite being mapped to the same j.u.l. level.)
// JEP 264[1], which introduced the Platform Logging API, | ||
// contains the following note: | ||
// | ||
// > An implementation of the LoggerFinder service should make it | ||
// > possible to distinguish system loggers (used by system classes | ||
// > from the Bootstrap Class Loader (BCL)) and application loggers | ||
// > (created by an application for its own usage). This distinction | ||
// > is important for platform security. The creator of a logger can | ||
// > pass the class or module for which the logger is created to the | ||
// > LoggerFinder so that the LoggerFinder can figure out which kind | ||
// > of logger to return. | ||
// | ||
// If backends support the distinction support this distinction and | ||
// once `LoggerFactory`'s API is updated to forward a module, we | ||
// should do that here. | ||
// | ||
// [1] https://openjdk.java.net/jeps/264 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this make sense?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure that I quite get the point: the System.Logger
API is intended for JDK classes, not for applications, or is it?
The javadoc of System.LoggerFinder
requires a slightly different thing:
* In addition, when a security manager is present, loggers provided to
* system classes should not be directly configurable through the logging
* backend without requiring permissions.
* <br>
* It is the responsibility of the provider of
* the concrete {@code LoggerFinder} implementation to ensure that
* these loggers are not configured by untrusted code without proper
* permission checks, as configuration performed on such loggers usually
* affects all applications in the same Java Runtime.
We don't know the logging backends and how they configure things. So we have to check with the SecurityManager
for all cases, don't we? But this is already checked by the constructor of the System.LoggerFinder
... I'm confused.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure that I quite get the point: the
System.Logger
API is intended for JDK classes, not for applications, or is it?
It will be used by JDK classes, but it's implemented here and the quote addresses implementations, so I assume it applies to us. Didn't think to look at the JavaDoc, though, and it's not mentioned there, so maybe it's outdated?
Re security manager, it is indeed weird that System.LoggerFinder
already does the check. Not sure what to do here.
module org.slf4j.jdk { | ||
requires org.slf4j; | ||
provides java.lang.System.LoggerFinder | ||
with org.slf4j.jdk.SLF4JSystemLoggerFinder; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this be in src/java9
? Would be more consistent but then again, the entire thing only works on Java 9+.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
java.lang.System.LoggerFinder was introduced in Java9. Thus there would be no point in a multi-release jar.
<plugin> | ||
<artifactId>maven-compiler-plugin</artifactId> | ||
<configuration> | ||
<release>11</release> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Which Java version do you want to target? Java 9 is the most lenient, but I wouldn't encourage use of outdated versions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would say 9 as the slf4j project currently requires Java 8 to at runtime and 9 at build time. Requiring 9 for this module seems consistent.
default: | ||
INTERNAL_LOGGER.error( | ||
"SLF4J internal error: unknown log level {} passed to `isLoggable` (likely by the JDK).", level); | ||
return true; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would pull the default
out of the switch
block, so in the highly unlikely case that the JDK adds another enum value, the compiler will complain about the switch not being exhaustive :-)
default: | |
INTERNAL_LOGGER.error( | |
"SLF4J internal error: unknown log level {} passed to `isLoggable` (likely by the JDK).", level); | |
return true; | |
} | |
} | |
INTERNAL_LOGGER.error( | |
"SLF4J internal error: unknown log level {} passed to `isLoggable` (likely by the JDK).", level); | |
return true; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The compiler doesn't do that check for switch statements (your IDE may, though). Whether the code is in the default
branch or not, makes no difference.
default: | ||
INTERNAL_LOGGER.error( | ||
"SLF4J internal error: unknown log level {} passed to `log` (likely by the JDK).", level); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same as above:
default: | |
INTERNAL_LOGGER.error( | |
"SLF4J internal error: unknown log level {} passed to `log` (likely by the JDK).", level); | |
} | |
} | |
INTERNAL_LOGGER.error( | |
"SLF4J internal error: unknown log level {} passed to `log` (likely by the JDK).", level); |
default: | ||
INTERNAL_LOGGER.error( | ||
"SLF4J internal error: unknown log level {} passed to `log` (likely by the JDK).", level); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same as above:
default: | |
INTERNAL_LOGGER.error( | |
"SLF4J internal error: unknown log level {} passed to `log` (likely by the JDK).", level); | |
} | |
} | |
INTERNAL_LOGGER.error( | |
"SLF4J internal error: unknown log level {} passed to `log` (likely by the JDK).", level); |
// JEP 264[1], which introduced the Platform Logging API, | ||
// contains the following note: | ||
// | ||
// > An implementation of the LoggerFinder service should make it | ||
// > possible to distinguish system loggers (used by system classes | ||
// > from the Bootstrap Class Loader (BCL)) and application loggers | ||
// > (created by an application for its own usage). This distinction | ||
// > is important for platform security. The creator of a logger can | ||
// > pass the class or module for which the logger is created to the | ||
// > LoggerFinder so that the LoggerFinder can figure out which kind | ||
// > of logger to return. | ||
// | ||
// If backends support the distinction support this distinction and | ||
// once `LoggerFactory`'s API is updated to forward a module, we | ||
// should do that here. | ||
// | ||
// [1] https://openjdk.java.net/jeps/264 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure that I quite get the point: the System.Logger
API is intended for JDK classes, not for applications, or is it?
The javadoc of System.LoggerFinder
requires a slightly different thing:
* In addition, when a security manager is present, loggers provided to
* system classes should not be directly configurable through the logging
* backend without requiring permissions.
* <br>
* It is the responsibility of the provider of
* the concrete {@code LoggerFinder} implementation to ensure that
* these loggers are not configured by untrusted code without proper
* permission checks, as configuration performed on such loggers usually
* affects all applications in the same Java Runtime.
We don't know the logging backends and how they configure things. So we have to check with the SecurityManager
for all cases, don't we? But this is already checked by the constructor of the System.LoggerFinder
... I'm confused.
|
||
@Test | ||
public void loggerFinderLoadedAsOnlyService() { | ||
// this method asserts that there is exactly one `LoggerFinder` and its of the correct type |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this an actual requirement? It could be over-specified. We only would have to change the getSLF4JSystemLoggerFinder
method to filter the SLF4JSystemLoggerFinder
(see below).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, that makes sense.
var loggerFinders = new ArrayList<System.LoggerFinder>(); | ||
// this fails when test is executed from the module path | ||
// because the module declaration does not declare | ||
// `uses System.LoggerFinder` | ||
ServiceLoader.load(System.LoggerFinder.class).forEach(loggerFinders::add); | ||
assertEquals("There should be exactly one `LoggerFinder`.", 1, loggerFinders.size()); | ||
var loggerFinder = loggerFinders.get(0); | ||
assertEquals("The `LoggerFinder` should be of type `SLF4JSystemLoggerFinder`.", | ||
SLF4JSystemLoggerFinder.class, | ||
loggerFinder.getClass()); | ||
return loggerFinder; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe:
var loggerFinders = new ArrayList<System.LoggerFinder>(); | |
// this fails when test is executed from the module path | |
// because the module declaration does not declare | |
// `uses System.LoggerFinder` | |
ServiceLoader.load(System.LoggerFinder.class).forEach(loggerFinders::add); | |
assertEquals("There should be exactly one `LoggerFinder`.", 1, loggerFinders.size()); | |
var loggerFinder = loggerFinders.get(0); | |
assertEquals("The `LoggerFinder` should be of type `SLF4JSystemLoggerFinder`.", | |
SLF4JSystemLoggerFinder.class, | |
loggerFinder.getClass()); | |
return loggerFinder; | |
// this fails when test is executed from the module path | |
// because the module declaration does not declare | |
// `uses System.LoggerFinder` | |
return ServiceLoader.load(System.LoggerFinder.class).stream() | |
.filter(finder → finder instanceof SLF4JSystemLoggerFinder) | |
.findAny() | |
.orElseThrow(); |
Note: this orElseThrow
requires JDK 10.
Rebase attempt |
In the last alpha, using this results in |
Indeed, that's not gonna work. :) |
Created #265. |
JEP 264: Platform Logging API and Service introduced a mechanism to Java 9 that allows third-party logging frameworks to handle JDK log messages (like those Swing produces; not JVM messages). This PR adapts
org.slf4j.LoggerFactory
andorg.slf4j.Logger
to the new interfacesjava.lang.System.LoggerFinder
andjava.lang.System.Logger
and - following theServiceLoader
documentation - creates a service provider-configuration file and a module declaration, so the integration works from class path and module path.Closes SLF4J-442.