Skip to content

Commit a5ebdf4

Browse files
authored
Fix logging in CLI (#11472)
* Fix logging in CLI Previously, unless `--logger-connect` was used, CLI would always fallback to console logging. In addition it would be misconfigured if `application.conf` was provided with logging configuration. This change makes sure that CLI uses the same logging infrastructure as the rest of the system. As a result, CLI will now by default not only log to the console and respect the provided configuration but also log to the file system. * Update docs * c&p error * More examples
1 parent 867c77d commit a5ebdf4

File tree

6 files changed

+177
-27
lines changed

6 files changed

+177
-27
lines changed

build.sbt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3689,6 +3689,7 @@ lazy val `engine-runner` = project
36893689
.dependsOn(cli)
36903690
.dependsOn(`profiling-utils`)
36913691
.dependsOn(`library-manager`)
3692+
.dependsOn(`distribution-manager`)
36923693
.dependsOn(`edition-updater`)
36933694
.dependsOn(`runtime-parser`)
36943695
.dependsOn(`logging-service`)

docs/infrastructure/logging.md

Lines changed: 109 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -35,13 +35,17 @@ involving the centralized logging service.
3535

3636
## Configuration
3737

38-
The logging settings should be placed under the `logging-service` key of the
39-
`application.conf` config. Each of the main components can customize format and
40-
output target via section in `application.conf` configuration file. The
38+
All logging settings are configured via the `logging-service` section of the
39+
`application.conf` config file. Each of the main components can customize format
40+
and output target via section in `application.conf` configuration file. The
4141
configuration is using HOCON-style, as defined by
4242
[lightbend/config](https://github.com/lightbend/config). Individual values
4343
accepted in the config are inspired by SLF4J's properties, formatting and
44-
implementations.
44+
implementations. Currently 3 components define logging configuration:
45+
46+
- [`project-manager`](../../lib/scala/project-manager/src/main/resources/application.conf)
47+
- [`launcher`](../../engine/launcher/src/main/resources/application.conf)
48+
- [CLI](../../engine/runner/src/main/resources/application.conf)
4549

4650
The configuration has two main sections:
4751

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

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

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

7377
### Custom Log Levels
7478

75-
The `logging-service.logger` configuration provides an ability to override the
76-
default application log level for particular loggers. In the `logger` subconfig
77-
the key specifies the logger name (or it's prefix) and the value specifies the
78-
log level for that logger.
79+
Possible log level values are (in the order of precedence):
80+
81+
- `error`
82+
- `warn`
83+
- `info`
84+
- `debug`
85+
- `trace`
86+
87+
The `logging-service.logger` configuration section provides an ability to
88+
override the default application log level for particular loggers. In the
89+
`logger` subconfig the key specifies the logger name (or it's prefix) and the
90+
value specifies the log level for that logger.
7991

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

118130
### Appenders
119131

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

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

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

186+
In the above example `%logger` format will be substituted with a class name for
187+
which the logger was created with.
188+
189+
By default, console pattern includes `%nopex` formatter which means that any
190+
stacktrace attached to the log message will always be ignored. By default other
191+
appenders do not have such formatting key. This means that if an exception is
192+
included in the logged messaged, a full stacktrace will be attached, if present.
193+
194+
For a full list of formatting keys please refer to the concrete implementation's
195+
[manual](https://logback.qos.ch/manual/layouts.html#ClassicPatternLayout).
196+
172197
#### File Appender
173198

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

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

360385
```
361386
ENSO_LOG_TO_FILE=false project-manager ...
362387
```
363388

364-
Users can also modify the default maximal log level, `DEBUG`, used when logging
365-
to a log file by setting the environment variable:
389+
Users can fully control the maximal log level used when logging to a log file by
390+
setting the environment variable:
366391

367392
```
368393
ENSO_LOG_TO_FILE_LOG_LEVEL=trace project-manager ...
369394
```
395+
396+
For example, in the above example `project-manager` will log events of up-to
397+
`trace` in the log file.
398+
399+
**Note** Logging to a file requires presence of the `file`
400+
[appender](#file-appender) in the `logging-service.appenders` section.
401+
402+
# How to use logging
403+
404+
Logging infrastructure uses a popular SLF4J interface which most of developers
405+
should be familiar with. In this section we include a only small number of
406+
examples, full user manual is available at SLF4J's
407+
[website](https://www.slf4j.org/manual.html).
408+
409+
## Log a simple INFO message
410+
411+
```
412+
import org.slf4j.Logger;
413+
import org.slf4j.LoggerFactory;
414+
415+
public class HelloWorld {
416+
417+
public static void main(String[] args) {
418+
Logger logger = LoggerFactory.getLogger(HelloWorld.class);
419+
logger.info("Hello World");
420+
}
421+
}
422+
```
423+
424+
## Log a simple INFO message only if TRACE is enabled
425+
426+
```
427+
import org.slf4j.Logger;
428+
import org.slf4j.LoggerFactory;
429+
430+
public class HelloWorld {
431+
432+
public static void main(String[] args) {
433+
Logger logger = LoggerFactory.getLogger(HelloWorld.class);
434+
if (logger.isTraceEnabled()) {
435+
logger.info("Hello World");
436+
}
437+
}
438+
}
439+
```
440+
441+
## Log an exception
442+
443+
```
444+
import org.slf4j.Logger;
445+
import org.slf4j.LoggerFactory;
446+
447+
public class HelloWorld {
448+
449+
public static void main(String[] args) {
450+
Logger logger = LoggerFactory.getLogger(HelloWorld.class);
451+
Throwable ex = new RuntimeException("foo")
452+
logger.error("Hello World", ex);
453+
}
454+
}
455+
```
456+
457+
Note that in order for the full stacktrace to be printed, pattern in the desired
458+
appender must not contain `%nopex` formatting key. See [formatting](#format) for
459+
details.

engine/runner/src/main/java/org/enso/runner/Main.java

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,8 @@ public class Main {
101101
private static final String WARNINGS_LIMIT = "warnings-limit";
102102
private static final String SYSTEM_PROPERTY = "vm.D";
103103

104+
private static final String DEFAULT_MAIN_METHOD_NAME = "main";
105+
104106
private static final org.slf4j.Logger logger = LoggerFactory.getLogger(Main.class);
105107

106108
Main() {}
@@ -854,20 +856,20 @@ private void runPackage(
854856
java.util.List<String> additionalArgs) {
855857
var topScope = context.getTopScope();
856858
var mainModule = topScope.getModule(mainModuleName);
857-
runMain(mainModule, projectPath, additionalArgs, "main");
859+
runMain(mainModule, projectPath, additionalArgs, DEFAULT_MAIN_METHOD_NAME);
858860
}
859861

860862
private void runSingleFile(
861863
PolyglotContext context, File file, java.util.List<String> additionalArgs) {
862864
var mainModule = context.evalModule(file);
863-
runMain(mainModule, file, additionalArgs, "main");
865+
runMain(mainModule, file, additionalArgs, DEFAULT_MAIN_METHOD_NAME);
864866
}
865867

866868
private void runMain(
867869
Module mainModule,
868870
File rootPkgPath,
869871
java.util.List<String> additionalArgs,
870-
String mainMethodName // = "main"
872+
String mainMethodName // = DEFAULT_MAIN_METHOD_NAME
871873
) {
872874
try {
873875
var mainType = mainModule.getAssociatedType();
@@ -881,7 +883,7 @@ private void runMain(
881883
throw exitFail();
882884
}
883885
var main = mainFun.get();
884-
if (!"main".equals(mainMethodName)) {
886+
if (!DEFAULT_MAIN_METHOD_NAME.equals(mainMethodName)) {
885887
main.execute(join(mainType, nil()));
886888
} else {
887889
// Opportunistically parse arguments and convert to ints.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package org.enso.runner;
2+
3+
import org.enso.distribution.Environment;
4+
5+
public class RunnerEnvironment implements Environment {}

engine/runner/src/main/java/org/enso/runner/RunnerLogging.java

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import java.util.concurrent.ExecutionException;
55
import java.util.concurrent.ExecutorService;
66
import java.util.concurrent.Executors;
7+
import org.enso.distribution.DistributionManager;
78
import org.enso.logger.masking.Masking;
89
import org.enso.logging.config.LoggerSetup;
910
import org.slf4j.Logger;
@@ -14,6 +15,8 @@ final class RunnerLogging {
1415
private RunnerLogging() {}
1516

1617
private static final Logger logger = LoggerFactory.getLogger(RunnerLogging.class);
18+
private static final DistributionManager distributionManager =
19+
new DistributionManager(new RunnerEnvironment());
1720

1821
/**
1922
* Sets up the runner's logging service.
@@ -49,17 +52,38 @@ private static void setupImpl(
4952
var success = future.get();
5053
if (success) {
5154
logger.trace("Connected to logging service at [{}]", connectionUri);
52-
return;
5355
} else {
5456
throw new RuntimeException("Failed to connect to logging service");
5557
}
5658
} catch (InterruptedException | ExecutionException e) {
5759
System.err.println(
5860
"Failed to connect to the logging service server, " + "falling back to local logging.");
5961
}
60-
}
61-
if (!setupConsoleAppender(logLevel, executorService, loggerSetup)) {
62-
System.err.println("Failed to initialize logging infrastructure");
62+
} else {
63+
var future =
64+
executorService.submit(
65+
() ->
66+
loggerSetup.setup(
67+
logLevel,
68+
distributionManager.detectPaths().logs(),
69+
"enso-cli",
70+
loggerSetup.getConfig()));
71+
try {
72+
var success = future.get();
73+
if (!success) {
74+
System.err.println("Failed to initialize logging from the configuration");
75+
} else {
76+
return;
77+
}
78+
} catch (InterruptedException | ExecutionException e) {
79+
System.err.println(
80+
"Failed to initialize logging from the configuration due to internal exception: "
81+
+ e.getMessage());
82+
}
83+
System.err.println("Attempting to fallback logging to console only");
84+
if (!setupConsoleAppender(logLevel, executorService, loggerSetup)) {
85+
System.err.println("Failed to initialize logging infrastructure");
86+
}
6387
}
6488
}
6589

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# CLI's application.conf
2+
3+
logging-service {
4+
logger {
5+
akka.actor = info
6+
akka.event = error
7+
akka.io = error
8+
akka.stream = error
9+
}
10+
appenders = [
11+
{
12+
name = "file",
13+
pattern = "[%level{lowercase=true}] [%d{yyyy-MM-dd'T'HH:mm:ssXXX}] [%logger] %msg%n"
14+
},
15+
{
16+
name = "console"
17+
pattern = "[%level{lowercase=true}] [%d{yyyy-MM-dd'T'HH:mm:ssXXX}] [%logger] %msg%n%nopex"
18+
}
19+
]
20+
default-appender = console
21+
default-appender = ${?ENSO_APPENDER_DEFAULT}
22+
log-to-file {
23+
enable = true
24+
enable = ${?ENSO_LOG_TO_FILE}
25+
log-level = trace
26+
log-level = ${?ENSO_LOG_TO_FILE_LOG_LEVEL}
27+
}
28+
}

0 commit comments

Comments
 (0)