Skip to content
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

Merged
merged 7 commits into from Aug 10, 2021
Merged

Adapter for Platform Logging (JEP 264) #232

merged 7 commits into from Aug 10, 2021

Conversation

nipafx
Copy link
Contributor

@nipafx nipafx commented Mar 10, 2020

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 and org.slf4j.Logger to the new interfaces java.lang.System.LoggerFinder and java.lang.System.Logger and - following the ServiceLoader documentation - creates a service provider-configuration file and a module declaration, so the integration works from class path and module path.

Closes SLF4J-442.

Copy link
Contributor Author

@nipafx nipafx left a 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>
Copy link
Contributor Author

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`
Copy link
Contributor Author

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.

Copy link
Member

@ceki ceki Aug 10, 2021

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
Copy link
Contributor Author

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

Comment on lines +49 to +51
INTERNAL_LOGGER.error(
"SLF4J internal error: unknown log level {} passed to `isLoggable` (likely by the JDK).", level);
return true;
Copy link
Contributor Author

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.

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.)

Comment on lines +13 to +29
// 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
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this make sense?

Copy link

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.

Copy link
Contributor Author

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.

Comment on lines +1 to +5
module org.slf4j.jdk {
requires org.slf4j;
provides java.lang.System.LoggerFinder
with org.slf4j.jdk.SLF4JSystemLoggerFinder;
}
Copy link
Contributor Author

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+.

Copy link
Member

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>
Copy link
Contributor Author

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.

Copy link
Member

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.

Comment on lines +48 to +52
default:
INTERNAL_LOGGER.error(
"SLF4J internal error: unknown log level {} passed to `isLoggable` (likely by the JDK).", level);
return true;
}
Copy link

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 :-)

Suggested change
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;

Copy link
Contributor Author

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.

Comment on lines +80 to +83
default:
INTERNAL_LOGGER.error(
"SLF4J internal error: unknown log level {} passed to `log` (likely by the JDK).", level);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same as above:

Suggested change
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);

Comment on lines +111 to +114
default:
INTERNAL_LOGGER.error(
"SLF4J internal error: unknown log level {} passed to `log` (likely by the JDK).", level);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same as above:

Suggested change
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);

Comment on lines +13 to +29
// 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
Copy link

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
Copy link

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).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, that makes sense.

Comment on lines 54 to 64
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;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe:

Suggested change
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(finderfinder instanceof SLF4JSystemLoggerFinder)
.findAny()
.orElseThrow();

Note: this orElseThrow requires JDK 10.

@ceki
Copy link
Member

ceki commented Aug 10, 2021

Rebase attempt

@ceki ceki merged commit 3c4b29b into qos-ch:2.0 Aug 10, 2021
@ben-manes
Copy link

In the last alpha, using this results in Illegal configuration-file syntax error. The META-INF/services was changed to an invalid line, package org.slf4j.jdk.platform.logging.SLF4JSystemLoggerFinder, whereas it should only be the FQCN per this PR. (I was trying to use this + slf4j-nop for testing)

@nipafx
Copy link
Contributor Author

nipafx commented Aug 29, 2021

Indeed, that's not gonna work. :)

@nipafx
Copy link
Contributor Author

nipafx commented Aug 29, 2021

Created #265.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants