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

Fix logging in CLI #11472

Merged
merged 8 commits into from
Nov 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -3689,6 +3689,7 @@ lazy val `engine-runner` = project
.dependsOn(cli)
.dependsOn(`profiling-utils`)
.dependsOn(`library-manager`)
.dependsOn(`distribution-manager`)
.dependsOn(`edition-updater`)
.dependsOn(`runtime-parser`)
.dependsOn(`logging-service`)
Expand Down
128 changes: 109 additions & 19 deletions docs/infrastructure/logging.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,17 @@ involving the centralized logging service.

## Configuration

The logging settings should be placed under the `logging-service` key of the
`application.conf` config. Each of the main components can customize format and
output target via section in `application.conf` configuration file. The
All logging settings are configured via the `logging-service` section of the
`application.conf` config file. Each of the main components can customize format
and output target via section in `application.conf` configuration file. The
configuration is using HOCON-style, as defined by
[lightbend/config](https://github.com/lightbend/config). Individual values
accepted in the config are inspired by SLF4J's properties, formatting and
implementations.
implementations. Currently 3 components define logging configuration:

- [`project-manager`](../../lib/scala/project-manager/src/main/resources/application.conf)
- [`launcher`](../../engine/launcher/src/main/resources/application.conf)
- [CLI](../../engine/runner/src/main/resources/application.conf)

The configuration has two main sections:

Expand All @@ -57,8 +61,8 @@ representation is available as an instance of
programmatically initialize loggers.

As per [configuration schema](https://github.com/lightbend/config) any key can
have a default value that can be overridden by an environment variable. For
example
be defined with a default value that can be overridden by an environment
variable. For example

```
{
Expand All @@ -72,10 +76,18 @@ it is defined during loading of the config file.

### Custom Log Levels

The `logging-service.logger` configuration provides an ability to override the
default application log level for particular loggers. In the `logger` subconfig
the key specifies the logger name (or it's prefix) and the value specifies the
log level for that logger.
Possible log level values are (in the order of precedence):

- `error`
- `warn`
- `info`
- `debug`
- `trace`

The `logging-service.logger` configuration section provides an ability to
override the default application log level for particular loggers. In the
`logger` subconfig the key specifies the logger name (or it's prefix) and the
value specifies the log level for that logger.

```
logging-service.logger {
Expand Down Expand Up @@ -117,7 +129,7 @@ properties always have a higher priority over those defined in the

### Appenders

Log output target is also configured in the `application.conf` files in the
Log output target is configured in the `application.conf` files in the
"appenders" section ("appender" is equivalent to `java.util.logging.Handler`
semantics). Each appender section can provide further required and optional
key/value pairs, to better customize the log target output.
Expand All @@ -133,8 +145,10 @@ Currently supported are
a sentry.io service

The appenders are defined by the `logging-service.appenders`. Currently only a
single appender can be selected at a time. The selection may also be done via an
environmental variable but it depends on which component we are executing.
single appender can be selected at a time, although additional
[logging to file](#logging-to-file) is supported. The selection may also be done
via an environmental variable but it depends on which component we are
executing.

- `project-manager` - project manager by default starts a centralized logging
server that collects logs (as defined in `logging-service.server` config key)
Expand Down Expand Up @@ -169,6 +183,17 @@ message via `pattern` field e.g.
]
```

In the above example `%logger` format will be substituted with a class name for
which the logger was created with.

By default, console pattern includes `%nopex` formatter which means that any
stacktrace attached to the log message will always be ignored. By default other
appenders do not have such formatting key. This means that if an exception is
included in the logged messaged, a full stacktrace will be attached, if present.

For a full list of formatting keys please refer to the concrete implementation's
[manual](https://logback.qos.ch/manual/layouts.html#ClassicPatternLayout).

#### File Appender

Enabled with `ENSO_APPENDER_DEFAULT=file` environment variable.
Expand Down Expand Up @@ -352,18 +377,83 @@ way it can verify that all logs are being reported within the provided code.
### Logging to file

By default Enso will attempt to persist (verbose) logs into a designated log
file. This means that even though a user might be shown `WARNING` level logs in
the console, logs with up to `DEBUG` level will be dumped into the log file. A
user can disable this parallel logging to a file by setting the environment
variable:
file. This means that even though a user might be shown only `WARNING` level
logs in the console, logs with up to `DEBUG` or `TRACE` level, including full
stacktraces, can be dumped into the log file. A user can disable this parallel
logging to a file by setting the environment variable:

```
ENSO_LOG_TO_FILE=false project-manager ...
```

Users can also modify the default maximal log level, `DEBUG`, used when logging
to a log file by setting the environment variable:
Users can fully control the maximal log level used when logging to a log file by
setting the environment variable:

```
ENSO_LOG_TO_FILE_LOG_LEVEL=trace project-manager ...
```

For example, in the above example `project-manager` will log events of up-to
`trace` in the log file.

**Note** Logging to a file requires presence of the `file`
[appender](#file-appender) in the `logging-service.appenders` section.

# How to use logging

Logging infrastructure uses a popular SLF4J interface which most of developers
should be familiar with. In this section we include a only small number of
examples, full user manual is available at SLF4J's
[website](https://www.slf4j.org/manual.html).

## Log a simple INFO message

```
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class HelloWorld {

public static void main(String[] args) {
Logger logger = LoggerFactory.getLogger(HelloWorld.class);
logger.info("Hello World");
}
}
Copy link
Member

Choose a reason for hiding this comment

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

I am probably going to use this from an Enso code, not for a a separate class with main main method.

Where will this message appear when I run ./enso --run hello_world.enso? Console? Somewhere else?

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 really don't understand your request. You asked for examples of using logging infrastructure so I essentially copy pasted API examples from SLF4J because that's it is to it.
Now you ask for logging in Enso which is a different thing and has been quickly developed by libs team and has not been integrated with backend at all AFAIK.

Copy link
Member

@JaroslavTulach JaroslavTulach Nov 7, 2024

Choose a reason for hiding this comment

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

I am probably going to use this from an Enso code

I beg your pardon. I mean logging from Enso runtime code (like PassManager).

logging in Enso ... developed by libs team

I am sorry for causing confusion. No, logging from Enso code isn't what I am interested in right now.

```

## Log a simple INFO message only if TRACE is enabled

```
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class HelloWorld {

public static void main(String[] args) {
Logger logger = LoggerFactory.getLogger(HelloWorld.class);
if (logger.isTraceEnabled()) {
logger.info("Hello World");
}
Copy link
Member

Choose a reason for hiding this comment

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

Is this the right pattern? I would expect simple logger.trace("Hello World"). How do I need to launch the CLI for such a message to appear in the console?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You asked for log a message into console only if verbosity is increased. Of course one can use .trace but that's different from your request.

Copy link
Member

@JaroslavTulach JaroslavTulach Nov 7, 2024

Choose a reason for hiding this comment

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

You asked for log a message into console only if verbosity is increased. Of course one can use .trace but that's different from your request.

I have asked for a way to "log a message to a console if a verbosity is increased". Does:

logger.trace("Hello World")

appear on the console when enso is invoked (and the logger code is performed)? Does the Hello World message appear on the console when enso is invoked with some logging parameters? If the answers are no to 1st question and yes to 2nd question, then logger.trace + description of the CLI arguments to enable seeing the message on console is a solution.

PS: Using isTraceEnabled() is probably only good for performance reasons, isn't it? If so, then your example might be:

    Logger logger = LoggerFactory.getLogger(HelloWorld.class);
    if (logger.isTraceEnabled()) {
      logger.trace("Hello World");
    }

e.g. using isTraceEnabled and .trace. Combining it with .info is pretty weird and not very intuitive from my perspective.

}
}
```

## Log an exception

```
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class HelloWorld {

public static void main(String[] args) {
Copy link
Member

Choose a reason for hiding this comment

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

Again, example of logging from Enso (including how to launch the CLI) would be more useful than standalone class unrelated to Enso. Is it guaranteed that ./enso --run hello.enso would print exception logged this way to console? Is there a test for that?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You clearly haven't read the documentation. We don't log stacktraces in console, on purpose. You can of course change that by removing %noexp but we are not going to change that for users. It's all documented.

Copy link
Member

Choose a reason for hiding this comment

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

We don't log stacktraces in console, on purpose.

Then your answer should be: It is not possible. I am not OK with such an answer, but that's a unrelated to the answer.

Logger logger = LoggerFactory.getLogger(HelloWorld.class);
Throwable ex = new RuntimeException("foo")
logger.error("Hello World", ex);
}
}
```

Note that in order for the full stacktrace to be printed, pattern in the desired
appender must not contain `%nopex` formatting key. See [formatting](#format) for
details.
10 changes: 6 additions & 4 deletions engine/runner/src/main/java/org/enso/runner/Main.java
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,8 @@ public class Main {
private static final String WARNINGS_LIMIT = "warnings-limit";
private static final String SYSTEM_PROPERTY = "vm.D";

private static final String DEFAULT_MAIN_METHOD_NAME = "main";

private static final org.slf4j.Logger logger = LoggerFactory.getLogger(Main.class);

Main() {}
Expand Down Expand Up @@ -854,20 +856,20 @@ private void runPackage(
java.util.List<String> additionalArgs) {
var topScope = context.getTopScope();
var mainModule = topScope.getModule(mainModuleName);
runMain(mainModule, projectPath, additionalArgs, "main");
runMain(mainModule, projectPath, additionalArgs, DEFAULT_MAIN_METHOD_NAME);
}

private void runSingleFile(
PolyglotContext context, File file, java.util.List<String> additionalArgs) {
var mainModule = context.evalModule(file);
runMain(mainModule, file, additionalArgs, "main");
runMain(mainModule, file, additionalArgs, DEFAULT_MAIN_METHOD_NAME);
}

private void runMain(
Module mainModule,
File rootPkgPath,
java.util.List<String> additionalArgs,
String mainMethodName // = "main"
String mainMethodName // = DEFAULT_MAIN_METHOD_NAME
) {
try {
var mainType = mainModule.getAssociatedType();
Expand All @@ -881,7 +883,7 @@ private void runMain(
throw exitFail();
}
var main = mainFun.get();
if (!"main".equals(mainMethodName)) {
if (!DEFAULT_MAIN_METHOD_NAME.equals(mainMethodName)) {
main.execute(join(mainType, nil()));
} else {
// Opportunistically parse arguments and convert to ints.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package org.enso.runner;

import org.enso.distribution.Environment;

public class RunnerEnvironment implements Environment {}
32 changes: 28 additions & 4 deletions engine/runner/src/main/java/org/enso/runner/RunnerLogging.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.enso.distribution.DistributionManager;
import org.enso.logger.masking.Masking;
import org.enso.logging.config.LoggerSetup;
import org.slf4j.Logger;
Expand All @@ -14,6 +15,8 @@ final class RunnerLogging {
private RunnerLogging() {}

private static final Logger logger = LoggerFactory.getLogger(RunnerLogging.class);
private static final DistributionManager distributionManager =
new DistributionManager(new RunnerEnvironment());

/**
* Sets up the runner's logging service.
Expand Down Expand Up @@ -49,17 +52,38 @@ private static void setupImpl(
var success = future.get();
if (success) {
logger.trace("Connected to logging service at [{}]", connectionUri);
return;
} else {
throw new RuntimeException("Failed to connect to logging service");
}
} catch (InterruptedException | ExecutionException e) {
System.err.println(
"Failed to connect to the logging service server, " + "falling back to local logging.");
}
}
if (!setupConsoleAppender(logLevel, executorService, loggerSetup)) {
System.err.println("Failed to initialize logging infrastructure");
} else {
var future =
executorService.submit(
() ->
loggerSetup.setup(
logLevel,
distributionManager.detectPaths().logs(),
"enso-cli",
loggerSetup.getConfig()));
try {
var success = future.get();
if (!success) {
System.err.println("Failed to initialize logging from the configuration");
} else {
return;
}
} catch (InterruptedException | ExecutionException e) {
System.err.println(
"Failed to initialize logging from the configuration due to internal exception: "
+ e.getMessage());
}
System.err.println("Attempting to fallback logging to console only");
if (!setupConsoleAppender(logLevel, executorService, loggerSetup)) {
System.err.println("Failed to initialize logging infrastructure");
}
}
}

Expand Down
28 changes: 28 additions & 0 deletions engine/runner/src/main/resources/application.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# CLI's application.conf

logging-service {
logger {
akka.actor = info
akka.event = error
akka.io = error
akka.stream = error
}
appenders = [
{
name = "file",
pattern = "[%level{lowercase=true}] [%d{yyyy-MM-dd'T'HH:mm:ssXXX}] [%logger] %msg%n"
},
{
name = "console"
pattern = "[%level{lowercase=true}] [%d{yyyy-MM-dd'T'HH:mm:ssXXX}] [%logger] %msg%n%nopex"
}
]
default-appender = console
default-appender = ${?ENSO_APPENDER_DEFAULT}
log-to-file {
enable = true
enable = ${?ENSO_LOG_TO_FILE}
log-level = trace
log-level = ${?ENSO_LOG_TO_FILE_LOG_LEVEL}
}
}
Loading