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
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`.