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.
- checksums.yaml +4 -4
- data/README.md +20 -10
- data/Rakefile +40 -0
- data/docs/api.md +506 -0
- data/docs/appenders.md +1074 -0
- data/docs/config.md +707 -0
- data/docs/index.md +223 -0
- data/docs/log.md +116 -0
- data/docs/metrics.md +171 -0
- data/docs/operations.md +446 -0
- data/docs/rails.md +890 -0
- data/docs/security.md +286 -0
- data/docs/testing.md +287 -0
- data/docs/upgrading.md +697 -0
- data/lib/semantic_logger/appender/async.rb +18 -5
- data/lib/semantic_logger/appender/bugsnag.rb +1 -1
- data/lib/semantic_logger/appender/cloudwatch_logs.rb +126 -29
- data/lib/semantic_logger/appender/elasticsearch.rb +2 -5
- data/lib/semantic_logger/appender/elasticsearch_base.rb +48 -23
- data/lib/semantic_logger/appender/elasticsearch_http.rb +15 -8
- data/lib/semantic_logger/appender/file.rb +141 -28
- data/lib/semantic_logger/appender/graylog.rb +22 -6
- data/lib/semantic_logger/appender/honeybadger.rb +1 -1
- data/lib/semantic_logger/appender/honeybadger_insights.rb +1 -1
- data/lib/semantic_logger/appender/http.rb +70 -11
- data/lib/semantic_logger/appender/io.rb +1 -1
- data/lib/semantic_logger/appender/kafka.rb +26 -12
- data/lib/semantic_logger/appender/loki.rb +13 -3
- data/lib/semantic_logger/appender/mongodb.rb +20 -1
- data/lib/semantic_logger/appender/new_relic.rb +7 -1
- data/lib/semantic_logger/appender/new_relic_logs.rb +11 -12
- data/lib/semantic_logger/appender/open_telemetry.rb +4 -3
- data/lib/semantic_logger/appender/opensearch.rb +2 -1
- data/lib/semantic_logger/appender/rabbitmq.rb +10 -4
- data/lib/semantic_logger/appender/sentry.rb +6 -1
- data/lib/semantic_logger/appender/sentry_ruby.rb +91 -17
- data/lib/semantic_logger/appender/splunk.rb +10 -1
- data/lib/semantic_logger/appender/syslog.rb +66 -25
- data/lib/semantic_logger/appender/tcp.rb +10 -4
- data/lib/semantic_logger/appender/udp.rb +11 -1
- data/lib/semantic_logger/appender/wrapper.rb +3 -2
- data/lib/semantic_logger/appender.rb +6 -3
- data/lib/semantic_logger/appenders.rb +11 -2
- data/lib/semantic_logger/base.rb +82 -32
- data/lib/semantic_logger/exception_details.rb +50 -0
- data/lib/semantic_logger/formatters/base.rb +52 -6
- data/lib/semantic_logger/formatters/color.rb +22 -3
- data/lib/semantic_logger/formatters/default.rb +28 -6
- data/lib/semantic_logger/formatters/logfmt.rb +155 -21
- data/lib/semantic_logger/formatters/loki.rb +26 -22
- data/lib/semantic_logger/formatters/one_line.rb +67 -3
- data/lib/semantic_logger/formatters/open_telemetry.rb +16 -17
- data/lib/semantic_logger/formatters/pattern.rb +11 -5
- data/lib/semantic_logger/formatters/raw.rb +4 -5
- data/lib/semantic_logger/formatters/syslog.rb +110 -17
- data/lib/semantic_logger/formatters/syslog_cee.rb +29 -15
- data/lib/semantic_logger/levels.rb +10 -1
- data/lib/semantic_logger/log.rb +183 -37
- data/lib/semantic_logger/loggable.rb +26 -3
- data/lib/semantic_logger/metric/new_relic.rb +1 -1
- data/lib/semantic_logger/metric/signalfx.rb +12 -8
- data/lib/semantic_logger/metric/statsd.rb +15 -7
- data/lib/semantic_logger/queue_processor/request.rb +55 -0
- data/lib/semantic_logger/queue_processor/throttled_counter.rb +46 -0
- data/lib/semantic_logger/queue_processor.rb +170 -68
- data/lib/semantic_logger/reporters/minitest.rb +4 -4
- data/lib/semantic_logger/semantic_logger.rb +15 -2
- data/lib/semantic_logger/subscriber.rb +96 -10
- data/lib/semantic_logger/sync_processor.rb +11 -0
- data/lib/semantic_logger/test/capture_log_events.rb +9 -6
- data/lib/semantic_logger/utils.rb +244 -16
- data/lib/semantic_logger/version.rb +1 -1
- data/lib/semantic_logger.rb +1 -1
- metadata +33 -6
- data/lib/semantic_logger/core_ext/thread.rb +0 -30
data/lib/semantic_logger/base.rb
CHANGED
|
@@ -28,23 +28,38 @@ module SemanticLogger
|
|
|
28
28
|
end
|
|
29
29
|
end
|
|
30
30
|
|
|
31
|
-
# Returns the
|
|
32
|
-
#
|
|
31
|
+
# Returns the level set by an enclosing `#with_level` block on the current fiber,
|
|
32
|
+
# otherwise the level set for this instance, otherwise the global default level.
|
|
33
33
|
def level
|
|
34
|
-
@
|
|
34
|
+
index = @with_level_used && with_level_index
|
|
35
|
+
return Levels.level(index) if index
|
|
36
|
+
|
|
37
|
+
@level || fallback_level
|
|
35
38
|
end
|
|
36
39
|
|
|
37
|
-
#
|
|
40
|
+
# Change the log level of this instance while the block runs, on the current fiber only.
|
|
38
41
|
#
|
|
39
|
-
#
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
42
|
+
# Other threads and fibers that share this instance keep logging at its own level.
|
|
43
|
+
# The level set with `#level=` is left untouched, so an instance that follows the
|
|
44
|
+
# global default level keeps following it after the block. Blocks can be nested.
|
|
45
|
+
#
|
|
46
|
+
# Matches Ruby's `Logger#with_level`, which Sidekiq calls to apply a job's `log_level`.
|
|
47
|
+
# Accepts the same levels as `#level=`. Returns the result of the block.
|
|
48
|
+
def with_level(level)
|
|
49
|
+
index = Levels.index(level)
|
|
50
|
+
overrides = (Thread.current[:semantic_logger_with_level] ||= {}.compare_by_identity)
|
|
51
|
+
previous = overrides[self]
|
|
52
|
+
@with_level_used = true
|
|
53
|
+
overrides[self] = index
|
|
54
|
+
begin
|
|
55
|
+
yield
|
|
56
|
+
ensure
|
|
57
|
+
if previous
|
|
58
|
+
overrides[self] = previous
|
|
59
|
+
else
|
|
60
|
+
overrides.delete(self)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
48
63
|
end
|
|
49
64
|
|
|
50
65
|
# Implement the log level calls
|
|
@@ -150,13 +165,11 @@ module SemanticLogger
|
|
|
150
165
|
if thread == Thread.current
|
|
151
166
|
Utils.extract_backtrace(caller)
|
|
152
167
|
else
|
|
153
|
-
log.thread_name = thread
|
|
168
|
+
log.thread_name = Log.thread_name_for(thread)
|
|
154
169
|
log.tags = (thread[:semantic_logger_tags] || []).clone
|
|
155
170
|
log.named_tags = (thread[:semantic_logger_named_tags] || {}).clone
|
|
156
171
|
thread.backtrace
|
|
157
172
|
end
|
|
158
|
-
# TODO: Keep backtrace instead of transforming into a text message at this point
|
|
159
|
-
# Maybe log_backtrace: true
|
|
160
173
|
if backtrace
|
|
161
174
|
message += "\n"
|
|
162
175
|
message << backtrace.join("\n")
|
|
@@ -321,6 +334,10 @@ module SemanticLogger
|
|
|
321
334
|
# (empty) instance tags allocate no garbage. Child loggers replace them via #tagged_child.
|
|
322
335
|
@instance_tags = [].freeze
|
|
323
336
|
@instance_named_tags = {}.freeze
|
|
337
|
+
# Set the first time `#with_level` is called on this instance, so that instances that
|
|
338
|
+
# never use it skip the fiber-local lookup when checking their level. Only the fiber
|
|
339
|
+
# that sets an override needs to see the flag, and that fiber set it itself.
|
|
340
|
+
@with_level_used = false
|
|
324
341
|
if level.nil?
|
|
325
342
|
# Allow the global default level to determine this loggers log level
|
|
326
343
|
@level_index = nil
|
|
@@ -330,11 +347,25 @@ module SemanticLogger
|
|
|
330
347
|
end
|
|
331
348
|
end
|
|
332
349
|
|
|
333
|
-
# Return the level index for fast comparisons
|
|
334
|
-
# Returns the global default level index if the level has not been explicitly
|
|
335
|
-
# set for this instance
|
|
350
|
+
# Return the level index for fast comparisons, resolved in the same order as `#level`.
|
|
336
351
|
def level_index
|
|
337
|
-
@level_index ||
|
|
352
|
+
(@with_level_used && with_level_index) || @level_index || fallback_level_index
|
|
353
|
+
end
|
|
354
|
+
|
|
355
|
+
# Level to use when no level has been set for this instance: the global default level.
|
|
356
|
+
def fallback_level
|
|
357
|
+
SemanticLogger.default_level
|
|
358
|
+
end
|
|
359
|
+
|
|
360
|
+
# Level index to use when no level has been set for this instance. Unlike
|
|
361
|
+
# `#fallback_level`, it honors `SemanticLogger.silence`.
|
|
362
|
+
def fallback_level_index
|
|
363
|
+
SemanticLogger.default_level_index
|
|
364
|
+
end
|
|
365
|
+
|
|
366
|
+
# Level index set for this instance by an enclosing `#with_level` block on the current fiber.
|
|
367
|
+
def with_level_index
|
|
368
|
+
Thread.current[:semantic_logger_with_level]&.[](self)
|
|
338
369
|
end
|
|
339
370
|
|
|
340
371
|
# Whether to log the supplied message based on the current filter if any
|
|
@@ -446,6 +477,9 @@ module SemanticLogger
|
|
|
446
477
|
end
|
|
447
478
|
|
|
448
479
|
# Measure the supplied block and log the message
|
|
480
|
+
#
|
|
481
|
+
# The options can hold untrusted data, so an option with a value that is not valid for it is
|
|
482
|
+
# ignored (see Log.argument?) and its default is used instead.
|
|
449
483
|
def measure_internal(level, index, message, params)
|
|
450
484
|
exception = nil
|
|
451
485
|
# Single parameter is a hash
|
|
@@ -456,9 +490,7 @@ module SemanticLogger
|
|
|
456
490
|
start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
457
491
|
begin
|
|
458
492
|
if block_given?
|
|
459
|
-
if (silence_level = params
|
|
460
|
-
# In case someone accidentally sets `silence: true` instead of `silence: :error`
|
|
461
|
-
silence_level = :error if silence_level == true
|
|
493
|
+
if (silence_level = measure_silence_level(params))
|
|
462
494
|
silence(silence_level) { yield(params) }
|
|
463
495
|
else
|
|
464
496
|
yield(params)
|
|
@@ -469,13 +501,15 @@ module SemanticLogger
|
|
|
469
501
|
ensure
|
|
470
502
|
# Must use ensure block otherwise a `return` in the yield above will skip the log entry
|
|
471
503
|
log = new_log(level, index)
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
504
|
+
# Only an Exception, since it is re-raised below
|
|
505
|
+
exception ||= params[:exception] if params[:exception].is_a?(Exception)
|
|
506
|
+
message = measure_option(params, :message) || message
|
|
507
|
+
duration =
|
|
475
508
|
if block_given?
|
|
476
509
|
1_000.0 * (Process.clock_gettime(Process::CLOCK_MONOTONIC) - start)
|
|
477
510
|
else
|
|
478
|
-
params
|
|
511
|
+
measure_option(params, :duration) ||
|
|
512
|
+
raise("Mandatory block missing when :duration option is not supplied")
|
|
479
513
|
end
|
|
480
514
|
|
|
481
515
|
# Extract options after block completes so that block can modify any of the options
|
|
@@ -485,13 +519,13 @@ module SemanticLogger
|
|
|
485
519
|
should_log = log.assign(
|
|
486
520
|
message: message,
|
|
487
521
|
payload: payload,
|
|
488
|
-
min_duration: params
|
|
522
|
+
min_duration: measure_option(params, :min_duration) || 0.0,
|
|
489
523
|
exception: exception,
|
|
490
|
-
metric: params
|
|
491
|
-
metric_amount: params
|
|
524
|
+
metric: measure_option(params, :metric),
|
|
525
|
+
metric_amount: measure_option(params, :metric_amount),
|
|
492
526
|
duration: duration,
|
|
493
|
-
log_exception: params
|
|
494
|
-
on_exception_level: params
|
|
527
|
+
log_exception: measure_option(params, :log_exception) || :partial,
|
|
528
|
+
on_exception_level: measure_option(params, :on_exception_level)
|
|
495
529
|
)
|
|
496
530
|
|
|
497
531
|
# Log level may change during assign due to :on_exception_level
|
|
@@ -500,6 +534,22 @@ module SemanticLogger
|
|
|
500
534
|
end
|
|
501
535
|
end
|
|
502
536
|
|
|
537
|
+
# Returns the value of the measure option that is also a log argument, or nil when it is not
|
|
538
|
+
# supplied or its value is not valid for that argument.
|
|
539
|
+
def measure_option(params, key)
|
|
540
|
+
value = params[key]
|
|
541
|
+
value if Log.argument?(key, value)
|
|
542
|
+
end
|
|
543
|
+
|
|
544
|
+
# Returns the level to silence other log messages to within a measure block, or nil when none
|
|
545
|
+
# is supplied or it is not a valid level.
|
|
546
|
+
def measure_silence_level(params)
|
|
547
|
+
silence_level = params[:silence]
|
|
548
|
+
# In case someone accidentally sets `silence: true` instead of `silence: :error`
|
|
549
|
+
silence_level = :error if silence_level == true
|
|
550
|
+
silence_level if Levels.valid?(silence_level)
|
|
551
|
+
end
|
|
552
|
+
|
|
503
553
|
# For measuring methods and logging their duration.
|
|
504
554
|
def measure_method(index:,
|
|
505
555
|
level:,
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
module SemanticLogger
|
|
2
|
+
# Separates the detail that Ruby's Exception#detailed_message adds to an exception's message,
|
|
3
|
+
# such as did_you_mean suggestions or error_highlight's source snippet, from the message itself.
|
|
4
|
+
#
|
|
5
|
+
# Internal: Log caches the result for each of its exceptions; see Log#exception_details.
|
|
6
|
+
module ExceptionDetails
|
|
7
|
+
# Ruby's own Exception#detailed_message: the message, decorated with the class name.
|
|
8
|
+
DETAILED_MESSAGE = Exception.instance_method(:detailed_message)
|
|
9
|
+
private_constant :DETAILED_MESSAGE
|
|
10
|
+
|
|
11
|
+
# Returns [String] the detail that #detailed_message adds to the exception's message, without
|
|
12
|
+
# the class name that Ruby also adds, or nil when there is none or it cannot be separated.
|
|
13
|
+
#
|
|
14
|
+
# Never raises, so that an exception's own #detailed_message cannot stop a log entry being
|
|
15
|
+
# written.
|
|
16
|
+
def self.extract(exception, source: false)
|
|
17
|
+
detailed = full_detailed_message(exception, source)
|
|
18
|
+
return unless detailed
|
|
19
|
+
|
|
20
|
+
base = DETAILED_MESSAGE.bind_call(exception, highlight: false)
|
|
21
|
+
# An override that does not build on Ruby's message cannot be separated from it.
|
|
22
|
+
return unless detailed.is_a?(String) && detailed.start_with?(base)
|
|
23
|
+
|
|
24
|
+
details = detailed.delete_prefix(base).sub(/\A\n+/, "").rstrip
|
|
25
|
+
details unless details.empty?
|
|
26
|
+
rescue StandardError
|
|
27
|
+
nil
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Returns [String] the exception's #detailed_message, with or without error_highlight's
|
|
31
|
+
# source snippet, or nil when the snippet cannot be left out.
|
|
32
|
+
def self.full_detailed_message(exception, source)
|
|
33
|
+
exception.detailed_message(highlight: false, error_highlight: source)
|
|
34
|
+
rescue ArgumentError
|
|
35
|
+
# An override of #detailed_message that does not accept extra keyword arguments. Calling it
|
|
36
|
+
# without `error_highlight: false` lets error_highlight add its source snippet, so only do so
|
|
37
|
+
# when the snippet was asked for, or error_highlight does not decorate this exception.
|
|
38
|
+
return if !source && error_highlighted?(exception)
|
|
39
|
+
|
|
40
|
+
exception.detailed_message(highlight: false)
|
|
41
|
+
end
|
|
42
|
+
private_class_method :full_detailed_message
|
|
43
|
+
|
|
44
|
+
# Whether error_highlight adds a source snippet to this exception's #detailed_message.
|
|
45
|
+
def self.error_highlighted?(exception)
|
|
46
|
+
defined?(ErrorHighlight::CoreExt) && exception.is_a?(ErrorHighlight::CoreExt)
|
|
47
|
+
end
|
|
48
|
+
private_class_method :error_highlighted?
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -3,7 +3,7 @@ module SemanticLogger
|
|
|
3
3
|
module Formatters
|
|
4
4
|
class Base
|
|
5
5
|
attr_accessor :log, :logger, :time_format, :log_host, :log_application, :log_environment, :precision,
|
|
6
|
-
:escape_control_chars
|
|
6
|
+
:escape_control_chars, :log_exception_details
|
|
7
7
|
|
|
8
8
|
# Printable escapes for the most common control characters. Any other
|
|
9
9
|
# control character is escaped to its hexadecimal `\xHH` form by
|
|
@@ -50,25 +50,39 @@ module SemanticLogger
|
|
|
50
50
|
# Default: PRECISION (6, except on older JRuby, where 3)
|
|
51
51
|
# escape_control_chars: [Boolean]
|
|
52
52
|
# Replace control characters (newlines, the ANSI escape, etc.) in
|
|
53
|
-
# untrusted log data (message,
|
|
54
|
-
# message
|
|
53
|
+
# untrusted log data (logger name, thread name, message, payload, tags,
|
|
54
|
+
# named tags, and the message and backtrace lines of the exception and
|
|
55
|
+
# of each exception that caused it) with a printable escaped form, e.g. "\n".
|
|
55
56
|
# This prevents log forging and terminal escape-sequence injection
|
|
56
57
|
# when logging data that may contain attacker-controlled content.
|
|
57
58
|
# Note: Has no effect on structured formatters such as :json, which
|
|
58
59
|
# already escape control characters via JSON encoding.
|
|
59
60
|
# Default: false (preserve newlines and ANSI colors in text output)
|
|
61
|
+
# log_exception_details: [true|false|:with_source]
|
|
62
|
+
# Whether to log the detail that Ruby's Exception#detailed_message adds to an
|
|
63
|
+
# exception's message, such as did_you_mean suggestions ("Did you mean? upcase") or
|
|
64
|
+
# the detail an exception class supplies by overriding #detailed_message.
|
|
65
|
+
# :with_source also logs error_highlight's snippet of the failing source line. It is
|
|
66
|
+
# about 100 times slower, since it re-parses that source file, and it copies source
|
|
67
|
+
# code into the log.
|
|
68
|
+
# On Ruby 3.2 a NameError or NoMethodError builds its message from receiver.inspect each
|
|
69
|
+
# time it is asked, and logging the details asks for it twice more, so turn this off when
|
|
70
|
+
# inspecting the receivers of logged exceptions is slow.
|
|
71
|
+
# Default: true for the Default, Color, and Pattern text formatters, false otherwise.
|
|
60
72
|
def initialize(time_format: nil,
|
|
61
73
|
log_host: true,
|
|
62
74
|
log_application: true,
|
|
63
75
|
log_environment: true,
|
|
64
76
|
precision: PRECISION,
|
|
65
|
-
escape_control_chars: false
|
|
77
|
+
escape_control_chars: false,
|
|
78
|
+
log_exception_details: default_log_exception_details)
|
|
66
79
|
@time_format = time_format || self.class.build_time_format(precision)
|
|
67
80
|
@log_host = log_host
|
|
68
81
|
@log_application = log_application
|
|
69
82
|
@log_environment = log_environment
|
|
70
83
|
@precision = precision
|
|
71
|
-
@escape_control_chars
|
|
84
|
+
@escape_control_chars = escape_control_chars
|
|
85
|
+
@log_exception_details = log_exception_details
|
|
72
86
|
end
|
|
73
87
|
|
|
74
88
|
# Return default time format string
|
|
@@ -101,10 +115,42 @@ module SemanticLogger
|
|
|
101
115
|
# Note: This escapes (preserves) control characters, and is opt-in. It is
|
|
102
116
|
# distinct from Log#cleansed_message, which unconditionally *strips* ANSI
|
|
103
117
|
# colorization from the message for structured (JSON/Loki) output.
|
|
118
|
+
#
|
|
119
|
+
# A string with invalid byte sequences, for example an exception message that quotes
|
|
120
|
+
# untrusted binary input, or in another encoding, is first converted to valid UTF-8
|
|
121
|
+
# (dropping the invalid bytes), since matching against it would raise and the log entry
|
|
122
|
+
# would be lost.
|
|
104
123
|
def escape_control_characters(value)
|
|
105
124
|
return value unless escape_control_chars && value
|
|
106
125
|
|
|
107
|
-
|
|
126
|
+
string = Utils.encode_utf8_string(value.to_s)
|
|
127
|
+
string.gsub(CONTROL_CHARS) { |char| CONTROL_CHAR_ESCAPES[char] || format("\\x%02x", char.ord) }
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# Whether this formatter logs exception details unless told otherwise. See #initialize.
|
|
131
|
+
def default_log_exception_details
|
|
132
|
+
false
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# Returns [String] the detail Ruby adds to the message of the supplied exception (see
|
|
136
|
+
# Log#exception_details), or nil when there is none or this formatter does not log it.
|
|
137
|
+
def exception_details_for(exception)
|
|
138
|
+
return unless log_exception_details
|
|
139
|
+
|
|
140
|
+
log.exception_details(exception, source: log_exception_details == :with_source)
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# Returns [String] the backtrace of the logged exception, followed by each exception
|
|
144
|
+
# that caused it, as laid out by Log#backtrace_to_s.
|
|
145
|
+
#
|
|
146
|
+
# When `escape_control_chars` is enabled, the message of each cause and each backtrace
|
|
147
|
+
# line is passed through #escape_control_characters, the same as the message of the
|
|
148
|
+
# logged exception: causes are often parser or validation errors that quote untrusted
|
|
149
|
+
# input, and a backtrace can be set from untrusted data.
|
|
150
|
+
def exception_backtrace
|
|
151
|
+
return log.backtrace_to_s unless escape_control_chars
|
|
152
|
+
|
|
153
|
+
log.backtrace_to_s { |text| escape_control_characters(text) }
|
|
108
154
|
end
|
|
109
155
|
|
|
110
156
|
# Return the Time as a formatted string
|
|
@@ -71,6 +71,8 @@ module SemanticLogger
|
|
|
71
71
|
#
|
|
72
72
|
# Note: The option :multiline is set to false if not supplied.
|
|
73
73
|
# Note: Has no effect if Awesome Print is not installed.
|
|
74
|
+
# Note: With `escape_control_chars: true` the payload is always rendered plain
|
|
75
|
+
# (without colors) and on a single line, then escaped.
|
|
74
76
|
#
|
|
75
77
|
# color_map: [Hash | SemanticLogger::Formatters::Color::ColorMap]
|
|
76
78
|
# ColorMaps each of the log levels to a color
|
|
@@ -97,7 +99,9 @@ module SemanticLogger
|
|
|
97
99
|
return if named_tags.nil? || named_tags.empty?
|
|
98
100
|
|
|
99
101
|
list = []
|
|
100
|
-
named_tags.each_pair
|
|
102
|
+
named_tags.each_pair do |name, value|
|
|
103
|
+
list << "#{color}#{escape_control_characters(name)}: #{escape_control_characters(value)}#{color_map.clear}"
|
|
104
|
+
end
|
|
101
105
|
"{#{list.join(', ')}}"
|
|
102
106
|
end
|
|
103
107
|
|
|
@@ -114,7 +118,7 @@ module SemanticLogger
|
|
|
114
118
|
|
|
115
119
|
if log.payload.respond_to?(:ai)
|
|
116
120
|
begin
|
|
117
|
-
"-- #{log.payload.ai(
|
|
121
|
+
"-- #{escape_control_characters(log.payload_to_s { |payload| payload.ai(ai_options) })}"
|
|
118
122
|
rescue StandardError
|
|
119
123
|
super
|
|
120
124
|
end
|
|
@@ -126,13 +130,28 @@ module SemanticLogger
|
|
|
126
130
|
def exception
|
|
127
131
|
return unless log.exception
|
|
128
132
|
|
|
129
|
-
|
|
133
|
+
details = exception_details
|
|
134
|
+
"-- Exception: #{color}#{log.exception.class}: " \
|
|
135
|
+
"#{escape_control_characters(log.exception.message)}#{color_map.clear}\n" \
|
|
136
|
+
"#{"#{details}\n" if details}#{exception_backtrace}"
|
|
130
137
|
end
|
|
131
138
|
|
|
132
139
|
def call(log, logger)
|
|
133
140
|
self.color = color_map[log.level]
|
|
134
141
|
super
|
|
135
142
|
end
|
|
143
|
+
|
|
144
|
+
private
|
|
145
|
+
|
|
146
|
+
# Returns [Hash] the Amazing Print options to render the payload with.
|
|
147
|
+
#
|
|
148
|
+
# When `escape_control_chars` is enabled the payload is rendered plain, on one line:
|
|
149
|
+
# the colors and line breaks Amazing Print adds cannot be told apart from control
|
|
150
|
+
# characters in the data (for example an object whose #inspect returns them), and
|
|
151
|
+
# escaping them all leaves no colors or line breaks to keep.
|
|
152
|
+
def ai_options
|
|
153
|
+
escape_control_chars ? @ai_options.merge(plain: true, multiline: false) : @ai_options
|
|
154
|
+
end
|
|
136
155
|
end
|
|
137
156
|
end
|
|
138
157
|
end
|
|
@@ -12,13 +12,16 @@ module SemanticLogger
|
|
|
12
12
|
|
|
13
13
|
# Name of the thread that logged the message.
|
|
14
14
|
def thread_name
|
|
15
|
-
format("%.30s", log.thread_name)
|
|
15
|
+
escape_control_characters(format("%.30s", log.thread_name))
|
|
16
16
|
end
|
|
17
17
|
|
|
18
18
|
# Ruby file name and line number that logged the message.
|
|
19
|
+
#
|
|
20
|
+
# The file name is escaped like the other fields, since it comes from the first line of the
|
|
21
|
+
# backtrace, which a log call can supply from untrusted data.
|
|
19
22
|
def file_name_and_line
|
|
20
23
|
file, line = log.file_name_and_line(true)
|
|
21
|
-
"#{file}:#{line}" if file
|
|
24
|
+
"#{escape_control_characters(file)}:#{line}" if file
|
|
22
25
|
end
|
|
23
26
|
|
|
24
27
|
# Returns [String] the available process info
|
|
@@ -43,7 +46,9 @@ module SemanticLogger
|
|
|
43
46
|
return if named_tags.nil? || named_tags.empty?
|
|
44
47
|
|
|
45
48
|
list = []
|
|
46
|
-
named_tags.each_pair
|
|
49
|
+
named_tags.each_pair do |name, value|
|
|
50
|
+
list << "#{escape_control_characters(name)}: #{escape_control_characters(value)}"
|
|
51
|
+
end
|
|
47
52
|
"{#{list.join(', ')}}"
|
|
48
53
|
end
|
|
49
54
|
|
|
@@ -54,7 +59,7 @@ module SemanticLogger
|
|
|
54
59
|
|
|
55
60
|
# Class / app name
|
|
56
61
|
def name
|
|
57
|
-
log.name
|
|
62
|
+
escape_control_characters(log.name)
|
|
58
63
|
end
|
|
59
64
|
|
|
60
65
|
# Log message
|
|
@@ -67,12 +72,22 @@ module SemanticLogger
|
|
|
67
72
|
pl = log.payload_to_s
|
|
68
73
|
return unless pl
|
|
69
74
|
|
|
70
|
-
"-- #{pl}"
|
|
75
|
+
"-- #{escape_control_characters(pl)}"
|
|
71
76
|
end
|
|
72
77
|
|
|
73
78
|
# Exception
|
|
74
79
|
def exception
|
|
75
|
-
|
|
80
|
+
return unless log.exception
|
|
81
|
+
|
|
82
|
+
details = exception_details
|
|
83
|
+
"-- Exception: #{log.exception.class}: #{escape_control_characters(log.exception.message)}\n" \
|
|
84
|
+
"#{"#{details}\n" if details}#{exception_backtrace}"
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# The detail Ruby adds to the exception's message, such as did_you_mean suggestions.
|
|
88
|
+
# See Base#initialize for the `log_exception_details` option.
|
|
89
|
+
def exception_details
|
|
90
|
+
escape_control_characters(exception_details_for(log.exception)) if log.exception
|
|
76
91
|
end
|
|
77
92
|
|
|
78
93
|
# Default text log format
|
|
@@ -84,6 +99,13 @@ module SemanticLogger
|
|
|
84
99
|
|
|
85
100
|
[time, level, process_info, tags, named_tags, duration, name, message, payload, exception].compact.join(" ")
|
|
86
101
|
end
|
|
102
|
+
|
|
103
|
+
private
|
|
104
|
+
|
|
105
|
+
# Text output is read by people, who benefit most from exception details.
|
|
106
|
+
def default_log_exception_details
|
|
107
|
+
true
|
|
108
|
+
end
|
|
87
109
|
end
|
|
88
110
|
end
|
|
89
111
|
end
|