-
Notifications
You must be signed in to change notification settings - Fork 323
/
logging.md
459 lines (356 loc) · 14.8 KB
/
logging.md
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
---
layout: developer-doc
title: Logging Service
category: infrastructure
tags: [infrastructure, logging, debug]
order: 6
---
# Logging
The Enso project features a centralised logging service to allow for the
aggregation of logs from multiple components. This service can be started with
one of the main components, allowing other components to connect to it. The
service aggregates all logs in one place for easier analysis of the interaction
between components. Components can also log to console or files directly without
involving the centralized logging service.
<!-- MarkdownTOC levels="2,3" autolink="true" -->
- [Configuration](#configuration)
- [Custom Log Levels](#custom-log-levels)
- [Appenders](#appenders)
- [Format](#format)
- [File](#file-appender)
- [Network](#socket-appender)
- [Sentry.io](#sentry-appender)
- [JVM Architecture](#jvm-architecture)
- [SLF4J Interface](#slf4j-interface)
- [Setting Up Logging](#setting-up-logging)
- [Log Masking](#log-masking)
- [Logging in Tests](#logging-in-tests)
- [Logging to file](#logging-to-file)
<!-- /MarkdownTOC -->
## Configuration
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. 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:
- [custom log levels](#custom-log-levels)
- [applications' appenders](#appenders) (also known as configuration of log
events output target)
During component's setup, its `application.conf` config file is parsed. The
config's keys and values are validated and, if correct, the parsed
representation is available as an instance of
`org.enso.logging.config.LoggingServiceConfig` class. The class encapsulates the
`logging-service` section of `application.conf` file and is used to
programmatically initialize loggers.
As per [configuration schema](https://github.com/lightbend/config) any key can
be defined with a default value that can be overridden by an environment
variable. For example
```
{
host = localhost
host = $ENSO_HOST
}
```
defines a `host` key once, except that `ENSO_HOST` values takes a precedence if
it is defined during loading of the config file.
### Custom Log Levels
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 {
akka.actor = info
akka.event = error
akka.io = error
slick {
jdbc.JdbcBackend.statement = debug
"*" = error
}
}
```
For example, the config above limits all `akka.actor.*` loggers to the info
level logging, and `akka.event.*` loggers can emit only the error level
messages.
Config supports globs (`*`). For example, the config above sets
`jdbc.JdbcBackend.statement` SQL statements logging to debug level, and the rest
of the slick loggers to error level.
Additionally, custom log events can be provided during runtime via system
properties, without re-packaging the updated config file. For example
```typescript
akka.actor = info;
```
is equivalent to
```typescript
-Dakka.actor.Logger.level=info
```
Any custom log level is therefore defined with `-Dx.y.Z.Logger.level` where `x`,
`y` and `Z` refer to the package elements and class name, respectively. System
properties always have a higher priority over those defined in the
`application.conf` file.
### Appenders
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.
Currently supported are
- console appender - the most basic appender that prints log events to stdout
- [file appender](#file-appender) - appender that writes log events to a file,
with optional rolling file policy
- [socket appender](#socket-appender) - appender that forwards log events to
some logging server
- [sentry.io appender](#sentry-appender) - appender that forwards log events to
a sentry.io service
The appenders are defined by the `logging-service.appenders`. Currently only a
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)
and the logs output can be overwritten by `ENSO_LOGSERVER_APPENDER` env
variable
- `ensoup` or `enso` - the default log output can be overwritten by defining the
`ENSO_APPENDER_DEFAULT` env variable
For example, for the project manager to output to `console` one simply executes
```
ENSO_LOGSERVER_APPENDER=console ./project-manager
```
#### Format
The pattern follows the classic's
[PatternLayout](https://logback.qos.ch/manual/layouts.html#ClassicPatternLayout)
format.
Appenders that store/display log events can specify the format of the log
message via `pattern` field e.g.
```typescript
appenders = [
{
name = "console"
pattern = "[%level{lowercase=true}] [%d{yyyy-MM-dd'T'HH:mm:ssXXX}] [%logger] %msg%n%nopex"
}
...
]
```
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.
File appender directs all log events to a log file:
```
{
name = "file"
append = <boolean, optional>
immediate-flush = <boolean, optional>
pattern = <string, optional>
rolling-policy {
max-file-size = <string, optional>
max-history = <int, optional>
max-total-size = <string, optional>
}
}
```
Rolling policy is a fully optional property of File Appender that would trigger
automatic log rotation. All properties are optional with some reasonable
defaults if missing (defined in `org.enso.logging.config.FileAppender` config
class).
#### Socket Appender
Enabled with `ENSO_APPENDER_DEFAULT=socket` environment variable.
Configuration
```
{
name = "socket"
hostname = <string, required>
port = <string, required>
}
```
The two fields can be overridden via environment variables:
- `hostname` has an equivalent `$ENSO_LOGSERVER_HOSTNAME` variable
- `port` has an equivalent `$ENSO_LOGSERVER_PORT` variable
#### Sentry Appender
Enabled with `ENSO_APPENDER_DEFAULT=sentry` environment variable.
```
{
name = "sentry"
dsn = <string, required>
flush-timeout = <int, optional>
debug = <boolean, optional>
}
```
Sentry's Appender has a single required field, `dsn`. The `dsn` value can be
provided via an environment variable `ENSO_APPENDER_SENTRY_DSN`. `flush-timeout`
determines how often logger should send its collected events to sentry.io
service. If `debug` value is `true`, logging will print to stdout additional
trace information of the logging process itself.
## JVM Architecture
Enso's logging makes use of two logging APIs - `java.util.logging` and
`org.slf4j`. The former is being used by the Truffle runtime, which itself
relies on `jul`, while the latter is used everywhere else. The implementation of
the logging is using off the shelf `Logback` implementation with some custom
setup methods. The two APIss cooperate by essentially forwarding log messages
from the former to the latter.
While typically any SLF4J customization would be performed via custom
`LoggerFactory` and `Logger` implementation that is returned via a
`StaticLoggerBinder` instance, this is not possible for our use-case:
- file logging requires Enso-specific directory which is only known during
runtime
- centralized logging
- modifying log levels without recompilation
### SLF4J Interface
The user code must not be calling any of the underlying implementations, such as
Log4J or Logback, and should only request loggers via factory methods.
One can use the `org.slf4j.LoggerFactory` directly to retrieve class-specific
logger. For Scala code, it is recommended to use the
`com.typesafe.scalalogging.Logger` instead which wraps the SLF4J logger with
macros that compute the log messages only if the given logging level is enabled,
and allows much prettier initialisation.
```java
package foo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Foo {
private Logger logger = LoggerFactory.getLogger(Foo.class);
public void bar() {
logger.info("Hello world!");
}
}
```
### Setting Up Logging
The `org.slf4j.Logger` instances have to know where to send log events. This
setting is typically performed once, when the service starts, and applies
globally during its execution. Currently, it is not possible to dynamically
change where log events are being stored. The main (abstract) class used for
setting up logging is `org.enso.logging.config.LoggerSetup`. An instance of that
class can be retrieved with the thread-safe
`org.enso.logging.config.LoggerSetup.get` factory method.
`org.enso.logging.config.LoggerSetup` provides a number of `setupXYZAppender`
methods that will direct loggers to send log events to an `XYZ` appender.
Setting a specific hard-coded appender programmatically should however be
avoided by the users. Instead, one should invoke one of the overloaded `setup`
variants that initialize loggers based on the provided `logging-service`
configuration.
```java
package foo;
import org.enso.logging.config.LoggerSetup;
import org.slf4j.event.Level;
public class MyService {
private Logger logger = LoggerFactory.getLogger(Foo.class);
...
public void start(Level logLevel) {
LoggerSetup.get().setup(logLevel);
logger.info("My service is starting...");
...
}
...
}
```
`org.enso.logging.service.LoggingSetupHelper` class was introduced to help with
the most common use cases - establishing a file-based logging in the Enso's
dedicated directories or connecting to an existing logging server once it starts
accepting connections. That is why services don't call `LoggerSetup` directly
but instead provide a service-specific implementation of
`org.enso.logging.service.LoggingSetupHelper`. `LoggingSetupHelper` and
`LoggerSetup` provide `teardown` methods to properly dispose of log events.
### Log Masking
Logs should not contain personally identifiable information (PII). The following
is considered PII:
- User code
- Values of executed expressions
- Values of user environment variables. Although variable names are not
considered PII and can be logged.
- File paths inside the user project directory. System and distribution paths
and a path to the user project can be logged.
Project logging library implements masking of PII. To utilize it
1. Logged object should implement an interface that defines custom log-string
representation of this object
2. The logging should be performed by supplying a template string with `{}`
placeholders, and the arguments
```scala
log.debug("Created {} at [{}].", obj, path)
```
String interpolation in log statements `s"Created $obj"` should be avoided
because it uses default `toString` implementation and can leak critical
information even if the object implements custom interface for masked logging.
### Logging in Tests
The Logging Service provides a helper function `TestLogger.gatherLogs` that will
execute the closure and collect all logs reported in the specified class. That
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 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 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");
}
}
```
## 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");
}
}
}
```
## Log an exception
```
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class HelloWorld {
public static void main(String[] args) {
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.