Skip to content

Stable logging API - the basic use case#86612

Merged
pgomulka merged 34 commits intoelastic:masterfrom
pgomulka:logging_api_minimum_use_case
Jun 13, 2022
Merged

Stable logging API - the basic use case#86612
pgomulka merged 34 commits intoelastic:masterfrom
pgomulka:logging_api_minimum_use_case

Conversation

@pgomulka
Copy link
Copy Markdown
Contributor

@pgomulka pgomulka commented May 10, 2022

Introducing a stable logging API under libs/logging.
This change covers the most common use cases for logging: fetching a logger with LogManager, emitting a log messages with Logger and Level.
It is influenced by log4j2-api, but do not include Marker and LogBuilder methods.
Also methods using org.apache.logging.log4j.util.Supplier are replaced with java.util.Supplier

The basic implementation is present in server and injected statically in LogConfigurator

relates #84478

@pgomulka pgomulka changed the title [draft] Stable logging API Stable logging API - the basic use case May 23, 2022
@pgomulka pgomulka added :Core/Infra/Logging Log management and logging utilities >feature and removed WIP labels May 23, 2022
@pgomulka pgomulka self-assigned this May 23, 2022
@elasticsearchmachine
Copy link
Copy Markdown
Collaborator

Hi @pgomulka, I've created a changelog YAML for you.

@pgomulka pgomulka marked this pull request as ready for review May 23, 2022 13:54
@elasticmachine elasticmachine added the Team:Core/Infra Meta label for core/infra team label May 23, 2022
@elasticmachine
Copy link
Copy Markdown
Collaborator

Pinging @elastic/es-core-infra (Team:Core/Infra)

@pgomulka pgomulka mentioned this pull request May 23, 2022
19 tasks
@pgomulka
Copy link
Copy Markdown
Contributor Author

should we mark this as experimental?

@ChrisHegarty
Copy link
Copy Markdown
Contributor

should we mark this as experimental?

Is there a standard way of doing that ?

LogManagerFactory INSTANCE = loadProvider();

