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/index.md
ADDED
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
---
|
|
2
|
+
layout: default
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
{:.hero-image}
|
|
6
|
+
|
|
7
|
+
## What is Semantic Logger?
|
|
8
|
+
{:.no_toc}
|
|
9
|
+
|
|
10
|
+
**Contents**
|
|
11
|
+
|
|
12
|
+
* TOC
|
|
13
|
+
{:toc}
|
|
14
|
+
|
|
15
|
+
Semantic Logger is a high-performance, asynchronous structured logging framework
|
|
16
|
+
for Ruby and Rails.
|
|
17
|
+
|
|
18
|
+
It does two things that ordinary loggers do not:
|
|
19
|
+
|
|
20
|
+
1. **It logs structured data, not just strings.** Along with the usual text message you can
|
|
21
|
+
attach a payload (any Hash), an exception, a duration, metrics, and tags. That data is
|
|
22
|
+
preserved all the way to the destination, so it stays searchable instead of being flattened
|
|
23
|
+
into a line of text.
|
|
24
|
+
2. **It logs asynchronously.** Log events are placed on an in-memory queue and written to their
|
|
25
|
+
destinations by a separate background thread. Your application is not blocked while logs are
|
|
26
|
+
written to disk, a database, or a remote service.
|
|
27
|
+
|
|
28
|
+
If you are using Rails, use the companion gem
|
|
29
|
+
[rails_semantic_logger](https://github.com/reidmorrison/rails_semantic_logger), which replaces
|
|
30
|
+
the Rails logger automatically. See the [Rails guide](rails.html).
|
|
31
|
+
|
|
32
|
+
## Why use it?
|
|
33
|
+
|
|
34
|
+
### The problem with traditional logging
|
|
35
|
+
|
|
36
|
+
With the standard Ruby logger you eventually end up building messages by hand:
|
|
37
|
+
|
|
38
|
+
~~~ruby
|
|
39
|
+
logger.info("Queried users table in #{duration} ms, with a result code of #{result}")
|
|
40
|
+
~~~
|
|
41
|
+
|
|
42
|
+
That reads fine for a human, but to a machine it is just a string. To answer a question like
|
|
43
|
+
"show me every query that took longer than 100 ms" you have to write fragile regular expressions
|
|
44
|
+
against your log files, and every developer formats their messages slightly differently.
|
|
45
|
+
|
|
46
|
+
### The Semantic Logger way
|
|
47
|
+
|
|
48
|
+
Log the message and the data separately:
|
|
49
|
+
|
|
50
|
+
~~~ruby
|
|
51
|
+
logger.info("Queried users table",
|
|
52
|
+
duration: duration,
|
|
53
|
+
result: result,
|
|
54
|
+
table: "users",
|
|
55
|
+
action: "query")
|
|
56
|
+
~~~
|
|
57
|
+
|
|
58
|
+
Now the same event is still readable by a human, but a machine can index `duration`, `result`,
|
|
59
|
+
`table`, and `action` as real fields. Send it to a JSON file, Elasticsearch, or Splunk and you can
|
|
60
|
+
search, filter, and build dashboards on those fields directly, no log parsing required.
|
|
61
|
+
|
|
62
|
+
### Reasons developers choose Semantic Logger
|
|
63
|
+
|
|
64
|
+
* **Fast.** Logging happens on a background thread, so even logging thousands of lines per second
|
|
65
|
+
does not slow your application down. See [how it works](#how-it-works) below.
|
|
66
|
+
* **Structured.** Every log entry can carry a payload, exception, duration, metrics, and tags
|
|
67
|
+
without losing that structure.
|
|
68
|
+
* **Human and machine readable at the same time.** Colorized text for developers, JSON for your
|
|
69
|
+
log aggregator, written from the same log call.
|
|
70
|
+
* **Many destinations at once.** Write to a file, the screen, and a remote service simultaneously,
|
|
71
|
+
each with its own log level and format. See [Appenders](appenders.html).
|
|
72
|
+
* **A familiar API.** It supports the standard `debug`/`info`/`warn`/`error`/`fatal` API, so
|
|
73
|
+
existing code and other gems keep working. You mostly just change how the logger is created.
|
|
74
|
+
* **Built for production.** Per-class log levels, change the log level of a running process with a
|
|
75
|
+
[signal](operations.html#linux-signals), tagged logging for tracing requests across threads, automatic exception
|
|
76
|
+
capture, and [metrics](metrics.html) for dashboards.
|
|
77
|
+
|
|
78
|
+
## Quick start
|
|
79
|
+
|
|
80
|
+
Install the gem:
|
|
81
|
+
|
|
82
|
+
~~~bash
|
|
83
|
+
gem install semantic_logger
|
|
84
|
+
~~~
|
|
85
|
+
|
|
86
|
+
Or add it to your `Gemfile`:
|
|
87
|
+
|
|
88
|
+
~~~ruby
|
|
89
|
+
gem "semantic_logger"
|
|
90
|
+
~~~
|
|
91
|
+
|
|
92
|
+
Configure it once when your application starts, then log:
|
|
93
|
+
|
|
94
|
+
~~~ruby
|
|
95
|
+
require "semantic_logger"
|
|
96
|
+
|
|
97
|
+
# Log :info and above. Use :trace, :debug, :info, :warn, :error, or :fatal.
|
|
98
|
+
SemanticLogger.default_level = :info
|
|
99
|
+
|
|
100
|
+
# Send log output to the screen using the colorized formatter.
|
|
101
|
+
SemanticLogger.add_appender(io: $stdout, formatter: :color)
|
|
102
|
+
|
|
103
|
+
# Create a logger for the current class. The class name is added to every message.
|
|
104
|
+
logger = SemanticLogger["MyApp"]
|
|
105
|
+
|
|
106
|
+
logger.info("Application started")
|
|
107
|
+
~~~
|
|
108
|
+
|
|
109
|
+
That is the whole setup. The [Programmer's Guide](api.html) covers the full logging API.
|
|
110
|
+
|
|
111
|
+
## A tour of the features
|
|
112
|
+
|
|
113
|
+
The examples below assume you already have a `logger` from `SemanticLogger["MyApp"]`.
|
|
114
|
+
|
|
115
|
+
#### Log a message with structured data
|
|
116
|
+
|
|
117
|
+
~~~ruby
|
|
118
|
+
logger.error("Outbound call failed", result: :failed, reason_code: -10)
|
|
119
|
+
~~~
|
|
120
|
+
|
|
121
|
+
#### Log an exception
|
|
122
|
+
|
|
123
|
+
Pass the exception directly. The class, message, and backtrace are all captured, including any
|
|
124
|
+
nested "caused by" exceptions.
|
|
125
|
+
|
|
126
|
+
~~~ruby
|
|
127
|
+
begin
|
|
128
|
+
# ... code that may raise
|
|
129
|
+
rescue => exception
|
|
130
|
+
logger.error("Import failed", exception)
|
|
131
|
+
end
|
|
132
|
+
~~~
|
|
133
|
+
|
|
134
|
+
#### Only evaluate expensive messages when needed
|
|
135
|
+
|
|
136
|
+
Pass a block. It runs only when the log level is active, so it is skipped entirely in production
|
|
137
|
+
when the level is higher.
|
|
138
|
+
|
|
139
|
+
~~~ruby
|
|
140
|
+
logger.debug { "Processed a total of #{records.sum(&:size)} bytes" }
|
|
141
|
+
~~~
|
|
142
|
+
|
|
143
|
+
#### Measure how long something takes
|
|
144
|
+
|
|
145
|
+
The message is written when the block completes, together with its duration. If the block raises,
|
|
146
|
+
the exception is logged with the duration and then re-raised.
|
|
147
|
+
|
|
148
|
+
~~~ruby
|
|
149
|
+
logger.measure_info("Called external interface") do
|
|
150
|
+
# Code to call the external service ...
|
|
151
|
+
end
|
|
152
|
+
~~~
|
|
153
|
+
|
|
154
|
+
#### Tag a block of related log entries
|
|
155
|
+
|
|
156
|
+
Every log entry inside the block carries the tags, which is invaluable for tracing one request
|
|
157
|
+
across many classes and threads.
|
|
158
|
+
|
|
159
|
+
~~~ruby
|
|
160
|
+
SemanticLogger.tagged(user: "jbloggs", request_id: "abc123") do
|
|
161
|
+
logger.info("Hello World")
|
|
162
|
+
logger.debug("More detail")
|
|
163
|
+
end
|
|
164
|
+
~~~
|
|
165
|
+
|
|
166
|
+
#### Name your threads
|
|
167
|
+
|
|
168
|
+
So concurrent log entries are easy to tell apart.
|
|
169
|
+
|
|
170
|
+
~~~ruby
|
|
171
|
+
Thread.current.name = "worker-1"
|
|
172
|
+
~~~
|
|
173
|
+
|
|
174
|
+
## How it works
|
|
175
|
+
|
|
176
|
+
Every logger forwards its log events to a single, shared background thread through an in-memory
|
|
177
|
+
queue. That thread writes each event to every registered appender (destination) in turn. Because
|
|
178
|
+
the writing happens off to the side, the thread that called `logger.info` returns immediately.
|
|
179
|
+
|
|
180
|
+

|
|
181
|
+
|
|
182
|
+
This design is why Semantic Logger is both fast and thread safe: log calls from hundreds of
|
|
183
|
+
concurrent threads simply enqueue events, and each appender writes them out sequentially in the
|
|
184
|
+
correct order. For the rare cases where you want logging to happen inline on the calling thread,
|
|
185
|
+
see [Synchronous Operation](operations.html#synchronous-operation).
|
|
186
|
+
|
|
187
|
+
This design is also tunable for production workloads. The in-memory queue is capped by default
|
|
188
|
+
(blocking when full so that no log message is lost), but it can instead drop messages to protect
|
|
189
|
+
application availability, give a slow appender its own thread, retry transient appender failures,
|
|
190
|
+
and be monitored at runtime. See
|
|
191
|
+
[Performance and reliability tuning](operations.html#performance-and-reliability-tuning) in the
|
|
192
|
+
Configuration guide.
|
|
193
|
+
|
|
194
|
+
### Ruby Support
|
|
195
|
+
|
|
196
|
+
For the complete list of supported Ruby versions, see
|
|
197
|
+
the [Testing file](https://github.com/reidmorrison/semantic_logger/blob/main/.github/workflows/ci.yml).
|
data/docs/log.md
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
---
|
|
2
|
+
layout: default
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
## Log Event
|
|
6
|
+
{:.no_toc}
|
|
7
|
+
|
|
8
|
+
**Contents**
|
|
9
|
+
|
|
10
|
+
* TOC
|
|
11
|
+
{:toc}
|
|
12
|
+
|
|
13
|
+
Every call to a logger (`logger.info`, `logger.measure_error`, and so on) builds a single **Log
|
|
14
|
+
event** object. That object is the unit of work that flows through the whole pipeline: it is
|
|
15
|
+
handed to each [filter](config.html#filtering), then to each appender's [formatter](config.html#custom-formatters), and
|
|
16
|
+
finally written out by the [appender](appenders.html) itself, usually on a background thread so the
|
|
17
|
+
calling application is not blocked.
|
|
18
|
+
|
|
19
|
+
You meet the Log event whenever you customize Semantic Logger:
|
|
20
|
+
|
|
21
|
+
* A **filter** Proc receives it and returns `true` to keep the entry. See [Filtering](config.html#filtering).
|
|
22
|
+
* A **formatter** receives it and turns it into the final output (text, JSON, ...). See [Custom formatters](config.html#custom-formatters).
|
|
23
|
+
* A custom **appender**'s `#log(log)` method receives it and writes it to a destination.
|
|
24
|
+
|
|
25
|
+
In every one of those places you are reading (and may modify) the same object documented here.
|
|
26
|
+
|
|
27
|
+
### The event is mutable
|
|
28
|
+
|
|
29
|
+
The Log event is a plain mutable Ruby object: every attribute below has both a reader and a
|
|
30
|
+
writer. Filters and formatters are allowed to change it before it is written, for example to
|
|
31
|
+
redact a message or enrich the payload:
|
|
32
|
+
|
|
33
|
+
```ruby
|
|
34
|
+
filter: ->(log) do
|
|
35
|
+
log.message = log.message.gsub(/\d{16}/, "[REDACTED]")
|
|
36
|
+
true # keep the (now edited) entry
|
|
37
|
+
end
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Because all appenders share one event instance per log call, keep any mutation deliberate: a
|
|
41
|
+
change made in a filter is visible to every appender that runs afterwards.
|
|
42
|
+
|
|
43
|
+
### Which fields are always present
|
|
44
|
+
|
|
45
|
+
A handful of attributes are populated for **every** event:
|
|
46
|
+
|
|
47
|
+
* `level`, `level_index`, `name`, `time`, `thread_name`, `tags`, `named_tags`.
|
|
48
|
+
|
|
49
|
+
The rest are **situational**: they are set only when relevant to that particular call. For example
|
|
50
|
+
`duration` is present only for `measure_*` calls, `exception` only when an exception was logged,
|
|
51
|
+
`metric`/`metric_amount`/`dimensions` only for metrics, and `backtrace` only when backtrace
|
|
52
|
+
capture is enabled (`SemanticLogger.backtrace_level`). Always guard against `nil` when reading a
|
|
53
|
+
situational field.
|
|
54
|
+
|
|
55
|
+
### Attributes
|
|
56
|
+
|
|
57
|
+
|Attribute|Type|Description|
|
|
58
|
+
|---------|----|-----------|
|
|
59
|
+
|`level`|`Symbol`|Log level of the call: `:trace`, `:debug`, `:info`, `:warn`, `:error`, `:fatal`.|
|
|
60
|
+
|`level_index`|`Integer`|Numeric form of the level for fast comparisons: `:trace=>0`, `:debug=>1`, `:info=>2`, `:warn=>3`, `:error=>4`, `:fatal=>5`.|
|
|
61
|
+
|`name`|`String`|Class or component name supplied to the logger, for example the `"MyClass"` in `SemanticLogger["MyClass"]`.|
|
|
62
|
+
|`message`|`String`|The text message. May be `nil` (for example, a metric-only event).|
|
|
63
|
+
|`payload`|`Hash`|Structured data logged alongside the message. `nil` when none was supplied.|
|
|
64
|
+
|`time`|`Time`|The moment the event was created.|
|
|
65
|
+
|`thread_name`|`String`|Name (or id) of the thread that made the log call.|
|
|
66
|
+
|`tags`|`Array<String>`|Tags active on the thread when the call was made.|
|
|
67
|
+
|`named_tags`|`Hash<String, Object>`|Named tags active on the thread when the call was made.|
|
|
68
|
+
|`context`|`Hash`|Named contexts captured in-line at the point the event was created. `nil` when none.|
|
|
69
|
+
|`duration`|`Float`|Time in milliseconds taken by a `measure_*` call. `nil` for ordinary log calls.|
|
|
70
|
+
|`exception`|`Exception`|The Ruby exception that was logged. `nil` when none. Use `each_exception` to walk a `cause` chain.|
|
|
71
|
+
|`backtrace`|`Array<String>`|Backtrace captured at the call site, present only when the level is at or above `SemanticLogger.backtrace_level`.|
|
|
72
|
+
|`metric`|`String`|The metric name, present for metric and `measure_*` calls. See [Metrics](metrics.html).|
|
|
73
|
+
|`metric_amount`|`Float`|Numeric amount for a counter or gauge metric, for example the quantity purchased.|
|
|
74
|
+
|`dimensions`|`Hash`|Dimensions (key/value labels) supplied for a metric.|
|
|
75
|
+
|
|
76
|
+
### Helper methods
|
|
77
|
+
|
|
78
|
+
These read-only helpers derive convenient values from the attributes above:
|
|
79
|
+
|
|
80
|
+
|Method|Returns|Description|
|
|
81
|
+
|------|-------|-----------|
|
|
82
|
+
|`payload?`|`true`/`false`|Whether the event carries a non-empty payload.|
|
|
83
|
+
|`payload_to_s`|`String` or `nil`|The payload rendered with `inspect`, or `nil` when absent.|
|
|
84
|
+
|`metric_only?`|`true`/`false`|`true` when the event has a metric but no message and no exception. Text appenders typically skip these; machine-readable (JSON) appenders usually keep them.|
|
|
85
|
+
|`duration_to_s`|`String` or `nil`|The duration in milliseconds as a short string, for example `"12ms"`. `nil` when there is no duration.|
|
|
86
|
+
|`duration_human`|`String` or `nil`|The duration in human readable form, scaling up to seconds, minutes, hours, or days as needed.|
|
|
87
|
+
|`level_to_s`|`String`|The level as a single upper-case character, for example `"I"` for `:info`.|
|
|
88
|
+
|`cleansed_message`|`String`|The message with Rails/ANSI color escape codes stripped. Used to keep terminal codes out of structured output.|
|
|
89
|
+
|`each_exception`|Enumerator|Iterates the exception and its nested `cause` chain, yielding `(exception, depth)`.|
|
|
90
|
+
|`backtrace_to_s`|`String`|The exception backtrace as a string, including every exception in the `cause` chain.|
|
|
91
|
+
|`file_name_and_line(short_name = false)`|`[String, Integer]` or `nil`|The file name and line number from the event's backtrace or exception. Pass `true` for just the base file name.|
|
|
92
|
+
|`to_h`|`Hash`|The event as a plain Hash (via the Raw formatter), including `host`, `application`, and `environment`. Handy for inspecting an event or building a custom formatter.|
|
|
93
|
+
|`set_context(key, value)`|`Hash`|Lazily initializes `context` and stores a key/value pair on it.|
|
|
94
|
+
|
|
95
|
+
### Inspecting an event
|
|
96
|
+
|
|
97
|
+
When writing a filter or formatter, the quickest way to see what a real event contains is to dump
|
|
98
|
+
it with `to_h`:
|
|
99
|
+
|
|
100
|
+
```ruby
|
|
101
|
+
SemanticLogger.add_appender(
|
|
102
|
+
io: $stdout,
|
|
103
|
+
filter: ->(log) do
|
|
104
|
+
pp log.to_h # one-off: inspect the event during development
|
|
105
|
+
true
|
|
106
|
+
end
|
|
107
|
+
)
|
|
108
|
+
```
|
data/docs/metrics.md
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
---
|
|
2
|
+
layout: default
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
## Metrics
|
|
6
|
+
{:.no_toc}
|
|
7
|
+
|
|
8
|
+
**Contents**
|
|
9
|
+
|
|
10
|
+
* TOC
|
|
11
|
+
{:toc}
|
|
12
|
+
|
|
13
|
+
A **metric** is a named number that Semantic Logger emits alongside a log entry, so the same call
|
|
14
|
+
that records *what happened* can also feed your dashboards and alerts.
|
|
15
|
+
|
|
16
|
+
Use metrics to track things like:
|
|
17
|
+
|
|
18
|
+
* How often an event happens (an error, a sign-up, a cache miss).
|
|
19
|
+
* How long something takes (an external API call, a database query).
|
|
20
|
+
* Running totals, such as the amount purchased per department over time.
|
|
21
|
+
|
|
22
|
+
There are two parts to using metrics:
|
|
23
|
+
|
|
24
|
+
1. **Emit** a metric by adding the `:metric` option to any log or `measure_` call. This is covered in
|
|
25
|
+
Steps 1 to 3 below.
|
|
26
|
+
2. **Subscribe** a destination (Statsd, New Relic, SignalFx, and so on) that receives those metrics.
|
|
27
|
+
Until a subscriber is registered, the `:metric` option is harmless and the number goes nowhere.
|
|
28
|
+
See [Send metrics to a backend](#send-metrics-to-a-backend).
|
|
29
|
+
|
|
30
|
+
Metric subscribers are notified asynchronously on the background log thread, so emitting a metric
|
|
31
|
+
never slows the calling thread. A metric rides along with its log entry, so it obeys the same level
|
|
32
|
+
and [filtering](config.html#filtering) rules: a metric on a `:trace` call is not emitted while the
|
|
33
|
+
level is `:info`.
|
|
34
|
+
|
|
35
|
+
> **Not to be confused with operational stats.** The metrics on this page are *application* metrics
|
|
36
|
+
> that your code emits about what it is doing. To monitor Semantic Logger's *own* health instead,
|
|
37
|
+
> such as queue sizes and the number of log entries processed or dropped, use
|
|
38
|
+
> [`SemanticLogger.stats`](operations.html#monitoring-the-background-thread).
|
|
39
|
+
|
|
40
|
+
## Step 1: Count something
|
|
41
|
+
|
|
42
|
+
The simplest metric counts how often something happens. Add `:metric` with a name to any log call,
|
|
43
|
+
and the metric is incremented by 1:
|
|
44
|
+
|
|
45
|
+
~~~ruby
|
|
46
|
+
logger.info(message: "Signed up", metric: "users/signup")
|
|
47
|
+
~~~
|
|
48
|
+
|
|
49
|
+
Supply `:metric_amount` to change the amount. To decrement:
|
|
50
|
+
|
|
51
|
+
~~~ruby
|
|
52
|
+
logger.info(message: "Item removed", metric: "cart/items", metric_amount: -1)
|
|
53
|
+
~~~
|
|
54
|
+
|
|
55
|
+
To add to a running total, such as the dollar amount of purchases per department:
|
|
56
|
+
|
|
57
|
+
~~~ruby
|
|
58
|
+
logger.info(message: "Purchase complete", metric: "departments/clothing", metric_amount: 189.42)
|
|
59
|
+
~~~
|
|
60
|
+
|
|
61
|
+
Note: Statsd counters are integers, so float amounts are rounded to the nearest integer.
|
|
62
|
+
|
|
63
|
+
## Step 2: Measure how long something takes
|
|
64
|
+
|
|
65
|
+
A duration metric records elapsed time. The easiest way is to add `:metric` to a `measure_` block
|
|
66
|
+
(see [Measure how long something takes](api.html#step-6-measure-how-long-something-takes) in the
|
|
67
|
+
Guide). The measured duration becomes the metric:
|
|
68
|
+
|
|
69
|
+
~~~ruby
|
|
70
|
+
logger.measure_info("Called supplier", metric: "supplier/add_user") do
|
|
71
|
+
# Code to call the external service ...
|
|
72
|
+
end
|
|
73
|
+
~~~
|
|
74
|
+
|
|
75
|
+
When you already have the duration, emit it on a plain log call by supplying both `:metric` and
|
|
76
|
+
`:duration` (in milliseconds):
|
|
77
|
+
|
|
78
|
+
~~~ruby
|
|
79
|
+
logger.info(message: "Called supplier", metric: "supplier/add_user", duration: 100.23)
|
|
80
|
+
~~~
|
|
81
|
+
|
|
82
|
+
## Step 3: Break a metric down with dimensions
|
|
83
|
+
|
|
84
|
+
Dimensions are key/value labels attached to a metric, so a backend that supports them (such as
|
|
85
|
+
SignalFx) can slice it, for example by user, action, or state. Add them with the `:dimensions`
|
|
86
|
+
option:
|
|
87
|
+
|
|
88
|
+
~~~ruby
|
|
89
|
+
# A counter, broken down by user:
|
|
90
|
+
logger.info(metric: "filters.count", dimensions: {user: "jbloggs"})
|
|
91
|
+
|
|
92
|
+
# A gauge with an amount and dimensions:
|
|
93
|
+
logger.info(metric: "filters.average", metric_amount: 1.2, dimensions: {user: "jbloggs"})
|
|
94
|
+
~~~
|
|
95
|
+
|
|
96
|
+
Not every backend supports dimensions. **Statsd** and **New Relic** ignore any metric that carries
|
|
97
|
+
dimensions; **SignalFx** is built around them. See each subscriber below.
|
|
98
|
+
|
|
99
|
+
## Send metrics to a backend
|
|
100
|
+
|
|
101
|
+
A metric goes nowhere until a **metric subscriber** is registered. Add one with
|
|
102
|
+
`SemanticLogger.add_appender(metric: ...)`, usually when your application starts. A subscriber
|
|
103
|
+
receives every logged entry that has a `:metric`, asynchronously on the background thread.
|
|
104
|
+
|
|
105
|
+
### Statsd
|
|
106
|
+
|
|
107
|
+
Send metrics to [Statsd](https://github.com/statsd/statsd) over UDP, which can roll them up and
|
|
108
|
+
forward them to [Graphite](https://graphiteapp.org), MongoDB, and others (requires the
|
|
109
|
+
`statsd-ruby` gem):
|
|
110
|
+
|
|
111
|
+
~~~ruby
|
|
112
|
+
SemanticLogger.add_appender(metric: :statsd, url: "udp://localhost:8125")
|
|
113
|
+
~~~
|
|
114
|
+
|
|
115
|
+
Counters are integers (float amounts are rounded). Does not support dimensions.
|
|
116
|
+
|
|
117
|
+
### New Relic
|
|
118
|
+
|
|
119
|
+
Forward metrics to New Relic so they can be displayed on custom dashboards (requires the
|
|
120
|
+
`newrelic_rpm` gem):
|
|
121
|
+
|
|
122
|
+
~~~ruby
|
|
123
|
+
SemanticLogger.add_appender(metric: :new_relic)
|
|
124
|
+
~~~
|
|
125
|
+
|
|
126
|
+
Does not support dimensions.
|
|
127
|
+
|
|
128
|
+
### SignalFx
|
|
129
|
+
|
|
130
|
+
Forward metrics to [SignalFx](https://www.splunk.com/en_us/products/infrastructure-monitoring.html),
|
|
131
|
+
which is built around dimensions:
|
|
132
|
+
|
|
133
|
+
~~~ruby
|
|
134
|
+
SemanticLogger.add_appender(metric: :signalfx, token: "SIGNALFX_ORG_ACCESS_TOKEN")
|
|
135
|
+
~~~
|
|
136
|
+
|
|
137
|
+
`application` and `host` are always sent as dimensions. To also forward specific named tags as
|
|
138
|
+
dimensions whenever they are present on a log entry, list them:
|
|
139
|
+
|
|
140
|
+
~~~ruby
|
|
141
|
+
SemanticLogger.add_appender(
|
|
142
|
+
metric: :signalfx,
|
|
143
|
+
token: "SIGNALFX_ORG_ACCESS_TOKEN",
|
|
144
|
+
dimensions: [:user_id, :state]
|
|
145
|
+
)
|
|
146
|
+
~~~
|
|
147
|
+
|
|
148
|
+
When a duration metric has no dimensions, SignalFx receives both a gauge and a counter, so you can
|
|
149
|
+
chart both the timing and the number of occurrences. When dimensions are present, the metric is sent
|
|
150
|
+
as-is.
|
|
151
|
+
|
|
152
|
+
### Elasticsearch and Splunk
|
|
153
|
+
|
|
154
|
+
These are ordinary log [appenders](appenders.html), not metric subscribers, but a metric is part of
|
|
155
|
+
the log entry, so `metric`, `metric_amount`, and any dimensions are written into the document
|
|
156
|
+
automatically. You can build dashboards on those fields directly, without registering a separate
|
|
157
|
+
metric subscriber.
|
|
158
|
+
|
|
159
|
+
## How metrics behave
|
|
160
|
+
|
|
161
|
+
**Asynchronous.** Subscribers run on the background logging thread, so emitting a metric never slows
|
|
162
|
+
down the thread that logged it.
|
|
163
|
+
|
|
164
|
+
**Follows the log level.** A metric is emitted only when its log entry is actually logged, so it
|
|
165
|
+
obeys the same log level and filtering as any other entry. Use this to your advantage: leave detailed
|
|
166
|
+
`:trace` level metrics in the code where they stay dormant under an `:info` level, then turn them on
|
|
167
|
+
when needed, for example by sending the `SIGUSR2` [signal](operations.html#linux-signals) to lower
|
|
168
|
+
the log level on a running process.
|