semantic_logger 5.0.0 → 5.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +9 -0
- data/Rakefile +29 -0
- data/docs/api.md +480 -0
- data/docs/appenders.md +990 -0
- data/docs/config.md +605 -0
- data/docs/index.md +197 -0
- data/docs/log.md +108 -0
- data/docs/metrics.md +168 -0
- data/docs/operations.md +400 -0
- data/docs/rails.md +816 -0
- data/docs/security.md +119 -0
- data/docs/testing.md +285 -0
- data/docs/upgrading.md +409 -0
- data/lib/semantic_logger/appender/cloudwatch_logs.rb +4 -2
- data/lib/semantic_logger/appender/elasticsearch.rb +2 -5
- data/lib/semantic_logger/appender/elasticsearch_base.rb +17 -17
- data/lib/semantic_logger/appender/elasticsearch_http.rb +14 -7
- data/lib/semantic_logger/appender/file.rb +5 -0
- data/lib/semantic_logger/appender/new_relic.rb +6 -0
- data/lib/semantic_logger/appender/new_relic_logs.rb +2 -1
- data/lib/semantic_logger/appender/open_telemetry.rb +2 -1
- data/lib/semantic_logger/appender/opensearch.rb +2 -1
- data/lib/semantic_logger/appender/rabbitmq.rb +4 -3
- data/lib/semantic_logger/appender/sentry.rb +5 -0
- data/lib/semantic_logger/appender/syslog.rb +6 -3
- data/lib/semantic_logger/appender/tcp.rb +4 -3
- data/lib/semantic_logger/appender/wrapper.rb +2 -1
- data/lib/semantic_logger/appender.rb +6 -3
- data/lib/semantic_logger/appenders.rb +2 -1
- data/lib/semantic_logger/base.rb +0 -2
- data/lib/semantic_logger/formatters/color.rb +5 -2
- data/lib/semantic_logger/formatters/default.rb +7 -2
- data/lib/semantic_logger/formatters/logfmt.rb +4 -2
- data/lib/semantic_logger/formatters/pattern.rb +3 -1
- data/lib/semantic_logger/formatters/syslog.rb +2 -1
- data/lib/semantic_logger/formatters/syslog_cee.rb +2 -1
- data/lib/semantic_logger/metric/signalfx.rb +4 -2
- data/lib/semantic_logger/queue_processor.rb +5 -2
- data/lib/semantic_logger/reporters/minitest.rb +4 -4
- data/lib/semantic_logger/test/capture_log_events.rb +1 -1
- data/lib/semantic_logger/version.rb +1 -1
- metadata +14 -3
data/docs/config.md
ADDED
|
@@ -0,0 +1,605 @@
|
|
|
1
|
+
---
|
|
2
|
+
layout: default
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
## Configuration
|
|
6
|
+
{:.no_toc}
|
|
7
|
+
|
|
8
|
+
**Contents**
|
|
9
|
+
|
|
10
|
+
* TOC
|
|
11
|
+
{:toc}
|
|
12
|
+
|
|
13
|
+
Semantic Logger is configured once, when your application starts, by setting a few global values
|
|
14
|
+
and adding one or more appenders (destinations). This page walks through that configuration from
|
|
15
|
+
the simplest, most common settings at the top to formatter and appender customization further down.
|
|
16
|
+
|
|
17
|
+
Production concerns such as process forking, performance tuning, signals, and log rotation are
|
|
18
|
+
covered separately in [Operations](operations.html).
|
|
19
|
+
|
|
20
|
+
If you are using Rails, most of this is handled for you by the companion gem
|
|
21
|
+
[rails_semantic_logger](rails.html); configure it through `config.semantic_logger` and
|
|
22
|
+
`config.rails_semantic_logger` instead.
|
|
23
|
+
|
|
24
|
+
A minimal configuration looks like this:
|
|
25
|
+
|
|
26
|
+
~~~ruby
|
|
27
|
+
require "semantic_logger"
|
|
28
|
+
|
|
29
|
+
# 1. Choose the lowest level to log.
|
|
30
|
+
SemanticLogger.default_level = :info
|
|
31
|
+
|
|
32
|
+
# 2. Add at least one destination.
|
|
33
|
+
SemanticLogger.add_appender(io: $stdout, formatter: :color)
|
|
34
|
+
|
|
35
|
+
# 3. Get a logger and use it.
|
|
36
|
+
logger = SemanticLogger["MyApp"]
|
|
37
|
+
logger.info("Ready")
|
|
38
|
+
~~~
|
|
39
|
+
|
|
40
|
+
The sections below explain each piece, and everything you can tune around it.
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## Global settings
|
|
45
|
+
|
|
46
|
+
These module level settings apply to the whole process. Set them once, before or just after adding
|
|
47
|
+
your appenders.
|
|
48
|
+
|
|
49
|
+
### Default log level
|
|
50
|
+
|
|
51
|
+
Semantic Logger logs `:info` and above by default. The levels, from most detail to least, are:
|
|
52
|
+
|
|
53
|
+
:trace, :debug, :info, :warn, :error, :fatal
|
|
54
|
+
|
|
55
|
+
Setting the level to `:debug` includes `:info`, `:warn`, `:error`, and `:fatal`, but not `:trace`.
|
|
56
|
+
To log everything, set the global default to `:trace`:
|
|
57
|
+
|
|
58
|
+
~~~ruby
|
|
59
|
+
SemanticLogger.default_level = :trace
|
|
60
|
+
~~~
|
|
61
|
+
|
|
62
|
+
Every logger and appender uses this global default unless it has been given its own level. Once a
|
|
63
|
+
logger or appender has an explicit level, changing `SemanticLogger.default_level` no longer affects
|
|
64
|
+
it. The global default can also be changed at runtime (see [Signals](operations.html#linux-signals)
|
|
65
|
+
for changing it in a running process without a restart):
|
|
66
|
+
|
|
67
|
+
~~~ruby
|
|
68
|
+
SemanticLogger.default_level = :debug
|
|
69
|
+
~~~
|
|
70
|
+
|
|
71
|
+
### Application, environment, and host name
|
|
72
|
+
|
|
73
|
+
Semantic Logger can include the application name, environment, and host name in every log entry.
|
|
74
|
+
Not every appender uses these fields, but structured appenders (JSON, Elasticsearch, Splunk, and so
|
|
75
|
+
on) and centralized logging systems rely on them to tell apart logs coming from different
|
|
76
|
+
applications and servers:
|
|
77
|
+
|
|
78
|
+
~~~ruby
|
|
79
|
+
SemanticLogger.application = "my_app"
|
|
80
|
+
SemanticLogger.environment = "production"
|
|
81
|
+
SemanticLogger.host = "web-server-1"
|
|
82
|
+
~~~
|
|
83
|
+
|
|
84
|
+
When not set explicitly, these default to:
|
|
85
|
+
|
|
86
|
+
- `application`: the `SEMANTIC_LOGGER_APP` environment variable, otherwise `"Semantic Logger"`.
|
|
87
|
+
- `environment`: the first of `SEMANTIC_LOGGER_ENV`, `RAILS_ENV`, or `RACK_ENV` that is set.
|
|
88
|
+
- `host`: the machine's host name.
|
|
89
|
+
|
|
90
|
+
Each value can also be overridden for a single appender by passing `application:`, `environment:`,
|
|
91
|
+
or `host:` to `add_appender` (see [Per-appender settings](#per-appender-settings)).
|
|
92
|
+
|
|
93
|
+
### Capturing backtraces
|
|
94
|
+
|
|
95
|
+
Semantic Logger can capture the file name and line number where each log entry was created, include
|
|
96
|
+
it in the output, and forward it to error services such as Bugsnag.
|
|
97
|
+
|
|
98
|
+
Capturing a backtrace is expensive, so it is controlled by its own level, which defaults to
|
|
99
|
+
`:error`. Only entries at this level or higher capture a backtrace:
|
|
100
|
+
|
|
101
|
+
~~~ruby
|
|
102
|
+
# Capture backtraces for :error and :fatal entries (the default)
|
|
103
|
+
SemanticLogger.backtrace_level = :error
|
|
104
|
+
~~~
|
|
105
|
+
|
|
106
|
+
To capture a backtrace for every entry, set it to `:trace`. To turn backtrace capture off entirely,
|
|
107
|
+
set it to `nil`. It is strongly recommended to leave this at `:error` or higher in production.
|
|
108
|
+
|
|
109
|
+
### Caching loggers
|
|
110
|
+
|
|
111
|
+
By default `SemanticLogger[...]` returns a brand new logger instance on every call. Enable logger
|
|
112
|
+
caching to have a single shared logger returned per class:
|
|
113
|
+
|
|
114
|
+
~~~ruby
|
|
115
|
+
SemanticLogger.cache_loggers = true
|
|
116
|
+
|
|
117
|
+
SemanticLogger[MyClass].equal?(SemanticLogger[MyClass]) # => true
|
|
118
|
+
~~~
|
|
119
|
+
|
|
120
|
+
This makes it possible to obtain a logger once and later change its level (or filter) so that every
|
|
121
|
+
holder of that logger sees the change:
|
|
122
|
+
|
|
123
|
+
~~~ruby
|
|
124
|
+
SemanticLogger[MyClass].level = :debug
|
|
125
|
+
~~~
|
|
126
|
+
|
|
127
|
+
Notes:
|
|
128
|
+
|
|
129
|
+
- Caching is opt-in and disabled by default.
|
|
130
|
+
- Only Classes and Modules are cached. A String always returns a new instance, since string call
|
|
131
|
+
sites commonly want an independent logger (for example to set a different level per call site).
|
|
132
|
+
- Anonymous classes (those without a name) are never cached.
|
|
133
|
+
- With caching enabled, `SemanticLogger[MyClass]` and the `SemanticLogger::Loggable` mixin's
|
|
134
|
+
`MyClass.logger` return the same instance.
|
|
135
|
+
- Setting `SemanticLogger.cache_loggers = false` clears the cache. It can also be cleared explicitly
|
|
136
|
+
with `SemanticLogger.clear_logger_cache`, for example after redefining a class.
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
## Per-appender settings
|
|
141
|
+
|
|
142
|
+
Each destination is added with `SemanticLogger.add_appender`. The full catalogue of destinations,
|
|
143
|
+
and the options specific to each, is in [Appenders](appenders.html). In addition to its own
|
|
144
|
+
settings, almost every appender accepts these common options:
|
|
145
|
+
|
|
146
|
+
| Option | Description |
|
|
147
|
+
|--------|-------------|
|
|
148
|
+
| `level` | Only write entries at this level or higher to this appender. Defaults to `SemanticLogger.default_level`. |
|
|
149
|
+
| `formatter` | How to format the output, for example `:default`, `:color`, or `:json`. See [Custom formatters](#custom-formatters). |
|
|
150
|
+
| `filter` | A `Regexp` or `Proc` selecting which entries this appender accepts. See [Filtering](#filtering). |
|
|
151
|
+
| `application`, `environment`, `host` | Override the global values for this appender only. |
|
|
152
|
+
|
|
153
|
+
A per-appender `level` lets each destination keep a different subset of the logs. For example, keep
|
|
154
|
+
a full trace log and a separate warnings-only log:
|
|
155
|
+
|
|
156
|
+
~~~ruby
|
|
157
|
+
require "semantic_logger"
|
|
158
|
+
|
|
159
|
+
SemanticLogger.default_level = :info
|
|
160
|
+
|
|
161
|
+
# Everything at :trace and above:
|
|
162
|
+
SemanticLogger.add_appender(file_name: "log/trace.log", level: :trace)
|
|
163
|
+
|
|
164
|
+
# Only warnings and above:
|
|
165
|
+
SemanticLogger.add_appender(file_name: "log/warnings.log", level: :warn)
|
|
166
|
+
~~~
|
|
167
|
+
|
|
168
|
+
---
|
|
169
|
+
|
|
170
|
+
## Filtering
|
|
171
|
+
|
|
172
|
+
A filter decides, for each log entry, whether it should be written. Use a filter to:
|
|
173
|
+
|
|
174
|
+
* Quiet a noisy library without modifying its code.
|
|
175
|
+
* Send only certain messages to a particular destination (for example, a dedicated audit file).
|
|
176
|
+
* Strip sensitive data out of a message before it is written.
|
|
177
|
+
|
|
178
|
+
### A filter is one of three things
|
|
179
|
+
|
|
180
|
+
1. **A regular expression.** It is matched against the **class name** of the logger (the value you
|
|
181
|
+
passed to `SemanticLogger["..."]`). The entry is kept only if the class name matches.
|
|
182
|
+
|
|
183
|
+
```ruby
|
|
184
|
+
filter: /MyClass/
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
2. **A Proc (or lambda).** It receives the whole log event and must return `true` to **keep** the
|
|
188
|
+
entry. Returning anything else (`false`, `nil`, a string, ...) **drops** it.
|
|
189
|
+
|
|
190
|
+
```ruby
|
|
191
|
+
filter: ->(log) { log.message !~ /heartbeat/ }
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
3. **A module or object that responds to `.call`.** Same contract as a Proc, but in a named,
|
|
195
|
+
reusable, testable place. Reach for this when the logic grows beyond a one-liner.
|
|
196
|
+
|
|
197
|
+
```ruby
|
|
198
|
+
module ExcludeHealthChecks
|
|
199
|
+
def self.call(log)
|
|
200
|
+
!log.message.to_s.start_with?("GET /health")
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
filter: ExcludeHealthChecks
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
> **The most common gotcha:** a Proc or `.call` filter must return **exactly `true`** to keep an
|
|
208
|
+
> entry. `0`, `"yes"`, or any truthy-but-not-`true` value will silently drop the entry. When in
|
|
209
|
+
> doubt, end the filter with an explicit boolean expression.
|
|
210
|
+
|
|
211
|
+
The log event passed to a Proc or module filter carries every attribute of the message: `name`,
|
|
212
|
+
`message`, `level`, `payload`, `tags`, `named_tags`, `duration`, `exception`, and more. The full
|
|
213
|
+
list is in [Log Event](log.html).
|
|
214
|
+
|
|
215
|
+
### Where a filter can be attached
|
|
216
|
+
|
|
217
|
+
* **On an appender** (a destination). The filter affects only what *that one destination* writes.
|
|
218
|
+
Use this to give one file or service a curated subset of the logs.
|
|
219
|
+
* **On a logger instance.** The filter affects *every* appender, but only for entries coming
|
|
220
|
+
*through that one logger*. Use this to quiet a single class or library across all destinations.
|
|
221
|
+
|
|
222
|
+
### Example: appender filter with a regular expression
|
|
223
|
+
|
|
224
|
+
Keep a full log in `development.log`, and additionally maintain a `my_class.log` that contains
|
|
225
|
+
**only** the messages from `MyClass`:
|
|
226
|
+
|
|
227
|
+
```ruby
|
|
228
|
+
require "semantic_logger"
|
|
229
|
+
|
|
230
|
+
# Step 1: a catch-all appender that records everything.
|
|
231
|
+
SemanticLogger.add_appender(file_name: "development.log")
|
|
232
|
+
|
|
233
|
+
# Step 2: a second appender that only keeps entries whose logger class name matches /MyClass/.
|
|
234
|
+
SemanticLogger.add_appender(file_name: "my_class.log", filter: /MyClass/)
|
|
235
|
+
|
|
236
|
+
# Step 3: log from two different classes.
|
|
237
|
+
SemanticLogger["MyClass"].info "Written to BOTH development.log and my_class.log"
|
|
238
|
+
SemanticLogger["OtherClass"].info "Written ONLY to development.log"
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
You can also set the filter after the appender exists:
|
|
242
|
+
|
|
243
|
+
```ruby
|
|
244
|
+
appender = SemanticLogger.add_appender(file_name: "my_class.log")
|
|
245
|
+
appender.filter = /MyClass/
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
### Example: appender filter with a Proc
|
|
249
|
+
|
|
250
|
+
A `summary.log` that contains everything **except** the (very chatty) messages from `MyClass`:
|
|
251
|
+
|
|
252
|
+
```ruby
|
|
253
|
+
SemanticLogger.add_appender(
|
|
254
|
+
file_name: "summary.log",
|
|
255
|
+
# Keep the entry (return true) unless it came from MyClass.
|
|
256
|
+
filter: ->(log) { log.name != "MyClass" }
|
|
257
|
+
)
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
Because the Proc receives the whole log event, you can filter on anything:
|
|
261
|
+
|
|
262
|
+
```ruby
|
|
263
|
+
# Drop entries below a duration threshold (only keep slow measure calls).
|
|
264
|
+
filter: ->(log) { log.duration.to_f >= 100 }
|
|
265
|
+
|
|
266
|
+
# Drop a specific noisy message regardless of which class logged it.
|
|
267
|
+
filter: ->(log) { log.message !~ /\Aheartbeat/ }
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
### Example: rewriting a message inside a filter
|
|
271
|
+
|
|
272
|
+
A filter can also **modify** the log event before it is written, as long as it still returns `true`
|
|
273
|
+
so the (now edited) entry is kept. This is handy for redacting sensitive data. Resque, for example,
|
|
274
|
+
logs the entire job payload, which may contain private information:
|
|
275
|
+
|
|
276
|
+
```ruby
|
|
277
|
+
Resque.logger.filter = ->(log) do
|
|
278
|
+
if log.name == "Resque" && (match = log.message.to_s.match(/\A(got|done): /))
|
|
279
|
+
log.message = match[1] # replace the full payload with just the action
|
|
280
|
+
end
|
|
281
|
+
true # always return true so the (edited) message is still logged
|
|
282
|
+
end
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
### Example: logger filter to quiet a library
|
|
286
|
+
|
|
287
|
+
When a library lets you replace its logger, attach a filter to a logger instance to suppress its
|
|
288
|
+
noise everywhere it is logged, without touching any appender:
|
|
289
|
+
|
|
290
|
+
```ruby
|
|
291
|
+
logger = SemanticLogger[Resque]
|
|
292
|
+
logger.filter = ->(log) { log.message !~ /\A\*\*\* Checking/ }
|
|
293
|
+
Resque.logger = logger
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
---
|
|
297
|
+
|
|
298
|
+
## Custom formatters
|
|
299
|
+
|
|
300
|
+
The formatter turns each log event into the text or JSON that an appender writes. Pass a
|
|
301
|
+
`formatter:` when adding an appender. The simplest options are the built-in formatters selected by
|
|
302
|
+
name (`:default`, `:color`, `:json`, `:logfmt`, and others; see [Appenders](appenders.html)). For
|
|
303
|
+
anything beyond those, you have three choices, in increasing order of effort: a pattern string, a
|
|
304
|
+
Proc, or a formatter class.
|
|
305
|
+
|
|
306
|
+
### Pattern formatter
|
|
307
|
+
|
|
308
|
+
For simple layout changes there is no need to write any code. The built-in `:pattern` formatter
|
|
309
|
+
builds each log line from a pattern string. Placeholders use the form `%{directive}`; to emit a
|
|
310
|
+
literal `%{...}`, escape it as `%%{...}`.
|
|
311
|
+
|
|
312
|
+
~~~ruby
|
|
313
|
+
# A message-only format on stdout, for end users:
|
|
314
|
+
SemanticLogger.add_appender(
|
|
315
|
+
io: $stdout,
|
|
316
|
+
formatter: {pattern: {pattern: "%{message}"}}
|
|
317
|
+
)
|
|
318
|
+
|
|
319
|
+
# A timestamped format to a file:
|
|
320
|
+
SemanticLogger.add_appender(
|
|
321
|
+
file_name: "application.log",
|
|
322
|
+
formatter: {pattern: {pattern: "%{time} %{level} %{name} -- %{message}"}}
|
|
323
|
+
)
|
|
324
|
+
~~~
|
|
325
|
+
|
|
326
|
+
The pattern is parsed once when the appender is created, so formatting every entry is fast. An
|
|
327
|
+
unknown directive (or an argument supplied to a directive that does not take one) raises an error
|
|
328
|
+
immediately, when the appender is configured.
|
|
329
|
+
|
|
330
|
+
Available directives:
|
|
331
|
+
|
|
332
|
+
| Directive | Description |
|
|
333
|
+
|------------------------|----------------------------------------------------------|
|
|
334
|
+
| `%{time}` | Formatted timestamp. A strftime format may be supplied, e.g. `%{time:%Y-%m-%dT%H:%M:%S.%6N}`. |
|
|
335
|
+
| `%{level}` | Full level name, e.g. `debug`. |
|
|
336
|
+
| `%{level_short}` | Single character level, e.g. `D`. |
|
|
337
|
+
| `%{name}` | Logger / class name. |
|
|
338
|
+
| `%{message}` | Log message. |
|
|
339
|
+
| `%{payload}` | Payload rendered as a string. |
|
|
340
|
+
| `%{exception_class}` | Class of the logged exception, e.g. `RuntimeError`. |
|
|
341
|
+
| `%{exception_message}` | Message of the logged exception. |
|
|
342
|
+
| `%{backtrace}` | Backtrace of the logged exception. |
|
|
343
|
+
| `%{duration}` | Human readable duration, e.g. `1.2ms`. |
|
|
344
|
+
| `%{duration_ms}` | Duration in milliseconds (numeric). |
|
|
345
|
+
| `%{thread_name}` | Name of the thread that logged the message. |
|
|
346
|
+
| `%{pid}` | Process id. |
|
|
347
|
+
| `%{file_name}` | Ruby file name that logged the message, e.g. `app.rb`. |
|
|
348
|
+
| `%{line}` | Line number within the Ruby file, e.g. `42`. |
|
|
349
|
+
| `%{tags}` | Tags, comma separated. |
|
|
350
|
+
| `%{named_tags}` | All named tags. One tag with `%{named_tags:request_id}`. |
|
|
351
|
+
| `%{host}` | Host name. |
|
|
352
|
+
| `%{application}` | Application name. |
|
|
353
|
+
| `%{environment}` | Environment name. |
|
|
354
|
+
|
|
355
|
+
When the pattern is omitted it defaults to a layout similar to the default text formatter:
|
|
356
|
+
`%{time} %{level} [%{pid}:%{thread_name}] %{name} -- %{message}`.
|
|
357
|
+
|
|
358
|
+
#### Example: a custom timestamp format
|
|
359
|
+
|
|
360
|
+
The `%{time}` directive accepts a [strftime](https://ruby-doc.org/core/Time.html#method-i-strftime)
|
|
361
|
+
format string, applied directly to the log time:
|
|
362
|
+
|
|
363
|
+
~~~ruby
|
|
364
|
+
SemanticLogger.add_appender(
|
|
365
|
+
io: $stdout,
|
|
366
|
+
formatter: {pattern: {pattern: "%{time:%Y-%m-%dT%H:%M:%S.%6N%z} %{level} -- %{message}"}}
|
|
367
|
+
)
|
|
368
|
+
# => 2017-04-05T01:05:52.868286+0000 info -- Hello World
|
|
369
|
+
~~~
|
|
370
|
+
|
|
371
|
+
#### Example: include a request id from the named tags
|
|
372
|
+
|
|
373
|
+
Named tags (set with `SemanticLogger.tagged(request_id: "...")`) can be pulled out individually with
|
|
374
|
+
`%{named_tags:key}`:
|
|
375
|
+
|
|
376
|
+
~~~ruby
|
|
377
|
+
SemanticLogger.add_appender(
|
|
378
|
+
io: $stdout,
|
|
379
|
+
formatter: {pattern: {pattern: "%{time} %{level} [%{named_tags:request_id}] %{name} -- %{message}"}}
|
|
380
|
+
)
|
|
381
|
+
~~~
|
|
382
|
+
|
|
383
|
+
### Formatter as a Proc
|
|
384
|
+
|
|
385
|
+
For full control with minimal ceremony, supply a block. It is called with two arguments, the log
|
|
386
|
+
event and the appender; a block may accept just the log event and ignore the appender. For the
|
|
387
|
+
structure of the log event, see [Log Event](log.html).
|
|
388
|
+
|
|
389
|
+
~~~ruby
|
|
390
|
+
formatter = proc do |log|
|
|
391
|
+
# This formatter just returns the log event as a string
|
|
392
|
+
log.inspect
|
|
393
|
+
end
|
|
394
|
+
SemanticLogger.add_appender(io: $stdout, formatter: formatter)
|
|
395
|
+
~~~
|
|
396
|
+
|
|
397
|
+
### Formatter as a class
|
|
398
|
+
|
|
399
|
+
When the formatting logic is substantial or reused, subclass one of the built-in formatters and
|
|
400
|
+
override just the methods you want to change.
|
|
401
|
+
|
|
402
|
+
Override the default text formatter to upper-case the level name:
|
|
403
|
+
|
|
404
|
+
~~~ruby
|
|
405
|
+
class MyFormatter < SemanticLogger::Formatters::Default
|
|
406
|
+
# Return the complete log level name in uppercase
|
|
407
|
+
def level
|
|
408
|
+
log.level.upcase
|
|
409
|
+
end
|
|
410
|
+
end
|
|
411
|
+
|
|
412
|
+
SemanticLogger.add_appender(file_name: "development.log", formatter: MyFormatter.new)
|
|
413
|
+
~~~
|
|
414
|
+
|
|
415
|
+
The colorized formatter can be customized the same way, keeping its color codes:
|
|
416
|
+
|
|
417
|
+
~~~ruby
|
|
418
|
+
class MyFormatter < SemanticLogger::Formatters::Color
|
|
419
|
+
def level
|
|
420
|
+
"#{color}#{log.level.upcase}#{color_map.clear}"
|
|
421
|
+
end
|
|
422
|
+
end
|
|
423
|
+
|
|
424
|
+
SemanticLogger.add_appender(file_name: "development.log", formatter: MyFormatter.new)
|
|
425
|
+
~~~
|
|
426
|
+
|
|
427
|
+
A common request is to leave out the process id, for example when running a single process per
|
|
428
|
+
container (where the pid is always 1):
|
|
429
|
+
|
|
430
|
+
~~~ruby
|
|
431
|
+
class NoPidFormatter < SemanticLogger::Formatters::Default
|
|
432
|
+
# Leave out the pid
|
|
433
|
+
def pid
|
|
434
|
+
end
|
|
435
|
+
end
|
|
436
|
+
|
|
437
|
+
SemanticLogger.add_appender(file_name: "development.log", formatter: NoPidFormatter.new)
|
|
438
|
+
~~~
|
|
439
|
+
|
|
440
|
+
See
|
|
441
|
+
[SemanticLogger::Formatters::Default](https://github.com/reidmorrison/semantic_logger/blob/main/lib/semantic_logger/formatters/default.rb)
|
|
442
|
+
and
|
|
443
|
+
[SemanticLogger::Formatters::Color](https://github.com/reidmorrison/semantic_logger/blob/main/lib/semantic_logger/formatters/color.rb)
|
|
444
|
+
for all the methods that can be overridden.
|
|
445
|
+
|
|
446
|
+
To replace the formatter on an appender that is already installed, for example in a Rails app:
|
|
447
|
+
|
|
448
|
+
~~~ruby
|
|
449
|
+
# Find the file appender and replace its formatter:
|
|
450
|
+
appender = SemanticLogger.appenders.find { |a| a.is_a?(SemanticLogger::Appender::File) }
|
|
451
|
+
appender.formatter = MyFormatter.new
|
|
452
|
+
~~~
|
|
453
|
+
|
|
454
|
+
### Escaping control characters
|
|
455
|
+
|
|
456
|
+
By design, the human readable text formatters (`:default` and `:color`) write log messages exactly
|
|
457
|
+
as supplied, including newlines and ANSI color codes. This is intentional and useful: multi-line
|
|
458
|
+
messages and colorized output make local logs easier to read.
|
|
459
|
+
|
|
460
|
+
When log messages can contain untrusted, attacker-controlled data (for example a user name, request
|
|
461
|
+
parameter, or `User-Agent` header), those same characters can be abused. A newline can forge an
|
|
462
|
+
additional, fake log entry ("log forging"), and an ANSI escape sequence can spoof or hide terminal
|
|
463
|
+
output when the log is viewed in a terminal.
|
|
464
|
+
|
|
465
|
+
Structured formatters such as `:json` are not affected, because JSON encoding always escapes control
|
|
466
|
+
characters. They are the recommended choice when forwarding logs that may contain untrusted data to
|
|
467
|
+
a centralized logging system.
|
|
468
|
+
|
|
469
|
+
For the text formatters, enable the `escape_control_chars` option to replace control characters in
|
|
470
|
+
untrusted log data (the message, tags, named tags, and exception message) with a printable, escaped
|
|
471
|
+
form. For example a newline is written as `\n` and the ANSI escape as `\e`. The option is **disabled
|
|
472
|
+
by default** to preserve the existing human readable output:
|
|
473
|
+
|
|
474
|
+
```ruby
|
|
475
|
+
# Text appender that escapes control characters in untrusted data:
|
|
476
|
+
SemanticLogger.add_appender(file_name: "production.log", formatter: {default: {escape_control_chars: true}})
|
|
477
|
+
|
|
478
|
+
# Colorized appender, still escaping control characters in the logged data
|
|
479
|
+
# (the formatter's own color codes are preserved):
|
|
480
|
+
SemanticLogger.add_appender(io: $stdout, formatter: {color: {escape_control_chars: true}})
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
The option only escapes the control characters in the logged data; it does not touch the formatter's
|
|
484
|
+
own decoration, so the `:color` formatter keeps emitting its color codes. Multi-line exception
|
|
485
|
+
backtraces are also preserved, since they are generated by Semantic Logger rather than supplied as
|
|
486
|
+
log data. The pattern formatter supports the same option (`{pattern: {pattern: "...",
|
|
487
|
+
escape_control_chars: true}}`), and the syslog, TCP, and UDP appenders enable it where appropriate;
|
|
488
|
+
see [Appenders](appenders.html).
|
|
489
|
+
|
|
490
|
+
---
|
|
491
|
+
|
|
492
|
+
## Custom appenders
|
|
493
|
+
|
|
494
|
+
To write your own log appender it should meet the following requirements:
|
|
495
|
+
|
|
496
|
+
* Inherit from `SemanticLogger::Subscriber`.
|
|
497
|
+
* In the initializer, connect to the resource being logged to.
|
|
498
|
+
* Implement `#log(log)`, which writes to the relevant resource.
|
|
499
|
+
* Implement `#flush` if the resource can be flushed.
|
|
500
|
+
* Write a test for the new appender.
|
|
501
|
+
|
|
502
|
+
The `#log` method receives the log event as its parameter. For its structure, see
|
|
503
|
+
[Log Event](log.html).
|
|
504
|
+
|
|
505
|
+
Basic outline for an appender:
|
|
506
|
+
|
|
507
|
+
~~~ruby
|
|
508
|
+
require "semantic_logger"
|
|
509
|
+
|
|
510
|
+
class SimpleAppender < SemanticLogger::Subscriber
|
|
511
|
+
attr_reader :host
|
|
512
|
+
|
|
513
|
+
# Add additional arguments to the initializer while supporting all existing ones.
|
|
514
|
+
def initialize(host: host, **args, &block)
|
|
515
|
+
@host = host
|
|
516
|
+
super(**args, &block)
|
|
517
|
+
end
|
|
518
|
+
|
|
519
|
+
# Display the log struct and the text formatted output
|
|
520
|
+
def log(log)
|
|
521
|
+
# Display the raw log structure
|
|
522
|
+
p log
|
|
523
|
+
|
|
524
|
+
# Display the formatted output
|
|
525
|
+
puts formatter.call(log, self)
|
|
526
|
+
end
|
|
527
|
+
|
|
528
|
+
# Optional
|
|
529
|
+
def flush
|
|
530
|
+
puts "Flush :)"
|
|
531
|
+
end
|
|
532
|
+
|
|
533
|
+
# Optional
|
|
534
|
+
def close
|
|
535
|
+
puts "Closing :)"
|
|
536
|
+
end
|
|
537
|
+
end
|
|
538
|
+
~~~
|
|
539
|
+
|
|
540
|
+
Register the appender by passing an instance to `add_appender`:
|
|
541
|
+
|
|
542
|
+
~~~ruby
|
|
543
|
+
SemanticLogger.add_appender(appender: SimpleAppender.new)
|
|
544
|
+
~~~
|
|
545
|
+
|
|
546
|
+
Look at the
|
|
547
|
+
[existing appenders](https://github.com/reidmorrison/semantic_logger/tree/main/lib/semantic_logger/appender)
|
|
548
|
+
for good examples. To have a custom appender included in the standard list, submit it with complete
|
|
549
|
+
working tests; see the
|
|
550
|
+
[Graylog Appender Test](https://github.com/reidmorrison/semantic_logger/blob/main/test/appender/graylog_test.rb)
|
|
551
|
+
for an example.
|
|
552
|
+
|
|
553
|
+
---
|
|
554
|
+
|
|
555
|
+
## Managing appenders and lifecycle
|
|
556
|
+
|
|
557
|
+
### Adding and removing appenders
|
|
558
|
+
|
|
559
|
+
`SemanticLogger.add_appender` returns the appender it created, which can be used to remove that
|
|
560
|
+
appender later:
|
|
561
|
+
|
|
562
|
+
~~~ruby
|
|
563
|
+
appender = SemanticLogger.add_appender(file_name: "development.log")
|
|
564
|
+
|
|
565
|
+
# ... later
|
|
566
|
+
SemanticLogger.remove_appender(appender)
|
|
567
|
+
~~~
|
|
568
|
+
|
|
569
|
+
Other appender management methods:
|
|
570
|
+
|
|
571
|
+
~~~ruby
|
|
572
|
+
# The list of currently active appenders
|
|
573
|
+
SemanticLogger.appenders
|
|
574
|
+
|
|
575
|
+
# Remove and close every appender
|
|
576
|
+
SemanticLogger.clear_appenders!
|
|
577
|
+
|
|
578
|
+
# Flush all appenders, then close them ( called automatically at process exit )
|
|
579
|
+
SemanticLogger.close
|
|
580
|
+
~~~
|
|
581
|
+
|
|
582
|
+
### Flushing
|
|
583
|
+
|
|
584
|
+
Semantic Logger automatically flushes all appenders (log files, etc.) when a process exits. The
|
|
585
|
+
`flush` method is not defined on individual logger instances, since there may be many of them. To
|
|
586
|
+
perform a global flush of all appenders and wait for any queued messages to be written:
|
|
587
|
+
|
|
588
|
+
~~~ruby
|
|
589
|
+
SemanticLogger.flush
|
|
590
|
+
~~~
|
|
591
|
+
|
|
592
|
+
### Capturing context with `on_log`
|
|
593
|
+
|
|
594
|
+
Register a block to be called for every log entry, just before it is placed on the queue. The block
|
|
595
|
+
runs inline on the thread that created the entry, so it can capture request-scoped or thread-local
|
|
596
|
+
context that would otherwise be lost once the entry is handed off to the background thread:
|
|
597
|
+
|
|
598
|
+
~~~ruby
|
|
599
|
+
SemanticLogger.on_log do |log|
|
|
600
|
+
log.set_context(:request_id, Thread.current[:request_id])
|
|
601
|
+
end
|
|
602
|
+
~~~
|
|
603
|
+
|
|
604
|
+
Because these callbacks run on the application's own thread, keep them fast. The captured context is
|
|
605
|
+
available to appenders and formatters as `log.context`.
|