semantic_logger 4.18.0 → 5.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +51 -81
- data/Rakefile +39 -3
- data/docs/api.md +480 -0
- data/docs/appenders.md +990 -0
- data/docs/config.md +605 -0
- data/docs/index.md +197 -0
- data/docs/log.md +108 -0
- data/docs/metrics.md +168 -0
- data/docs/operations.md +400 -0
- data/docs/rails.md +816 -0
- data/docs/security.md +119 -0
- data/docs/testing.md +285 -0
- data/docs/upgrading.md +409 -0
- data/lib/semantic_logger/appender/async.rb +86 -173
- data/lib/semantic_logger/appender/cloudwatch_logs.rb +8 -6
- data/lib/semantic_logger/appender/elasticsearch.rb +8 -187
- data/lib/semantic_logger/appender/elasticsearch_base.rb +212 -0
- data/lib/semantic_logger/appender/elasticsearch_http.rb +16 -9
- data/lib/semantic_logger/appender/file.rb +25 -4
- data/lib/semantic_logger/appender/graylog.rb +2 -2
- 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.rb +6 -0
- data/lib/semantic_logger/appender/new_relic_logs.rb +4 -3
- data/lib/semantic_logger/appender/open_telemetry.rb +10 -7
- data/lib/semantic_logger/appender/opensearch.rb +36 -0
- data/lib/semantic_logger/appender/rabbitmq.rb +5 -4
- data/lib/semantic_logger/appender/sentry.rb +5 -0
- data/lib/semantic_logger/appender/sentry_ruby.rb +2 -2
- 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 +11 -7
- data/lib/semantic_logger/appender/tcp.rb +4 -3
- data/lib/semantic_logger/appender/udp.rb +2 -2
- data/lib/semantic_logger/appender/wrapper.rb +5 -4
- data/lib/semantic_logger/appender.rb +36 -22
- data/lib/semantic_logger/appenders.rb +27 -5
- data/lib/semantic_logger/base.rb +100 -23
- 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 +9 -3
- data/lib/semantic_logger/formatters/default.rb +11 -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 +6 -4
- data/lib/semantic_logger/formatters/loki.rb +4 -4
- data/lib/semantic_logger/formatters/open_telemetry.rb +15 -5
- data/lib/semantic_logger/formatters/pattern.rb +237 -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 +16 -4
- data/lib/semantic_logger/formatters/syslog_cee.rb +3 -2
- data/lib/semantic_logger/formatters.rb +2 -0
- data/lib/semantic_logger/log.rb +18 -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 +6 -4
- data/lib/semantic_logger/metric/statsd.rb +2 -2
- data/lib/semantic_logger/processor.rb +21 -0
- data/lib/semantic_logger/queue_processor.rb +372 -0
- data/lib/semantic_logger/reporters/minitest.rb +8 -8
- 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 +3 -3
- 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 +28 -8
- data/lib/semantic_logger/appender/async_batch.rb +0 -93
|
@@ -10,7 +10,7 @@ module SemanticLogger
|
|
|
10
10
|
self.logger = logger
|
|
11
11
|
self.log = log
|
|
12
12
|
|
|
13
|
-
{streams: [build_stream]}
|
|
13
|
+
Utils.to_json({streams: [build_stream]})
|
|
14
14
|
end
|
|
15
15
|
|
|
16
16
|
# Returns [String] a JSON batch of logs
|
|
@@ -22,7 +22,7 @@ module SemanticLogger
|
|
|
22
22
|
build_stream
|
|
23
23
|
end
|
|
24
24
|
|
|
25
|
-
{streams: streams}
|
|
25
|
+
Utils.to_json({streams: streams})
|
|
26
26
|
end
|
|
27
27
|
|
|
28
28
|
private
|
|
@@ -82,7 +82,7 @@ module SemanticLogger
|
|
|
82
82
|
|
|
83
83
|
log.context.each do |key, value|
|
|
84
84
|
serialized_value = if value.is_a?(Hash)
|
|
85
|
-
|
|
85
|
+
Utils.to_json(value)
|
|
86
86
|
else
|
|
87
87
|
value.to_s
|
|
88
88
|
end
|
|
@@ -144,7 +144,7 @@ module SemanticLogger
|
|
|
144
144
|
|
|
145
145
|
result[string_key] = case value
|
|
146
146
|
when Hash
|
|
147
|
-
|
|
147
|
+
Utils.to_json(stringify_hash(value))
|
|
148
148
|
else
|
|
149
149
|
value.to_s
|
|
150
150
|
end
|
|
@@ -5,10 +5,19 @@ module SemanticLogger
|
|
|
5
5
|
# primitives allowed by OTLP logs in Ruby: String, Integer, Float, TrueClass, FalseClass
|
|
6
6
|
PRIMS = [String, Integer, Float, TrueClass, FalseClass].freeze
|
|
7
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
|
+
|
|
8
17
|
# Log level
|
|
9
18
|
def level
|
|
10
19
|
hash[:level] = log.level.to_s
|
|
11
|
-
hash[:level_index] = severity_number(log.
|
|
20
|
+
hash[:level_index] = severity_number(log.level)
|
|
12
21
|
end
|
|
13
22
|
|
|
14
23
|
# Payload is submitted directly as attributes
|
|
@@ -35,10 +44,11 @@ module SemanticLogger
|
|
|
35
44
|
|
|
36
45
|
out[k.to_s] =
|
|
37
46
|
if v.is_a?(Hash)
|
|
38
|
-
# Stringify whole hash.
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
+
)
|
|
42
52
|
else
|
|
43
53
|
coerce_value(v)
|
|
44
54
|
end
|
|
@@ -0,0 +1,237 @@
|
|
|
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 do |name, value|
|
|
176
|
+
"#{escape_control_characters(name)}: #{escape_control_characters(value)}"
|
|
177
|
+
end.join(", ")
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
# Host name.
|
|
182
|
+
def host
|
|
183
|
+
logger&.host if log_host
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
# Application name.
|
|
187
|
+
def application
|
|
188
|
+
logger&.application if log_application
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
# Environment name.
|
|
192
|
+
def environment
|
|
193
|
+
logger&.environment if log_environment
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
def call(log, logger)
|
|
197
|
+
self.log = log
|
|
198
|
+
self.logger = logger
|
|
199
|
+
|
|
200
|
+
@tokens.each_with_object(+"") do |token, out|
|
|
201
|
+
out << (token.is_a?(Token) ? public_send(token.method_name, *token.arguments).to_s : token)
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
private
|
|
206
|
+
|
|
207
|
+
# Parse the pattern string into an array of tokens: frozen literal strings
|
|
208
|
+
# and Token structs for each %{directive} placeholder. %%{...} is an escape
|
|
209
|
+
# that produces a literal %{...}.
|
|
210
|
+
def compile(string)
|
|
211
|
+
tokens = []
|
|
212
|
+
pos = 0
|
|
213
|
+
|
|
214
|
+
string.scan(/%%?\{[^}]+\}/) do |match|
|
|
215
|
+
current = Regexp.last_match
|
|
216
|
+
tokens << string[pos...current.begin(0)].freeze if current.begin(0) > pos
|
|
217
|
+
|
|
218
|
+
if match.start_with?("%%")
|
|
219
|
+
tokens << match[1..].freeze
|
|
220
|
+
else
|
|
221
|
+
name, arg = match[/\{([^}]+)\}/, 1].split(":", 2)
|
|
222
|
+
name = name.strip.to_sym
|
|
223
|
+
raise(ArgumentError, "Invalid pattern directive: %{#{name}}") unless DIRECTIVES.key?(name)
|
|
224
|
+
raise(ArgumentError, "%{#{name}} does not accept an argument") if arg && !DIRECTIVES[name]
|
|
225
|
+
|
|
226
|
+
tokens << Token.new(name, arg ? [arg.strip] : []).freeze
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
pos = current.end(0)
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
tokens << string[pos..].freeze if pos < string.length
|
|
233
|
+
tokens
|
|
234
|
+
end
|
|
235
|
+
end
|
|
236
|
+
end
|
|
237
|
+
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
|
|
@@ -2,7 +2,8 @@ begin
|
|
|
2
2
|
require "syslog_protocol"
|
|
3
3
|
rescue LoadError
|
|
4
4
|
raise LoadError,
|
|
5
|
-
|
|
5
|
+
"Gem syslog_protocol is required for remote logging using the Syslog protocol. " \
|
|
6
|
+
'Please add the gem "syslog_protocol" to your Gemfile.'
|
|
6
7
|
end
|
|
7
8
|
|
|
8
9
|
module SemanticLogger
|
|
@@ -49,14 +50,25 @@ module SemanticLogger
|
|
|
49
50
|
# level_map: [Hash | SemanticLogger::Formatters::Syslog::LevelMap]
|
|
50
51
|
# Supply a custom map of SemanticLogger levels to syslog levels.
|
|
51
52
|
#
|
|
53
|
+
# escape_control_chars: [Boolean]
|
|
54
|
+
# Replace control characters (newlines, the ANSI escape, etc.) in
|
|
55
|
+
# untrusted log data with a printable escaped form so that they cannot
|
|
56
|
+
# forge or split syslog records.
|
|
57
|
+
# Default: true (unlike other formatters, since syslog frames records
|
|
58
|
+
# with a separator)
|
|
59
|
+
#
|
|
52
60
|
# Example:
|
|
53
61
|
# # Change the warn level to LOG_NOTICE level instead of a the default of LOG_WARNING.
|
|
54
62
|
# 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
|
|
63
|
+
def initialize(facility: ::Syslog::LOG_USER, level_map: LevelMap.new, max_size: Integer,
|
|
64
|
+
escape_control_chars: true, **args)
|
|
56
65
|
@facility = facility
|
|
57
|
-
@level_map = level_map.is_a?(LevelMap) ? level_map : LevelMap.new(level_map)
|
|
66
|
+
@level_map = level_map.is_a?(LevelMap) ? level_map : LevelMap.new(**level_map)
|
|
58
67
|
@max_size = max_size
|
|
59
|
-
|
|
68
|
+
# Syslog frames records with a separator, so embedded newlines or other
|
|
69
|
+
# control characters in untrusted log data can forge or split records.
|
|
70
|
+
# Default to escaping them, overridable for backwards compatibility.
|
|
71
|
+
super(escape_control_chars: escape_control_chars, **args)
|
|
60
72
|
end
|
|
61
73
|
|
|
62
74
|
# Time is part of the syslog packet and is not included in the formatted message.
|
|
@@ -2,7 +2,8 @@ begin
|
|
|
2
2
|
require "syslog_protocol"
|
|
3
3
|
rescue LoadError
|
|
4
4
|
raise LoadError,
|
|
5
|
-
|
|
5
|
+
"Gem syslog_protocol is required for remote logging using the Syslog protocol. " \
|
|
6
|
+
'Please add the gem "syslog_protocol" to your Gemfile.'
|
|
6
7
|
end
|
|
7
8
|
|
|
8
9
|
module SemanticLogger
|
|
@@ -38,7 +39,7 @@ module SemanticLogger
|
|
|
38
39
|
|
|
39
40
|
def call(log, logger)
|
|
40
41
|
hash = super
|
|
41
|
-
create_syslog_packet("@cee: #{
|
|
42
|
+
create_syslog_packet("@cee: #{Utils.to_json(hash)}")
|
|
42
43
|
end
|
|
43
44
|
|
|
44
45
|
private
|
|
@@ -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
|
@@ -129,11 +129,17 @@ module SemanticLogger
|
|
|
129
129
|
true
|
|
130
130
|
end
|
|
131
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
|
+
|
|
132
138
|
# Assign known keys to self, all other keys to the payload.
|
|
133
139
|
def assign_hash(hash)
|
|
134
140
|
self.payload ||= {}
|
|
135
141
|
hash.each_pair do |key, value|
|
|
136
|
-
if respond_to?(:"#{key}=")
|
|
142
|
+
if ASSIGNABLE_KEYS.include?(key) && respond_to?(:"#{key}=")
|
|
137
143
|
public_send(:"#{key}=", value)
|
|
138
144
|
else
|
|
139
145
|
payload[key] = value
|
|
@@ -250,7 +256,7 @@ module SemanticLogger
|
|
|
250
256
|
"#{$$}:#{format("%.#{thread_name_length}s", thread_name)}#{file_name}"
|
|
251
257
|
end
|
|
252
258
|
|
|
253
|
-
CALLER_REGEXP = /^(.*):(\d+)
|
|
259
|
+
CALLER_REGEXP = /^(.*):(\d+).*/
|
|
254
260
|
|
|
255
261
|
# Extract the filename and line number from the last entry in the supplied backtrace
|
|
256
262
|
def extract_file_and_line(stack, short_name = false)
|
|
@@ -269,9 +275,17 @@ module SemanticLogger
|
|
|
269
275
|
extract_file_and_line(stack, short_name)
|
|
270
276
|
end
|
|
271
277
|
|
|
272
|
-
# 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.
|
|
273
284
|
def cleansed_message
|
|
274
|
-
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
|
|
275
289
|
end
|
|
276
290
|
|
|
277
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.
|
|
@@ -66,8 +66,10 @@ module SemanticLogger
|
|
|
66
66
|
# logger.info(metric: 'Common/User/authorize', duration: 1.4)
|
|
67
67
|
#
|
|
68
68
|
# Then it is translated into the following 2 log entries under the covers:
|
|
69
|
-
# logger.info(metric: 'Application.average', metric_amount: 1.4,
|
|
70
|
-
#
|
|
69
|
+
# logger.info(metric: 'Application.average', metric_amount: 1.4,
|
|
70
|
+
# dimensions: {class: 'Common::User', action: 'authorize'})
|
|
71
|
+
# logger.info(metric: 'Application.counter', metric_amount: 1,
|
|
72
|
+
# dimensions: {class: 'Common::User', action: 'authorize'})
|
|
71
73
|
#
|
|
72
74
|
# Similarly with a measure block which automatically supplies the duration:
|
|
73
75
|
# logger.measure_info(metric: 'Common/User/authorize') do
|
|
@@ -78,10 +80,10 @@ module SemanticLogger
|
|
|
78
80
|
url: "https://ingest.signalfx.com",
|
|
79
81
|
formatter: nil,
|
|
80
82
|
**args,
|
|
81
|
-
&
|
|
83
|
+
&)
|
|
82
84
|
formatter ||= SemanticLogger::Formatters::Signalfx.new(token: token, dimensions: dimensions)
|
|
83
85
|
|
|
84
|
-
super(url: url, formatter: formatter, **args, &
|
|
86
|
+
super(url: url, formatter: formatter, **args, &)
|
|
85
87
|
|
|
86
88
|
@header["X-SF-TOKEN"] = token
|
|
87
89
|
@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
|
|
@@ -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
|