semantic_logger 5.0.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.
Files changed (75) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +20 -10
  3. data/Rakefile +40 -0
  4. data/docs/api.md +506 -0
  5. data/docs/appenders.md +1074 -0
  6. data/docs/config.md +707 -0
  7. data/docs/index.md +223 -0
  8. data/docs/log.md +116 -0
  9. data/docs/metrics.md +171 -0
  10. data/docs/operations.md +446 -0
  11. data/docs/rails.md +890 -0
  12. data/docs/security.md +286 -0
  13. data/docs/testing.md +287 -0
  14. data/docs/upgrading.md +697 -0
  15. data/lib/semantic_logger/appender/async.rb +18 -5
  16. data/lib/semantic_logger/appender/bugsnag.rb +1 -1
  17. data/lib/semantic_logger/appender/cloudwatch_logs.rb +126 -29
  18. data/lib/semantic_logger/appender/elasticsearch.rb +2 -5
  19. data/lib/semantic_logger/appender/elasticsearch_base.rb +48 -23
  20. data/lib/semantic_logger/appender/elasticsearch_http.rb +15 -8
  21. data/lib/semantic_logger/appender/file.rb +141 -28
  22. data/lib/semantic_logger/appender/graylog.rb +22 -6
  23. data/lib/semantic_logger/appender/honeybadger.rb +1 -1
  24. data/lib/semantic_logger/appender/honeybadger_insights.rb +1 -1
  25. data/lib/semantic_logger/appender/http.rb +70 -11
  26. data/lib/semantic_logger/appender/io.rb +1 -1
  27. data/lib/semantic_logger/appender/kafka.rb +26 -12
  28. data/lib/semantic_logger/appender/loki.rb +13 -3
  29. data/lib/semantic_logger/appender/mongodb.rb +20 -1
  30. data/lib/semantic_logger/appender/new_relic.rb +7 -1
  31. data/lib/semantic_logger/appender/new_relic_logs.rb +11 -12
  32. data/lib/semantic_logger/appender/open_telemetry.rb +4 -3
  33. data/lib/semantic_logger/appender/opensearch.rb +2 -1
  34. data/lib/semantic_logger/appender/rabbitmq.rb +10 -4
  35. data/lib/semantic_logger/appender/sentry.rb +6 -1
  36. data/lib/semantic_logger/appender/sentry_ruby.rb +91 -17
  37. data/lib/semantic_logger/appender/splunk.rb +10 -1
  38. data/lib/semantic_logger/appender/syslog.rb +66 -25
  39. data/lib/semantic_logger/appender/tcp.rb +10 -4
  40. data/lib/semantic_logger/appender/udp.rb +11 -1
  41. data/lib/semantic_logger/appender/wrapper.rb +3 -2
  42. data/lib/semantic_logger/appender.rb +6 -3
  43. data/lib/semantic_logger/appenders.rb +11 -2
  44. data/lib/semantic_logger/base.rb +82 -32
  45. data/lib/semantic_logger/exception_details.rb +50 -0
  46. data/lib/semantic_logger/formatters/base.rb +52 -6
  47. data/lib/semantic_logger/formatters/color.rb +22 -3
  48. data/lib/semantic_logger/formatters/default.rb +28 -6
  49. data/lib/semantic_logger/formatters/logfmt.rb +155 -21
  50. data/lib/semantic_logger/formatters/loki.rb +26 -22
  51. data/lib/semantic_logger/formatters/one_line.rb +67 -3
  52. data/lib/semantic_logger/formatters/open_telemetry.rb +16 -17
  53. data/lib/semantic_logger/formatters/pattern.rb +11 -5
  54. data/lib/semantic_logger/formatters/raw.rb +4 -5
  55. data/lib/semantic_logger/formatters/syslog.rb +110 -17
  56. data/lib/semantic_logger/formatters/syslog_cee.rb +29 -15
  57. data/lib/semantic_logger/levels.rb +10 -1
  58. data/lib/semantic_logger/log.rb +183 -37
  59. data/lib/semantic_logger/loggable.rb +26 -3
  60. data/lib/semantic_logger/metric/new_relic.rb +1 -1
  61. data/lib/semantic_logger/metric/signalfx.rb +12 -8
  62. data/lib/semantic_logger/metric/statsd.rb +15 -7
  63. data/lib/semantic_logger/queue_processor/request.rb +55 -0
  64. data/lib/semantic_logger/queue_processor/throttled_counter.rb +46 -0
  65. data/lib/semantic_logger/queue_processor.rb +170 -68
  66. data/lib/semantic_logger/reporters/minitest.rb +4 -4
  67. data/lib/semantic_logger/semantic_logger.rb +15 -2
  68. data/lib/semantic_logger/subscriber.rb +96 -10
  69. data/lib/semantic_logger/sync_processor.rb +11 -0
  70. data/lib/semantic_logger/test/capture_log_events.rb +9 -6
  71. data/lib/semantic_logger/utils.rb +244 -16
  72. data/lib/semantic_logger/version.rb +1 -1
  73. data/lib/semantic_logger.rb +1 -1
  74. metadata +33 -6
  75. data/lib/semantic_logger/core_ext/thread.rb +0 -30
