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/upgrading.md ADDED
@@ -0,0 +1,697 @@
1
+ ---
2
+ layout: default
3
+ title: Upgrading
4
+ description: >-
5
+ Version-by-version upgrade notes for Semantic Logger, from v4.0 through the
6
+ v5.2 security fixes, remote syslog facility fix, and deprecation of the Splunk SDK appender.
7
+ ---
8
+
9
+
10
+ **Contents**
11
+
12
+ * TOC
13
+ {:toc}
14
+
15
+ ### Upgrading to Semantic Logger v5.2
16
+
17
+ v5.2 sends the configured facility to remote syslog, deprecates the `:splunk` appender, stops
18
+ replacing Ruby's `Thread#name`, and fixes several security issues. None of the security fixes change
19
+ the public API, but a few change the output, which matters if you parse the logs or alert on them,
20
+ and the file appender's `permissions:` option can now make `add_appender` raise for a log file that
21
+ worked in v5.1.
22
+
23
+ #### Set the Sentry user and fingerprint with named tags
24
+
25
+ The `:sentry_ruby` appender takes the Sentry user (`user_id`, `username`, `user_email`, `ip_address`,
26
+ and `user`) and `fingerprint` from named tags or the payload. Taking them from the payload is
27
+ deprecated and will be **removed in v6**, since the payload often holds untrusted data, such as a
28
+ request body, which could then set the user an error is reported for. It emits a deprecation warning
29
+ the first time it happens. Until then, the named tags win over the payload, which no longer replaces
30
+ a user field they set, and only a String, Symbol, or Integer is taken from the payload for a user
31
+ field. Set them with named tags instead, which can now also hold the `fingerprint`:
32
+
33
+ ~~~ruby
34
+ # Before:
35
+ logger.error("Payment failed", user_id: user.id, fingerprint: ["payment-failed"])
36
+
37
+ # After:
38
+ SemanticLogger.tagged(user_id: user.id, fingerprint: ["payment-failed"]) do
39
+ logger.error("Payment failed")
40
+ end
41
+ ~~~
42
+
43
+ #### Replace the `:splunk` appender with `:splunk_http`
44
+
45
+ The `:splunk` appender depends on the `splunk-sdk-ruby` gem, which was last released in 2015 and
46
+ does not verify the server's TLS certificate. It now emits a deprecation warning and will be
47
+ **removed in v6**. Send logs to the Splunk HTTP Event Collector instead:
48
+
49
+ ~~~ruby
50
+ # Before:
51
+ SemanticLogger.add_appender(appender: :splunk, index: "main")
52
+
53
+ # After:
54
+ SemanticLogger.add_appender(
55
+ appender: :splunk_http,
56
+ url: "https://splunk.example.com:8088/services/collector/event",
57
+ token: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
58
+ index: "main"
59
+ )
60
+ ~~~
61
+
62
+ #### Remote syslog sends the configured facility
63
+
64
+ When logging to a remote syslog server over TCP or UDP, earlier versions sent the wrong facility.
65
+ The `facility:` option is documented as one of Ruby's `::Syslog::LOG_*` facility constants, but the
66
+ value was sent as if it were already the syslog protocol's facility number. Those constants are the
67
+ facility number shifted left by 3 bits, so the default `::Syslog::LOG_USER` (8) went out as facility
68
+ 8, which is `uucp`, instead of facility 1, `user`. For example a `:info` message, at the default
69
+ `LOG_NOTICE` severity, was sent as `<69>` rather than `<13>`. Logging to a local syslog daemon was
70
+ not affected.
71
+
72
+ v5.2 sends the facility the constant names. If your syslog server filters or routes on facility,
73
+ for example an rsyslog rule for `uucp.*`, change it to the facility you configured: `user` unless
74
+ you set one.
75
+
76
+ The `facility:` option now only accepts the `::Syslog::LOG_*` facility constants, and raises
77
+ `ArgumentError` for other values when the appender is created. Previously a constant other than
78
+ `::Syslog::LOG_KERN`, `::Syslog::LOG_USER`, or `::Syslog::LOG_MAIL` raised `ArgumentError` while
79
+ logging. If you passed a bare facility number to get around that, switch to the constant. Check
80
+ this even if nothing raises: a bare 0, 8, or 16 is now read as `::Syslog::LOG_KERN`,
81
+ `::Syslog::LOG_USER`, or `::Syslog::LOG_MAIL`, so `16`, which used to send `local0`, now sends
82
+ `mail`:
83
+
84
+ ~~~ruby
85
+ # Before (16 was sent as facility local0, and is now read as ::Syslog::LOG_MAIL):
86
+ SemanticLogger.add_appender(appender: :syslog, url: "udp://myloghost:514", facility: 16)
87
+
88
+ # After:
89
+ SemanticLogger.add_appender(appender: :syslog, url: "udp://myloghost:514", facility: ::Syslog::LOG_LOCAL0)
90
+ ~~~
91
+
92
+ This applies to the `:syslog` and `:syslog_cee` formatters. An application that logs only to a remote
93
+ server, and so does not need the `syslog` gem, can use the same constants from
94
+ `SemanticLogger::Formatters::Syslog`, for example `SemanticLogger::Formatters::Syslog::LOG_LOCAL0`.
95
+
96
+ #### The file appender's `permissions:` refuses a log file it cannot secure
97
+
98
+ With `permissions:`, the file appender only writes to a regular file owned by the process, with a
99
+ single hard link, that the file name names directly rather than through a symbolic link.
100
+ `SemanticLogger.add_appender` now raises `IOError`, giving the reason, for any other log file,
101
+ which v5.1 wrote to, changing the permissions of whatever a link pointed at. So a configuration
102
+ that worked in v5.1 can now fail at boot, for example `permissions:` on a log file that is a
103
+ symbolic link to `/dev/stdout`, or on a log file that another user created. With `permissions:`,
104
+ a log file that cannot be opened also raises `IOError`, instead of the operating system's error,
105
+ such as `Errno::ENOENT`, which is its `cause`. A log file that fails the check, or to open, when it
106
+ is reopened later, on a rotation or after a fork, is reported to the internal logger and not
107
+ written to until it is fixed, instead of raising from the logging thread.
108
+
109
+ If you set `permissions:` on such a log file, either fix the file or remove `permissions:`. To log
110
+ to standard output, use `io: $stdout`:
111
+
112
+ ~~~ruby
113
+ # Before, with log/production.log a symbolic link to /dev/stdout:
114
+ SemanticLogger.add_appender(file_name: "log/production.log", permissions: 0o640)
115
+
116
+ # After:
117
+ SemanticLogger.add_appender(io: $stdout)
118
+ ~~~
119
+
120
+ #### Ruby's own `Thread#name` is no longer replaced
121
+
122
+ Earlier versions replaced `Thread#name` and `Thread#name=` with Semantic Logger's own versions,
123
+ which were written before Ruby had them and kept the name away from Ruby itself. v5.2 leaves Ruby's
124
+ own in place, so the name now also reaches the operating system, profilers, and `Thread#inspect`.
125
+ On MRI, log entries do not change: a thread that has no name is still logged with its object id.
126
+
127
+ Code anywhere in the application that calls `Thread#name` or `Thread#name=` now gets Ruby's
128
+ behaviour:
129
+
130
+ * `Thread#name` returns `nil` for a thread that has no name, where it returned the thread's object
131
+ id as a String (on JRuby, its JVM thread name). The main thread has no name on MRI; JRuby names it
132
+ `main`.
133
+ * `Thread#name=` is Ruby's built-in implementation again, with Ruby's standard behaviour, instead of
134
+ Semantic Logger's version, which accepted any value and converted it with `to_s`. Ruby's version
135
+ raises `TypeError` for a value that is not a String or `nil`, such as a Symbol or an Integer. It
136
+ raises `ArgumentError` for a String that contains a NUL byte (`"\0"`), or whose encoding is not
137
+ ASCII-compatible, such as UTF-16. Setting `nil` leaves the thread with no name, where it set the
138
+ name to `""`.
139
+
140
+ ~~~ruby
141
+ # Before:
142
+ Thread.current.name = worker_number
143
+ Thread.current.name.start_with?("worker")
144
+
145
+ # After:
146
+ Thread.current.name = "worker-#{worker_number}"
147
+ Thread.current.name&.start_with?("worker")
148
+ ~~~
149
+
150
+ Tests that expect `thread_name: Thread.current.name` from the
151
+ [test helpers](testing.html) no longer work on a thread that has no name, such as the main thread
152
+ on MRI: `Thread.current.name` is now `nil`, while the log entry still holds the thread's object id.
153
+ The RSpec matchers `be_a_semantic_logger_event` and `log_semantic_logger_event` then fail. Worse,
154
+ `assert_semantic_logger_event` in Minitest takes `nil` to mean "do not check", so the test still
155
+ passes but no longer checks the thread name. Expect the object id when the thread has no name:
156
+
157
+ ~~~ruby
158
+ # Before:
159
+ assert_semantic_logger_event(event, thread_name: Thread.current.name)
160
+
161
+ # After:
162
+ assert_semantic_logger_event(event, thread_name: Thread.current.name || Thread.current.object_id.to_s)
163
+ ~~~
164
+
165
+ Semantic Logger's own threads are now named after their appender, such as `SL File`, with
166
+ `SL Appenders` for the main one, instead of `SemanticLogger::Appender::File` and
167
+ `SemanticLogger::Appenders`. Linux keeps only the first 15 bytes of a thread name, so the longer
168
+ names all looked the same in tools that list threads, such as `top -H`. The new names also appear
169
+ in the thread column of the messages Semantic Logger logs about its own problems, such as an
170
+ appender that cannot write.
171
+
172
+ On JRuby:
173
+
174
+ * A Ruby thread that has no name is logged with its object id, instead of its JVM thread name, such
175
+ as `Ruby-0-Thread-1: app/worker.rb:12`. Name the thread to give it a readable name in the logs. A
176
+ thread created by Java code, such as a servlet container's request thread, is still logged with
177
+ its JVM thread name, which now also stays current when Java code renames the thread.
178
+ * Naming a Ruby thread sets its JVM thread name in JRuby's own format, such as
179
+ `Ruby-0-Thread-2@My Worker: app/worker.rb:12`, instead of exactly `My Worker`. Update any JVM
180
+ monitoring that matches thread names exactly.
181
+
182
+ #### Output changes
183
+
184
+ * `:logfmt`: spaces, control characters, invisible format characters (such as a zero width space),
185
+ `=`, `"`, and Unicode line separators in keys are replaced with `_`, and invalid UTF-8 bytes are
186
+ dropped. A tag such as `second breakfast` is now emitted as `second_breakfast=true`. A tag,
187
+ named tag, or payload key named like one of the entry's own fields, which are the time key
188
+ (`timestamp` by default), `level`, `name`, `message`, `duration`, `duration_ms`, `tag`, and the
189
+ exception fields, is emitted with its source as a prefix, such as `payload.name="shrek"` or
190
+ `tags.message=true`, instead of replacing the field, and so is a key that starts with `tags.`,
191
+ `named_tags.`, or `payload.`. A logged exception's class and message are emitted as
192
+ `exception_name` and `exception_message` (followed by `stack_trace` and `cause` as before),
193
+ instead of replacing the entry's `name` and `message`, and the detail that
194
+ `log_exception_details: true` adds as `exception_details`. Other keys that are the same once
195
+ encoded, such as `:user` and `"user"`, are emitted once, with the value from the later source:
196
+ the payload over named tags, and named tags over tags. A payload that is not a Hash is emitted
197
+ as a single `payload` field. A nested value (a Hash, Array, `Struct`,
198
+ `Data`, `Set`, or any other value whose JSON is an object or an array, such as the exception
199
+ backtrace) is emitted as quoted JSON, `user="{\"id\":7}"`, instead of its `#inspect` text.
200
+ * `:syslog`: an entry with an exception stays on one line; the newlines between the exception and
201
+ its backtrace are escaped as `\n`.
202
+ * `:default`, `:color`, `:pattern`, and `:syslog`: with `escape_control_chars`, the logger name,
203
+ thread name, file name, payload, the messages of the exceptions that caused the logged exception,
204
+ and the backtrace lines are escaped too. Invalid UTF-8 bytes are dropped instead of the entry failing to
205
+ format. With escaping on, `:color` renders the payload plain and on one line, since Amazing
206
+ Print's colors and line breaks cannot be told apart from control characters in the data.
207
+ * `:one_line`: line breaks are removed from every field (name, thread name, file name, tags, named
208
+ tags, payload, message, and exception), carriage returns as well as newlines, and
209
+ `escape_control_chars` is honoured.
210
+ * JSON based formatters: `Infinity` and `NaN` payload values are serialized as strings, and
211
+ structures nested deeper than 32 levels are replaced with a placeholder such as
212
+ `#<Hash nested too deeply>`, instead of the whole entry being dropped. A `Struct`, `Data`, or
213
+ `OpenStruct` value is written as an object of its members, and a `Set` as an array, as Rails
214
+ already does, instead of its `#inspect` text. An exception in the payload is written as its
215
+ message, and any other object whose JSON fails because of invalid UTF-8 as its `#to_s`, with
216
+ the invalid bytes dropped. The `:elasticsearch` and `:opensearch` appenders write their bulk lines the same way.
217
+ * JSON based formatters and `:one_line`: invalid UTF-8 bytes in the message are dropped, and a
218
+ message in another encoding is converted to UTF-8, instead of the entry failing to format.
219
+ * `:loki`: a nested Hash, Array, `Struct`, or `Set` in the payload is written once as JSON, keeping
220
+ the JSON types of its values, so `{number: 1}` is written as `{"number":1}` rather than
221
+ `{"number":"1"}`, and `[1, 2, 3]` as `[1,2,3]`. A payload that is not a Hash is written as a
222
+ single `payload` field.
223
+ * `:open_telemetry`: a `Struct`, `Set`, or other structure in the payload, or in an Array in it, is
224
+ written as JSON instead of its `#inspect`. A payload that is not a Hash is submitted as a single
225
+ `payload` attribute.
226
+ * Hashes logged as the payload, for example `logger.info("Webhook", params)`: a key that is also
227
+ a log argument (`duration`, `metric_amount`, `backtrace`, `log_exception`, ...) is only used as
228
+ that argument when its value is valid for it. Otherwise it stays in the payload, where it was
229
+ previously consumed or made the call raise. A `nil` value is ignored. When an explicit
230
+ `payload:` is also given, the other keys win over its values of the same name, a String key such
231
+ as `"account_id"` having the same name as `:account_id`.
232
+ * Hashes returned from a logging block, for example `logger.info("Webhook") { {user: "bob"} }`, are
233
+ checked the same way. A log argument from the block, such as `message`, `duration`, `exception`,
234
+ or `backtrace`, is only used when the log entry does not have it yet, and is otherwise kept in the
235
+ payload. A backtrace captured because the level is at or above `backtrace_level` counts, so the
236
+ block's `backtrace` no longer replaces it. The block's keys no longer replace values the log call
237
+ supplied under the same name, whether as a String or a Symbol, and win over the values of the
238
+ block's own `payload` with the same name.
239
+ * The HTTP appender and the appenders built on it (`:elasticsearch_http`, `:splunk_http`, `:loki`,
240
+ `:signalfx`): the url in the error logged for a rejected request shows only the scheme, host,
241
+ and port, and `#inspect` shows only the object address and that url.
242
+ * The `:statsd` metrics appender sends a counter as a single `metric:amount|c` packet.
243
+
244
+ #### Other behaviour changes
245
+
246
+ * An async or batching appender discards a log message that it cannot format, instead of
247
+ retrying it. Discarded messages are reported to the internal logger and counted in the new
248
+ `failed` key of the appender's stats. Every error raised by a built-in appender's formatter
249
+ counts, as does a message too large for a UDP datagram, a MongoDB document that BSON cannot
250
+ serialize, and a message GELF cannot send. A custom appender can format through
251
+ `format_log(log)` (or `format_batch(logs)`) instead of `formatter.call` to get the same
252
+ treatment, or override `#message_error?` for errors its own client raises.
253
+ * The text formatters write a payload nested more than 32 levels deep as the JSON formatters
254
+ write it, cut off with their placeholder, such as `"#<Hash nested too deeply>"`.
255
+ * A log message that overflows the stack while it is written is discarded, and the worker
256
+ thread carries on in a new thread, instead of the worker stopping for good.
257
+ * `#inspect` on every appender shows only its class, object address, and what it says is safe to
258
+ show, such as `url="https://es.example.com:9200"`, `queue_name="logs"`, or `group="/my/app"`,
259
+ instead of its instance variables. A custom appender can show more by overriding
260
+ `#inspect_details`. An appender added with `async: true` or `batch: true` shows as
261
+ `#<SemanticLogger::Appender::Async:0x... batch=true appender=#<...>>`.
262
+ * `:cloudwatch_logs` sends the buffered messages in as many requests as the PutLogEvents limits
263
+ need, oldest first, cuts short a message larger than 1 MB, and reports to the internal logger a
264
+ request that fails, and the messages that AWS rejects as too new, too old, or expired. It formats each message when it is logged, writes invalid UTF-8 as valid
265
+ UTF-8, and discards a message that its formatter cannot write, or writes as something other than
266
+ a String with content, instead of failing the whole request.
267
+ * `:sentry_ruby` leaves the log entry's payload and named tags unchanged, so every appender after
268
+ it, and your own Hash, keep the keys it reads. A user field, or `user` Hash, in the payload no
269
+ longer replaces one from the named tags. A payload user field that is not a String, Symbol, or
270
+ Integer, a `fingerprint` that is not an Array of Strings, Symbols, or numbers, and a payload that
271
+ is not a Hash, are sent as data instead.
272
+
273
+ #### Stricter `logger_measure_method` arguments
274
+
275
+ `logger_measure_method` now raises `ArgumentError` for a method name that is not a valid Ruby
276
+ method name, a `min_duration` that is not a finite number, and an invalid `log_exception` or
277
+ level. Numeric Strings, such as `min_duration: "100"`, are still accepted.
278
+
279
+ ### Upgrading to Semantic Logger v5.1
280
+
281
+ v5.1 deprecates several legacy appenders and one appender option, and changes when the file appender
282
+ opens its log file.
283
+
284
+ The deprecated appenders and option all still work in v5.1, but each now emits a Ruby deprecation
285
+ warning and will be **removed in v6**. The warnings use `category: :deprecated`, so they are silent
286
+ by default and appear when deprecation warnings are enabled (for example `ruby -W:deprecated`, or
287
+ Rails development mode). Switch away now to be ready for v6.
288
+
289
+ #### Replace the `:sentry` appender with `:sentry_ruby`
290
+
291
+ The `:sentry` appender depends on the end-of-life `sentry-raven` gem. Use the `:sentry_ruby`
292
+ appender, backed by the maintained `sentry-ruby` gem.
293
+
294
+ In your `Gemfile`:
295
+
296
+ ~~~ruby
297
+ # Remove:
298
+ gem "sentry-raven"
299
+ # Add:
300
+ gem "sentry-ruby"
301
+ ~~~
302
+
303
+ When adding the appender:
304
+
305
+ ~~~ruby
306
+ # Before:
307
+ SemanticLogger.add_appender(appender: :sentry)
308
+
309
+ # After:
310
+ SemanticLogger.add_appender(appender: :sentry_ruby)
311
+ ~~~
312
+
313
+ #### Replace the `:new_relic` appender with `:new_relic_logs`
314
+
315
+ The `:new_relic` appender reports `:error` and `:fatal` entries to New Relic as **error events** via
316
+ `newrelic_rpm`. It is superseded by `:new_relic_logs`, which forwards log entries to New Relic Logs:
317
+
318
+ ~~~ruby
319
+ # Before:
320
+ SemanticLogger.add_appender(appender: :new_relic)
321
+
322
+ # After:
323
+ SemanticLogger.add_appender(appender: :new_relic_logs)
324
+ ~~~
325
+
326
+ Note that the behaviour differs. `:new_relic` created New Relic *error events* (and defaulted to the
327
+ `:error` level), whereas `:new_relic_logs` forwards *log messages* to New Relic's log ingestion. Set
328
+ `level:` to match what you want forwarded, and update any New Relic dashboards or alerts that relied
329
+ on the old error events. Both appenders require the `newrelic_rpm` gem.
330
+
331
+ #### Remove the `type:` option from the Elasticsearch and OpenSearch appenders
332
+
333
+ Document `_type` has been unused since Elasticsearch 7, so the `type:` option (on `:elasticsearch`,
334
+ `:elasticsearch_http`, and `:opensearch`) is now ignored on all server versions: the bulk appenders
335
+ no longer send a `_type`, and the `:elasticsearch_http` appender posts to the typeless `_doc`
336
+ endpoint. Passing `type:` logs a deprecation warning. Remove it:
337
+
338
+ ~~~ruby
339
+ # Before:
340
+ SemanticLogger.add_appender(appender: :elasticsearch, url: "http://localhost:9200", type: "log")
341
+
342
+ # After:
343
+ SemanticLogger.add_appender(appender: :elasticsearch, url: "http://localhost:9200")
344
+ ~~~
345
+
346
+ #### The file appender opens its log file when it is created
347
+
348
+ The file appender now opens its log file at creation rather than on the first write, matching the
349
+ other appenders. A bad path or insufficient permissions therefore raises immediately from
350
+ `SemanticLogger.add_appender`, instead of surfacing later on the appender thread while log messages
351
+ are lost. This restores the pre-4.9 behavior.
352
+
353
+ As a result the log file is created as soon as the appender is added, even if nothing has been
354
+ logged yet, the same as the standard Ruby and Rails loggers. If you add appenders inside a `rescue`
355
+ or a conditional that assumed creation could never fail, review it: the failure now happens earlier
356
+ and in the calling thread.
357
+
358
+ #### Rails: log file failures are caught at boot
359
+
360
+ If you use [rails_semantic_logger](rails.html), the companion gem's v5.1 release pairs with the
361
+ eager open above: a log file that cannot be opened is now caught during boot and the app falls back
362
+ to standard error at the `:warn` level, rather than booting against a silently broken appender. v5.1
363
+ also drops support for Sidekiq v4, v5, and v6. See
364
+ [Upgrading to v5.1](rails.html#upgrading-to-v51) for details.
365
+
366
+ ### Upgrading to Semantic Logger v5.0
367
+
368
+ #### Minimum Ruby version is now 3.2
369
+
370
+ Semantic Logger v5 requires Ruby 3.2 or later. Earlier Ruby versions are end-of-life and are no
371
+ longer supported or tested.
372
+
373
+ If you are on an older Ruby, upgrade Ruby before upgrading Semantic Logger, or stay on the v4.x
374
+ series.
375
+
376
+ #### `SemanticLogger::Appender::AsyncBatch` has been removed
377
+
378
+ The internal asynchronous proxy classes have been consolidated. Batch processing now runs through
379
+ the same `SemanticLogger::Appender::Async` proxy (backed by an internal `QueueProcessor`), so the
380
+ separate `SemanticLogger::Appender::AsyncBatch` class no longer exists.
381
+
382
+ This only affects code that referenced the class directly, which is uncommon since appenders are
383
+ added through `SemanticLogger.add_appender`. The `batch:`, `batch_size:`, and `batch_seconds:`
384
+ options are unchanged:
385
+
386
+ ~~~ruby
387
+ SemanticLogger.add_appender(appender: :http, url: "https://example.com/log", batch: true)
388
+ ~~~
389
+
390
+ What changed:
391
+
392
+ - `SemanticLogger.add_appender(..., batch: true)` now returns a `SemanticLogger::Appender::Async`
393
+ (with `#batch?` returning `true`) instead of a `SemanticLogger::Appender::AsyncBatch`.
394
+ - Referencing the constant `SemanticLogger::Appender::AsyncBatch` now raises `NameError`.
395
+
396
+ If you have a custom appender or test asserting on the proxy class, change
397
+ `instance_of?`/`is_a?(SemanticLogger::Appender::AsyncBatch)` checks to
398
+ `SemanticLogger::Appender::Async` and, if needed, check `appender.batch?`.
399
+
400
+ #### Appenders are reopened automatically after fork
401
+
402
+ Previously, applications that fork (Puma, Unicorn, Spring, Resque, `Process.daemon`, etc.) had to
403
+ call `SemanticLogger.reopen` themselves in an after-fork hook, otherwise the child shared the
404
+ parent's file handles and background thread.
405
+
406
+ In v5 this happens automatically: a `Process._fork` / `Process.daemon` hook calls
407
+ `SemanticLogger.reopen` in the child process. The call is guarded to run once per process.
408
+
409
+ For most applications you can now **remove** the manual reopen hook, for example:
410
+
411
+ ~~~ruby
412
+ # No longer needed in v5 — handled automatically:
413
+ before_fork { SemanticLogger.flush }
414
+ after_fork { SemanticLogger.reopen }
415
+ ~~~
416
+
417
+ If you have a reason to manage this yourself (for example you reopen at a very specific point in
418
+ your boot sequence), disable the automatic behaviour:
419
+
420
+ ~~~ruby
421
+ SemanticLogger.reopen_on_fork = false
422
+ ~~~
423
+
424
+ #### Recommended: enable logger caching
425
+
426
+ By default `SemanticLogger[SomeClass]` returns a **new** `Logger` instance on every call. This means
427
+ that if one part of the code obtains a logger and later changes its `level` (or filter), other
428
+ holders of "the same" logger do not see the change, because they hold different instances.
429
+
430
+ v5 adds opt-in caching so that a `Class` or `Module` maps to a single shared `Logger` instance:
431
+
432
+ ~~~ruby
433
+ SemanticLogger.cache_loggers = true
434
+ ~~~
435
+
436
+ With caching enabled:
437
+
438
+ - `SemanticLogger[MyClass]` and the `Loggable` mixin's `MyClass.logger` return the **same** instance.
439
+ - Changing that logger's level or filter is visible to every holder.
440
+ - Strings (`SemanticLogger["Name"]`) always get a fresh instance, and anonymous classes (no `name`)
441
+ are never cached.
442
+
443
+ Enabling caching is recommended for most applications. It is opt-in (default `false`) to preserve
444
+ existing behaviour for code that relied on getting an independent logger per call. If you need to
445
+ discard cached loggers (for example in tests, or after redefining a class), call
446
+ `SemanticLogger.clear_logger_cache`.
447
+
448
+ #### Control characters are escaped in Syslog output
449
+
450
+ To prevent log-injection via embedded control characters (newlines, escape sequences, etc.), the
451
+ Syslog formatter now escapes control characters in records by default. If you relied on raw control
452
+ characters reaching syslog, this output now differs. The text formatters (default, color) also gain
453
+ an opt-in `escape_control_chars` option (default `false`) for the same protection. See the
454
+ [Security](security.html) page for details.
455
+
456
+ #### `Formatters::Base#cleanse` renamed
457
+
458
+ The internal `SemanticLogger::Formatters::Base#cleanse` method was renamed to
459
+ `#escape_control_characters`. This only affects custom formatters or subclasses that called the old
460
+ method name directly; update them to call `#escape_control_characters`.
461
+
462
+ #### Rails: appenders are now configured in one block
463
+
464
+ If you use [rails_semantic_logger](rails.html), the companion gem's v5 release moves appender
465
+ configuration into a single `config.rails_semantic_logger.appenders do |appenders| ... end` block.
466
+ The v4 options (`format`, `add_file_appender`, `ap_options`, `filter`, `console_logger`) still work
467
+ but are deprecated and will be removed in v6. See
468
+ [Migrating from v4 to v5](rails.html#migrating-from-v4-to-v5) for the full before/after mapping.
469
+
470
+ ### Upgrading to Semantic Logger v4.18
471
+
472
+ #### Async queue is now bounded by default
473
+
474
+ The default `max_queue_size` for async appenders changed from `-1` (unbounded) to `10_000`.
475
+ Under sustained high log volume, messages that exceed the queue limit will be dropped rather than
476
+ causing memory to grow without bound.
477
+
478
+ If your application logs at very high throughput and you were relying on the unbounded default,
479
+ set the limit explicitly when adding each appender:
480
+
481
+ ~~~ruby
482
+ SemanticLogger.add_appender(io: $stdout, async: true, max_queue_size: -1)
483
+ ~~~
484
+
485
+ Alternatively, tune the value to match your application's tolerance for log loss vs. memory usage.
486
+
487
+ #### Invalid exception values are now wrapped
488
+
489
+ Previously, passing a non-`Exception` object to the `exception:` keyword argument would silently
490
+ misbehave. It is now wrapped in an `ArgumentError` with a description of the invalid value and a
491
+ backtrace pointing to the call site:
492
+
493
+ ~~~ruby
494
+ # This previously caused undefined behaviour — now raises a descriptive ArgumentError
495
+ logger.error("Something went wrong", exception: "a plain string")
496
+ ~~~
497
+
498
+ #### New Relic formatter — trace and span metadata
499
+
500
+ Previously, New Relic trace metadata (`trace.id`, `span.id`, `entity.name`, etc.) was captured at
501
+ format time, so it worked with any appender type. In v4.18, this metadata is captured via an
502
+ `on_log` callback that is only registered when `SemanticLogger::Appender::NewRelicLogs` is
503
+ instantiated.
504
+
505
+ If you use a plain IO or File appender with `SemanticLogger::Formatters::NewRelicLogs` as the
506
+ formatter (rather than the dedicated `NewRelicLogs` appender), you must register the callback
507
+ manually in your initializer, otherwise `trace.id`, `span.id`, and `entity.name` will be absent
508
+ from all logs:
509
+
510
+ ~~~ruby
511
+ require "semantic_logger/appender/new_relic_logs"
512
+ SemanticLogger.on_log(SemanticLogger::Appender::NewRelicLogs::CAPTURE_CONTEXT)
513
+ ~~~
514
+
515
+ This line should be added before any log messages are emitted.
516
+
517
+ ---
518
+
519
+ ### Upgrading to Semantic Logger v4.17
520
+
521
+ #### New Relic formatter (breaking changes for New Relic users)
522
+
523
+ The New Relic log formatter (`SemanticLogger::Formatters::NewRelicLogs`) has been significantly
524
+ updated to produce a structure that New Relic can natively parse and index as individual
525
+ attributes. Most users will benefit from richer log correlation in New Relic, but there are
526
+ several breaking changes to be aware of.
527
+
528
+ ##### Log output structure
529
+
530
+ Previously, all semantic fields (`payload`, `tags`, `named_tags`, `metric`, etc.) were serialized
531
+ as a JSON string inside the `message` key. They are now emitted as top-level keys:
532
+
533
+ Before (v4.16):
534
+ ~~~json
535
+ {
536
+ "message": "{\"message\":\"Order processed\",\"payload\":{\"order_id\":123},\"tags\":[],\"named_tags\":{}}",
537
+ "timestamp": 1234567890000,
538
+ "log.level": "INFO"
539
+ }
540
+ ~~~
541
+
542
+ After (v4.17):
543
+ ~~~json
544
+ {
545
+ "message": "Order processed",
546
+ "payload": { "order_id": 123 },
547
+ "timestamp": 1234567890000,
548
+ "logger": { "name": "OrderService" },
549
+ "thread": { "name": "main" }
550
+ }
551
+ ~~~
552
+
553
+ **Impact**: Any New Relic alert queries, dashboards, or NRQL that references fields inside the
554
+ old `message` JSON string (e.g. `message.payload.order_id`) must be updated to reference the
555
+ new top-level keys (e.g. `payload.order_id`).
556
+
557
+ Note that New Relic's log ingestion flattens nested objects. A `payload` value that is itself a
558
+ nested Hash may not be fully indexed as individually searchable attributes. If you need payload
559
+ fields to be queryable in New Relic, promote them to `named_tags` instead:
560
+
561
+ ~~~ruby
562
+ # Payload fields may not be individually queryable in New Relic:
563
+ logger.info "Order processed", order_id: 123, amount: 49.99
564
+
565
+ # Use named_tags for fields you want to query directly in New Relic:
566
+ SemanticLogger.tagged(order_id: 123, amount: 49.99) do
567
+ logger.info "Order processed"
568
+ end
569
+ ~~~
570
+
571
+ ##### named_tags are now merged to the top level
572
+
573
+ Previously `named_tags` were nested inside the `message` JSON string. They are now merged
574
+ directly into the top-level log entry, making them first-class attributes in New Relic.
575
+
576
+ If a named tag key conflicts with an existing top-level key (e.g. `message`, `timestamp`), it is
577
+ dropped and the conflicting key names are recorded under `named_tag_conflicts`.
578
+
579
+ ##### Error, logger, and thread fields restructured
580
+
581
+ Before (v4.16):
582
+ ~~~json
583
+ {
584
+ "error.message": "Something went wrong",
585
+ "error.class": "RuntimeError",
586
+ "error.stack": "...",
587
+ "log.level": "ERROR",
588
+ "logger.name": "OrderService",
589
+ "thread.name": "main"
590
+ }
591
+ ~~~
592
+
593
+ After (v4.17):
594
+ ~~~json
595
+ {
596
+ "error": { "message": "Something went wrong", "class": "RuntimeError", "stack": "..." },
597
+ "logger": { "name": "OrderService" },
598
+ "thread": { "name": "main" }
599
+ }
600
+ ~~~
601
+
602
+ Update any New Relic alert conditions or NRQL queries that reference the old dot-notation keys.
603
+
604
+ #### Suppressing timestamps in formatters
605
+
606
+ v4.17 added `:notime` as an explicit `time_format` value to suppress timestamp output. If you
607
+ were previously passing `time_format: nil` expecting it to suppress output, use `:notime` instead:
608
+
609
+ ~~~ruby
610
+ SemanticLogger.add_appender(io: $stdout, formatter: MyFormatter.new(time_format: :notime))
611
+ ~~~
612
+
613
+ ---
614
+
615
+ ### Upgrading to Semantic Logger v4.9
616
+
617
+ These changes should not be noticeable by the majority of users of Semantic Logger, since
618
+ they are to the internal API. It is possible that advanced users may be using these internal
619
+ API's directly.
620
+
621
+ This does not affect any calls to the public api `SemanticLogger.add_appender`.
622
+
623
+ File and IO are now separate appenders. When creating the File appender explicitly, its arguments
624
+ have changed. For example, when requesting an IO stream, it needs to be changed from:
625
+
626
+ ~~~ruby
627
+ SemanticLogger::Appender::File.new(io: $stderr)
628
+ ~~~
629
+ to:
630
+ ~~~ruby
631
+ SemanticLogger::Appender::IO.new($stderr)
632
+ ~~~
633
+
634
+ Additionally, this needs to be changed from:
635
+ ~~~ruby
636
+ SemanticLogger::Appender::File.new(file_name: "file.log")
637
+ ~~~
638
+ to:
639
+ ~~~ruby
640
+ SemanticLogger::Appender::File.new("file.log")
641
+ ~~~
642
+
643
+ Rails Semantic Logger, if used, needs to be upgraded to v4.9 when upgrading to Semantic Logger v4.9.
644
+
645
+ ---
646
+
647
+ ### Upgrading to Semantic Logger v4.4
648
+
649
+ With some forking frameworks it is necessary to call `reopen` after the fork. With v4.4 the
650
+ workaround for Ruby 2.5 crashes is no longer needed.
651
+ I.e. Please remove the following line if being called anywhere:
652
+
653
+ ~~~ruby
654
+ SemanticLogger::Processor.instance.instance_variable_set(:@queue, Queue.new)
655
+ ~~~
656
+
657
+ ---
658
+
659
+ ### Upgrading to Semantic Logger v4.0
660
+
661
+ The following changes need to be made when upgrading to V4:
662
+ - Ruby V2.3 / JRuby V9.1 is now the minimum runtime version.
663
+ - Replace calls to `Logger#with_payload` with `SemanticLogger.named_tagged`.
664
+ - Replace calls to `Logger#payload` with `SemanticLogger.named_tags`.
665
+ - MongoDB Appender requires Mongo Ruby Client V2 or greater.
666
+ - Appenders now write payload data in a separate `:payload` tag instead of mixing them
667
+ directly into the root elements to avoid name clashes.
668
+
669
+ As a result any calls like the following:
670
+
671
+ ~~~ruby
672
+ logger.debug foo: 'foo', bar: 'bar'
673
+ ~~~
674
+
675
+ Must be replaced with the following in v4:
676
+
677
+ ~~~ruby
678
+ logger.debug payload: {foo: 'foo', bar: 'bar'}
679
+ ~~~
680
+
681
+ Similarly, for measure blocks:
682
+
683
+ ~~~ruby
684
+ logger.measure_info('How long is the sleep', foo: 'foo', bar: 'bar') { sleep 1 }
685
+ ~~~
686
+
687
+ Must be replaced with the following in v4:
688
+
689
+ ~~~ruby
690
+ logger.measure_info('How long is the sleep', payload: {foo: 'foo', bar: 'bar'}) { sleep 1 }
691
+ ~~~
692
+
693
+ The common log call has not changed, and the payload is still logged directly:
694
+
695
+ ~~~ruby
696
+ logger.debug('log this', foo: 'foo', bar: 'bar')
697
+ ~~~