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