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
data/docs/appenders.md ADDED
@@ -0,0 +1,1074 @@
1
+ ---
2
+ layout: default
3
+ title: Appenders
4
+ description: >-
5
+ Every destination Semantic Logger writes to and how to configure it: files
6
+ and streams, structured formats, network protocols, log aggregators, and
7
+ error monitors.
8
+ ---
9
+
10
+
11
+ **Contents**
12
+
13
+ * TOC
14
+ {:toc}
15
+
16
+ An **appender** is a destination that log entries are written to: a file, the screen, a database, or
17
+ a remote service. Semantic Logger can write to several appenders at the same time, each with its own
18
+ log level and format, all from the same log call. For example you might log colorized text to the
19
+ screen, JSON to a file, and errors to an external service simultaneously.
20
+
21
+ ## Step 1: Add a destination
22
+
23
+ Every destination is added through one method, `SemanticLogger.add_appender`, usually once at startup.
24
+ The keyword you pass selects the kind of destination:
25
+
26
+ ~~~ruby
27
+ # A text file
28
+ SemanticLogger.add_appender(file_name: "development.log")
29
+
30
+ # An IO stream, such as the screen
31
+ SemanticLogger.add_appender(io: $stdout)
32
+
33
+ # A built-in appender, selected by name
34
+ SemanticLogger.add_appender(appender: :elasticsearch, url: "http://localhost:9200")
35
+
36
+ # An existing Ruby or Rails logger
37
+ SemanticLogger.add_appender(logger: Logger.new($stdout))
38
+
39
+ # A metrics destination (see the Metrics guide)
40
+ SemanticLogger.add_appender(metric: :statsd, url: "udp://localhost:8125")
41
+ ~~~
42
+
43
+ In a Rails app using [rails_semantic_logger](rails.html), put `add_appender` calls in an initializer
44
+ such as `config/initializers/semantic_logger.rb`. Otherwise add them wherever you configure Semantic
45
+ Logger when the application boots.
46
+
47
+ ## Step 2: Set the level, format, and filter
48
+
49
+ In addition to its own settings, almost every appender accepts these common options, so each
50
+ destination can keep a different subset of the logs in a different format:
51
+
52
+ | Option | Description |
53
+ |--------|-------------|
54
+ | `level` | Only write entries at this level or higher to this appender. Defaults to `SemanticLogger.default_level`. |
55
+ | `formatter` | How to format the output, for example `:default`, `:color`, or `:json`. See [Custom formatters](config.html#custom-formatters). |
56
+ | `filter` | A `Regexp` or `Proc` selecting which entries this appender accepts. See [Filtering](config.html#filtering). |
57
+ | `application`, `environment`, `host` | Override the global values for this appender only. |
58
+
59
+ For example, write only warnings and above to a file, formatted as JSON:
60
+
61
+ ~~~ruby
62
+ SemanticLogger.add_appender(file_name: "errors.log", level: :warn, formatter: :json)
63
+ ~~~
64
+
65
+ ## Step 3: Pick your destinations
66
+
67
+ The tables below list every built-in destination. Pick the ones you need and jump to their section
68
+ for a full example.
69
+
70
+ Appenders for third-party services need their backing gem (shown in the "Gem" column). Add it to your
71
+ `Gemfile` and run `bundle install`, or `gem install` it directly. The gem is loaded lazily the first
72
+ time the appender is used, and is never a hard dependency of Semantic Logger itself.
73
+
74
+ **Files and streams**
75
+
76
+ | Destination | `add_appender` argument | Gem |
77
+ |-------------|-------------------------|-----|
78
+ | [Text file](#text-file) | `file_name:` | |
79
+ | [IO stream](#io-streams) (`$stdout`, `$stderr`, ...) | `io:` | |
80
+ | [Another Ruby or Rails logger](#logger-log4r-etc) | `logger:` | |
81
+
82
+ **Network protocols**
83
+
84
+ | Destination | `add_appender` argument | Gem |
85
+ |-------------|-------------------------|-----|
86
+ | [HTTP(S)](#https) | `appender: :http` | |
87
+ | [TCP (+SSL)](#tcp-appender-ssl) | `appender: :tcp` | `net_tcp_client` |
88
+ | [UDP](#udp-appender) | `appender: :udp` | |
89
+ | [Syslog](#syslog) | `appender: :syslog` | `syslog` (local, Ruby 3.4+), `syslog_protocol` (remote), `net_tcp_client` (TCP) |
90
+
91
+ **Centralized logging and log aggregators**
92
+
93
+ | Destination | `add_appender` argument | Gem |
94
+ |-------------|-------------------------|-----|
95
+ | [Elasticsearch](#elasticsearch) | `appender: :elasticsearch` | `elasticsearch` |
96
+ | [OpenSearch](#opensearch) | `appender: :opensearch` | `opensearch-ruby` |
97
+ | [Graylog](#graylog) | `appender: :graylog` | `gelf` |
98
+ | [Splunk over HTTP](#splunk-http) | `appender: :splunk_http` | |
99
+ | [Splunk SDK](#splunk-sdk) (deprecated) | `appender: :splunk` | `splunk-sdk-ruby` |
100
+ | [Grafana Loki](#grafana-loki) | `appender: :loki` | |
101
+ | [CloudWatch Logs](#cloudwatch-logs) | `appender: :cloudwatch_logs` | `aws-sdk-cloudwatchlogs` |
102
+ | [OpenTelemetry](#opentelemetry) | `appender: :open_telemetry` | `opentelemetry-logs-sdk` |
103
+ | [Logstash](#logstash) | `logger:` | `logstash-logger` |
104
+ | [logentries.com](#logentriescom) | `appender: :tcp` | `net_tcp_client` |
105
+ | [loggly.com](#logglycom) | `appender: :http` | |
106
+ | [Papertrail](#papertrail) | `appender: :syslog` | `syslog_protocol`, `net_tcp_client` |
107
+
108
+ **Error and exception monitoring**
109
+
110
+ | Destination | `add_appender` argument | Gem |
111
+ |-------------|-------------------------|-----|
112
+ | [Bugsnag](#bugsnag) | `appender: :bugsnag` | `bugsnag` |
113
+ | [Sentry](#sentry) | `appender: :sentry_ruby` | `sentry-ruby` |
114
+ | [Honeybadger](#honeybadger-and-honeybadger-insights) | `appender: :honeybadger` | `honeybadger` |
115
+ | [Honeybadger Insights](#honeybadger-and-honeybadger-insights) | `appender: :honeybadger_insights` | `honeybadger` |
116
+ | [New Relic](#newrelic) | `appender: :new_relic` | `newrelic_rpm` |
117
+ | [Rollbar](#rollbar) | via `SemanticLogger.on_log` | `rollbar` |
118
+
119
+ **Databases and message queues**
120
+
121
+ | Destination | `add_appender` argument | Gem |
122
+ |-------------|-------------------------|-----|
123
+ | [MongoDB](#mongodb) | `appender: :mongodb` | `mongo` |
124
+ | [Apache Kafka](#apache-kafka) | `appender: :kafka` | `ruby-kafka` |
125
+ | [RabbitMQ](#rabbitmq-amqp) | `appender: :rabbitmq` | `bunny` |
126
+
127
+ For metrics destinations such as Statsd, SignalFx, and New Relic, see [Metrics](metrics.html).
128
+
129
+ > **Tip:** To ensure no log messages are lost, prefer TCP over UDP. Because of Semantic Logger's
130
+ > asynchronous design, the performance difference between the two will not impact your application.
131
+
132
+ ## Files and streams
133
+
134
+ ### Text File
135
+
136
+ Log to a file, choosing a formatter to suit the reader:
137
+
138
+ ~~~ruby
139
+ # Standard text:
140
+ SemanticLogger.add_appender(file_name: "development.log")
141
+
142
+ # Colorized text, for a terminal:
143
+ SemanticLogger.add_appender(file_name: "development.log", formatter: :color)
144
+
145
+ # JSON, for a machine:
146
+ SemanticLogger.add_appender(file_name: "development.log", formatter: :json)
147
+ ~~~
148
+
149
+ The file is opened when the appender is added, so a bad path or insufficient permissions raise
150
+ immediately from `add_appender` instead of failing later on the logging thread.
151
+
152
+ For performance the log file is not re-opened on every call, so rotate it with a copy-truncate
153
+ operation rather than deleting the file. See [Log rotation](operations.html#log-rotation).
154
+
155
+ Log files frequently contain sensitive information. By default the file is created using the process
156
+ umask (the standard Ruby behavior). To restrict access, supply `permissions:`, applied both when the
157
+ file is created and to an existing log file:
158
+
159
+ ~~~ruby
160
+ # Owner read/write, group read, no access for others:
161
+ SemanticLogger.add_appender(file_name: "production.log", permissions: 0o640)
162
+ ~~~
163
+
164
+ With `permissions:`, the log file must be a regular file owned by the process, with a single hard
165
+ link, that the file name names directly rather than through a symbolic link. Nothing is written to
166
+ any other file, such as a log file linked to `/dev/stdout`, nor when the file cannot be opened:
167
+ `add_appender` raises `IOError` giving the reason, and a file that fails the check when it is
168
+ reopened later, for example on a rotation, is reported and not written to until it is fixed. To log
169
+ to standard output, use `io: $stdout` instead. See
170
+ [Log file permissions](security.html#log-file-permissions).
171
+
172
+ ### IO Streams
173
+
174
+ Log to any IO stream instance, such as `$stdout` or `$stderr`:
175
+
176
+ ~~~ruby
177
+ # Log errors and above to standard error:
178
+ SemanticLogger.add_appender(io: $stderr, level: :error)
179
+ ~~~
180
+
181
+ #### Splitting output across stdout and stderr
182
+
183
+ A common pattern routes lower severity entries to `$stdout` and warnings and errors to `$stderr`.
184
+ Semantic Logger allows one appender per console stream, so add one for each and use `level:` and/or
185
+ `filter:` to control what each writes:
186
+
187
+ ~~~ruby
188
+ stdout_filter = ->(log) { %i[trace debug info].include?(log.level) }
189
+
190
+ # Informational messages to stdout:
191
+ SemanticLogger.add_appender(io: $stdout, formatter: :color, level: :trace, filter: stdout_filter)
192
+
193
+ # Warnings and above to stderr:
194
+ SemanticLogger.add_appender(io: $stderr, formatter: :color, level: :warn)
195
+ ~~~
196
+
197
+ Adding a second appender for a console stream that already has one is ignored (to avoid duplicate
198
+ console output), but `$stdout` and `$stderr` are tracked separately.
199
+
200
+ ### Logger, log4r, etc.
201
+
202
+ Semantic Logger can write to another logging library, either to gain the Semantic Logger interface
203
+ while still writing to an existing destination, or to reach a destination it does not support natively:
204
+
205
+ ~~~ruby
206
+ ruby_logger = Logger.new($stdout)
207
+
208
+ # Log to an existing Ruby Logger instance
209
+ SemanticLogger.add_appender(logger: ruby_logger)
210
+ ~~~
211
+
212
+ Note: `:trace` level messages are mapped to `:debug`.
213
+
214
+ ## Structured output formats
215
+
216
+ The file, IO, and HTTP appenders can emit machine-readable output by choosing a structured
217
+ `formatter`. Each format below produces a single line per entry.
218
+
219
+ ### JSON
220
+
221
+ `formatter: :json` produces output with this layout:
222
+
223
+ ~~~json
224
+ {
225
+ "timestamp": "ISO-8601",
226
+
227
+ "application": "Application name",
228
+ "environment": "Custom Environment name",
229
+ "host": "Host name",
230
+ "pid": "Process Id",
231
+ "thread": "Thread name or id",
232
+ "file": "filename",
233
+ "line": "line number",
234
+
235
+ "level": "trace|debug|info|warn|error|fatal",
236
+ "level_index": "0|1|2|3|4|5",
237
+ "message": "The message text without any colorization",
238
+ "name": "Name of the class that generated the log message. Including namespace, if any.",
239
+ "tags": ["tag_name 1", "tag_name 2"],
240
+ "duration": "Human readable duration",
241
+ "duration_ms": "Duration in milliseconds",
242
+ "metric": "Name of the metric",
243
+ "metric_amount": "Size of the metric, usually 1",
244
+
245
+ "named_tags": {
246
+ "tag1": "any named tags will be inside this named_tags tag",
247
+ "tag2": "any named tags will be inside this named_tags tag"
248
+ },
249
+
250
+ "payload": {
251
+ "field1": "any custom payload fields will be inside this payload tag",
252
+ "field2": "any custom payload fields will be inside this payload tag"
253
+ },
254
+
255
+ "exception": {
256
+ "name": "Exception class name",
257
+ "message": "Exception message",
258
+ "stack_trace": ["line 1", "line 2"],
259
+ "cause": {
260
+ "name": "Exception class name",
261
+ "message": "Exception message",
262
+ "stack_trace": ["line 1", "line 2"]
263
+ }
264
+ }
265
+ }
266
+ ~~~
267
+
268
+ Notes:
269
+
270
+ * The layout above is formatted for readability. The real output is a single line terminated by one
271
+ newline, with no embedded newlines.
272
+ * A field with a `nil` value is excluded from the output.
273
+ * To also log the detail Ruby adds to an exception's message, such as did_you_mean suggestions, as
274
+ `exception.details`, enable `log_exception_details`: `formatter: {json: {log_exception_details: true}}`.
275
+ See [Exception details](config.html#exception-details).
276
+
277
+ ### Fluentd
278
+
279
+ `formatter: :fluentd` is the same as `:json`, except it renames the log level fields to `severity` and
280
+ `severity_index` so they are recognized by the Kubernetes Fluentd log collector:
281
+
282
+ ~~~ruby
283
+ SemanticLogger.add_appender(io: $stdout, formatter: :fluentd)
284
+ ~~~
285
+
286
+ Differences from `:json`:
287
+
288
+ * `level` / `level_index` become `severity` / `severity_index`.
289
+ * `host` is excluded by default (under Fluentd it is usually the not-very-useful container id). Pass
290
+ `log_host: true` to include it.
291
+ * The process fields `pid`, `thread`, `file`, and `line` are excluded by default. Pass
292
+ `need_process_info: true` to include them.
293
+
294
+ Construct the formatter explicitly to override these defaults:
295
+
296
+ ~~~ruby
297
+ formatter = SemanticLogger::Formatters::Fluentd.new(log_host: true, need_process_info: true)
298
+ SemanticLogger.add_appender(io: $stdout, formatter: formatter)
299
+ ~~~
300
+
301
+ ### ECS (Elastic Common Schema)
302
+
303
+ `formatter: :ecs` emits each entry using the nested field names of the
304
+ [Elastic Common Schema](https://www.elastic.co/docs/reference/ecs) (targeting ECS 8.x), so logs
305
+ integrate cleanly with Filebeat and the Elastic stack without an ingest pipeline to rename fields. The
306
+ typical deployment writes ECS JSON to stdout or a file and lets Filebeat or Elastic Agent ship it to
307
+ Elasticsearch:
308
+
309
+ ~~~ruby
310
+ SemanticLogger.add_appender(io: $stdout, formatter: :ecs)
311
+ SemanticLogger.add_appender(file_name: "production.log", formatter: :ecs)
312
+ ~~~
313
+
314
+ Semantic Logger fields map to ECS as follows:
315
+
316
+ | Semantic Logger | ECS |
317
+ | :--- | :--- |
318
+ | `time` | `@timestamp` |
319
+ | `level` | `log.level` |
320
+ | `name` | `log.logger` |
321
+ | `file` / `line` | `log.origin.file.name` / `log.origin.file.line` |
322
+ | `message` | `message` |
323
+ | `thread` | `process.thread.name` |
324
+ | `pid` | `process.pid` |
325
+ | `host` | `host.hostname` |
326
+ | `application` | `service.name` |
327
+ | `environment` | `service.environment` |
328
+ | `exception` | `error.type` / `error.message` / `error.stack_trace` |
329
+ | `duration` | `event.duration` (nanoseconds) |
330
+ | `tags` | `tags` |
331
+ | `named_tags` | `labels.*` |
332
+ | `payload`, `metric`, `metric_amount` | nested under a custom namespace (see below) |
333
+
334
+ ECS reserves the top-level field names it defines, so Semantic Logger data with no native ECS home
335
+ (`payload`, `metric`, and `metric_amount`) is nested under a custom top-level namespace,
336
+ `semantic_logger` by default. A proper-noun namespace is
337
+ [the approach ECS recommends](https://www.elastic.co/docs/reference/ecs/ecs-custom-fields-in-ecs) for
338
+ custom fields, since it never collides with a current or future ECS field. Rename it with
339
+ `namespace:`:
340
+
341
+ ~~~ruby
342
+ formatter = SemanticLogger::Formatters::Ecs.new(namespace: "my_app")
343
+ SemanticLogger.add_appender(io: $stdout, formatter: formatter)
344
+ ~~~
345
+
346
+ Or set `namespace: nil` to merge the payload directly into ECS `labels` alongside the named tags:
347
+
348
+ ~~~ruby
349
+ formatter = SemanticLogger::Formatters::Ecs.new(namespace: nil)
350
+ SemanticLogger.add_appender(io: $stdout, formatter: formatter)
351
+ ~~~
352
+
353
+ ### Logfmt
354
+
355
+ `formatter: :logfmt` emits each entry as a single line of `key=value` pairs in
356
+ [logfmt](https://brandur.org/logfmt) format, common with Heroku and Grafana Loki tooling:
357
+
358
+ ~~~ruby
359
+ SemanticLogger.add_appender(io: $stdout, formatter: :logfmt)
360
+ ~~~
361
+
362
+ ~~~
363
+ timestamp="2024-07-20T08:32:05.375276Z" level="info" name="MyClass" message="Hello World" tag="success"
364
+ ~~~
365
+
366
+ Notes:
367
+
368
+ * The timestamp is ISO 8601, under the key `timestamp` unless the formatter's `time_key` is set
369
+ (`formatter: {logfmt: {time_key: :ts}}`). All values are quoted and escaped, so a newline in the
370
+ data cannot forge or split a record.
371
+ * Keys (from tags, named tags, and the payload) cannot be quoted in logfmt, so spaces, control
372
+ characters (including the Unicode line and paragraph separators), invisible format characters
373
+ (such as a zero width space), `=`, and `"` in a key are replaced with `_`, and invalid UTF-8 is
374
+ dropped. For example the tag `second breakfast` is emitted as `second_breakfast=true`.
375
+ * Named tags and payload fields are merged into the top-level `key=value` pairs. A key named like
376
+ one of the entry's own fields (the time key, `level`, `name`, `message`, `duration`,
377
+ `duration_ms`, `tag`, or an exception field) is emitted with its source as a prefix, so that
378
+ logged data cannot replace those fields: `payload.level="fatal"`, `named_tags.message="..."`, or
379
+ `tags.message=true`. A key that starts with `tags.`, `named_tags.`, or `payload.` is prefixed
380
+ too, as in `payload.named_tags.level`, so that it cannot pass for another source's field.
381
+ * Other keys that are the same once encoded, including keys that only differ by type (such as
382
+ `:user` and `"user"`), are emitted once, with the value from the later source: the payload wins
383
+ over named tags, and named tags over tags.
384
+ * Unnamed tags are emitted as keys with a `true` value.
385
+ * A nested value, such as a Hash or an Array in the payload, is emitted as its JSON text, quoted:
386
+ `user="{\"id\":7}"`.
387
+ * `tag="success"` is emitted on every entry, becoming `tag="exception"` when the entry carries an
388
+ exception, which is then included as the fields `exception_name`, `exception_message`,
389
+ `stack_trace`, and, when the exception has a cause, `cause`. With `log_exception_details: true`
390
+ (off by default), the detail Ruby adds to the exception's message is included as
391
+ `exception_details`; see [Exception details](config.html#exception-details).
392
+
393
+ ## Network protocols
394
+
395
+ ### HTTP(S)
396
+
397
+ The HTTP appender sends JSON to most services that accept log messages over HTTP or HTTPS:
398
+
399
+ ~~~ruby
400
+ SemanticLogger.add_appender(appender: :http, url: "http://localhost:8088/path")
401
+
402
+ # For HTTPS, just change the scheme:
403
+ SemanticLogger.add_appender(appender: :http, url: "https://localhost:8088/path")
404
+ ~~~
405
+
406
+ The JSON being sent can be customized with a formatter:
407
+
408
+ ~~~ruby
409
+ formatter = Proc.new do |log, logger|
410
+ h = log.to_h(logger.host, logger.application)
411
+
412
+ # Change time from iso8601 to seconds since epoch
413
+ h[:timestamp] = log.time.utc.to_f
414
+
415
+ # Render to JSON
416
+ h.to_json
417
+ end
418
+
419
+ SemanticLogger.add_appender(appender: :http, url: "https://localhost:8088/path", formatter: formatter)
420
+ ~~~
421
+
422
+ #### Query parameters
423
+
424
+ Some log servers are configured through the query string of the ingestion url rather than through
425
+ headers or the body. A query string on `url:` is kept and sent with every request that submits log
426
+ data, for example when pointing at
427
+ [VictoriaLogs](https://docs.victoriametrics.com/victorialogs/data-ingestion/):
428
+
429
+ ~~~ruby
430
+ SemanticLogger.add_appender(
431
+ appender: :http,
432
+ url: "http://localhost:9428/insert/jsonline?_msg_field=message&_time_field=timestamp"
433
+ )
434
+ ~~~
435
+
436
+ The query is applied after the request path, so it also works for the appenders built on top of the
437
+ HTTP appender (`:elasticsearch_http`, `:splunk_http`, `:loki`, and the `:signalfx` metrics appender),
438
+ each of which appends its own path to the url:
439
+
440
+ ~~~ruby
441
+ SemanticLogger.add_appender(
442
+ appender: :elasticsearch_http,
443
+ url: "http://localhost:9428/insert/elasticsearch?_msg_field=message&_time_field=timestamp"
444
+ )
445
+ ~~~
446
+
447
+ #### Batching
448
+
449
+ By default each entry is sent in its own HTTP request. To send multiple entries in one request as a
450
+ JSON array, enable batching with `batch: true`. This suits endpoints that accept an array and create
451
+ one document per element, such as the
452
+ [Filebeat http_endpoint input](https://www.elastic.co/guide/en/beats/filebeat/current/filebeat-input-http_endpoint.html):
453
+
454
+ ~~~ruby
455
+ SemanticLogger.add_appender(appender: :http, url: "http://localhost:8088/path", batch: true)
456
+ ~~~
457
+
458
+ With batching the appender runs on its own thread and flushes once `batch_size` entries have
459
+ accumulated (default 300) or `batch_seconds` have elapsed (default 5), whichever comes first:
460
+
461
+ ~~~ruby
462
+ SemanticLogger.add_appender(
463
+ appender: :http,
464
+ url: "http://localhost:8088/path",
465
+ batch: true,
466
+ batch_size: 100,
467
+ batch_seconds: 10
468
+ )
469
+ ~~~
470
+
471
+ ### TCP Appender (+SSL)
472
+
473
+ The TCP appender sends JSON or other formatted messages to services that accept log messages over TCP,
474
+ optionally with SSL:
475
+
476
+ ~~~ruby
477
+ # Plain TCP:
478
+ SemanticLogger.add_appender(appender: :tcp, server: "localhost:8088")
479
+
480
+ # TCP with SSL:
481
+ SemanticLogger.add_appender(appender: :tcp, server: "localhost:8088", ssl: true)
482
+
483
+ # With a self-signed certificate, or one from a private certificate authority, trust that
484
+ # certificate instead of disabling verification:
485
+ SemanticLogger.add_appender(
486
+ appender: :tcp,
487
+ server: "localhost:8088",
488
+ ssl: {ca_file: "/path/to/ca.pem"}
489
+ )
490
+ ~~~
491
+
492
+ Customize the message with a formatter:
493
+
494
+ ~~~ruby
495
+ formatter = Proc.new do |log, logger|
496
+ h = log.to_h(logger.host, logger.application)
497
+ h[:timestamp] = log.time.utc.to_f # seconds since epoch
498
+ h.to_json
499
+ end
500
+
501
+ SemanticLogger.add_appender(appender: :tcp, server: "localhost:8088", formatter: formatter)
502
+ ~~~
503
+
504
+ See [Net::TCPClient](https://github.com/reidmorrison/net_tcp_client) for the remaining connection
505
+ options.
506
+
507
+ The TCP and UDP appenders separate records with a newline, so they default to the JSON formatter,
508
+ which escapes embedded newlines and is safe with untrusted data. If you switch to a text formatter
509
+ such as `:default` or `:color`, enable `escape_control_chars` so a newline in the logged data cannot
510
+ forge a record. Those formatters still write an entry with an exception over several lines (the
511
+ exception, then its backtrace), so a receiver that frames records by newline splits such an entry;
512
+ use the JSON formatter or the `:syslog` formatter when every record must be a single line:
513
+
514
+ ~~~ruby
515
+ SemanticLogger.add_appender(
516
+ appender: :tcp,
517
+ server: "localhost:8088",
518
+ formatter: {default: {escape_control_chars: true}}
519
+ )
520
+ ~~~
521
+
522
+ ### UDP Appender
523
+
524
+ The UDP appender sends JSON or other formatted messages over UDP:
525
+
526
+ ~~~ruby
527
+ SemanticLogger.add_appender(appender: :udp, server: "localhost:8088")
528
+ ~~~
529
+
530
+ Customize the message with a formatter:
531
+
532
+ ~~~ruby
533
+ formatter = Proc.new do |log, logger|
534
+ h = log.to_h(logger.host, logger.application)
535
+ h[:timestamp] = log.time.utc.to_f # seconds since epoch
536
+ h.to_json
537
+ end
538
+
539
+ SemanticLogger.add_appender(appender: :udp, server: "localhost:8088", formatter: formatter)
540
+ ~~~
541
+
542
+ ### Syslog
543
+
544
+ Log to a local Syslog daemon. Since Ruby 3.4 `syslog` is a bundled gem, so add `gem "syslog"` to the
545
+ Gemfile:
546
+
547
+ ~~~ruby
548
+ SemanticLogger.add_appender(appender: :syslog)
549
+ ~~~
550
+
551
+ Log to a remote Syslog server over TCP (the `net_tcp_client` and `syslog_protocol` gems are required).
552
+ The default packet size is 1024 bytes:
553
+
554
+ ~~~ruby
555
+ SemanticLogger.add_appender(appender: :syslog, url: "tcp://myloghost:514", max_size: 2048)
556
+ ~~~
557
+
558
+ `max_size` also applies when `formatter:` names the `:syslog` or `:syslog_cee` formatter. A
559
+ formatter passed as an instance uses its own `max_size`, which also defaults to 1024 bytes:
560
+
561
+ ~~~ruby
562
+ SemanticLogger.add_appender(appender: :syslog, url: "udp://myloghost:514", formatter: :syslog_cee, max_size: 2048)
563
+ ~~~
564
+
565
+ Or over UDP (the `syslog_protocol` gem is required):
566
+
567
+ ~~~ruby
568
+ SemanticLogger.add_appender(appender: :syslog, url: "udp://myloghost:514")
569
+ ~~~
570
+
571
+ Set the facility with one of Ruby's `::Syslog::LOG_*` facility constants (the default is
572
+ `::Syslog::LOG_USER`). For remote syslog without the `syslog` gem, the same constants are available
573
+ from `SemanticLogger::Formatters::Syslog`, for example `SemanticLogger::Formatters::Syslog::LOG_LOCAL0`:
574
+
575
+ ~~~ruby
576
+ SemanticLogger.add_appender(appender: :syslog, url: "udp://myloghost:514", facility: ::Syslog::LOG_LOCAL0)
577
+ ~~~
578
+
579
+ The `facility:`, `level_map:`, and `max_size:` options also apply when `formatter:` names the `:syslog` or
580
+ `:syslog_cee` formatter, for example `formatter: :syslog_cee`. Options given to the formatter itself,
581
+ as in `formatter: {syslog: {facility: ::Syslog::LOG_LOCAL1}}`, take precedence. A formatter passed
582
+ as an instance keeps its own settings.
583
+
584
+ Add a filter to exclude noisy entries such as health checks:
585
+
586
+ ~~~ruby
587
+ SemanticLogger.add_appender(
588
+ appender: :syslog,
589
+ url: "udp://myloghost:514",
590
+ filter: Proc.new { |log| log.message !~ /(health_check|Not logged in)/ }
591
+ )
592
+ ~~~
593
+
594
+ Syslog frames each record, so embedded newlines or other control characters in untrusted data could
595
+ forge or split records. The syslog formatters therefore escape control characters by default. The
596
+ `:syslog` formatter also escapes the newlines between an exception and its backtrace lines (written
597
+ as `\n`), so that an entry with an exception is a single line. To pass control characters through
598
+ unchanged, which also restores the multi-line exception layout:
599
+
600
+ ~~~ruby
601
+ SemanticLogger.add_appender(
602
+ appender: :syslog,
603
+ url: "tcp://myloghost:514",
604
+ formatter: {syslog: {escape_control_chars: false}}
605
+ )
606
+ ~~~
607
+
608
+ Note: `:trace` level messages are mapped to `:debug`.
609
+
610
+ ## Centralized logging and aggregators
611
+
612
+ ### Elasticsearch
613
+
614
+ Forward all log entries to Elasticsearch (requires the `elasticsearch` gem). By default entries are
615
+ written to a daily index named `semantic_logger-YYYY.MM.DD`; override it with `index:`:
616
+
617
+ ~~~ruby
618
+ SemanticLogger.add_appender(
619
+ appender: :elasticsearch,
620
+ url: "http://localhost:9200",
621
+ index: "my-index",
622
+ data_stream: true
623
+ )
624
+ ~~~
625
+
626
+ For an end-to-end walkthrough with Kibana, see [Centralized Logging](operations.html#centralized-logging).
627
+
628
+ ### OpenSearch
629
+
630
+ Forward all log entries to OpenSearch, for example AWS OpenSearch (requires the `opensearch-ruby`
631
+ gem). OpenSearch is a fork of Elasticsearch and uses the same bulk indexing API, so this appender
632
+ accepts the same options as [Elasticsearch](#elasticsearch). Use it instead of the Elasticsearch
633
+ appender when talking to an OpenSearch server, since recent `elasticsearch` gems reject
634
+ non-Elasticsearch servers with an `Elasticsearch::UnsupportedProductError`:
635
+
636
+ ~~~ruby
637
+ SemanticLogger.add_appender(
638
+ appender: :opensearch,
639
+ url: "http://localhost:9200",
640
+ index: "my-index",
641
+ data_stream: true
642
+ )
643
+ ~~~
644
+
645
+ ### Graylog
646
+
647
+ Send log entries to a [Graylog](https://www.graylog.org) server (requires the `gelf` gem). Data is
648
+ sent as JSON, so all of the semantic structure is retained rather than being flattened into text.
649
+
650
+ Over TCP:
651
+
652
+ ~~~ruby
653
+ SemanticLogger.add_appender(appender: :graylog, url: "tcp://localhost:12201")
654
+ ~~~
655
+
656
+ Or over UDP:
657
+
658
+ ~~~ruby
659
+ SemanticLogger.add_appender(appender: :graylog, url: "udp://localhost:12201")
660
+ ~~~
661
+
662
+ If not using Rails, the `facility` can be removed, or set to a custom string describing the
663
+ application. Note: `:trace` level messages are mapped to `:debug`.
664
+
665
+ ### Splunk HTTP
666
+
667
+ To write to the Splunk HTTP Collector, follow the Splunk instructions to enable the
668
+ [HTTP Event Collector](http://dev.splunk.com/view/event-collector/SP-CAAAE7F) and generate a token:
669
+
670
+ ~~~ruby
671
+ SemanticLogger.add_appender(
672
+ appender: :splunk_http,
673
+ url: "http://localhost:8088/services/collector/event",
674
+ token: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
675
+ )
676
+ ~~~
677
+
678
+ For HTTPS, change the URL scheme to `https`. Once entries have been sent, open the Splunk web
679
+ interface, select Search, limit to the new host (`host=hostname`), switch to table view, and select
680
+ the interesting columns: `host`, `duration`, `name`, `level`, `message`.
681
+
682
+ **Performance:** against a local Splunk instance, the HTTP collector handled only about 30 entries per
683
+ second. For much higher throughput, write to Splunk with the [TCP appender](#tcp-appender-ssl)
684
+ instead, which reached about 1,400 entries per second (1,200 with SSL).
685
+
686
+ ### Splunk SDK
687
+
688
+ > **Deprecated:** `appender: :splunk` is deprecated and will be removed in v6. Use the
689
+ > [HTTP Event Collector](#splunk-http) (`appender: :splunk_http`) or the
690
+ > [TCP appender](#tcp-appender-ssl) instead.
691
+
692
+ `appender: :splunk` submits entries through the `splunk-sdk-ruby` gem to the Splunk management
693
+ port. That gem was last released in 2015 and does not verify the server's TLS certificate, so the
694
+ connection, including the login credentials, is open to interception.
695
+
696
+ ### Grafana Loki
697
+
698
+ Send log entries to [Grafana Loki](https://grafana.com/docs/loki) via its
699
+ [HTTP push API](https://grafana.com/docs/loki/latest/reference/loki-http-api/#ingest-logs):
700
+
701
+ ~~~ruby
702
+ SemanticLogger.add_appender(
703
+ appender: :loki,
704
+ url: "https://logs-prod-001.grafana.net",
705
+ username: "grafana_username",
706
+ password: "grafana_token_here",
707
+ compress: true
708
+ )
709
+ ~~~
710
+
711
+ Set the URL, username, and password to match your Loki instance. Set `compress: true` to compress the
712
+ log messages. The ingestion path is appended to the url, so a query string on the url is kept and sent
713
+ with every push.
714
+
715
+ ### CloudWatch Logs
716
+
717
+ Forward all log entries to AWS CloudWatch Logs (requires the `aws-sdk-cloudwatchlogs` gem):
718
+
719
+ ~~~ruby
720
+ SemanticLogger.add_appender(
721
+ appender: :cloudwatch_logs,
722
+ client_kwargs: {region: "eu-west-1"},
723
+ group: "/my/application",
724
+ create_stream: true
725
+ )
726
+ ~~~
727
+
728
+ ### OpenTelemetry
729
+
730
+ Send log entries to [OpenTelemetry](https://opentelemetry.io) through its
731
+ [Logs API](https://opentelemetry.io/docs/specs/otel/logs/), so they can be exported to any
732
+ OpenTelemetry-compatible backend (the OTLP collector, Honeycomb, Datadog, Grafana, and so on).
733
+
734
+ This appender requires the `opentelemetry-logs-sdk` gem plus an exporter such as
735
+ `opentelemetry-exporter-otlp-logs`:
736
+
737
+ ~~~ruby
738
+ gem "opentelemetry-logs-sdk"
739
+ gem "opentelemetry-exporter-otlp-logs"
740
+ ~~~
741
+
742
+ Configure the OpenTelemetry SDK once at startup, then add the appender. `OpenTelemetry::SDK.configure`
743
+ reads the standard `OTEL_*` environment variables (for example `OTEL_EXPORTER_OTLP_ENDPOINT`) and
744
+ installs a logger provider, which the appender picks up automatically:
745
+
746
+ ~~~ruby
747
+ require "opentelemetry-logs-sdk"
748
+ require "opentelemetry-exporter-otlp-logs"
749
+
750
+ OpenTelemetry::SDK.configure
751
+
752
+ SemanticLogger.add_appender(appender: :open_telemetry)
753
+ ~~~
754
+
755
+ Each entry is emitted with its level mapped to the matching OpenTelemetry severity number, the message
756
+ as the record body, and the payload as record attributes. The appender registers a
757
+ `SemanticLogger.on_log` subscriber that captures the current OpenTelemetry context as each entry is
758
+ logged, so log records are correlated with the active trace and span.
759
+
760
+ | Option | Description |
761
+ |--------|-------------|
762
+ | `name` | Instrumentation scope name reported to OpenTelemetry. Defaults to `"SemanticLogger"`. |
763
+ | `version` | Instrumentation scope version. Defaults to the Semantic Logger gem version. |
764
+ | `metrics` | Whether to forward metric-only log entries. Defaults to `true`. |
765
+
766
+ ### Logstash
767
+
768
+ Forward log entries to Logstash through the `logstash-logger` gem. Configure a `LogStashLogger` and
769
+ hand it to Semantic Logger as a `logger:` appender:
770
+
771
+ ~~~ruby
772
+ require "logstash-logger"
773
+
774
+ # See https://github.com/dwbutler/logstash-logger for further options
775
+ log_stash = LogStashLogger.new(type: :tcp, host: "localhost", port: 5229)
776
+
777
+ SemanticLogger.add_appender(logger: log_stash)
778
+ ~~~
779
+
780
+ Note: `:trace` level messages are mapped to `:debug`.
781
+
782
+ ### logentries.com
783
+
784
+ Obtain a token by following the [logentries instructions](https://logentries.com/doc/input-token/),
785
+ then prefix each JSON line with the token using a small custom formatter over the TCP appender:
786
+
787
+ ~~~ruby
788
+ module Logentries
789
+ class Formatter < SemanticLogger::Formatters::Json
790
+ attr_accessor :token
791
+
792
+ def initialize(token)
793
+ @token = token
794
+ end
795
+
796
+ def call(log, logger)
797
+ "#{token} #{super(log, logger)}"
798
+ end
799
+ end
800
+ end
801
+
802
+ formatter = Logentries::Formatter.new("xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx")
803
+ SemanticLogger.add_appender(appender: :tcp, server: "api.logentries.com:20000", ssl: true, formatter: formatter)
804
+ ~~~
805
+
806
+ ### loggly.com
807
+
808
+ After signing up with Loggly, obtain a token under `Source Setup` -> `Customer Tokens`, then post to
809
+ the Loggly input URL with the HTTP appender:
810
+
811
+ ~~~ruby
812
+ SemanticLogger.add_appender(
813
+ appender: :http,
814
+ url: "https://logs-01.loggly.com/inputs/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/tag/semantic_logger/"
815
+ )
816
+ ~~~
817
+
818
+ Once entries have been sent, open the Loggly web interface, select Search, and start with
819
+ `tag:"semantic_logger"`. In the Field Explorer switch to Grid view and add the columns `host`,
820
+ `duration`, `name`, `level`, and `message`.
821
+
822
+ ### Papertrail
823
+
824
+ Papertrail accepts log entries over TLS TCP in syslog format:
825
+
826
+ ~~~ruby
827
+ config.semantic_logger.add_appender(
828
+ appender: :syslog,
829
+ url: "tcp://something.papertrailapp.com:1234",
830
+ tcp_client: {
831
+ ssl: {
832
+ ca_file: File.join(Rails.root, "config", "papertrail-bundle.pem")
833
+ }
834
+ }
835
+ )
836
+ ~~~
837
+
838
+ See [Papertrail's documentation](http://help.papertrailapp.com/kb/configuration/encrypting-remote-syslog-with-tls-ssl/)
839
+ for more.
840
+
841
+ ## Error and exception monitoring
842
+
843
+ > **Note:** These appenders forward the payload as supplied. Take care not to push sensitive
844
+ > information in tags or a payload.
845
+
846
+ ### Bugsnag
847
+
848
+ Forward `:info`, `:warn`, or `:error` entries to Bugsnag (requires the `bugsnag` gem). Configure
849
+ Bugsnag following the
850
+ [Ruby Bugsnag documentation](https://bugsnag.com/docs/notifiers/ruby#sending-handled-exceptions), then
851
+ add the appender, choosing the minimum level to forward:
852
+
853
+ ~~~ruby
854
+ # :error and above (the default)
855
+ SemanticLogger.add_appender(appender: :bugsnag)
856
+
857
+ # :warn and above
858
+ SemanticLogger.add_appender(appender: :bugsnag, level: :warn)
859
+
860
+ # :info and above
861
+ SemanticLogger.add_appender(appender: :bugsnag, level: :info)
862
+ ~~~
863
+
864
+ ### Sentry
865
+
866
+ Use the `sentry-ruby` gem and the corresponding appender:
867
+
868
+ ~~~ruby
869
+ SemanticLogger.add_appender(appender: :sentry_ruby)
870
+ ~~~
871
+
872
+ > **Deprecated:** the older `appender: :sentry` (backed by the end-of-life `sentry-raven` gem) still
873
+ > works but is deprecated and will be removed in v6. Switch to `appender: :sentry_ruby`.
874
+
875
+ Some logging context is forwarded to Sentry:
876
+
877
+ * From named tags, `transaction_name`. See
878
+ <https://docs.sentry.io/platforms/ruby/enriching-events/transaction-name/>.
879
+ * From named tags, `user` is built from the `user_id`, `username`, `user_email`, and
880
+ `ip_address` keys, plus any `user` key that is itself a hash. See
881
+ <https://docs.sentry.io/platforms/ruby/enriching-events/identify-user/>.
882
+ * From named tags, `fingerprint` (an Array of Strings, Symbols, or numbers) configures grouping
883
+ granularity. See
884
+ <https://docs.sentry.io/platforms/ruby/usage/sdk-fingerprinting/>.
885
+ * Named tags are sent as Sentry tags. See
886
+ <https://docs.sentry.io/platforms/ruby/enriching-events/tags/>.
887
+ * Unnamed tags are sent as the `:tag` tag, separated by commas.
888
+ * Everything else from the payload and context is added to `extras`, including a value of the
889
+ wrong type for one of the keys above.
890
+
891
+ > **Deprecated:** the `user` and `fingerprint` keys are also still taken from the payload, but that
892
+ > is deprecated and will be removed in v6, since the payload often holds untrusted data, such as a
893
+ > request body, that could set the user an error is reported for. Set them with named tags. The
894
+ > named tags win over the payload, and a user field is only taken from the payload when it is a
895
+ > String, Symbol, or Integer.
896
+
897
+ ~~~ruby
898
+ SemanticLogger.tagged(transaction_name: "foo", user_id: 42, username: "joe", fingerprint: ["bar"], baz: "quz") do
899
+ logger.error("some message")
900
+ end
901
+ ~~~
902
+
903
+ ### Honeybadger and Honeybadger Insights
904
+
905
+ Forward errors to Honeybadger (requires the `honeybadger` gem):
906
+
907
+ ~~~ruby
908
+ SemanticLogger.add_appender(appender: :honeybadger)
909
+ ~~~
910
+
911
+ Or forward all log entries to Honeybadger Insights as events:
912
+
913
+ ~~~ruby
914
+ SemanticLogger.add_appender(appender: :honeybadger_insights)
915
+ ~~~
916
+
917
+ Both appenders use the Honeybadger
918
+ [gem configuration](https://docs.honeybadger.io/lib/ruby/gem-reference/configuration/).
919
+
920
+ ### NewRelic
921
+
922
+ New Relic supports Error Events on both its paid and free plans. The appender sends `:error` and
923
+ `:fatal` entries to New Relic as error events by default (requires the `newrelic_rpm` gem):
924
+
925
+ ~~~ruby
926
+ SemanticLogger.add_appender(appender: :new_relic)
927
+
928
+ # To also send warnings:
929
+ SemanticLogger.add_appender(appender: :new_relic, level: :warn)
930
+ ~~~
931
+
932
+ > **Deprecated:** `appender: :new_relic` (error events via `newrelic_rpm`) is deprecated and will be
933
+ > removed in v6. To forward log entries to New Relic, use `appender: :new_relic_logs` instead.
934
+
935
+ ### Rollbar
936
+
937
+ Rollbar needs its own current-thread context, so it cannot run as a regular appender unless logging is
938
+ [synchronous](operations.html#synchronous-operation). Integrate it with an `on_log` subscriber instead
939
+ (as recommended by @gingerlime):
940
+
941
+ ~~~ruby
942
+ SemanticLogger.on_log do |log|
943
+ next unless log.try(:level) == :error
944
+
945
+ err = RuntimeError.new(log.try(:message))
946
+ err.set_backtrace(log.backtrace) if log.backtrace
947
+ Rollbar.error(err, :log_extra => log.to_h)
948
+ end
949
+ ~~~
950
+
951
+ It sends the whole entry, payload included, so register it after any `on_log` subscriber that
952
+ [redacts sensitive data](security.html#redacting-sensitive-data): subscribers run in the order they
953
+ were registered.
954
+
955
+ ## Databases and message queues
956
+
957
+ ### MongoDB
958
+
959
+ Write log entries as documents into a MongoDB capped collection (requires the `mongo` gem):
960
+
961
+ ~~~ruby
962
+ appender = SemanticLogger::Appender::MongoDB.new(
963
+ uri: "mongodb://127.0.0.1:27017/test",
964
+ collection_size: 1024**3, # 1 gigabyte
965
+ application: Rails.application.class.name
966
+ )
967
+ SemanticLogger.add_appender(appender: appender)
968
+ ~~~
969
+
970
+ Each entry is stored as a document:
971
+
972
+ ~~~javascript
973
+ > db.semantic_logger.findOne()
974
+ {
975
+ "_id" : ObjectId("53a8d5b99b9eb4f282000001"),
976
+ "time" : ISODate("2014-06-24T01:34:49.489Z"),
977
+ "host_name" : "appserver1",
978
+ "pid" : null,
979
+ "thread_name" : "2160245740",
980
+ "name" : "Example",
981
+ "level" : "info",
982
+ "level_index" : 2,
983
+ "application" : "my_application",
984
+ "message" : "This message is written to mongo as a document"
985
+ }
986
+ ~~~
987
+
988
+ ### Apache Kafka
989
+
990
+ Publish log entries to an Apache Kafka broker (requires the `ruby-kafka` gem):
991
+
992
+ ~~~ruby
993
+ SemanticLogger.add_appender(
994
+ appender: :kafka,
995
+ seed_brokers: ["kafka1:9092", "kafka2:9092"]
996
+ )
997
+ ~~~
998
+
999
+ ### RabbitMQ (AMQP)
1000
+
1001
+ Stream log entries through a queue on a RabbitMQ broker (requires the `bunny` gem):
1002
+
1003
+ ~~~ruby
1004
+ SemanticLogger.add_appender(
1005
+ appender: :rabbitmq,
1006
+ queue_name: "semantic_logger",
1007
+ rabbitmq_host: "localhost",
1008
+ username: "the-username",
1009
+ password: "the-password"
1010
+ )
1011
+ ~~~
1012
+
1013
+ ## Logging to several destinations at once
1014
+
1015
+ Add as many appenders as you like; every entry is written to all of them. Use a per-appender `level`
1016
+ so each destination keeps a different subset.
1017
+
1018
+ ~~~ruby
1019
+ require "semantic_logger"
1020
+
1021
+ SemanticLogger.default_level = :info
1022
+
1023
+ # Everything at :warn and above to one file:
1024
+ SemanticLogger.add_appender(file_name: "log/warnings.log", level: :warn)
1025
+
1026
+ # Everything at :trace and above to another:
1027
+ SemanticLogger.add_appender(file_name: "log/trace.log", level: :trace)
1028
+
1029
+ logger = SemanticLogger["MyClass"]
1030
+ logger.level = :trace
1031
+ logger.trace "This is a trace message"
1032
+ logger.info "This is an info message"
1033
+ logger.warn "This is a warning message"
1034
+ ~~~
1035
+
1036
+ Each file receives only the entries at or above its level:
1037
+
1038
+ ~~~
1039
+ ==> trace.log <==
1040
+ 2013-08-02 14:15:56.733532 T [35669:70176909690580] MyClass -- This is a trace message
1041
+ 2013-08-02 14:15:56.734273 I [35669:70176909690580] MyClass -- This is an info message
1042
+ 2013-08-02 14:15:56.735273 W [35669:70176909690580] MyClass -- This is a warning message
1043
+
1044
+ ==> warnings.log <==
1045
+ 2013-08-02 14:15:56.735273 W [35669:70176909690580] MyClass -- This is a warning message
1046
+ ~~~
1047
+
1048
+ A common combination is colorized text to a local file plus a remote aggregator:
1049
+
1050
+ ~~~ruby
1051
+ SemanticLogger.add_appender(file_name: "development.log", formatter: :color)
1052
+ SemanticLogger.add_appender(appender: :syslog, url: "tcp://myloghost:514")
1053
+ ~~~
1054
+
1055
+ ## Standalone appenders
1056
+
1057
+ An appender can be created and logged to directly, using the same API as any logger. This is useful to
1058
+ send specific activity on the current thread to a separate destination, without writing it to the
1059
+ appenders registered with Semantic Logger:
1060
+
1061
+ ~~~ruby
1062
+ require "semantic_logger"
1063
+
1064
+ appender = SemanticLogger::Appender::File.new("separate.log", level: :info, formatter: :color)
1065
+
1066
+ appender.warn "Only send this to separate.log"
1067
+
1068
+ appender.measure_info "Called supplier" do
1069
+ # Call supplier ...
1070
+ end
1071
+ ~~~
1072
+
1073
+ Note: once an appender has been registered with Semantic Logger, do not also call it directly.
1074
+ Non-deterministic concurrency issues arise when it is used across threads.