static LogManagerFactory loadProvider() {
ServiceLoader<LogManagerFactory> sl = ServiceLoader.load(LogManagerFactory.class, ClassLoader.getSystemClassLoader());
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Instead of using service loader directly here, can we have a setter method (since this package is only exported to server) and have server set this early? We won't always necessarily have this on the system classloader. @ChrisHegarty wdyt?

Copy link
Copy Markdown
Contributor Author

@pgomulka pgomulka May 24, 2022

Choose a reason for hiding this comment

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

I was thinking that this class will change when we move the log4j off the system classloader. Something similar to what we have here https://github.com/ChrisHegarty/elasticsearch/blob/logging-module/libs/logging/src/main/java/org/elasticsearch/logging/spi/LogManagerFactory.java

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

How about we look up the implementation using the service loader variant that accepts a module layer? Or is the question more related to the use of service loader at all?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The latter. While eventually I think service loader will be used when we can move log4j off the system classloader, I was suggesting starting by loading this in server and setting statically here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

What is the benefit of setting this statically from server? We would have to remember to set this with LogConfigurator, and once we move implementation off the system classloader to remove it.
I personally think this is an easier to read solution, but don't have strong opinion against setting as well.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

For stable plugin api, we are thinking about changing the way the system classloader is setup. Right now, everything from server is in the system classloader. For stable plugin api, we want to first load the stable api jars, then create a separate loader, parented by the stable api jars, that is everything from server. That separation would break this ServerLoader lookup, because the logging api impl would not be on the system classloader, and this code could not statically find the loader wtih the impl (since it will not yet have been created). Thus, setting this statically as an init of the logging api allows us to delay until server has actually been loaded.

@pgomulka
Copy link
Copy Markdown
Contributor Author

@elasticmachine run elasticsearch-ci/example-plugins

* @return The Logger.
*/
public static Logger getLogger(final String name) {
return LogManagerFactory.provider().getLogger(name);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I assume null in results in a NPE? If so, maybe a class-level comment, e.g.

Unless otherwise noted, passing a null argument to a method of any class will cause a NullPointerException

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

actually this depends on log4j. and if a null is passed as a name or clazz parameter, it will take caler's class
StackLocatorUtil.getCallerClass(2)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

We should clarify this in the javadoc, no ? Or alternatively, just disallow null (throw NPE)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Has this been address in the javadoc? I'm not seeing it

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I did not add the javadoc. This depends on how log4j does this and I did not want to mention this in the API's javadoc. I think is is rare that a name is null.

import org.elasticsearch.logging.internal.spi.LogManagerFactory;

/**
* A class used for creating loggers.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Maybe expand this a little, e.g.
"A Manager of {@code Loggers}. This class consists of factory methods for creating and retrieving Loggers."

* @param level the logging level
* @param messageSupplier A function, which when called, produces the desired log String message;
*/
void log(Level level, java.util.function.Supplier<String> messageSupplier, Throwable throwable);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggest, highlighting the LAZY aspect. e.g.

"Logs a lazily supplied String message associated with a given throwable. If the logger is currently enabled for the specified log message level, then a message is logged that is the result produced by the given supplier function."

Note: missing at param for throwable

@pgomulka pgomulka requested a review from rjernst May 31, 2022 14:51
Copy link
Copy Markdown
Member

@rjernst rjernst left a comment

Choose a reason for hiding this comment

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

This looks good overall. I have a few more nits.

I also think it would be good to generate the javadocs and see what they look like. I think some formatting is missing in several methods.

Also, this will need to be followed up by adding the jar to the release configuration.

tasks.named("loggerUsageCheck").configure {enabled = false }

dependencies {
testImplementation "com.carrotsearch.randomizedtesting:randomizedtesting-runner:${versions.randomizedrunner}"
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

these should all be pulled in by the test:framework dep below right?

* @return The Logger.
*/
public static Logger getLogger(final String name) {
return LogManagerFactory.provider().getLogger(name);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Has this been address in the javadoc? I'm not seeing it

* Logs a message String with the given level.
*
* @param level the logging level
* @param message the message CharSequence to log.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

String not CharSequence?


/**
* Main interface for logging. Most operations are done through this interface.
* This is interface heavily influenced by org.apache.logging.log4j2.Logger.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

reversed words: is interface -> interface is

* that is the result produced by the given supplier function.
* @param messageSupplier A function, which when called, produces the desired log String message;
*/
void error(java.util.function.Supplier<String> messageSupplier);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Is the fully qualified name for Supplier needed?

* @param message the message to log
* @param p0 parameter to the message.
*/
void trace(String message, Object p0);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I wonder if we should omit the single arg variants of these methods for all levels. The reason is it can be ambiguous with the general method taking an array of args. I'm not sure if there is a performance hit for that, but we are talking about logging so I can't imagine a single implicit array creation is going to make a huge difference.

Copy link
Copy Markdown
Contributor Author

@pgomulka pgomulka Jun 8, 2022

Choose a reason for hiding this comment

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

good point, I will remove them. Should we do the same for variants of this (p0, p1... p5 ) ?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Yeah I think we should. We can always add them back.

import org.elasticsearch.logging.Logger;

/**
* SPI for creating new loggers
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

not really SPI anymore, it's just a static holder for the factory

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

but doesn't it still serve a purpose of SPI? the implementation has to provide the concrete getLogger methods that this class declares

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

SPI normally has the connotation of being loaded by ServiceProvider, but this is not.

public class LogManagerFactoryImpl extends LogManagerFactory {
@Override
public Logger getLogger(String name) {
// TODO PG logger impl instance caching
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can you create an issue for this?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks! Please add a link to the issue in the comment :)

Copy link
Copy Markdown
Member

@rjernst rjernst left a comment

Choose a reason for hiding this comment

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

LGTM

}

private org.apache.logging.log4j.util.Supplier<?> mapSupplier(Supplier<String> msgSupplier) {
return (org.apache.logging.log4j.util.Supplier<Object>) () -> msgSupplier.get();
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

is the cast necessary given the return type?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

:Core/Infra/Logging Log management and logging utilities >feature Team:Core/Infra Meta label for core/infra team v8.4.0

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants