semantic_logger 5.1.0 → 5.2.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 +13 -12
- data/Rakefile +15 -4
- data/docs/api.md +37 -11
- data/docs/appenders.md +123 -39
- data/docs/config.md +115 -13
- data/docs/index.md +31 -5
- data/docs/log.md +19 -11
- data/docs/metrics.md +5 -2
- data/docs/operations.md +63 -17
- data/docs/rails.md +77 -3
- data/docs/security.md +183 -16
- data/docs/testing.md +5 -3
- data/docs/upgrading.md +295 -7
- data/lib/semantic_logger/appender/async.rb +18 -5
- data/lib/semantic_logger/appender/bugsnag.rb +1 -1
- data/lib/semantic_logger/appender/cloudwatch_logs.rb +122 -27
- data/lib/semantic_logger/appender/elasticsearch_base.rb +31 -6
- data/lib/semantic_logger/appender/elasticsearch_http.rb +1 -1
- data/lib/semantic_logger/appender/file.rb +138 -30
- data/lib/semantic_logger/appender/graylog.rb +22 -6
- data/lib/semantic_logger/appender/honeybadger.rb +1 -1
- data/lib/semantic_logger/appender/honeybadger_insights.rb +1 -1
- data/lib/semantic_logger/appender/http.rb +70 -11
- data/lib/semantic_logger/appender/io.rb +1 -1
- data/lib/semantic_logger/appender/kafka.rb +26 -12
- data/lib/semantic_logger/appender/loki.rb +13 -3
- data/lib/semantic_logger/appender/mongodb.rb +20 -1
- data/lib/semantic_logger/appender/new_relic.rb +1 -1
- data/lib/semantic_logger/appender/new_relic_logs.rb +9 -11
- data/lib/semantic_logger/appender/open_telemetry.rb +2 -2
- data/lib/semantic_logger/appender/rabbitmq.rb +6 -1
- data/lib/semantic_logger/appender/sentry.rb +1 -1
- data/lib/semantic_logger/appender/sentry_ruby.rb +91 -17
- data/lib/semantic_logger/appender/splunk.rb +10 -1
- data/lib/semantic_logger/appender/syslog.rb +64 -26
- data/lib/semantic_logger/appender/tcp.rb +6 -1
- data/lib/semantic_logger/appender/udp.rb +11 -1
- data/lib/semantic_logger/appender/wrapper.rb +1 -1
- data/lib/semantic_logger/appenders.rb +9 -1
- data/lib/semantic_logger/base.rb +82 -30
- data/lib/semantic_logger/exception_details.rb +50 -0
- data/lib/semantic_logger/formatters/base.rb +52 -6
- data/lib/semantic_logger/formatters/color.rb +18 -2
- data/lib/semantic_logger/formatters/default.rb +22 -5
- data/lib/semantic_logger/formatters/logfmt.rb +153 -21
- data/lib/semantic_logger/formatters/loki.rb +26 -22
- data/lib/semantic_logger/formatters/one_line.rb +67 -3
- data/lib/semantic_logger/formatters/open_telemetry.rb +16 -17
- data/lib/semantic_logger/formatters/pattern.rb +8 -4
- data/lib/semantic_logger/formatters/raw.rb +4 -5
- data/lib/semantic_logger/formatters/syslog.rb +110 -18
- data/lib/semantic_logger/formatters/syslog_cee.rb +29 -16
- data/lib/semantic_logger/levels.rb +10 -1
- data/lib/semantic_logger/log.rb +183 -37
- data/lib/semantic_logger/loggable.rb +26 -3
- data/lib/semantic_logger/metric/new_relic.rb +1 -1
- data/lib/semantic_logger/metric/signalfx.rb +8 -6
- data/lib/semantic_logger/metric/statsd.rb +15 -7
- data/lib/semantic_logger/queue_processor/request.rb +55 -0
- data/lib/semantic_logger/queue_processor/throttled_counter.rb +46 -0
- data/lib/semantic_logger/queue_processor.rb +165 -66
- data/lib/semantic_logger/semantic_logger.rb +15 -2
- data/lib/semantic_logger/subscriber.rb +96 -10
- data/lib/semantic_logger/sync_processor.rb +11 -0
- data/lib/semantic_logger/test/capture_log_events.rb +8 -5
- data/lib/semantic_logger/utils.rb +244 -16
- data/lib/semantic_logger/version.rb +1 -1
- data/lib/semantic_logger.rb +1 -1
- metadata +21 -5
- data/lib/semantic_logger/core_ext/thread.rb +0 -30
data/docs/config.md
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
---
|
|
2
2
|
layout: default
|
|
3
|
+
title: Configuration
|
|
4
|
+
description: >-
|
|
5
|
+
Configure Semantic Logger once at startup: global settings, per-appender
|
|
6
|
+
levels and formats, filtering, and writing your own formatters and
|
|
7
|
+
appenders.
|
|
3
8
|
---
|
|
4
9
|
|
|
5
|
-
## Configuration
|
|
6
|
-
{:.no_toc}
|
|
7
10
|
|
|
8
11
|
**Contents**
|
|
9
12
|
|
|
@@ -270,8 +273,11 @@ filter: ->(log) { log.message !~ /\Aheartbeat/ }
|
|
|
270
273
|
### Example: rewriting a message inside a filter
|
|
271
274
|
|
|
272
275
|
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
|
|
274
|
-
|
|
276
|
+
so the (now edited) entry is kept. This is handy for redacting sensitive data in a logger's filter,
|
|
277
|
+
as below, or in `on_log` (see [Redacting sensitive data](security.html#redacting-sensitive-data)).
|
|
278
|
+
Do not redact in an appender's filter: every appender writes the same log event, so the appenders
|
|
279
|
+
added before it have already written the data. Resque, for example, logs the entire job payload,
|
|
280
|
+
which may contain private information:
|
|
275
281
|
|
|
276
282
|
```ruby
|
|
277
283
|
Resque.logger.filter = ->(log) do
|
|
@@ -339,6 +345,7 @@ Available directives:
|
|
|
339
345
|
| `%{payload}` | Payload rendered as a string. |
|
|
340
346
|
| `%{exception_class}` | Class of the logged exception, e.g. `RuntimeError`. |
|
|
341
347
|
| `%{exception_message}` | Message of the logged exception. |
|
|
348
|
+
| `%{exception_details}` | Detail Ruby adds to the exception's message, such as did_you_mean suggestions. See [Exception details](#exception-details). |
|
|
342
349
|
| `%{backtrace}` | Backtrace of the logged exception. |
|
|
343
350
|
| `%{duration}` | Human readable duration, e.g. `1.2ms`. |
|
|
344
351
|
| `%{duration_ms}` | Duration in milliseconds (numeric). |
|
|
@@ -467,9 +474,11 @@ characters. They are the recommended choice when forwarding logs that may contai
|
|
|
467
474
|
a centralized logging system.
|
|
468
475
|
|
|
469
476
|
For the text formatters, enable the `escape_control_chars` option to replace control characters in
|
|
470
|
-
untrusted log data (the message, tags, named tags,
|
|
471
|
-
|
|
472
|
-
|
|
477
|
+
untrusted log data (the logger name, thread name, message, payload, tags, named tags, the message,
|
|
478
|
+
details, and backtrace lines of the logged exception, and the message and backtrace lines of each
|
|
479
|
+
exception that caused it) with a printable, escaped form. For example a newline is written as `\n`
|
|
480
|
+
and the ANSI escape as `\e`. The option is **disabled by default** to preserve the existing human
|
|
481
|
+
readable output:
|
|
473
482
|
|
|
474
483
|
```ruby
|
|
475
484
|
# Text appender that escapes control characters in untrusted data:
|
|
@@ -481,11 +490,67 @@ SemanticLogger.add_appender(io: $stdout, formatter: {color: {escape_control_char
|
|
|
481
490
|
```
|
|
482
491
|
|
|
483
492
|
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.
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
493
|
+
own decoration, so the `:color` formatter keeps emitting its color codes. It does render the payload
|
|
494
|
+
without Amazing Print's colors, and on a single line, since the colors and line breaks Amazing Print
|
|
495
|
+
adds cannot be told apart from those in the data. The text formatters still lay out an exception and
|
|
496
|
+
its backtrace over several lines, since that layout is generated by Semantic Logger rather than
|
|
497
|
+
supplied as log data. The `:syslog` formatter, which escapes control characters by
|
|
498
|
+
default, is the exception: it also escapes the newlines between an exception and its backtrace lines,
|
|
499
|
+
so that each entry is a single line. The pattern formatter supports the same option (`{pattern:
|
|
500
|
+
{pattern: "...", escape_control_chars: true}}`), and the syslog, TCP, and UDP appenders enable it
|
|
501
|
+
where appropriate; see [Appenders](appenders.html).
|
|
502
|
+
|
|
503
|
+
### Exception details
|
|
504
|
+
|
|
505
|
+
Since Ruby 3.2, Ruby keeps some of the most useful information about an exception out of its
|
|
506
|
+
`message`, and adds it in `Exception#detailed_message` instead: did_you_mean suggestions, the
|
|
507
|
+
error_highlight snippet that points at the failing call, and any detail an exception class adds by
|
|
508
|
+
overriding `#detailed_message`. Semantic Logger logs that detail, without repeating the class name
|
|
509
|
+
that Ruby also adds, on the line after the exception message:
|
|
510
|
+
|
|
511
|
+
~~~
|
|
512
|
+
2026-10-25 12:00:00.000000 E [12345:1234 orders.rb:12] Orders -- Failed to ship -- Exception: NoMethodError: undefined method 'upcas' for an instance of String
|
|
513
|
+
Did you mean? upcase
|
|
514
|
+
upcase!
|
|
515
|
+
orders.rb:12:in 'Orders#ship'
|
|
516
|
+
...
|
|
517
|
+
~~~
|
|
518
|
+
|
|
519
|
+
The `log_exception_details` formatter option controls it:
|
|
520
|
+
|
|
521
|
+
| Value | Logs |
|
|
522
|
+
|-------|------|
|
|
523
|
+
| `true` | The detail, without the error_highlight source snippet. |
|
|
524
|
+
| `:with_source` | The detail, including the error_highlight source snippet. It is about 100 times slower, because error_highlight re-parses the source file of the failing line, and it copies source code into the log. |
|
|
525
|
+
| `false` | No detail. The exception message is unchanged either way. |
|
|
526
|
+
|
|
527
|
+
It defaults to `true` for the `:default`, `:color`, and `:pattern` formatters, and to `false` for
|
|
528
|
+
every other formatter, including `:one_line` and the syslog formatters, so their output does not
|
|
529
|
+
change. The syslog appender also turns it off for the formatter it uses with a local syslog daemon.
|
|
530
|
+
When enabled, `:one_line` joins the detail onto the same line, after ` -- `. With the `:json`
|
|
531
|
+
formatter and the others built on `:raw`, it adds an `exception.details` field (and `details`
|
|
532
|
+
within each `cause`), which `:logfmt` writes as `exception_details`:
|
|
533
|
+
|
|
534
|
+
```ruby
|
|
535
|
+
# Include the error_highlight source snippet in development:
|
|
536
|
+
SemanticLogger.add_appender(io: $stdout, formatter: {color: {log_exception_details: :with_source}})
|
|
537
|
+
|
|
538
|
+
# Add exception.details to JSON output:
|
|
539
|
+
SemanticLogger.add_appender(file_name: "production.json", formatter: {json: {log_exception_details: true}})
|
|
540
|
+
```
|
|
541
|
+
|
|
542
|
+
The pattern formatter's `%{exception_details}` directive interpolates the detail.
|
|
543
|
+
|
|
544
|
+
Notes:
|
|
545
|
+
|
|
546
|
+
* On Ruby 3.2, a `NameError` or `NoMethodError` calls `inspect` on its receiver every time its
|
|
547
|
+
message is built, and logging the detail builds it twice more. If the receivers of logged
|
|
548
|
+
exceptions are slow to inspect, set `log_exception_details: false`. Ruby 3.3 and later do not
|
|
549
|
+
inspect the receiver.
|
|
550
|
+
* An exception class that overrides `#detailed_message` without accepting extra keyword arguments
|
|
551
|
+
(`def detailed_message(highlight: false)`) cannot be asked to leave out error_highlight's source
|
|
552
|
+
snippet. When error_highlight decorates that exception (a `NameError`, `TypeError`, or
|
|
553
|
+
`ArgumentError`), its detail is only logged with `:with_source`.
|
|
489
554
|
|
|
490
555
|
---
|
|
491
556
|
|
|
@@ -522,7 +587,7 @@ class SimpleAppender < SemanticLogger::Subscriber
|
|
|
522
587
|
p log
|
|
523
588
|
|
|
524
589
|
# Display the formatted output
|
|
525
|
-
puts
|
|
590
|
+
puts format_log(log)
|
|
526
591
|
end
|
|
527
592
|
|
|
528
593
|
# Optional
|
|
@@ -537,6 +602,43 @@ class SimpleAppender < SemanticLogger::Subscriber
|
|
|
537
602
|
end
|
|
538
603
|
~~~
|
|
539
604
|
|
|
605
|
+
When the appender runs asynchronously (`async: true` or batching), an exception raised by `#log`
|
|
606
|
+
is retried after a back-off, on the basis that the destination may be briefly unavailable. An
|
|
607
|
+
exception caused by the content of one message cannot succeed on retry, so that message is discarded
|
|
608
|
+
instead. `#message_error?(exception)` decides which is which. Format each message with
|
|
609
|
+
`format_log(log)` (or a batch with `format_batch(logs)`) rather than `formatter.call`: any error the
|
|
610
|
+
formatter raises is then raised as a `SemanticLogger::Subscriber::FormatError`, which
|
|
611
|
+
`#message_error?` recognizes, since a formatter can only fail because of the content of the message.
|
|
612
|
+
By default it also recognizes the errors raised when serializing a value that cannot be written
|
|
613
|
+
(`JSON::GeneratorError`, `JSON::NestingError`, `EncodingError`, and `FloatDomainError`). Override it
|
|
614
|
+
to recognize errors your appender's client raises for content it cannot write:
|
|
615
|
+
|
|
616
|
+
~~~ruby
|
|
617
|
+
def message_error?(exception)
|
|
618
|
+
exception.is_a?(MyClient::InvalidDocument) || super
|
|
619
|
+
end
|
|
620
|
+
~~~
|
|
621
|
+
|
|
622
|
+
A `SystemStackError` is handled separately: the message is discarded, and since a thread must not
|
|
623
|
+
overflow its stack twice (on macOS that crashes Ruby), the worker carries on in a new thread. Do not
|
|
624
|
+
rescue it in `#log` or `#batch`.
|
|
625
|
+
|
|
626
|
+
An appender that supports batching implements `#batch(logs)`. When `#batch` raises a message error,
|
|
627
|
+
the whole batch is discarded by default, since it may already have written some of the messages.
|
|
628
|
+
If `#batch` writes nothing unless every message can be written, for example because it formats
|
|
629
|
+
them all into one request before sending it, return `true` from `#batch_all_or_nothing?`. The batch
|
|
630
|
+
is then split up and retried, so that only the offending message is discarded:
|
|
631
|
+
|
|
632
|
+
~~~ruby
|
|
633
|
+
def batch(logs)
|
|
634
|
+
client.send_all(logs.map { |log| format_log(log) })
|
|
635
|
+
end
|
|
636
|
+
|
|
637
|
+
def batch_all_or_nothing?
|
|
638
|
+
true
|
|
639
|
+
end
|
|
640
|
+
~~~
|
|
641
|
+
|
|
540
642
|
Register the appender by passing an instance to `add_appender`:
|
|
541
643
|
|
|
542
644
|
~~~ruby
|
data/docs/index.md
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
---
|
|
2
2
|
layout: default
|
|
3
|
+
heading: What is Semantic Logger?
|
|
4
|
+
mermaid: true
|
|
3
5
|
---
|
|
4
6
|
|
|
5
|
-
{:.hero-image}
|
|
6
|
-
|
|
7
|
-
## What is Semantic Logger?
|
|
8
|
-
{:.no_toc}
|
|
9
7
|
|
|
10
8
|
**Contents**
|
|
11
9
|
|
|
@@ -177,7 +175,35 @@ Every logger forwards its log events to a single, shared background thread throu
|
|
|
177
175
|
queue. That thread writes each event to every registered appender (destination) in turn. Because
|
|
178
176
|
the writing happens off to the side, the thread that called `logger.info` returns immediately.
|
|
179
177
|
|
|
180
|
-
|
|
178
|
+
~~~mermaid
|
|
179
|
+
flowchart LR
|
|
180
|
+
subgraph callers["Application threads"]
|
|
181
|
+
direction TB
|
|
182
|
+
A["OrderProcessor<br/>logger.info"]
|
|
183
|
+
B["PaymentGateway<br/>logger.measure_info"]
|
|
184
|
+
C["Rails<br/>logger.error"]
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
Q["In-memory queue<br/>max_queue_size: 10,000"]
|
|
188
|
+
P(["Processor<br/>one background thread"])
|
|
189
|
+
|
|
190
|
+
subgraph appenders["Appenders"]
|
|
191
|
+
direction TB
|
|
192
|
+
D["Screen"]
|
|
193
|
+
E["File"]
|
|
194
|
+
F["Elasticsearch"]
|
|
195
|
+
G["Sentry"]
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
A -->|"enqueue, return"| Q
|
|
199
|
+
B --> Q
|
|
200
|
+
C --> Q
|
|
201
|
+
Q --> P
|
|
202
|
+
P --> D
|
|
203
|
+
P --> E
|
|
204
|
+
P --> F
|
|
205
|
+
P --> G
|
|
206
|
+
~~~
|
|
181
207
|
|
|
182
208
|
This design is why Semantic Logger is both fast and thread safe: log calls from hundreds of
|
|
183
209
|
concurrent threads simply enqueue events, and each appender writes them out sequentially in the
|
data/docs/log.md
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
---
|
|
2
2
|
layout: default
|
|
3
|
+
title: Log Event
|
|
4
|
+
description: >-
|
|
5
|
+
The Log event object that carries every entry through the pipeline: which
|
|
6
|
+
fields are always present, its attributes and helper methods, and how to
|
|
7
|
+
inspect one.
|
|
3
8
|
---
|
|
4
9
|
|
|
5
|
-
## Log Event
|
|
6
|
-
{:.no_toc}
|
|
7
10
|
|
|
8
11
|
**Contents**
|
|
9
12
|
|
|
@@ -22,23 +25,27 @@ You meet the Log event whenever you customize Semantic Logger:
|
|
|
22
25
|
* A **formatter** receives it and turns it into the final output (text, JSON, ...). See [Custom formatters](config.html#custom-formatters).
|
|
23
26
|
* A custom **appender**'s `#log(log)` method receives it and writes it to a destination.
|
|
24
27
|
|
|
25
|
-
In every one of those places you are reading
|
|
28
|
+
In every one of those places you are reading the same object documented here.
|
|
26
29
|
|
|
27
30
|
### The event is mutable
|
|
28
31
|
|
|
29
32
|
The Log event is a plain mutable Ruby object: every attribute below has both a reader and a
|
|
30
|
-
writer.
|
|
31
|
-
|
|
33
|
+
writer. All appenders share one event instance per log call, so change it only before it is handed
|
|
34
|
+
to them: in an [`on_log`](config.html#capturing-context-with-on_log) subscriber, which runs for
|
|
35
|
+
every entry, or in the `filter` of a logger instance, which runs for that logger's entries. For
|
|
36
|
+
example, to add context to every entry:
|
|
32
37
|
|
|
33
38
|
```ruby
|
|
34
|
-
|
|
35
|
-
log.
|
|
36
|
-
true # keep the (now edited) entry
|
|
39
|
+
SemanticLogger.on_log do |log|
|
|
40
|
+
log.set_context(:request_id, Thread.current[:request_id])
|
|
37
41
|
end
|
|
38
42
|
```
|
|
39
43
|
|
|
40
|
-
|
|
41
|
-
|
|
44
|
+
To redact sensitive data, see [Redacting sensitive data](security.html#redacting-sensitive-data).
|
|
45
|
+
|
|
46
|
+
Do not change the event in an appender's `filter` or in a formatter: the appenders that ran before
|
|
47
|
+
it have already written the event, and one that runs asynchronously may be reading it on its own
|
|
48
|
+
thread at the same time.
|
|
42
49
|
|
|
43
50
|
### Which fields are always present
|
|
44
51
|
|
|
@@ -87,7 +94,8 @@ These read-only helpers derive convenient values from the attributes above:
|
|
|
87
94
|
|`level_to_s`|`String`|The level as a single upper-case character, for example `"I"` for `:info`.|
|
|
88
95
|
|`cleansed_message`|`String`|The message with Rails/ANSI color escape codes stripped. Used to keep terminal codes out of structured output.|
|
|
89
96
|
|`each_exception`|Enumerator|Iterates the exception and its nested `cause` chain, yielding `(exception, depth)`.|
|
|
90
|
-
|`backtrace_to_s`|`String`|The exception backtrace as a string, including every exception in the `cause` chain.|
|
|
97
|
+
|`backtrace_to_s`|`String`|The exception backtrace as a string, including every exception in the `cause` chain. With a block, each cause's message and each backtrace line is replaced by the value the block returns, for example to escape it.|
|
|
98
|
+
|`exception_details(exception, source: false)`|`String` or `nil`|The detail Ruby's `Exception#detailed_message` adds to the message of `exception` (this event's exception or one of its causes), such as did_you_mean suggestions, without the class name. `source: true` also includes error_highlight's source snippet. `nil` when there is none. Cached on the event.|
|
|
91
99
|
|`file_name_and_line(short_name = false)`|`[String, Integer]` or `nil`|The file name and line number from the event's backtrace or exception. Pass `true` for just the base file name.|
|
|
92
100
|
|`to_h`|`Hash`|The event as a plain Hash (via the Raw formatter), including `host`, `application`, and `environment`. Handy for inspecting an event or building a custom formatter.|
|
|
93
101
|
|`set_context(key, value)`|`Hash`|Lazily initializes `context` and stores a key/value pair on it.|
|
data/docs/metrics.md
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
---
|
|
2
2
|
layout: default
|
|
3
|
+
title: Metrics
|
|
4
|
+
description: >-
|
|
5
|
+
Emit named numeric metrics alongside log entries: counters, durations,
|
|
6
|
+
breaking a metric down with dimensions, and sending them on to a metrics
|
|
7
|
+
backend.
|
|
3
8
|
---
|
|
4
9
|
|
|
5
|
-
## Metrics
|
|
6
|
-
{:.no_toc}
|
|
7
10
|
|
|
8
11
|
**Contents**
|
|
9
12
|
|
data/docs/operations.md
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
---
|
|
2
2
|
layout: default
|
|
3
|
+
title: Operations
|
|
4
|
+
description: >-
|
|
5
|
+
Running Semantic Logger in production: process forking, log rotation, tuning
|
|
6
|
+
the background pipeline, Linux signals, and shipping to a centralized
|
|
7
|
+
system.
|
|
3
8
|
---
|
|
4
9
|
|
|
5
|
-
## Operations
|
|
6
|
-
{:.no_toc}
|
|
7
10
|
|
|
8
11
|
**Contents**
|
|
9
12
|
|
|
@@ -111,19 +114,13 @@ By default the queue is capped (`max_queue_size`, default `10,000`). When it fil
|
|
|
111
114
|
because an appender cannot keep up), `logger.info` **blocks** until there is room, guaranteeing no
|
|
112
115
|
message is lost at the cost of briefly slowing the application.
|
|
113
116
|
|
|
114
|
-
|
|
115
|
-
|
|
117
|
+
There are two places a slow appender can hold up the application, and a `non_blocking` setting for
|
|
118
|
+
each.
|
|
116
119
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
non_blocking: true
|
|
122
|
-
)
|
|
123
|
-
~~~
|
|
124
|
-
|
|
125
|
-
Dropped messages are counted and reported at most once every `dropped_message_report_seconds`
|
|
126
|
-
(default `30`) so they do not go unnoticed:
|
|
120
|
+
**A slow appender that runs on its own thread.** An appender added with `async: true` (or a batching
|
|
121
|
+
appender) has a second queue of its own, between the main queue and that appender. Set
|
|
122
|
+
`non_blocking: true` on it so that, once its own queue is full, messages for that appender are
|
|
123
|
+
**dropped** instead of holding up the main thread:
|
|
127
124
|
|
|
128
125
|
~~~ruby
|
|
129
126
|
SemanticLogger.add_appender(
|
|
@@ -134,6 +131,30 @@ SemanticLogger.add_appender(
|
|
|
134
131
|
)
|
|
135
132
|
~~~
|
|
136
133
|
|
|
134
|
+
The main thread never waits on that appender, so the main queue keeps draining and every other
|
|
135
|
+
appender still receives every message. Prefer this when you know which appender is slow, such as one
|
|
136
|
+
that writes to a remote service. Dropped messages are counted and reported to the internal logger at
|
|
137
|
+
most once every `dropped_message_report_seconds` (default `30`), and appear as `dropped` for that
|
|
138
|
+
appender in `SemanticLogger.stats[:appenders]`.
|
|
139
|
+
|
|
140
|
+
**Everything else.** The main queue still fills if an appender that is not async slows down (for
|
|
141
|
+
example `io: $stdout` writing to a pipe that is not being read), or if the main thread cannot keep up
|
|
142
|
+
with the volume of messages. Set `SemanticLogger.non_blocking = true` so that messages are
|
|
143
|
+
**dropped** instead of blocking `logger.info` once the main queue is full:
|
|
144
|
+
|
|
145
|
+
~~~ruby
|
|
146
|
+
SemanticLogger.non_blocking = true
|
|
147
|
+
~~~
|
|
148
|
+
|
|
149
|
+
This can be set at any time, before or after appenders are added. Dropped messages are reported to
|
|
150
|
+
the internal logger at most once every 30 seconds, and the count is available from
|
|
151
|
+
`SemanticLogger.stats[:dropped]`. It is not supported in synchronous mode (`SemanticLogger.sync!`),
|
|
152
|
+
which has no queue.
|
|
153
|
+
|
|
154
|
+
Either setting keeps logging calls from blocking; neither makes `SemanticLogger.flush` return sooner.
|
|
155
|
+
A flush, including the one that runs automatically when the process exits, still waits for every
|
|
156
|
+
message already on the queue to be written.
|
|
157
|
+
|
|
137
158
|
`non_blocking` applies only to a capped queue. An uncapped queue (`max_queue_size: -1`) never blocks
|
|
138
159
|
and never drops, but can grow without bound.
|
|
139
160
|
|
|
@@ -144,8 +165,26 @@ a transient failure (such as a brief network blip to a remote appender) does not
|
|
|
144
165
|
logging. Each restart sleeps with an increasing back-off (1 second, then 2, ...), reset as soon as a
|
|
145
166
|
message is processed successfully.
|
|
146
167
|
|
|
168
|
+
A message that fails because of its own content, rather than the destination, is not retried: for
|
|
169
|
+
example a value the formatter cannot serialize (a `JSON::GeneratorError`, `JSON::NestingError`,
|
|
170
|
+
encoding error, or `FloatDomainError`). That one message is discarded, and the thread carries on
|
|
171
|
+
without backing off. When batching with an appender that sends the whole batch in a single request,
|
|
172
|
+
such as the HTTP, Loki, Splunk HTTP, Elasticsearch, and OpenSearch appenders, only that message is
|
|
173
|
+
discarded and the rest of the batch is still written. With an appender that may have written part
|
|
174
|
+
of the batch before failing, the whole batch is discarded, so that no message is written twice.
|
|
175
|
+
|
|
176
|
+
The first discarded message is reported to the internal logger straight away, with its error. After
|
|
177
|
+
that, the number discarded is reported, with the latest error, at most once every
|
|
178
|
+
`dropped_message_report_seconds` (default `30`). The total appears as `failed` for that appender in
|
|
179
|
+
`SemanticLogger.stats[:appenders]`.
|
|
180
|
+
|
|
181
|
+
A `JSON::ParserError`, such as from a destination that answered with an HTML error page, is a
|
|
182
|
+
problem with the destination, so it is retried like any other error. The appender decides which
|
|
183
|
+
errors are caused by message content; see [Custom appenders](config.html#custom-appenders).
|
|
184
|
+
|
|
147
185
|
After `async_max_retries` (default `100`) consecutive failed restarts the worker thread gives up,
|
|
148
|
-
rather than spinning forever on a persistent failure
|
|
186
|
+
rather than spinning forever on a persistent failure. From then on new log messages for that
|
|
187
|
+
appender are dropped once its queue is full, instead of blocking the application:
|
|
149
188
|
|
|
150
189
|
~~~ruby
|
|
151
190
|
SemanticLogger.add_appender(
|
|
@@ -159,6 +198,12 @@ SemanticLogger.add_appender(
|
|
|
159
198
|
Set `async_max_retries: -1` to retry indefinitely. The back-off still applies and still resets after
|
|
160
199
|
a successful message.
|
|
161
200
|
|
|
201
|
+
A flush or close is never left waiting on a worker thread that cannot answer it. When a batch cannot
|
|
202
|
+
be written, a flush or close taken off the queue with it still runs before the thread backs off; a
|
|
203
|
+
close then stops the thread instead of restarting it. If the worker thread ends without answering,
|
|
204
|
+
for example once it gives up, `SemanticLogger.flush` (including the flush at exit) stops waiting on
|
|
205
|
+
that appender.
|
|
206
|
+
|
|
162
207
|
### Give a slow appender its own thread
|
|
163
208
|
|
|
164
209
|
If one destination is slow, such as a remote HTTP service, run just that appender on its own thread
|
|
@@ -200,12 +245,13 @@ SemanticLogger.stats
|
|
|
200
245
|
# capped: true,
|
|
201
246
|
# max_queue_size: 10_000,
|
|
202
247
|
# processed: 1_529,
|
|
203
|
-
# dropped: 0
|
|
248
|
+
# dropped: 0,
|
|
249
|
+
# failed: 0 } # discarded because their content could not be written
|
|
204
250
|
# ]
|
|
205
251
|
# }
|
|
206
252
|
~~~
|
|
207
253
|
|
|
208
|
-
The `processed` and `
|
|
254
|
+
The `processed`, `dropped`, and `failed` counters are cumulative since process startup. Reading `stats` is
|
|
209
255
|
thread-safe and adds no locking to the logging hot path.
|
|
210
256
|
|
|
211
257
|
Semantic Logger also warns when an entry has waited on the queue too long. Tune the threshold and how
|
data/docs/rails.md
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
---
|
|
2
2
|
layout: default
|
|
3
|
+
title: Rails
|
|
4
|
+
description: >-
|
|
5
|
+
Using Semantic Logger in Rails through the rails_semantic_logger gem:
|
|
6
|
+
configuring appenders, tuning what Rails logs, common recipes, and
|
|
7
|
+
upgrading.
|
|
3
8
|
---
|
|
4
9
|
|
|
5
|
-
## Rails
|
|
6
|
-
{:.no_toc}
|
|
7
10
|
|
|
8
11
|
**Contents**
|
|
9
12
|
|
|
@@ -275,7 +278,7 @@ you can write color to the screen and JSON to a file at the same time.
|
|
|
275
278
|
| `:color` | Plain text with color (uses Amazing Print for the payload when installed). |
|
|
276
279
|
| `:json` | One JSON object per entry. |
|
|
277
280
|
| `:logfmt` | `key=value` logfmt output. |
|
|
278
|
-
| `:one_line` | Each entry reduced to a single line. |
|
|
281
|
+
| `:one_line` | Each entry reduced to a single line: line breaks are removed from every field (or escaped with `escape_control_chars: true`) and an exception shows only its class and message, without the backtrace. |
|
|
279
282
|
| A class instance | Any instance of a class derived from `SemanticLogger::Formatters::Base`. |
|
|
280
283
|
| A `Proc` | Called with the `log` entry; returns the formatted output. |
|
|
281
284
|
|
|
@@ -677,6 +680,21 @@ If you use a forking server (Puma, Unicorn) or fork worker processes, see
|
|
|
677
680
|
[Process Forking](operations.html#process-forking). With Semantic Logger v5 appenders are reopened automatically after
|
|
678
681
|
a fork, so the manual `after_fork { SemanticLogger.reopen }` hook is usually no longer needed.
|
|
679
682
|
|
|
683
|
+
### Drop messages instead of blocking
|
|
684
|
+
|
|
685
|
+
By default, if log messages arrive faster than the appenders can write them, calls to `logger.info`
|
|
686
|
+
wait once the in-memory queue is full, so requests, jobs, and shutdown hooks that log can stall behind
|
|
687
|
+
a slow destination. When staying responsive matters more than keeping every message, have them dropped
|
|
688
|
+
instead, in `config/application.rb` or `config/environments/production.rb`:
|
|
689
|
+
|
|
690
|
+
~~~ruby
|
|
691
|
+
config.semantic_logger.non_blocking = true
|
|
692
|
+
~~~
|
|
693
|
+
|
|
694
|
+
This needs Semantic Logger v5.2 or later. See
|
|
695
|
+
[Drop messages instead of blocking](operations.html#drop-messages-instead-of-blocking) for how it
|
|
696
|
+
relates to the per-appender `non_blocking:` option and how dropped messages are reported.
|
|
697
|
+
|
|
680
698
|
### Log rotation
|
|
681
699
|
|
|
682
700
|
Because the log file is held open between writes, rotate it with a **copy-truncate** strategy rather
|
|
@@ -712,6 +730,39 @@ After they initialize, Rails Semantic Logger replaces the loggers of these libra
|
|
|
712
730
|
|
|
713
731
|
---
|
|
714
732
|
|
|
733
|
+
## Upgrading to v5.1
|
|
734
|
+
|
|
735
|
+
v5.1 requires Semantic Logger v5.1 and needs no configuration changes. Two behaviors differ.
|
|
736
|
+
|
|
737
|
+
### A log file that cannot be opened now fails at boot
|
|
738
|
+
|
|
739
|
+
Semantic Logger v5.1 opens the file appender's log file when the appender is created, rather than on
|
|
740
|
+
the first write. A missing directory, a bad path, or insufficient permissions therefore raises
|
|
741
|
+
during boot, where Rails Semantic Logger catches it and falls back to logging to standard error at
|
|
742
|
+
the `:warn` level, with a message explaining the problem.
|
|
743
|
+
|
|
744
|
+
Previously that failure surfaced asynchronously on the appender thread after the application had
|
|
745
|
+
already booted, so the fallback never ran and the app kept running against an appender that silently
|
|
746
|
+
dropped every message. If you have been running with an unwritable log path without noticing, this
|
|
747
|
+
release is when you will find out.
|
|
748
|
+
|
|
749
|
+
As a side effect, the log file is created as soon as the appender is added, even if nothing has been
|
|
750
|
+
logged yet. This matches the standard Ruby and Rails loggers.
|
|
751
|
+
|
|
752
|
+
### Sidekiq v4, v5, and v6 are no longer supported
|
|
753
|
+
|
|
754
|
+
Sidekiq v7 and v8 are supported and tested. The patches for older versions have been removed, along
|
|
755
|
+
with the pre-7.1.6 error handler branches. Sidekiq v7 and v8 both predate this gem's Rails 7.2 and
|
|
756
|
+
Ruby 3.2 minimums, so upgrading Sidekiq is the path forward.
|
|
757
|
+
|
|
758
|
+
On Sidekiq 8 two of its own settings are now honored: `logged_job_attributes` adds job attributes to
|
|
759
|
+
the logging context, and `skip_default_job_logging` suppresses the `Start #perform` /
|
|
760
|
+
`Completed #perform` messages (an alternative to
|
|
761
|
+
`RailsSemanticLogger::Sidekiq::JobLogger.perform_messages = false`). See
|
|
762
|
+
[Sidekiq](#sidekiq) for the logging configuration.
|
|
763
|
+
|
|
764
|
+
---
|
|
765
|
+
|
|
715
766
|
## Migrating from v4 to v5
|
|
716
767
|
|
|
717
768
|
### Ruby and Rails minimums
|
|
@@ -814,3 +865,26 @@ and will be **removed in v6**. Each is replaced by the [appenders block](#config
|
|
|
814
865
|
| `config.rails_semantic_logger.filter` | `filter:` on the appender |
|
|
815
866
|
| `config.rails_semantic_logger.console_logger` | Declare (or omit) an `add_console` appender |
|
|
816
867
|
| `config.rails_semantic_logger.add_file_appender` | Declare appenders in the block (doing so already replaces the default file appender) |
|
|
868
|
+
|
|
869
|
+
### Silencing the warnings until you migrate
|
|
870
|
+
|
|
871
|
+
The gem registers its deprecator with the application as
|
|
872
|
+
`Rails.application.deprecators[:rails_semantic_logger]`, so the standard Rails deprecation settings
|
|
873
|
+
govern these warnings:
|
|
874
|
+
|
|
875
|
+
~~~ruby
|
|
876
|
+
# Hide them until you have migrated
|
|
877
|
+
config.active_support.report_deprecations = false
|
|
878
|
+
|
|
879
|
+
# Or silence only this gem's warnings, leaving the rest of the application's in place
|
|
880
|
+
Rails.application.deprecators[:rails_semantic_logger].silenced = true
|
|
881
|
+
|
|
882
|
+
# Or make them fatal, e.g. in config/environments/test.rb, so CI catches them
|
|
883
|
+
config.active_support.deprecation = :raise
|
|
884
|
+
~~~
|
|
885
|
+
|
|
886
|
+
The warnings are recorded when the option is set and emitted once Rails has applied the settings
|
|
887
|
+
above. Rails reads `config/environments/*.rb` well before it applies deprecation configuration, so
|
|
888
|
+
warning from the option setter itself would land where none of these settings could reach it. Each
|
|
889
|
+
option is reported once per boot, attributed to the place it was set. Options set later, from
|
|
890
|
+
`config/initializers/*` or at runtime, warn immediately instead.
|