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