semantic_logger 4.17.0 → 5.0.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 +42 -81
- data/Rakefile +8 -1
- data/lib/semantic_logger/appender/async.rb +86 -173
- data/lib/semantic_logger/appender/cloudwatch_logs.rb +4 -4
- data/lib/semantic_logger/appender/elasticsearch.rb +6 -182
- data/lib/semantic_logger/appender/elasticsearch_base.rb +212 -0
- data/lib/semantic_logger/appender/elasticsearch_http.rb +2 -2
- data/lib/semantic_logger/appender/file.rb +20 -4
- data/lib/semantic_logger/appender/graylog.rb +2 -2
- data/lib/semantic_logger/appender/honeybadger_insights.rb +1 -1
- data/lib/semantic_logger/appender/http.rb +27 -2
- data/lib/semantic_logger/appender/io.rb +8 -4
- data/lib/semantic_logger/appender/kafka.rb +2 -2
- data/lib/semantic_logger/appender/loki.rb +2 -4
- data/lib/semantic_logger/appender/mongodb.rb +3 -6
- data/lib/semantic_logger/appender/new_relic_logs.rb +12 -2
- data/lib/semantic_logger/appender/open_telemetry.rb +25 -11
- data/lib/semantic_logger/appender/opensearch.rb +35 -0
- data/lib/semantic_logger/appender/rabbitmq.rb +3 -3
- data/lib/semantic_logger/appender/sentry_ruby.rb +8 -3
- data/lib/semantic_logger/appender/splunk.rb +2 -2
- data/lib/semantic_logger/appender/splunk_http.rb +3 -3
- data/lib/semantic_logger/appender/syslog.rb +5 -4
- data/lib/semantic_logger/appender/tcp.rb +2 -2
- data/lib/semantic_logger/appender/udp.rb +2 -2
- data/lib/semantic_logger/appender/wrapper.rb +4 -4
- data/lib/semantic_logger/appender.rb +30 -19
- data/lib/semantic_logger/appenders.rb +26 -5
- data/lib/semantic_logger/base.rb +113 -21
- data/lib/semantic_logger/concerns/compatibility.rb +2 -2
- data/lib/semantic_logger/core_ext/process.rb +34 -0
- data/lib/semantic_logger/formatters/base.rb +46 -7
- data/lib/semantic_logger/formatters/color.rb +6 -3
- data/lib/semantic_logger/formatters/default.rb +6 -4
- data/lib/semantic_logger/formatters/ecs.rb +151 -0
- data/lib/semantic_logger/formatters/fluentd.rb +15 -4
- data/lib/semantic_logger/formatters/json.rb +6 -1
- data/lib/semantic_logger/formatters/logfmt.rb +2 -2
- data/lib/semantic_logger/formatters/loki.rb +4 -4
- data/lib/semantic_logger/formatters/new_relic_logs.rb +4 -6
- data/lib/semantic_logger/formatters/open_telemetry.rb +40 -2
- data/lib/semantic_logger/formatters/pattern.rb +235 -0
- data/lib/semantic_logger/formatters/raw.rb +2 -2
- data/lib/semantic_logger/formatters/signalfx.rb +2 -2
- data/lib/semantic_logger/formatters/syslog.rb +14 -3
- data/lib/semantic_logger/formatters/syslog_cee.rb +1 -1
- data/lib/semantic_logger/formatters.rb +2 -0
- data/lib/semantic_logger/log.rb +23 -4
- data/lib/semantic_logger/logger.rb +2 -2
- data/lib/semantic_logger/metric/new_relic.rb +2 -2
- data/lib/semantic_logger/metric/signalfx.rb +2 -2
- data/lib/semantic_logger/metric/statsd.rb +2 -2
- data/lib/semantic_logger/processor.rb +22 -1
- data/lib/semantic_logger/queue_processor.rb +369 -0
- data/lib/semantic_logger/reporters/minitest.rb +4 -4
- data/lib/semantic_logger/semantic_logger.rb +103 -11
- data/lib/semantic_logger/subscriber.rb +15 -2
- data/lib/semantic_logger/sync_processor.rb +25 -3
- data/lib/semantic_logger/test/capture_log_events.rb +2 -2
- data/lib/semantic_logger/test/minitest.rb +8 -4
- data/lib/semantic_logger/test/rspec.rb +249 -0
- data/lib/semantic_logger/utils.rb +83 -4
- data/lib/semantic_logger/version.rb +1 -1
- data/lib/semantic_logger.rb +9 -0
- metadata +15 -6
- data/lib/semantic_logger/appender/async_batch.rb +0 -93
|
@@ -47,7 +47,7 @@ module SemanticLogger
|
|
|
47
47
|
hash = super
|
|
48
48
|
|
|
49
49
|
result = {
|
|
50
|
-
**
|
|
50
|
+
**linking_metadata(log),
|
|
51
51
|
message: hash[:message].to_s,
|
|
52
52
|
tags: hash[:tags],
|
|
53
53
|
metric: hash[:metric],
|
|
@@ -115,11 +115,9 @@ module SemanticLogger
|
|
|
115
115
|
|
|
116
116
|
private
|
|
117
117
|
|
|
118
|
-
#
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
def newrelic_metadata
|
|
122
|
-
NewRelic::Agent.linking_metadata.transform_keys(&:to_sym)
|
|
118
|
+
# returns "entity.name", "trace.id" etc from NewRelic::Agent.linking_metadata
|
|
119
|
+
def linking_metadata(log)
|
|
120
|
+
(log.context && log.context[:new_relic_metadata]) || {}
|
|
123
121
|
end
|
|
124
122
|
end
|
|
125
123
|
end
|
|
@@ -2,21 +2,59 @@ require "json"
|
|
|
2
2
|
module SemanticLogger
|
|
3
3
|
module Formatters
|
|
4
4
|
class OpenTelemetry < Raw
|
|
5
|
+
# primitives allowed by OTLP logs in Ruby: String, Integer, Float, TrueClass, FalseClass
|
|
6
|
+
PRIMS = [String, Integer, Float, TrueClass, FalseClass].freeze
|
|
7
|
+
|
|
8
|
+
# Returns the attributes hash submitted to the OpenTelemetry SDK.
|
|
9
|
+
#
|
|
10
|
+
# Unlike the JSON formatters there is no single `.to_json` boundary here:
|
|
11
|
+
# the hash is handed to the OTLP exporter, which requires valid UTF-8. Cleanse
|
|
12
|
+
# the whole structure so binary / non UTF-8 strings cannot break the export.
|
|
13
|
+
def call(log, logger)
|
|
14
|
+
Utils.encode_utf8(super)
|
|
15
|
+
end
|
|
16
|
+
|
|
5
17
|
# Log level
|
|
6
18
|
def level
|
|
7
19
|
hash[:level] = log.level.to_s
|
|
8
|
-
hash[:level_index] = severity_number(log.
|
|
20
|
+
hash[:level_index] = severity_number(log.level)
|
|
9
21
|
end
|
|
10
22
|
|
|
11
23
|
# Payload is submitted directly as attributes
|
|
12
24
|
def payload
|
|
13
25
|
return unless log.payload.respond_to?(:empty?) && !log.payload.empty?
|
|
14
26
|
|
|
15
|
-
hash[:payload] = log.payload
|
|
27
|
+
hash[:payload] = coerce_map(log.payload)
|
|
16
28
|
end
|
|
17
29
|
|
|
18
30
|
private
|
|
19
31
|
|
|
32
|
+
def coerce_value(v)
|
|
33
|
+
case v
|
|
34
|
+
when *PRIMS then v
|
|
35
|
+
when Array then v.map { |e| coerce_value(e) }.compact # arrays of scalars only.
|
|
36
|
+
when NilClass then nil # drop nils by caller.
|
|
37
|
+
else v.to_s # stringify objects / hashes.
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def coerce_map(h)
|
|
42
|
+
h.each_with_object({}) do |(k, v), out|
|
|
43
|
+
next if v.nil?
|
|
44
|
+
|
|
45
|
+
out[k.to_s] =
|
|
46
|
+
if v.is_a?(Hash)
|
|
47
|
+
# Stringify whole hash. Cleanse here too: this runs while building the
|
|
48
|
+
# hash, before the top-level call cleanse, so it must not raise on its own.
|
|
49
|
+
Utils.to_json(
|
|
50
|
+
v.transform_values { |vv| coerce_value(vv) }.transform_keys!(&:to_s)
|
|
51
|
+
)
|
|
52
|
+
else
|
|
53
|
+
coerce_value(v)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
20
58
|
def severity_number(severity)
|
|
21
59
|
case severity
|
|
22
60
|
when :trace
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
require "time"
|
|
2
|
+
|
|
3
|
+
module SemanticLogger
|
|
4
|
+
module Formatters
|
|
5
|
+
# Formats log messages using a configurable pattern string, so a custom
|
|
6
|
+
# log line layout can be specified directly in the configuration without
|
|
7
|
+
# having to write a new formatter class.
|
|
8
|
+
#
|
|
9
|
+
# Pattern placeholders use the form `%{directive}`, where `directive` is the
|
|
10
|
+
# name of any of the formatting methods (inherited from Default, or defined
|
|
11
|
+
# below). Named tags support a parameterized form: `%{named_tags:request_id}`
|
|
12
|
+
# returns the value of a single named tag. Use `%%{...}` to emit a literal
|
|
13
|
+
# `%{...}` without interpolation.
|
|
14
|
+
#
|
|
15
|
+
# Example:
|
|
16
|
+
# SemanticLogger.add_appender(
|
|
17
|
+
# io: $stdout,
|
|
18
|
+
# formatter: {
|
|
19
|
+
# pattern: { pattern: "%{time} %{level} %{name} -- %{message}" }
|
|
20
|
+
# }
|
|
21
|
+
# )
|
|
22
|
+
#
|
|
23
|
+
# Available directives:
|
|
24
|
+
# time Formatted timestamp. Optionally accepts a strftime
|
|
25
|
+
# format, e.g. time:%Y-%m-%dT%H:%M:%S.%6N.
|
|
26
|
+
# level Full level name, e.g. "debug".
|
|
27
|
+
# level_short Single character level, e.g. "D".
|
|
28
|
+
# name Logger / class name.
|
|
29
|
+
# message Log message.
|
|
30
|
+
# payload Payload rendered as a string.
|
|
31
|
+
# exception_class Class of the logged exception, e.g. "RuntimeError".
|
|
32
|
+
# exception_message Message of the logged exception.
|
|
33
|
+
# backtrace Backtrace of the logged exception.
|
|
34
|
+
# duration Human readable duration, e.g. "1.2ms".
|
|
35
|
+
# duration_ms Duration in milliseconds (numeric).
|
|
36
|
+
# thread_name Name of the thread that logged the message.
|
|
37
|
+
# pid Process id.
|
|
38
|
+
# file_name Ruby file name that logged the message, e.g. "app.rb".
|
|
39
|
+
# line Line number within the Ruby file, e.g. 42.
|
|
40
|
+
# tags Tags, comma separated.
|
|
41
|
+
# named_tags All named tags, or one tag with named_tags:key.
|
|
42
|
+
# host Host name.
|
|
43
|
+
# application Application name.
|
|
44
|
+
# environment Environment name.
|
|
45
|
+
class Pattern < Default
|
|
46
|
+
attr_reader :pattern
|
|
47
|
+
|
|
48
|
+
# Approximates the Default formatter's output.
|
|
49
|
+
DEFAULT_PATTERN = "%{time} %{level} [%{pid}:%{thread_name}] %{name} -- %{message}".freeze
|
|
50
|
+
|
|
51
|
+
# The directives that may appear in a pattern. The value is whether the
|
|
52
|
+
# directive accepts a parameter, e.g. %{named_tags:request_id}.
|
|
53
|
+
DIRECTIVES = {
|
|
54
|
+
time: true,
|
|
55
|
+
level: false,
|
|
56
|
+
level_short: false,
|
|
57
|
+
name: false,
|
|
58
|
+
message: false,
|
|
59
|
+
payload: false,
|
|
60
|
+
exception_class: false,
|
|
61
|
+
exception_message: false,
|
|
62
|
+
backtrace: false,
|
|
63
|
+
duration: false,
|
|
64
|
+
duration_ms: false,
|
|
65
|
+
thread_name: false,
|
|
66
|
+
pid: false,
|
|
67
|
+
file_name: false,
|
|
68
|
+
line: false,
|
|
69
|
+
tags: false,
|
|
70
|
+
named_tags: true,
|
|
71
|
+
host: false,
|
|
72
|
+
application: false,
|
|
73
|
+
environment: false
|
|
74
|
+
}.freeze
|
|
75
|
+
|
|
76
|
+
# A single interpolated directive within a compiled pattern.
|
|
77
|
+
Token = Struct.new(:method_name, :arguments)
|
|
78
|
+
private_constant :Token
|
|
79
|
+
|
|
80
|
+
# Parameters:
|
|
81
|
+
# pattern: [String]
|
|
82
|
+
# The pattern string used to format every log entry.
|
|
83
|
+
# Default: DEFAULT_PATTERN
|
|
84
|
+
#
|
|
85
|
+
# Plus all the options supported by SemanticLogger::Formatters::Base.
|
|
86
|
+
def initialize(pattern: DEFAULT_PATTERN, **args)
|
|
87
|
+
@pattern = pattern
|
|
88
|
+
super(**args)
|
|
89
|
+
# Parse the pattern once, up front, so that formatting every log entry
|
|
90
|
+
# is just a walk over the pre-compiled tokens (no regex on the hot path).
|
|
91
|
+
# Unknown directives raise here, at configuration time, not per log.
|
|
92
|
+
@tokens = compile(pattern)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Formatted timestamp. With a strftime format argument, e.g.
|
|
96
|
+
# %{time:%Y-%m-%dT%H:%M:%S.%6N}, the time is formatted with that string.
|
|
97
|
+
# Without an argument it uses the formatter's configured time_format.
|
|
98
|
+
def time(format = nil)
|
|
99
|
+
return super() if format.nil?
|
|
100
|
+
|
|
101
|
+
log.time.strftime(format)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Full level name, e.g. "debug" (Default formatter uses the short "D").
|
|
105
|
+
def level
|
|
106
|
+
log.level.to_s
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Single character level, e.g. "D".
|
|
110
|
+
def level_short
|
|
111
|
+
log.level_to_s
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# Log message (without the "-- " prefix the Default formatter adds).
|
|
115
|
+
def message
|
|
116
|
+
escape_control_characters(log.message)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# Raw payload rendered as a string.
|
|
120
|
+
def payload
|
|
121
|
+
log.payload_to_s
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# Class of the logged exception, e.g. "RuntimeError".
|
|
125
|
+
def exception_class
|
|
126
|
+
log.exception&.class
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# Message of the logged exception.
|
|
130
|
+
def exception_message
|
|
131
|
+
escape_control_characters(log.exception&.message)
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# Backtrace of the logged exception.
|
|
135
|
+
def backtrace
|
|
136
|
+
log.backtrace_to_s if log.exception
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# Human readable duration (without the Default formatter's surrounding parentheses).
|
|
140
|
+
def duration
|
|
141
|
+
log.duration_human
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# Duration in milliseconds.
|
|
145
|
+
def duration_ms
|
|
146
|
+
log.duration
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# Name of the Ruby file that logged the message, e.g. "app.rb".
|
|
150
|
+
def file_name
|
|
151
|
+
log.file_name_and_line(true)&.first
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# Line number within the Ruby file that logged the message.
|
|
155
|
+
def line
|
|
156
|
+
log.file_name_and_line(true)&.last
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# Tags joined by a comma (without the Default formatter's surrounding brackets).
|
|
160
|
+
def tags
|
|
161
|
+
return if log.tags.nil? || log.tags.empty?
|
|
162
|
+
|
|
163
|
+
log.tags.map { |tag| escape_control_characters(tag) }.join(", ")
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# With a key: the value of a single named tag, e.g. %{named_tags:request_id}.
|
|
167
|
+
# Without a key: all named tags rendered as "key: value, ...".
|
|
168
|
+
def named_tags(key = nil)
|
|
169
|
+
named = log.named_tags
|
|
170
|
+
return if named.nil? || named.empty?
|
|
171
|
+
|
|
172
|
+
if key
|
|
173
|
+
escape_control_characters(named[key.to_sym] || named[key.to_s])
|
|
174
|
+
else
|
|
175
|
+
named.map { |name, value| "#{escape_control_characters(name)}: #{escape_control_characters(value)}" }.join(", ")
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
# Host name.
|
|
180
|
+
def host
|
|
181
|
+
logger&.host if log_host
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
# Application name.
|
|
185
|
+
def application
|
|
186
|
+
logger&.application if log_application
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
# Environment name.
|
|
190
|
+
def environment
|
|
191
|
+
logger&.environment if log_environment
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
def call(log, logger)
|
|
195
|
+
self.log = log
|
|
196
|
+
self.logger = logger
|
|
197
|
+
|
|
198
|
+
@tokens.each_with_object(+"") do |token, out|
|
|
199
|
+
out << (token.is_a?(Token) ? public_send(token.method_name, *token.arguments).to_s : token)
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
private
|
|
204
|
+
|
|
205
|
+
# Parse the pattern string into an array of tokens: frozen literal strings
|
|
206
|
+
# and Token structs for each %{directive} placeholder. %%{...} is an escape
|
|
207
|
+
# that produces a literal %{...}.
|
|
208
|
+
def compile(string)
|
|
209
|
+
tokens = []
|
|
210
|
+
pos = 0
|
|
211
|
+
|
|
212
|
+
string.scan(/%%?\{[^}]+\}/) do |match|
|
|
213
|
+
current = Regexp.last_match
|
|
214
|
+
tokens << string[pos...current.begin(0)].freeze if current.begin(0) > pos
|
|
215
|
+
|
|
216
|
+
if match.start_with?("%%")
|
|
217
|
+
tokens << match[1..].freeze
|
|
218
|
+
else
|
|
219
|
+
name, arg = match[/\{([^}]+)\}/, 1].split(":", 2)
|
|
220
|
+
name = name.strip.to_sym
|
|
221
|
+
raise(ArgumentError, "Invalid pattern directive: %{#{name}}") unless DIRECTIVES.key?(name)
|
|
222
|
+
raise(ArgumentError, "%{#{name}} does not accept an argument") if arg && !DIRECTIVES[name]
|
|
223
|
+
|
|
224
|
+
tokens << Token.new(name, arg ? [arg.strip] : []).freeze
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
pos = current.end(0)
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
tokens << string[pos..].freeze if pos < string.length
|
|
231
|
+
tokens
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
end
|
|
@@ -18,12 +18,12 @@ module SemanticLogger
|
|
|
18
18
|
|
|
19
19
|
# Application name
|
|
20
20
|
def application
|
|
21
|
-
hash[:application] = logger.application if log_application && logger
|
|
21
|
+
hash[:application] = logger.application if log_application && logger&.application
|
|
22
22
|
end
|
|
23
23
|
|
|
24
24
|
# Environment
|
|
25
25
|
def environment
|
|
26
|
-
hash[:environment] = logger.environment if log_environment && logger
|
|
26
|
+
hash[:environment] = logger.environment if log_environment && logger&.environment
|
|
27
27
|
end
|
|
28
28
|
|
|
29
29
|
# Date & time
|
|
@@ -101,7 +101,7 @@ module SemanticLogger
|
|
|
101
101
|
data[:counter] = [hash]
|
|
102
102
|
end
|
|
103
103
|
|
|
104
|
-
|
|
104
|
+
Utils.to_json(data)
|
|
105
105
|
end
|
|
106
106
|
|
|
107
107
|
# Returns [Hash] a batch of log messages.
|
|
@@ -138,7 +138,7 @@ module SemanticLogger
|
|
|
138
138
|
end
|
|
139
139
|
end
|
|
140
140
|
|
|
141
|
-
|
|
141
|
+
Utils.to_json(data)
|
|
142
142
|
end
|
|
143
143
|
|
|
144
144
|
private
|
|
@@ -49,14 +49,25 @@ module SemanticLogger
|
|
|
49
49
|
# level_map: [Hash | SemanticLogger::Formatters::Syslog::LevelMap]
|
|
50
50
|
# Supply a custom map of SemanticLogger levels to syslog levels.
|
|
51
51
|
#
|
|
52
|
+
# escape_control_chars: [Boolean]
|
|
53
|
+
# Replace control characters (newlines, the ANSI escape, etc.) in
|
|
54
|
+
# untrusted log data with a printable escaped form so that they cannot
|
|
55
|
+
# forge or split syslog records.
|
|
56
|
+
# Default: true (unlike other formatters, since syslog frames records
|
|
57
|
+
# with a separator)
|
|
58
|
+
#
|
|
52
59
|
# Example:
|
|
53
60
|
# # Change the warn level to LOG_NOTICE level instead of a the default of LOG_WARNING.
|
|
54
61
|
# SemanticLogger.add_appender(appender: :syslog, level_map: {warn: ::Syslog::LOG_NOTICE})
|
|
55
|
-
def initialize(facility: ::Syslog::LOG_USER, level_map: LevelMap.new, max_size: Integer
|
|
62
|
+
def initialize(facility: ::Syslog::LOG_USER, level_map: LevelMap.new, max_size: Integer,
|
|
63
|
+
escape_control_chars: true, **args)
|
|
56
64
|
@facility = facility
|
|
57
|
-
@level_map = level_map.is_a?(LevelMap) ? level_map : LevelMap.new(level_map)
|
|
65
|
+
@level_map = level_map.is_a?(LevelMap) ? level_map : LevelMap.new(**level_map)
|
|
58
66
|
@max_size = max_size
|
|
59
|
-
|
|
67
|
+
# Syslog frames records with a separator, so embedded newlines or other
|
|
68
|
+
# control characters in untrusted log data can forge or split records.
|
|
69
|
+
# Default to escaping them, overridable for backwards compatibility.
|
|
70
|
+
super(escape_control_chars: escape_control_chars, **args)
|
|
60
71
|
end
|
|
61
72
|
|
|
62
73
|
# Time is part of the syslog packet and is not included in the formatted message.
|
|
@@ -3,6 +3,7 @@ module SemanticLogger
|
|
|
3
3
|
autoload :Base, "semantic_logger/formatters/base"
|
|
4
4
|
autoload :Color, "semantic_logger/formatters/color"
|
|
5
5
|
autoload :Default, "semantic_logger/formatters/default"
|
|
6
|
+
autoload :Ecs, "semantic_logger/formatters/ecs"
|
|
6
7
|
autoload :Json, "semantic_logger/formatters/json"
|
|
7
8
|
autoload :Raw, "semantic_logger/formatters/raw"
|
|
8
9
|
autoload :OneLine, "semantic_logger/formatters/one_line"
|
|
@@ -11,6 +12,7 @@ module SemanticLogger
|
|
|
11
12
|
autoload :Syslog, "semantic_logger/formatters/syslog"
|
|
12
13
|
autoload :Fluentd, "semantic_logger/formatters/fluentd"
|
|
13
14
|
autoload :Logfmt, "semantic_logger/formatters/logfmt"
|
|
15
|
+
autoload :Pattern, "semantic_logger/formatters/pattern"
|
|
14
16
|
autoload :SyslogCee, "semantic_logger/formatters/syslog_cee"
|
|
15
17
|
autoload :NewRelicLogs, "semantic_logger/formatters/new_relic_logs"
|
|
16
18
|
autoload :Loki, "semantic_logger/formatters/loki"
|
data/lib/semantic_logger/log.rb
CHANGED
|
@@ -93,6 +93,11 @@ module SemanticLogger
|
|
|
93
93
|
self.dimensions = dimensions
|
|
94
94
|
|
|
95
95
|
if exception
|
|
96
|
+
unless exception.is_a?(Exception)
|
|
97
|
+
exception = ArgumentError.new("Invalid value for logger exception: #{exception.inspect}")
|
|
98
|
+
exception.set_backtrace(Utils.extract_backtrace(caller))
|
|
99
|
+
end
|
|
100
|
+
|
|
96
101
|
case log_exception
|
|
97
102
|
when :full
|
|
98
103
|
self.exception = exception
|
|
@@ -124,11 +129,17 @@ module SemanticLogger
|
|
|
124
129
|
true
|
|
125
130
|
end
|
|
126
131
|
|
|
132
|
+
# Keys that #assign_hash may assign directly to a log attribute. Every other
|
|
133
|
+
# key is folded into the payload, preventing a supplied hash from overwriting
|
|
134
|
+
# sensitive fields such as :level, :name, or :time, and keeping this path
|
|
135
|
+
# consistent with #extract_arguments.
|
|
136
|
+
ASSIGNABLE_KEYS = (NON_PAYLOAD_KEYS + %i[payload]).freeze
|
|
137
|
+
|
|
127
138
|
# Assign known keys to self, all other keys to the payload.
|
|
128
139
|
def assign_hash(hash)
|
|
129
140
|
self.payload ||= {}
|
|
130
141
|
hash.each_pair do |key, value|
|
|
131
|
-
if respond_to?(:"#{key}=")
|
|
142
|
+
if ASSIGNABLE_KEYS.include?(key) && respond_to?(:"#{key}=")
|
|
132
143
|
public_send(:"#{key}=", value)
|
|
133
144
|
else
|
|
134
145
|
payload[key] = value
|
|
@@ -245,7 +256,7 @@ module SemanticLogger
|
|
|
245
256
|
"#{$$}:#{format("%.#{thread_name_length}s", thread_name)}#{file_name}"
|
|
246
257
|
end
|
|
247
258
|
|
|
248
|
-
CALLER_REGEXP = /^(.*):(\d+)
|
|
259
|
+
CALLER_REGEXP = /^(.*):(\d+).*/
|
|
249
260
|
|
|
250
261
|
# Extract the filename and line number from the last entry in the supplied backtrace
|
|
251
262
|
def extract_file_and_line(stack, short_name = false)
|
|
@@ -264,9 +275,17 @@ module SemanticLogger
|
|
|
264
275
|
extract_file_and_line(stack, short_name)
|
|
265
276
|
end
|
|
266
277
|
|
|
267
|
-
# Strip the standard Rails colorizing from the logged message
|
|
278
|
+
# Strip the standard Rails colorizing from the logged message.
|
|
279
|
+
#
|
|
280
|
+
# Note: This unconditionally *strips* ANSI colorization, and is used to keep
|
|
281
|
+
# terminal escape codes out of structured (JSON/Loki) output. It is distinct
|
|
282
|
+
# from Formatters::Base#escape_control_characters, which instead *escapes*
|
|
283
|
+
# (preserves) control characters and is opt-in for the text formatters.
|
|
268
284
|
def cleansed_message
|
|
269
|
-
message.to_s
|
|
285
|
+
msg = message.to_s
|
|
286
|
+
return msg.strip unless msg.include?("\e")
|
|
287
|
+
|
|
288
|
+
msg.gsub(/\e\[[\d;]*[mz]?|\e/, "").strip
|
|
270
289
|
end
|
|
271
290
|
|
|
272
291
|
# Return the payload in text form
|
|
@@ -65,9 +65,9 @@ module SemanticLogger
|
|
|
65
65
|
#
|
|
66
66
|
# Subscribers are called inline before handing off to the queue so that
|
|
67
67
|
# they can capture additional context information as needed.
|
|
68
|
-
def log(log, message = nil, progname = nil, &
|
|
68
|
+
def log(log, message = nil, progname = nil, &)
|
|
69
69
|
# Compatibility with ::Logger
|
|
70
|
-
return add(log, message, progname, &
|
|
70
|
+
return add(log, message, progname, &) unless log.is_a?(SemanticLogger::Log)
|
|
71
71
|
|
|
72
72
|
Logger.call_subscribers(log)
|
|
73
73
|
|
|
@@ -38,9 +38,9 @@ module SemanticLogger
|
|
|
38
38
|
# regular expression. All other messages will be ignored.
|
|
39
39
|
# Proc: Only include log messages where the supplied Proc returns true
|
|
40
40
|
# The Proc must return true or false.
|
|
41
|
-
def initialize(prefix: "Custom", **args, &
|
|
41
|
+
def initialize(prefix: "Custom", **args, &)
|
|
42
42
|
@prefix = prefix
|
|
43
|
-
super(**args, &
|
|
43
|
+
super(**args, &)
|
|
44
44
|
end
|
|
45
45
|
|
|
46
46
|
# Returns metric name to use.
|
|
@@ -78,10 +78,10 @@ module SemanticLogger
|
|
|
78
78
|
url: "https://ingest.signalfx.com",
|
|
79
79
|
formatter: nil,
|
|
80
80
|
**args,
|
|
81
|
-
&
|
|
81
|
+
&)
|
|
82
82
|
formatter ||= SemanticLogger::Formatters::Signalfx.new(token: token, dimensions: dimensions)
|
|
83
83
|
|
|
84
|
-
super(url: url, formatter: formatter, **args, &
|
|
84
|
+
super(url: url, formatter: formatter, **args, &)
|
|
85
85
|
|
|
86
86
|
@header["X-SF-TOKEN"] = token
|
|
87
87
|
@full_url = "#{url}/#{END_POINT}"
|
|
@@ -24,7 +24,7 @@ module SemanticLogger
|
|
|
24
24
|
# Example:
|
|
25
25
|
# SemanticLogger.add_appender(
|
|
26
26
|
# metric: :statsd,
|
|
27
|
-
# url: 'localhost:8125'
|
|
27
|
+
# url: 'udp://localhost:8125'
|
|
28
28
|
# )
|
|
29
29
|
def initialize(url: "udp://localhost:8125")
|
|
30
30
|
@url = url
|
|
@@ -47,7 +47,7 @@ module SemanticLogger
|
|
|
47
47
|
else
|
|
48
48
|
amount = (log.metric_amount || 1).round
|
|
49
49
|
if amount.negative?
|
|
50
|
-
amount.times { @statsd.decrement(metric) }
|
|
50
|
+
amount.abs.times { @statsd.decrement(metric) }
|
|
51
51
|
else
|
|
52
52
|
amount.times { @statsd.increment(metric) }
|
|
53
53
|
end
|
|
@@ -23,7 +23,7 @@ module SemanticLogger
|
|
|
23
23
|
|
|
24
24
|
attr_reader :appenders
|
|
25
25
|
|
|
26
|
-
def initialize(max_queue_size:
|
|
26
|
+
def initialize(max_queue_size: 10_000)
|
|
27
27
|
@appenders = Appenders.new(self.class.logger.dup)
|
|
28
28
|
super(appender: @appenders, max_queue_size: max_queue_size)
|
|
29
29
|
end
|
|
@@ -35,5 +35,26 @@ module SemanticLogger
|
|
|
35
35
|
thread
|
|
36
36
|
true
|
|
37
37
|
end
|
|
38
|
+
|
|
39
|
+
# Returns [Hash] operational statistics for the logging pipeline.
|
|
40
|
+
#
|
|
41
|
+
# queue_size: [Integer] Number of log messages waiting on the main pipeline queue.
|
|
42
|
+
# capped: [Boolean] Whether the main queue has a maximum size.
|
|
43
|
+
# max_queue_size: [Integer] Maximum queue size, or nil when uncapped.
|
|
44
|
+
# thread_active: [Boolean] Whether the main pipeline thread is running.
|
|
45
|
+
# processed: [Integer] Cumulative number of log messages processed since startup.
|
|
46
|
+
# dropped: [Integer] Cumulative number of log messages dropped at the main queue.
|
|
47
|
+
# appenders: [Array<Hash>] Per-appender statistics, see Appenders#stats.
|
|
48
|
+
def stats
|
|
49
|
+
{
|
|
50
|
+
queue_size: queue.size,
|
|
51
|
+
capped: capped?,
|
|
52
|
+
max_queue_size: capped? ? max_queue_size : nil,
|
|
53
|
+
thread_active: active? || false,
|
|
54
|
+
processed: processed_count,
|
|
55
|
+
dropped: dropped_count,
|
|
56
|
+
appenders: appenders.stats
|
|
57
|
+
}
|
|
58
|
+
end
|
|
38
59
|
end
|
|
39
60
|
end
|