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/index.md ADDED
@@ -0,0 +1,223 @@
1
+ ---
2
+ layout: default
3
+ heading: What is Semantic Logger?
4
+ mermaid: true
5
+ ---
6
+
7
+
8
+ **Contents**
9
+
10
+ * TOC
11
+ {:toc}
12
+
13
+ Semantic Logger is a high-performance, asynchronous structured logging framework
14
+ for Ruby and Rails.
15
+
16
+ It does two things that ordinary loggers do not:
17
+
18
+ 1. **It logs structured data, not just strings.** Along with the usual text message you can
19
+ attach a payload (any Hash), an exception, a duration, metrics, and tags. That data is
20
+ preserved all the way to the destination, so it stays searchable instead of being flattened
21
+ into a line of text.
22
+ 2. **It logs asynchronously.** Log events are placed on an in-memory queue and written to their
23
+ destinations by a separate background thread. Your application is not blocked while logs are
24
+ written to disk, a database, or a remote service.
25
+
26
+ If you are using Rails, use the companion gem
27
+ [rails_semantic_logger](https://github.com/reidmorrison/rails_semantic_logger), which replaces
28
+ the Rails logger automatically. See the [Rails guide](rails.html).
29
+
30
+ ## Why use it?
31
+
32
+ ### The problem with traditional logging
33
+
34
+ With the standard Ruby logger you eventually end up building messages by hand:
35
+
36
+ ~~~ruby
37
+ logger.info("Queried users table in #{duration} ms, with a result code of #{result}")
38
+ ~~~
39
+
40
+ That reads fine for a human, but to a machine it is just a string. To answer a question like
41
+ "show me every query that took longer than 100 ms" you have to write fragile regular expressions
42
+ against your log files, and every developer formats their messages slightly differently.
43
+
44
+ ### The Semantic Logger way
45
+
46
+ Log the message and the data separately:
47
+
48
+ ~~~ruby
49
+ logger.info("Queried users table",
50
+ duration: duration,
51
+ result: result,
52
+ table: "users",
53
+ action: "query")
54
+ ~~~
55
+
56
+ Now the same event is still readable by a human, but a machine can index `duration`, `result`,
57
+ `table`, and `action` as real fields. Send it to a JSON file, Elasticsearch, or Splunk and you can
58
+ search, filter, and build dashboards on those fields directly, no log parsing required.
59
+
60
+ ### Reasons developers choose Semantic Logger
61
+
62
+ * **Fast.** Logging happens on a background thread, so even logging thousands of lines per second
63
+ does not slow your application down. See [how it works](#how-it-works) below.
64
+ * **Structured.** Every log entry can carry a payload, exception, duration, metrics, and tags
65
+ without losing that structure.
66
+ * **Human and machine readable at the same time.** Colorized text for developers, JSON for your
67
+ log aggregator, written from the same log call.
68
+ * **Many destinations at once.** Write to a file, the screen, and a remote service simultaneously,
69
+ each with its own log level and format. See [Appenders](appenders.html).
70
+ * **A familiar API.** It supports the standard `debug`/`info`/`warn`/`error`/`fatal` API, so
71
+ existing code and other gems keep working. You mostly just change how the logger is created.
72
+ * **Built for production.** Per-class log levels, change the log level of a running process with a
73
+ [signal](operations.html#linux-signals), tagged logging for tracing requests across threads, automatic exception
74
+ capture, and [metrics](metrics.html) for dashboards.
75
+
76
+ ## Quick start
77
+
78
+ Install the gem:
79
+
80
+ ~~~bash
81
+ gem install semantic_logger
82
+ ~~~
83
+
84
+ Or add it to your `Gemfile`:
85
+
86
+ ~~~ruby
87
+ gem "semantic_logger"
88
+ ~~~
89
+
90
+ Configure it once when your application starts, then log:
91
+
92
+ ~~~ruby
93
+ require "semantic_logger"
94
+
95
+ # Log :info and above. Use :trace, :debug, :info, :warn, :error, or :fatal.
96
+ SemanticLogger.default_level = :info
97
+
98
+ # Send log output to the screen using the colorized formatter.
99
+ SemanticLogger.add_appender(io: $stdout, formatter: :color)
100
+
101
+ # Create a logger for the current class. The class name is added to every message.
102
+ logger = SemanticLogger["MyApp"]
103
+
104
+ logger.info("Application started")
105
+ ~~~
106
+
107
+ That is the whole setup. The [Programmer's Guide](api.html) covers the full logging API.
108
+
109
+ ## A tour of the features
110
+
111
+ The examples below assume you already have a `logger` from `SemanticLogger["MyApp"]`.
112
+
113
+ #### Log a message with structured data
114
+
115
+ ~~~ruby
116
+ logger.error("Outbound call failed", result: :failed, reason_code: -10)
117
+ ~~~
118
+
119
+ #### Log an exception
120
+
121
+ Pass the exception directly. The class, message, and backtrace are all captured, including any
122
+ nested "caused by" exceptions.
123
+
124
+ ~~~ruby
125
+ begin
126
+ # ... code that may raise
127
+ rescue => exception
128
+ logger.error("Import failed", exception)
129
+ end
130
+ ~~~
131
+
132
+ #### Only evaluate expensive messages when needed
133
+
134
+ Pass a block. It runs only when the log level is active, so it is skipped entirely in production
135
+ when the level is higher.
136
+
137
+ ~~~ruby
138
+ logger.debug { "Processed a total of #{records.sum(&:size)} bytes" }
139
+ ~~~
140
+
141
+ #### Measure how long something takes
142
+
143
+ The message is written when the block completes, together with its duration. If the block raises,
144
+ the exception is logged with the duration and then re-raised.
145
+
146
+ ~~~ruby
147
+ logger.measure_info("Called external interface") do
148
+ # Code to call the external service ...
149
+ end
150
+ ~~~
151
+
152
+ #### Tag a block of related log entries
153
+
154
+ Every log entry inside the block carries the tags, which is invaluable for tracing one request
155
+ across many classes and threads.
156
+
157
+ ~~~ruby
158
+ SemanticLogger.tagged(user: "jbloggs", request_id: "abc123") do
159
+ logger.info("Hello World")
160
+ logger.debug("More detail")
161
+ end
162
+ ~~~
163
+
164
+ #### Name your threads
165
+
166
+ So concurrent log entries are easy to tell apart.
167
+
168
+ ~~~ruby
169
+ Thread.current.name = "worker-1"
170
+ ~~~
171
+
172
+ ## How it works
173
+
174
+ Every logger forwards its log events to a single, shared background thread through an in-memory
175
+ queue. That thread writes each event to every registered appender (destination) in turn. Because
176
+ the writing happens off to the side, the thread that called `logger.info` returns immediately.
177
+
178
+ ~~~mermaid
179
+ flowchart LR
180
+ subgraph callers["Application threads"]
181
+ direction TB
182
+ A["OrderProcessor<br/>logger.info"]
183
+ B["PaymentGateway<br/>logger.measure_info"]
184
+ C["Rails<br/>logger.error"]
185
+ end
186
+
187
+ Q["In-memory queue<br/>max_queue_size: 10,000"]
188
+ P(["Processor<br/>one background thread"])
189
+
190
+ subgraph appenders["Appenders"]
191
+ direction TB
192
+ D["Screen"]
193
+ E["File"]
194
+ F["Elasticsearch"]
195
+ G["Sentry"]
196
+ end
197
+
198
+ A -->|"enqueue, return"| Q
199
+ B --> Q
200
+ C --> Q
201
+ Q --> P
202
+ P --> D
203
+ P --> E
204
+ P --> F
205
+ P --> G
206
+ ~~~
207
+
208
+ This design is why Semantic Logger is both fast and thread safe: log calls from hundreds of
209
+ concurrent threads simply enqueue events, and each appender writes them out sequentially in the
210
+ correct order. For the rare cases where you want logging to happen inline on the calling thread,
211
+ see [Synchronous Operation](operations.html#synchronous-operation).
212
+
213
+ This design is also tunable for production workloads. The in-memory queue is capped by default
214
+ (blocking when full so that no log message is lost), but it can instead drop messages to protect
215
+ application availability, give a slow appender its own thread, retry transient appender failures,
216
+ and be monitored at runtime. See
217
+ [Performance and reliability tuning](operations.html#performance-and-reliability-tuning) in the
218
+ Configuration guide.
219
+
220
+ ### Ruby Support
221
+
222
+ For the complete list of supported Ruby versions, see
223
+ the [Testing file](https://github.com/reidmorrison/semantic_logger/blob/main/.github/workflows/ci.yml).
data/docs/log.md ADDED
@@ -0,0 +1,116 @@
1
+ ---
2
+ layout: default
3
+ title: Log Event
4
+ description: >-
5
+ The Log event object that carries every entry through the pipeline: which
6
+ fields are always present, its attributes and helper methods, and how to
7
+ inspect one.
8
+ ---
9
+
10
+
11
+ **Contents**
12
+
13
+ * TOC
14
+ {:toc}
15
+
16
+ Every call to a logger (`logger.info`, `logger.measure_error`, and so on) builds a single **Log
17
+ event** object. That object is the unit of work that flows through the whole pipeline: it is
18
+ handed to each [filter](config.html#filtering), then to each appender's [formatter](config.html#custom-formatters), and
19
+ finally written out by the [appender](appenders.html) itself, usually on a background thread so the
20
+ calling application is not blocked.
21
+
22
+ You meet the Log event whenever you customize Semantic Logger:
23
+
24
+ * A **filter** Proc receives it and returns `true` to keep the entry. See [Filtering](config.html#filtering).
25
+ * A **formatter** receives it and turns it into the final output (text, JSON, ...). See [Custom formatters](config.html#custom-formatters).
26
+ * A custom **appender**'s `#log(log)` method receives it and writes it to a destination.
27
+
28
+ In every one of those places you are reading the same object documented here.
29
+
30
+ ### The event is mutable
31
+
32
+ The Log event is a plain mutable Ruby object: every attribute below has both a reader and a
33
+ writer. All appenders share one event instance per log call, so change it only before it is handed
34
+ to them: in an [`on_log`](config.html#capturing-context-with-on_log) subscriber, which runs for
35
+ every entry, or in the `filter` of a logger instance, which runs for that logger's entries. For
36
+ example, to add context to every entry:
37
+
38
+ ```ruby
39
+ SemanticLogger.on_log do |log|
40
+ log.set_context(:request_id, Thread.current[:request_id])
41
+ end
42
+ ```
43
+
44
+ To redact sensitive data, see [Redacting sensitive data](security.html#redacting-sensitive-data).
45
+
46
+ Do not change the event in an appender's `filter` or in a formatter: the appenders that ran before
47
+ it have already written the event, and one that runs asynchronously may be reading it on its own
48
+ thread at the same time.
49
+
50
+ ### Which fields are always present
51
+
52
+ A handful of attributes are populated for **every** event:
53
+
54
+ * `level`, `level_index`, `name`, `time`, `thread_name`, `tags`, `named_tags`.
55
+
56
+ The rest are **situational**: they are set only when relevant to that particular call. For example
57
+ `duration` is present only for `measure_*` calls, `exception` only when an exception was logged,
58
+ `metric`/`metric_amount`/`dimensions` only for metrics, and `backtrace` only when backtrace
59
+ capture is enabled (`SemanticLogger.backtrace_level`). Always guard against `nil` when reading a
60
+ situational field.
61
+
62
+ ### Attributes
63
+
64
+ |Attribute|Type|Description|
65
+ |---------|----|-----------|
66
+ |`level`|`Symbol`|Log level of the call: `:trace`, `:debug`, `:info`, `:warn`, `:error`, `:fatal`.|
67
+ |`level_index`|`Integer`|Numeric form of the level for fast comparisons: `:trace=>0`, `:debug=>1`, `:info=>2`, `:warn=>3`, `:error=>4`, `:fatal=>5`.|
68
+ |`name`|`String`|Class or component name supplied to the logger, for example the `"MyClass"` in `SemanticLogger["MyClass"]`.|
69
+ |`message`|`String`|The text message. May be `nil` (for example, a metric-only event).|
70
+ |`payload`|`Hash`|Structured data logged alongside the message. `nil` when none was supplied.|
71
+ |`time`|`Time`|The moment the event was created.|
72
+ |`thread_name`|`String`|Name (or id) of the thread that made the log call.|
73
+ |`tags`|`Array<String>`|Tags active on the thread when the call was made.|
74
+ |`named_tags`|`Hash<String, Object>`|Named tags active on the thread when the call was made.|
75
+ |`context`|`Hash`|Named contexts captured in-line at the point the event was created. `nil` when none.|
76
+ |`duration`|`Float`|Time in milliseconds taken by a `measure_*` call. `nil` for ordinary log calls.|
77
+ |`exception`|`Exception`|The Ruby exception that was logged. `nil` when none. Use `each_exception` to walk a `cause` chain.|
78
+ |`backtrace`|`Array<String>`|Backtrace captured at the call site, present only when the level is at or above `SemanticLogger.backtrace_level`.|
79
+ |`metric`|`String`|The metric name, present for metric and `measure_*` calls. See [Metrics](metrics.html).|
80
+ |`metric_amount`|`Float`|Numeric amount for a counter or gauge metric, for example the quantity purchased.|
81
+ |`dimensions`|`Hash`|Dimensions (key/value labels) supplied for a metric.|
82
+
83
+ ### Helper methods
84
+
85
+ These read-only helpers derive convenient values from the attributes above:
86
+
87
+ |Method|Returns|Description|
88
+ |------|-------|-----------|
89
+ |`payload?`|`true`/`false`|Whether the event carries a non-empty payload.|
90
+ |`payload_to_s`|`String` or `nil`|The payload rendered with `inspect`, or `nil` when absent.|
91
+ |`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.|
92
+ |`duration_to_s`|`String` or `nil`|The duration in milliseconds as a short string, for example `"12ms"`. `nil` when there is no duration.|
93
+ |`duration_human`|`String` or `nil`|The duration in human readable form, scaling up to seconds, minutes, hours, or days as needed.|
94
+ |`level_to_s`|`String`|The level as a single upper-case character, for example `"I"` for `:info`.|
95
+ |`cleansed_message`|`String`|The message with Rails/ANSI color escape codes stripped. Used to keep terminal codes out of structured output.|
96
+ |`each_exception`|Enumerator|Iterates the exception and its nested `cause` chain, yielding `(exception, depth)`.|
97
+ |`backtrace_to_s`|`String`|The exception backtrace as a string, including every exception in the `cause` chain. With a block, each cause's message and each backtrace line is replaced by the value the block returns, for example to escape it.|
98
+ |`exception_details(exception, source: false)`|`String` or `nil`|The detail Ruby's `Exception#detailed_message` adds to the message of `exception` (this event's exception or one of its causes), such as did_you_mean suggestions, without the class name. `source: true` also includes error_highlight's source snippet. `nil` when there is none. Cached on the event.|
99
+ |`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.|
100
+ |`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.|
101
+ |`set_context(key, value)`|`Hash`|Lazily initializes `context` and stores a key/value pair on it.|
102
+
103
+ ### Inspecting an event
104
+
105
+ When writing a filter or formatter, the quickest way to see what a real event contains is to dump
106
+ it with `to_h`:
107
+
108
+ ```ruby
109
+ SemanticLogger.add_appender(
110
+ io: $stdout,
111
+ filter: ->(log) do
112
+ pp log.to_h # one-off: inspect the event during development
113
+ true
114
+ end
115
+ )
116
+ ```
data/docs/metrics.md ADDED
@@ -0,0 +1,171 @@
1
+ ---
2
+ layout: default
3
+ title: Metrics
4
+ description: >-
5
+ Emit named numeric metrics alongside log entries: counters, durations,
6
+ breaking a metric down with dimensions, and sending them on to a metrics
7
+ backend.
8
+ ---
9
+
10
+
11
+ **Contents**
12
+
13
+ * TOC
14
+ {:toc}
15
+
16
+ A **metric** is a named number that Semantic Logger emits alongside a log entry, so the same call
17
+ that records *what happened* can also feed your dashboards and alerts.
18
+
19
+ Use metrics to track things like:
20
+
21
+ * How often an event happens (an error, a sign-up, a cache miss).
22
+ * How long something takes (an external API call, a database query).
23
+ * Running totals, such as the amount purchased per department over time.
24
+
25
+ There are two parts to using metrics:
26
+
27
+ 1. **Emit** a metric by adding the `:metric` option to any log or `measure_` call. This is covered in
28
+ Steps 1 to 3 below.
29
+ 2. **Subscribe** a destination (Statsd, New Relic, SignalFx, and so on) that receives those metrics.
30
+ Until a subscriber is registered, the `:metric` option is harmless and the number goes nowhere.
31
+ See [Send metrics to a backend](#send-metrics-to-a-backend).
32
+
33
+ Metric subscribers are notified asynchronously on the background log thread, so emitting a metric
34
+ never slows the calling thread. A metric rides along with its log entry, so it obeys the same level
35
+ and [filtering](config.html#filtering) rules: a metric on a `:trace` call is not emitted while the
36
+ level is `:info`.
37
+
38
+ > **Not to be confused with operational stats.** The metrics on this page are *application* metrics
39
+ > that your code emits about what it is doing. To monitor Semantic Logger's *own* health instead,
40
+ > such as queue sizes and the number of log entries processed or dropped, use
41
+ > [`SemanticLogger.stats`](operations.html#monitoring-the-background-thread).
42
+
43
+ ## Step 1: Count something
44
+
45
+ The simplest metric counts how often something happens. Add `:metric` with a name to any log call,
46
+ and the metric is incremented by 1:
47
+
48
+ ~~~ruby
49
+ logger.info(message: "Signed up", metric: "users/signup")
50
+ ~~~
51
+
52
+ Supply `:metric_amount` to change the amount. To decrement:
53
+
54
+ ~~~ruby
55
+ logger.info(message: "Item removed", metric: "cart/items", metric_amount: -1)
56
+ ~~~
57
+
58
+ To add to a running total, such as the dollar amount of purchases per department:
59
+
60
+ ~~~ruby
61
+ logger.info(message: "Purchase complete", metric: "departments/clothing", metric_amount: 189.42)
62
+ ~~~
63
+
64
+ Note: Statsd counters are integers, so float amounts are rounded to the nearest integer.
65
+
66
+ ## Step 2: Measure how long something takes
67
+
68
+ A duration metric records elapsed time. The easiest way is to add `:metric` to a `measure_` block
69
+ (see [Measure how long something takes](api.html#step-6-measure-how-long-something-takes) in the
70
+ Guide). The measured duration becomes the metric:
71
+
72
+ ~~~ruby
73
+ logger.measure_info("Called supplier", metric: "supplier/add_user") do
74
+ # Code to call the external service ...
75
+ end
76
+ ~~~
77
+
78
+ When you already have the duration, emit it on a plain log call by supplying both `:metric` and
79
+ `:duration` (in milliseconds):
80
+
81
+ ~~~ruby
82
+ logger.info(message: "Called supplier", metric: "supplier/add_user", duration: 100.23)
83
+ ~~~
84
+
85
+ ## Step 3: Break a metric down with dimensions
86
+
87
+ Dimensions are key/value labels attached to a metric, so a backend that supports them (such as
88
+ SignalFx) can slice it, for example by user, action, or state. Add them with the `:dimensions`
89
+ option:
90
+
91
+ ~~~ruby
92
+ # A counter, broken down by user:
93
+ logger.info(metric: "filters.count", dimensions: {user: "jbloggs"})
94
+
95
+ # A gauge with an amount and dimensions:
96
+ logger.info(metric: "filters.average", metric_amount: 1.2, dimensions: {user: "jbloggs"})
97
+ ~~~
98
+
99
+ Not every backend supports dimensions. **Statsd** and **New Relic** ignore any metric that carries
100
+ dimensions; **SignalFx** is built around them. See each subscriber below.
101
+
102
+ ## Send metrics to a backend
103
+
104
+ A metric goes nowhere until a **metric subscriber** is registered. Add one with
105
+ `SemanticLogger.add_appender(metric: ...)`, usually when your application starts. A subscriber
106
+ receives every logged entry that has a `:metric`, asynchronously on the background thread.
107
+
108
+ ### Statsd
109
+
110
+ Send metrics to [Statsd](https://github.com/statsd/statsd) over UDP, which can roll them up and
111
+ forward them to [Graphite](https://graphiteapp.org), MongoDB, and others (requires the
112
+ `statsd-ruby` gem):
113
+
114
+ ~~~ruby
115
+ SemanticLogger.add_appender(metric: :statsd, url: "udp://localhost:8125")
116
+ ~~~
117
+
118
+ Counters are integers (float amounts are rounded). Does not support dimensions.
119
+
120
+ ### New Relic
121
+
122
+ Forward metrics to New Relic so they can be displayed on custom dashboards (requires the
123
+ `newrelic_rpm` gem):
124
+
125
+ ~~~ruby
126
+ SemanticLogger.add_appender(metric: :new_relic)
127
+ ~~~
128
+
129
+ Does not support dimensions.
130
+
131
+ ### SignalFx
132
+
133
+ Forward metrics to [SignalFx](https://www.splunk.com/en_us/products/infrastructure-monitoring.html),
134
+ which is built around dimensions:
135
+
136
+ ~~~ruby
137
+ SemanticLogger.add_appender(metric: :signalfx, token: "SIGNALFX_ORG_ACCESS_TOKEN")
138
+ ~~~
139
+
140
+ `application` and `host` are always sent as dimensions. To also forward specific named tags as
141
+ dimensions whenever they are present on a log entry, list them:
142
+
143
+ ~~~ruby
144
+ SemanticLogger.add_appender(
145
+ metric: :signalfx,
146
+ token: "SIGNALFX_ORG_ACCESS_TOKEN",
147
+ dimensions: [:user_id, :state]
148
+ )
149
+ ~~~
150
+
151
+ When a duration metric has no dimensions, SignalFx receives both a gauge and a counter, so you can
152
+ chart both the timing and the number of occurrences. When dimensions are present, the metric is sent
153
+ as-is.
154
+
155
+ ### Elasticsearch and Splunk
156
+
157
+ These are ordinary log [appenders](appenders.html), not metric subscribers, but a metric is part of
158
+ the log entry, so `metric`, `metric_amount`, and any dimensions are written into the document
159
+ automatically. You can build dashboards on those fields directly, without registering a separate
160
+ metric subscriber.
161
+
162
+ ## How metrics behave
163
+
164
+ **Asynchronous.** Subscribers run on the background logging thread, so emitting a metric never slows
165
+ down the thread that logged it.
166
+
167
+ **Follows the log level.** A metric is emitted only when its log entry is actually logged, so it
168
+ obeys the same log level and filtering as any other entry. Use this to your advantage: leave detailed
169
+ `:trace` level metrics in the code where they stay dormant under an `:info` level, then turn them on
170
+ when needed, for example by sending the `SIGUSR2` [signal](operations.html#linux-signals) to lower
171
+ the log level on a running process.