@@ -0,0 +1,446 @@
1
+ ---
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.
8
+ ---
9
+
10
+
11
+ **Contents**
12
+
13
+ * TOC
14
+ {:toc}
15
+
16
+ This page covers running Semantic Logger in production: keeping logging alive across process forks,
17
+ rotating log files, tuning the background pipeline, controlling a running process with signals, and
18
+ shipping logs to a centralized system. The defaults are good for most applications, so reach for
19
+ these topics when you have a specific operational need.
20
+
21
+ For first-time setup (global settings, appenders, formatters, and filtering), see
22
+ [Configuration](config.html).
23
+
24
+ ## Process forking
25
+
26
+ Frameworks such as Puma, Unicorn, and Resque **fork** the process: they start a worker by cloning the
27
+ parent. A forked child does not inherit a working copy of the parent's log file handles or background
28
+ thread, so unless those are re-opened in the child, logging quietly stops.
29
+
30
+ ### It is automatic (default)
31
+
32
+ As of v5 you do not need to do anything. Semantic Logger installs a `Process._fork` hook (Ruby 3.1
33
+ and later) that calls `SemanticLogger.reopen` in the child after `fork`, `Process.daemon`,
34
+ `IO.popen`, `Kernel#system`, and backticks. That covers every forking framework (Puma, Unicorn,
35
+ Resque, Spring, Phusion Passenger, parallel tests, and so on).
36
+
37
+ `reopen` runs only once per process after a fork, so it is safe even if something else also calls it.
38
+
39
+ ### Reopen manually
40
+
41
+ You only need the steps below if you turned the automatic hook off, or you rotated logs in a way that
42
+ did not fork (see [Log rotation](#log-rotation)).
43
+
44
+ 1. Disable the automatic hook during boot, if you want full manual control:
45
+
46
+ ~~~ruby
47
+ SemanticLogger.reopen_on_fork = false
48
+ ~~~
49
+
50
+ 2. Call `reopen` yourself after each fork, or after an in-process log rotation. Within the same
51
+ process, pass `force: true` to bypass the once-per-process guard:
52
+
53
+ ~~~ruby
54
+ SemanticLogger.reopen # after a fork
55
+ SemanticLogger.reopen(force: true) # same process, e.g. after external log rotation
56
+ ~~~
57
+
58
+ You might opt out if another library forks in ways you do not want to trigger a reopen, or you need
59
+ to control exactly when the appender thread restarts.
60
+
61
+ ## Log rotation
62
+
63
+ For performance the log file is **not** re-opened on every write, so a log file must be rotated with
64
+ a **copy-truncate** strategy (copy the file aside, then truncate the original in place). Deleting or
65
+ renaming the file would leave Semantic Logger writing to a handle that no longer points at the live
66
+ file.
67
+
68
+ Linux's `logrotate` does this well. To set it up:
69
+
70
+ 1. Create a config file for your application, for example `/etc/logrotate.d/my_app`.
71
+ 2. Point it at your log directory and include `copytruncate`. For daily rotation:
72
+
73
+ ~~~
74
+ /var/www/rails/my_app/log/*.log {
75
+ daily
76
+ missingok
77
+ copytruncate
78
+ rotate 14
79
+ compress
80
+ delaycompress
81
+ notifempty
82
+ }
83
+ ~~~
84
+
85
+ Or, to rotate by size for very high volume logging:
86
+
87
+ ~~~
88
+ /var/www/rails/my_app/log/*.log {
89
+ size 2G
90
+ missingok
91
+ copytruncate
92
+ rotate 7
93
+ compress
94
+ nodelaycompress
95
+ notifempty
96
+ dateformat .%Y%m%d
97
+ }
98
+ ~~~
99
+
100
+ Other rotation tools work too, as long as they use copy-truncate. If your tool cannot copy-truncate
101
+ and instead moves the file, reopen the handles in-process afterwards with
102
+ `SemanticLogger.reopen(force: true)` (see [Process forking](#process-forking)).
103
+
104
+ ## Performance and reliability tuning
105
+
106
+ Every logger hands its events to one shared background thread through an in-memory queue. That thread
107
+ writes each event to every appender in turn, so the call to `logger.info` returns immediately. The
108
+ knobs below tune that pipeline. The defaults suit most applications; reach for a knob when you have a
109
+ specific throughput, availability, or reliability requirement.
110
+
111
+ ### Drop messages instead of blocking
112
+
113
+ By default the queue is capped (`max_queue_size`, default `10,000`). When it fills (for example
114
+ because an appender cannot keep up), `logger.info` **blocks** until there is room, guaranteeing no
115
+ message is lost at the cost of briefly slowing the application.
116
+
117
+ There are two places a slow appender can hold up the application, and a `non_blocking` setting for
118
+ each.
119
+
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:
124
+
125
+ ~~~ruby
126
+ SemanticLogger.add_appender(
127
+ file_name: "production.log",
128
+ async: true,
129
+ non_blocking: true,
130
+ dropped_message_report_seconds: 60
131
+ )
132
+ ~~~
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
+
158
+ `non_blocking` applies only to a capped queue. An uncapped queue (`max_queue_size: -1`) never blocks
159
+ and never drops, but can grow without bound.
160
+
161
+ ### Retry a failing appender
162
+
163
+ If an appender raises while the worker thread is writing, the thread logs the error and restarts, so
164
+ a transient failure (such as a brief network blip to a remote appender) does not permanently stop
165
+ logging. Each restart sleeps with an increasing back-off (1 second, then 2, ...), reset as soon as a
166
+ message is processed successfully.
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
+
185
+ After `async_max_retries` (default `100`) consecutive failed restarts the worker thread gives up,
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:
188
+
189
+ ~~~ruby
190
+ SemanticLogger.add_appender(
191
+ appender: :http,
192
+ url: "https://example.com/log",
193
+ async: true,
194
+ async_max_retries: 20
195
+ )
196
+ ~~~
197
+
198
+ Set `async_max_retries: -1` to retry indefinitely. The back-off still applies and still resets after
199
+ a successful message.
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
+
207
+ ### Give a slow appender its own thread
208
+
209
+ If one destination is slow, such as a remote HTTP service, run just that appender on its own thread
210
+ and queue so it cannot hold up the others:
211
+
212
+ ~~~ruby
213
+ SemanticLogger.add_appender(appender: :http, url: "https://example.com/log", async: true)
214
+ ~~~
215
+
216
+ ### Monitoring the background thread
217
+
218
+ The background thread can occasionally fall behind, for example when an appender is slow or a sudden
219
+ burst of logging occurs. Check the queue at runtime:
220
+
221
+ ~~~ruby
222
+ # Number of log entries still waiting to be written
223
+ SemanticLogger.queue_size
224
+ ~~~
225
+
226
+ For a fuller operational picture, including per-appender queues, use `SemanticLogger.stats`. It
227
+ returns a Hash describing the main pipeline and every appender, handy for exporting Semantic Logger's
228
+ own health to a monitoring system such as Prometheus or statsd:
229
+
230
+ ~~~ruby
231
+ SemanticLogger.stats
232
+ # => {
233
+ # queue_size: 0, # entries waiting on the main pipeline queue
234
+ # capped: true, # whether the main queue has a maximum size
235
+ # max_queue_size: 10_000, # nil when uncapped
236
+ # thread_active: true, # whether the main pipeline thread is running
237
+ # processed: 1_532, # cumulative entries processed since startup
238
+ # dropped: 0, # cumulative entries dropped at the main queue
239
+ # appenders: [
240
+ # { name: "SemanticLogger::Appender::File", async: false },
241
+ # { name: "SemanticLogger::Appender::Http",
242
+ # async: true, # this appender has its own thread and queue
243
+ # thread_active: true,
244
+ # queue_size: 3,
245
+ # capped: true,
246
+ # max_queue_size: 10_000,
247
+ # processed: 1_529,
248
+ # dropped: 0,
249
+ # failed: 0 } # discarded because their content could not be written
250
+ # ]
251
+ # }
252
+ ~~~
253
+
254
+ The `processed`, `dropped`, and `failed` counters are cumulative since process startup. Reading `stats` is
255
+ thread-safe and adds no locking to the logging hot path.
256
+
257
+ Semantic Logger also warns when an entry has waited on the queue too long. Tune the threshold and how
258
+ often it is checked:
259
+
260
+ ~~~ruby
261
+ # Warn when an entry has been on the queue longer than this many seconds ( default: 30 )
262
+ SemanticLogger.lag_threshold_s
263
+
264
+ # Number of messages to process between lag checks ( default: 1,000 )
265
+ SemanticLogger.lag_check_interval = 1_000
266
+ ~~~
267
+
268
+ If a sustained burst is overwhelming logging, reduce the volume by raising the log level, reduce the
269
+ number of appenders, or speed up the slow appender.
270
+
271
+ ### Synchronous operation
272
+
273
+ Synchronous mode bypasses the background thread and logs inline on the calling thread. This disables
274
+ a core design principle of Semantic Logger and slows the calling thread, so it is not recommended for
275
+ most applications. It can suit short-lived or single-threaded programs, or forked environments where
276
+ you would rather not re-create the logging thread.
277
+
278
+ Enable it **before** adding any appenders:
279
+
280
+ ~~~ruby
281
+ SemanticLogger.sync!
282
+ ~~~
283
+
284
+ To guarantee it is set early enough, replace the require with the synchronous variant:
285
+
286
+ ~~~ruby
287
+ require "semantic_logger/sync"
288
+ ~~~
289
+
290
+ Or, in a Gemfile:
291
+
292
+ ~~~ruby
293
+ gem "semantic_logger", require: "semantic_logger/sync"
294
+ ~~~
295
+
296
+ ## Linux signals
297
+
298
+ On Linux, Unix, and Mac, Semantic Logger can respond to signals, for example to change the log level
299
+ of a running process without restarting it. It registers no signal handlers on startup, so as not to
300
+ interfere with any your application already uses.
301
+
302
+ **Step 1: enable signal handling** during boot:
303
+
304
+ ~~~ruby
305
+ # config/initializers/semantic_logger.rb, or during startup of a standalone app
306
+ SemanticLogger.add_signal_handler
307
+ ~~~
308
+
309
+ **Step 2: send the signal** you need. The capabilities are below.
310
+
311
+ ### Change the log level (USR2)
312
+
313
+ Send `SIGUSR2` to rotate the global default level, without restarting. Each signal moves the level
314
+ one step through this sequence, wrapping from `:trace` back to `:fatal`:
315
+
316
+ :fatal :error :warn :info :debug :trace
317
+
318
+ ~~~
319
+ kill -SIGUSR2 1234
320
+ ~~~
321
+
322
+ This changes only the global default level. Loggers whose level was set explicitly in the application
323
+ are unaffected.
324
+
325
+ ### Dump all threads (TTIN)
326
+
327
+ Send `TTIN` to write every thread, with its backtrace where available, to the log. Naming your
328
+ threads (`Thread.current.name = "My Worker"`) makes the dump far more useful:
329
+
330
+ ~~~
331
+ kill -TTIN 1234
332
+ ~~~
333
+
334
+ On JRuby this differs from the standard `QUIT`-triggered Java thread dump, which includes system
335
+ threads and Java stack traces.
336
+
337
+ ### JRuby garbage collection logging
338
+
339
+ On JRuby, any garbage collection that takes longer than 100ms is logged as a warning to the regular
340
+ appenders, giving visibility into GC pauses that could affect active requests.
341
+
342
+ ### Choose your own signals
343
+
344
+ Pass different signals, or set one to `nil` to skip it. Set the GC threshold to `nil` to skip the
345
+ JRuby garbage collection logging:
346
+
347
+ ~~~ruby
348
+ # Log level change on USR1, thread dump on USR2, GC threshold of 100,000 micro-seconds
349
+ SemanticLogger.add_signal_handler("USR1", "USR2", 100000)
350
+ ~~~
351
+
352
+ ## Centralized logging
353
+
354
+ Once you run more than one process or server, reading log files one at a time stops scaling. A
355
+ **centralized logging** system collects the events from every process into one place where you can
356
+ search, filter, and build dashboards across all of them at once. This is where Semantic Logger's
357
+ structured output pays off: the payload, tags, duration, and metrics on each entry arrive as real
358
+ fields, not text that has to be re-parsed.
359
+
360
+ This walks through one popular stack end to end as a concrete example:
361
+
362
+ * **Semantic Logger** forwards structured events from your application.
363
+ * **Elasticsearch** stores and indexes them.
364
+ * **Kibana** provides search and dashboards on top of Elasticsearch.
365
+
366
+ The same shape applies to other aggregators (Graylog, Splunk, Loki, Logstash, Syslog). See
367
+ [Other destinations](#other-destinations) and [Appenders](appenders.html).
368
+
369
+ ### Step 1: Run Elasticsearch and Kibana
370
+
371
+ Install and start both. Any installation method works; these notes use
372
+ [homebrew](https://brew.sh) on macOS, follow the product links for other platforms.
373
+
374
+ ~~~
375
+ brew install elasticsearch
376
+ brew install kibana
377
+ ~~~
378
+
379
+ Start each one (follow the on-screen instructions to auto-start them), and confirm Elasticsearch is
380
+ reachable, by default at `http://localhost:9200`, and Kibana at `http://localhost:5601`.
381
+
382
+ ### Step 2: Forward your application's logs
383
+
384
+ Add the Elasticsearch appender so Semantic Logger ships every entry to Elasticsearch. In a Rails app
385
+ using [rails_semantic_logger](rails.html), put this in an initializer; otherwise add it where you
386
+ configure Semantic Logger at startup:
387
+
388
+ ~~~ruby
389
+ SemanticLogger.add_appender(
390
+ appender: :elasticsearch,
391
+ url: "http://localhost:9200"
392
+ )
393
+ ~~~
394
+
395
+ By default entries are written to a daily index named `semantic_logger-YYYY.MM.DD`, so the index
396
+ pattern to search in Kibana is `semantic_logger-*`. See [Elasticsearch](appenders.html#elasticsearch)
397
+ for options such as a custom index name or data streams.
398
+
399
+ Restart the application and exercise it so it generates a few log entries. If nothing appears later,
400
+ check the application's own log (for example `log/development.log`) for connection errors.
401
+
402
+ ### Step 3: View the logs in Kibana
403
+
404
+ 1. Open Kibana at [http://localhost:5601](http://localhost:5601).
405
+ 2. Create an **index pattern** (called a "data view" in newer Kibana versions) that matches
406
+ `semantic_logger-*`.
407
+ 3. When asked for the time field, choose `timestamp`.
408
+ 4. Open **Discover**. Your application's log entries appear. If the list is empty, widen the time
409
+ range in the top right.
410
+ 5. Add a few columns so each entry is readable at a glance, for example `host`, `level`, `name`, and
411
+ `message`.
412
+
413
+ The exact menu names vary between Kibana versions, but the three things you need are always the same:
414
+ an index pattern of `semantic_logger-*`, a time field of `timestamp`, and the Discover view.
415
+
416
+ ### Step 4: Search
417
+
418
+ In Discover, query against the structured fields directly. A few examples:
419
+
420
+ ~~~
421
+ # Only error level entries
422
+ level: error
423
+
424
+ # Only entries from one host
425
+ host: mymachine
426
+
427
+ # Find a value in the logging tags
428
+ tags: 17262353
429
+ ~~~
430
+
431
+ Because the payload, tags, and metrics are real fields, you can filter and build dashboards on them
432
+ without writing log-parsing expressions.
433
+
434
+ ### Other destinations
435
+
436
+ Elasticsearch and Kibana are just one option. Semantic Logger also forwards to other centralized
437
+ logging systems and aggregators, including:
438
+
439
+ * Logstash
440
+ * Graylog
441
+ * Splunk
442
+ * Grafana Loki
443
+ * Loggly
444
+ * Syslog
445
+
446
+ See [Appenders](appenders.html) to configure any of these as a destination.