semantic_logger 4.7.2 → 4.10.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +50 -20
- data/lib/semantic_logger/appender/async.rb +1 -1
- data/lib/semantic_logger/appender/async_batch.rb +3 -1
- data/lib/semantic_logger/appender/bugsnag.rb +36 -23
- data/lib/semantic_logger/appender/elasticsearch.rb +24 -6
- data/lib/semantic_logger/appender/file.rb +249 -68
- data/lib/semantic_logger/appender/io.rb +68 -0
- data/lib/semantic_logger/appender/kafka.rb +4 -0
- data/lib/semantic_logger/appender/sentry_ruby.rb +138 -0
- data/lib/semantic_logger/appender/splunk.rb +2 -1
- data/lib/semantic_logger/appender/splunk_http.rb +3 -2
- data/lib/semantic_logger/appender/syslog.rb +5 -3
- data/lib/semantic_logger/appender/wrapper.rb +3 -2
- data/lib/semantic_logger/appender.rb +13 -6
- data/lib/semantic_logger/appenders.rb +31 -27
- data/lib/semantic_logger/base.rb +16 -7
- data/lib/semantic_logger/formatters/base.rb +1 -0
- data/lib/semantic_logger/formatters/color.rb +3 -3
- data/lib/semantic_logger/formatters/logfmt.rb +72 -0
- data/lib/semantic_logger/formatters/syslog.rb +2 -1
- data/lib/semantic_logger/formatters/syslog_cee.rb +2 -1
- data/lib/semantic_logger/formatters.rb +10 -11
- data/lib/semantic_logger/log.rb +2 -4
- data/lib/semantic_logger/loggable.rb +8 -1
- data/lib/semantic_logger/logger.rb +16 -6
- data/lib/semantic_logger/processor.rb +3 -3
- data/lib/semantic_logger/semantic_logger.rb +18 -10
- data/lib/semantic_logger/subscriber.rb +10 -0
- data/lib/semantic_logger/sync_processor.rb +5 -5
- data/lib/semantic_logger/test/capture_log_events.rb +34 -0
- data/lib/semantic_logger/utils.rb +29 -10
- data/lib/semantic_logger/version.rb +1 -1
- data/lib/semantic_logger.rb +4 -0
- metadata +13 -10
@@ -0,0 +1,68 @@
|
|
1
|
+
# File appender
|
2
|
+
#
|
3
|
+
# Writes log messages to a file or open iostream
|
4
|
+
#
|
5
|
+
module SemanticLogger
|
6
|
+
module Appender
|
7
|
+
class IO < SemanticLogger::Subscriber
|
8
|
+
# Create a Stream Logger appender instance.
|
9
|
+
#
|
10
|
+
# Parameters
|
11
|
+
# io [IO]
|
12
|
+
# An IO stream to which to write the log messages to.
|
13
|
+
#
|
14
|
+
# :level [:trace | :debug | :info | :warn | :error | :fatal]
|
15
|
+
# Override the log level for this appender.
|
16
|
+
# Default: SemanticLogger.default_level
|
17
|
+
#
|
18
|
+
# :formatter: [Object|Proc]
|
19
|
+
# An instance of a class that implements #call, or a Proc to be used to format
|
20
|
+
# the output from this appender
|
21
|
+
# Default: Use the built-in formatter (See: #call)
|
22
|
+
#
|
23
|
+
# :filter [Regexp|Proc]
|
24
|
+
# RegExp: Only include log messages where the class name matches the supplied
|
25
|
+
# regular expression. All other messages will be ignored.
|
26
|
+
# Proc: Only include log messages where the supplied Proc returns true
|
27
|
+
# The Proc must return true or false.
|
28
|
+
#
|
29
|
+
# Example
|
30
|
+
# require "semantic_logger"
|
31
|
+
#
|
32
|
+
# # Enable trace level logging
|
33
|
+
# SemanticLogger.default_level = :info
|
34
|
+
#
|
35
|
+
# # Log to screen
|
36
|
+
# SemanticLogger.add_appender(io: $stdout, formatter: :color)
|
37
|
+
#
|
38
|
+
# logger = SemanticLogger['test']
|
39
|
+
# logger.info 'Hello World'
|
40
|
+
def initialize(io, **args, &block)
|
41
|
+
@io = io
|
42
|
+
unless @io.respond_to?(:write)
|
43
|
+
raise(ArgumentError, "SemanticLogging::Appender::IO io is not a valid IO instance: #{io.inspect}")
|
44
|
+
end
|
45
|
+
|
46
|
+
super(**args, &block)
|
47
|
+
end
|
48
|
+
|
49
|
+
def log(log)
|
50
|
+
# Since only one appender thread will be writing to the file at a time
|
51
|
+
# it is not necessary to protect access to the file with a semaphore
|
52
|
+
# Allow this logger to filter out log levels lower than it's own
|
53
|
+
@io.write(formatter.call(log, self) << "\n")
|
54
|
+
true
|
55
|
+
end
|
56
|
+
|
57
|
+
# Flush all pending logs to disk.
|
58
|
+
# Waits for all sent documents to be written to disk
|
59
|
+
def flush
|
60
|
+
@io.flush if @io.respond_to?(:flush)
|
61
|
+
end
|
62
|
+
|
63
|
+
def console_output?
|
64
|
+
[$stderr, $stdout].include?(@io)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,138 @@
|
|
1
|
+
begin
|
2
|
+
require "sentry-ruby"
|
3
|
+
rescue LoadError
|
4
|
+
raise LoadError, 'Gem sentry-ruby is required for logging purposes. Please add the gem "sentry-ruby" to your Gemfile.'
|
5
|
+
end
|
6
|
+
|
7
|
+
# Send log messages to sentry
|
8
|
+
#
|
9
|
+
# Example:
|
10
|
+
# SemanticLogger.add_appender(appender: :sentry_ruby)
|
11
|
+
#
|
12
|
+
module SemanticLogger
|
13
|
+
module Appender
|
14
|
+
class SentryRuby < SemanticLogger::Subscriber
|
15
|
+
# Create Appender
|
16
|
+
#
|
17
|
+
# Parameters
|
18
|
+
# level: [:trace | :debug | :info | :warn | :error | :fatal]
|
19
|
+
# Override the log level for this appender.
|
20
|
+
# Default: :error
|
21
|
+
#
|
22
|
+
# formatter: [Object|Proc|Symbol|Hash]
|
23
|
+
# An instance of a class that implements #call, or a Proc to be used to format
|
24
|
+
# the output from this appender
|
25
|
+
# Default: Use the built-in formatter (See: #call)
|
26
|
+
#
|
27
|
+
# filter: [Regexp|Proc]
|
28
|
+
# RegExp: Only include log messages where the class name matches the supplied.
|
29
|
+
# regular expression. All other messages will be ignored.
|
30
|
+
# Proc: Only include log messages where the supplied Proc returns true
|
31
|
+
# The Proc must return true or false.
|
32
|
+
#
|
33
|
+
# host: [String]
|
34
|
+
# Name of this host to appear in log messages.
|
35
|
+
# Default: SemanticLogger.host
|
36
|
+
#
|
37
|
+
# application: [String]
|
38
|
+
# Name of this application to appear in log messages.
|
39
|
+
# Default: SemanticLogger.application
|
40
|
+
def initialize(level: :error, **args, &block)
|
41
|
+
# Replace the Sentry Ruby logger so that we can identify its log
|
42
|
+
# messages and not forward them to Sentry
|
43
|
+
::Sentry.init { |config| config.logger = SemanticLogger[::Sentry] }
|
44
|
+
super(level: level, **args, &block)
|
45
|
+
end
|
46
|
+
|
47
|
+
# Send an error notification to sentry
|
48
|
+
def log(log)
|
49
|
+
# Ignore logs coming from Sentry itself
|
50
|
+
return false if log.name == "Sentry"
|
51
|
+
|
52
|
+
context = formatter.call(log, self)
|
53
|
+
payload = context.delete(:payload) || {}
|
54
|
+
named_tags = context[:named_tags] || {}
|
55
|
+
transaction_name = named_tags.delete(:transaction_name)
|
56
|
+
|
57
|
+
user = extract_user!(named_tags, payload)
|
58
|
+
tags = extract_tags!(context)
|
59
|
+
|
60
|
+
fingerprint = payload.delete(:fingerprint)
|
61
|
+
|
62
|
+
::Sentry.with_scope do |scope|
|
63
|
+
scope.set_user(user) if user
|
64
|
+
scope.set_level(context.delete(:level)) if context[:level]
|
65
|
+
scope.set_fingerprint(fingerprint) if fingerprint
|
66
|
+
scope.set_transaction_name(transaction_name) if transaction_name
|
67
|
+
scope.set_tags(tags)
|
68
|
+
scope.set_extras(context)
|
69
|
+
scope.set_extras(payload)
|
70
|
+
|
71
|
+
if log.exception
|
72
|
+
::Sentry.capture_exception(log.exception)
|
73
|
+
elsif log.backtrace
|
74
|
+
::Sentry.capture_message(context[:message], backtrace: log.backtrace)
|
75
|
+
else
|
76
|
+
::Sentry.capture_message(context[:message])
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
true
|
81
|
+
end
|
82
|
+
|
83
|
+
private
|
84
|
+
|
85
|
+
# Use Raw Formatter by default
|
86
|
+
def default_formatter
|
87
|
+
SemanticLogger::Formatters::Raw.new
|
88
|
+
end
|
89
|
+
|
90
|
+
# Extract user data from named tags or payload.
|
91
|
+
#
|
92
|
+
# Keys :user_id and :user_email will be used as :id and :email respectively.
|
93
|
+
# Keys :username and :ip_address will be used verbatim.
|
94
|
+
#
|
95
|
+
# Any additional value nested in a :user key will be added, provided any of
|
96
|
+
# the above keys is already present.
|
97
|
+
#
|
98
|
+
def extract_user!(*sources)
|
99
|
+
keys = {user_id: :id, username: :username, user_email: :email, ip_address: :ip_address}
|
100
|
+
|
101
|
+
user = {}
|
102
|
+
|
103
|
+
sources.each do |source|
|
104
|
+
keys.each do |source_key, target_key|
|
105
|
+
value = source.delete(source_key)
|
106
|
+
user[target_key] = value if value
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
return if user.empty?
|
111
|
+
|
112
|
+
sources.each do |source|
|
113
|
+
extras = source.delete(:user)
|
114
|
+
user.merge!(extras) if extras.is_a?(Hash)
|
115
|
+
end
|
116
|
+
|
117
|
+
user
|
118
|
+
end
|
119
|
+
|
120
|
+
# Extract tags.
|
121
|
+
#
|
122
|
+
# Named tags will be stringified (both key and value).
|
123
|
+
# Unnamed tags will be stringified and joined with a comma. Then they will
|
124
|
+
# be used as a "tag" named tag. If such a tag already exists, it is also
|
125
|
+
# joined with a comma.
|
126
|
+
#
|
127
|
+
# Finally, the tag names are limited to 32 characters and the tag values to 256.
|
128
|
+
#
|
129
|
+
def extract_tags!(context)
|
130
|
+
named_tags = context.delete(:named_tags) || {}
|
131
|
+
named_tags = named_tags.map { |k, v| [k.to_s, v.to_s] }.to_h
|
132
|
+
tags = context.delete(:tags)
|
133
|
+
named_tags.merge!("tag" => tags.join(", ")) { |_, v1, v2| "#{v1}, #{v2}" } if tags
|
134
|
+
named_tags.map { |k, v| [k[0...32], v[0...256]] }.to_h
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
@@ -1,7 +1,8 @@
|
|
1
1
|
begin
|
2
2
|
require "splunk-sdk-ruby"
|
3
3
|
rescue LoadError
|
4
|
-
raise LoadError,
|
4
|
+
raise LoadError,
|
5
|
+
'Gem splunk-sdk-ruby is required for logging to Splunk. Please add the gem "splunk-sdk-ruby" to your Gemfile.'
|
5
6
|
end
|
6
7
|
|
7
8
|
# Splunk log appender.
|
@@ -89,8 +89,9 @@ module SemanticLogger
|
|
89
89
|
# For splunk format requirements see:
|
90
90
|
# https://docs.splunk.com/Documentation/Splunk/latest/Data/FormateventsforHTTPEventCollector
|
91
91
|
def call(log, logger)
|
92
|
-
h
|
93
|
-
|
92
|
+
h = SemanticLogger::Formatters::Raw.new(time_format: :seconds).call(log, logger)
|
93
|
+
h.delete(:host)
|
94
|
+
message = {
|
94
95
|
source: logger.application,
|
95
96
|
host: logger.host,
|
96
97
|
time: h.delete(:time),
|
@@ -57,7 +57,7 @@ module SemanticLogger
|
|
57
57
|
# Only used with the TCP protocol.
|
58
58
|
# Specify custom parameters to pass into Net::TCPClient.new
|
59
59
|
# For a list of options see the net_tcp_client documentation:
|
60
|
-
# https://github.com/
|
60
|
+
# https://github.com/reidmorrison/net_tcp_client/blob/master/lib/net/tcp_client/tcp_client.rb
|
61
61
|
#
|
62
62
|
# level: [:trace | :debug | :info | :warn | :error | :fatal]
|
63
63
|
# Override the log level for this appender.
|
@@ -151,7 +151,8 @@ module SemanticLogger
|
|
151
151
|
begin
|
152
152
|
require "syslog_protocol"
|
153
153
|
rescue LoadError
|
154
|
-
raise LoadError,
|
154
|
+
raise LoadError,
|
155
|
+
"Missing gem: syslog_protocol. This gem is required when logging over TCP or UDP. To fix this error: gem install syslog_protocol"
|
155
156
|
end
|
156
157
|
|
157
158
|
# The net_tcp_client gem is required when logging over TCP.
|
@@ -159,7 +160,8 @@ module SemanticLogger
|
|
159
160
|
begin
|
160
161
|
require "net/tcp_client"
|
161
162
|
rescue LoadError
|
162
|
-
raise LoadError,
|
163
|
+
raise LoadError,
|
164
|
+
"Missing gem: net_tcp_client. This gem is required when logging over TCP. To fix this error: gem install net_tcp_client"
|
163
165
|
end
|
164
166
|
end
|
165
167
|
end
|
@@ -32,7 +32,7 @@ module SemanticLogger
|
|
32
32
|
# require 'logger'
|
33
33
|
# require 'semantic_logger'
|
34
34
|
#
|
35
|
-
# ruby_logger = Logger.new(
|
35
|
+
# ruby_logger = Logger.new($stdout)
|
36
36
|
# SemanticLogger.add_appender(logger: ruby_logger)
|
37
37
|
#
|
38
38
|
# logger = SemanticLogger['test']
|
@@ -45,7 +45,8 @@ module SemanticLogger
|
|
45
45
|
# Check if the custom appender responds to all the log levels. For example Ruby ::Logger
|
46
46
|
does_not_implement = LEVELS[1..-1].find { |i| !@logger.respond_to?(i) }
|
47
47
|
if does_not_implement
|
48
|
-
raise(ArgumentError,
|
48
|
+
raise(ArgumentError,
|
49
|
+
"Supplied logger does not implement:#{does_not_implement}. It must implement all of #{LEVELS[1..-1].inspect}")
|
49
50
|
end
|
50
51
|
|
51
52
|
super(**args, &block)
|
@@ -9,6 +9,7 @@ module SemanticLogger
|
|
9
9
|
autoload :File, "semantic_logger/appender/file"
|
10
10
|
autoload :Graylog, "semantic_logger/appender/graylog"
|
11
11
|
autoload :Honeybadger, "semantic_logger/appender/honeybadger"
|
12
|
+
autoload :IO, "semantic_logger/appender/io"
|
12
13
|
autoload :Kafka, "semantic_logger/appender/kafka"
|
13
14
|
autoload :Sentry, "semantic_logger/appender/sentry"
|
14
15
|
autoload :Http, "semantic_logger/appender/http"
|
@@ -21,6 +22,7 @@ module SemanticLogger
|
|
21
22
|
autoload :Tcp, "semantic_logger/appender/tcp"
|
22
23
|
autoload :Udp, "semantic_logger/appender/udp"
|
23
24
|
autoload :Wrapper, "semantic_logger/appender/wrapper"
|
25
|
+
autoload :SentryRuby, "semantic_logger/appender/sentry_ruby"
|
24
26
|
# @formatter:on
|
25
27
|
|
26
28
|
# Returns [SemanticLogger::Subscriber] appender for the supplied options
|
@@ -32,7 +34,7 @@ module SemanticLogger
|
|
32
34
|
appender = build(**args, &block)
|
33
35
|
|
34
36
|
# If appender implements #batch, then it should use the batch proxy by default.
|
35
|
-
batch
|
37
|
+
batch = true if batch.nil? && appender.respond_to?(:batch)
|
36
38
|
|
37
39
|
if batch == true
|
38
40
|
Appender::AsyncBatch.new(
|
@@ -56,8 +58,10 @@ module SemanticLogger
|
|
56
58
|
|
57
59
|
# Returns [Subscriber] instance from the supplied options.
|
58
60
|
def self.build(io: nil, file_name: nil, appender: nil, metric: nil, logger: nil, **args, &block)
|
59
|
-
if
|
60
|
-
SemanticLogger::Appender::File.new(
|
61
|
+
if file_name
|
62
|
+
SemanticLogger::Appender::File.new(file_name, **args, &block)
|
63
|
+
elsif io
|
64
|
+
SemanticLogger::Appender::IO.new(io, **args, &block)
|
61
65
|
elsif logger
|
62
66
|
SemanticLogger::Appender::Wrapper.new(logger: logger, **args, &block)
|
63
67
|
elsif appender
|
@@ -66,7 +70,8 @@ module SemanticLogger
|
|
66
70
|
elsif appender.is_a?(Subscriber)
|
67
71
|
appender
|
68
72
|
else
|
69
|
-
raise(ArgumentError,
|
73
|
+
raise(ArgumentError,
|
74
|
+
"Parameter :appender must be either a Symbol or an object derived from SemanticLogger::Subscriber, not: #{appender.inspect}")
|
70
75
|
end
|
71
76
|
elsif metric
|
72
77
|
if metric.is_a?(Symbol)
|
@@ -74,10 +79,12 @@ module SemanticLogger
|
|
74
79
|
elsif metric.is_a?(Subscriber)
|
75
80
|
metric
|
76
81
|
else
|
77
|
-
raise(ArgumentError,
|
82
|
+
raise(ArgumentError,
|
83
|
+
"Parameter :metric must be either a Symbol or an object derived from SemanticLogger::Subscriber, not: #{appender.inspect}")
|
78
84
|
end
|
79
85
|
else
|
80
|
-
raise(ArgumentError,
|
86
|
+
raise(ArgumentError,
|
87
|
+
"To create an appender it must supply one of the following: :io, :file_name, :appender, :metric, or :logger")
|
81
88
|
end
|
82
89
|
end
|
83
90
|
|
@@ -10,42 +10,48 @@ module SemanticLogger
|
|
10
10
|
|
11
11
|
def add(**args, &block)
|
12
12
|
appender = SemanticLogger::Appender.factory(**args, &block)
|
13
|
+
|
14
|
+
if appender.respond_to?(:console_output?) && appender.console_output? && console_output?
|
15
|
+
logger.warn "Ignoring attempt to add a second console appender: #{appender.class.name} since it would result in duplicate console output."
|
16
|
+
return
|
17
|
+
end
|
18
|
+
|
13
19
|
self << appender
|
14
20
|
appender
|
15
21
|
end
|
16
22
|
|
23
|
+
# Whether any of the existing appenders already output to the console?
|
24
|
+
# I.e. Writes to stdout or stderr.
|
25
|
+
def console_output?
|
26
|
+
any? { |appender| appender.respond_to?(:console_output?) && appender.console_output? }
|
27
|
+
end
|
28
|
+
|
17
29
|
def log(log)
|
18
30
|
each do |appender|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
logger.error "Failed to log to appender: #{appender.name}", e
|
23
|
-
end
|
31
|
+
appender.log(log) if appender.should_log?(log)
|
32
|
+
rescue Exception => e
|
33
|
+
logger.error "Failed to log to appender: #{appender.name}", e
|
24
34
|
end
|
25
35
|
end
|
26
36
|
|
27
37
|
def flush
|
28
38
|
each do |appender|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
logger.error "Failed to flush appender: #{appender.name}", e
|
34
|
-
end
|
39
|
+
logger.trace "Flushing appender: #{appender.name}"
|
40
|
+
appender.flush
|
41
|
+
rescue Exception => e
|
42
|
+
logger.error "Failed to flush appender: #{appender.name}", e
|
35
43
|
end
|
36
44
|
logger.trace "All appenders flushed"
|
37
45
|
end
|
38
46
|
|
39
47
|
def close
|
40
|
-
each do |appender|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
logger.error "Failed to close appender: #{appender.name}", e
|
48
|
-
end
|
48
|
+
to_a.each do |appender|
|
49
|
+
logger.trace "Closing appender: #{appender.name}"
|
50
|
+
delete(appender)
|
51
|
+
appender.flush
|
52
|
+
appender.close
|
53
|
+
rescue Exception => e
|
54
|
+
logger.error "Failed to close appender: #{appender.name}", e
|
49
55
|
end
|
50
56
|
logger.trace "All appenders closed and removed from appender list"
|
51
57
|
end
|
@@ -53,14 +59,12 @@ module SemanticLogger
|
|
53
59
|
# After a fork the appender thread is not running, start it if it is not running.
|
54
60
|
def reopen
|
55
61
|
each do |appender|
|
56
|
-
|
57
|
-
next unless appender.respond_to?(:reopen)
|
62
|
+
next unless appender.respond_to?(:reopen)
|
58
63
|
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
end
|
64
|
+
logger.trace "Reopening appender: #{appender.name}"
|
65
|
+
appender.reopen
|
66
|
+
rescue Exception => e
|
67
|
+
logger.error "Failed to re-open appender: #{appender.name}", e
|
64
68
|
end
|
65
69
|
logger.trace "All appenders re-opened"
|
66
70
|
end
|
data/lib/semantic_logger/base.rb
CHANGED
@@ -63,7 +63,7 @@ module SemanticLogger
|
|
63
63
|
# SemanticLogger.default_level = :info
|
64
64
|
#
|
65
65
|
# # Log to screen
|
66
|
-
# SemanticLogger.add_appender(io:
|
66
|
+
# SemanticLogger.add_appender(io: $stdout, formatter: :color)
|
67
67
|
#
|
68
68
|
# # And log to a file at the same time
|
69
69
|
# SemanticLogger.add_appender(file_name: 'application.log', formatter: :color)
|
@@ -136,7 +136,7 @@ module SemanticLogger
|
|
136
136
|
|
137
137
|
backtrace =
|
138
138
|
if thread == Thread.current
|
139
|
-
Utils.extract_backtrace
|
139
|
+
Utils.extract_backtrace(caller)
|
140
140
|
else
|
141
141
|
log.thread_name = thread.name
|
142
142
|
log.tags = (thread[:semantic_logger_tags] || []).clone
|
@@ -188,7 +188,8 @@ module SemanticLogger
|
|
188
188
|
# - For better performance with clean tags, see `SemanticLogger.tagged`.
|
189
189
|
def tagged(*tags, &block)
|
190
190
|
# Allow named tags to be passed into the logger
|
191
|
-
|
191
|
+
# Rails::Rack::Logger passes logs as an array with a single argument
|
192
|
+
if tags.size == 1 && !tags.first.is_a?(Array)
|
192
193
|
tag = tags[0]
|
193
194
|
return yield if tag.nil? || tag == ""
|
194
195
|
|
@@ -263,14 +264,22 @@ module SemanticLogger
|
|
263
264
|
# For example if set to :warn, this appender would only log :warn and :fatal
|
264
265
|
# log messages when other appenders could be logging :info and lower
|
265
266
|
#
|
266
|
-
# filter [Regexp|Proc]
|
267
|
+
# filter [Regexp|Proc|Module]
|
267
268
|
# RegExp: Only include log messages where the class name matches the supplied
|
268
269
|
# regular expression. All other messages will be ignored
|
269
270
|
# Proc: Only include log messages where the supplied Proc returns true
|
270
271
|
# The Proc must return true or false
|
272
|
+
# Module: A module that implements `.call`. For example:
|
273
|
+
# module ComplexFilter
|
274
|
+
# def self.call(log)
|
275
|
+
# (/\AExclude/ =~ log.message).nil?
|
276
|
+
# end
|
277
|
+
# end
|
271
278
|
def initialize(klass, level = nil, filter = nil)
|
272
|
-
# Support filtering all messages to this logger
|
273
|
-
|
279
|
+
# Support filtering all messages to this logger instance.
|
280
|
+
unless filter.nil? || filter.is_a?(Regexp) || filter.is_a?(Proc) || filter.respond_to?(:call)
|
281
|
+
raise ":filter must be a Regexp, Proc, or implement :call"
|
282
|
+
end
|
274
283
|
|
275
284
|
@filter = filter.is_a?(Regexp) ? filter.freeze : filter
|
276
285
|
@name = klass.is_a?(String) ? klass : klass.name
|
@@ -368,7 +377,7 @@ module SemanticLogger
|
|
368
377
|
exception = e
|
369
378
|
ensure
|
370
379
|
# Must use ensure block otherwise a `return` in the yield above will skip the log entry
|
371
|
-
log
|
380
|
+
log = Log.new(name, level, index)
|
372
381
|
exception ||= params[:exception]
|
373
382
|
message = params[:message] if params[:message]
|
374
383
|
duration =
|
@@ -109,14 +109,14 @@ module SemanticLogger
|
|
109
109
|
def payload
|
110
110
|
return unless log.payload?
|
111
111
|
|
112
|
-
if
|
113
|
-
super
|
114
|
-
else
|
112
|
+
if log.payload.respond_to?(:ai)
|
115
113
|
begin
|
116
114
|
"-- #{log.payload.ai(@ai_options)}"
|
117
115
|
rescue StandardError
|
118
116
|
super
|
119
117
|
end
|
118
|
+
else
|
119
|
+
super
|
120
120
|
end
|
121
121
|
end
|
122
122
|
|
@@ -0,0 +1,72 @@
|
|
1
|
+
require "json"
|
2
|
+
|
3
|
+
module SemanticLogger
|
4
|
+
module Formatters
|
5
|
+
# Produces logfmt formatted messages
|
6
|
+
#
|
7
|
+
# The following fields are extracted from the raw log and included in the formatted message:
|
8
|
+
# :timestamp, :level, :name, :message, :duration, :tags, :named_tags
|
9
|
+
#
|
10
|
+
# E.g.
|
11
|
+
# timestamp="2020-07-20T08:32:05.375276Z" level=info name="DefaultTest" base="breakfast" spaces="second breakfast" double_quotes="\"elevensies\"" single_quotes="'lunch'" tag="success"
|
12
|
+
#
|
13
|
+
# All timestamps are ISO8601 formatteed
|
14
|
+
# All user supplied values are escaped and surrounded by double quotes to avoid ambiguious message delimeters
|
15
|
+
# `tags` are treated as keys with boolean values. Tag names are not formatted or validated, ensure you use valid logfmt format for tag names.
|
16
|
+
# `named_tags` are flattened are merged into the top level message field. Any conflicting fields are overridden.
|
17
|
+
# `payload` values take precedence over `tags` and `named_tags`. Any conflicting fields are overridden.
|
18
|
+
#
|
19
|
+
# Futher Reading https://brandur.org/logfmt
|
20
|
+
class Logfmt < Raw
|
21
|
+
def initialize(time_format: :iso_8601, time_key: :timestamp, **args)
|
22
|
+
super(time_format: time_format, time_key: time_key, **args)
|
23
|
+
end
|
24
|
+
|
25
|
+
def call(log, logger)
|
26
|
+
@raw = super(log, logger)
|
27
|
+
|
28
|
+
raw_to_logfmt
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def raw_to_logfmt
|
34
|
+
@parsed = @raw.slice(time_key, :level, :name, :message, :duration).merge(tag: "success")
|
35
|
+
handle_tags
|
36
|
+
handle_payload
|
37
|
+
handle_exception
|
38
|
+
|
39
|
+
flatten_log
|
40
|
+
end
|
41
|
+
|
42
|
+
def handle_tags
|
43
|
+
tags = @raw.fetch(:tags){ [] }
|
44
|
+
.each_with_object({}){ |tag, accum| accum[tag] = true }
|
45
|
+
|
46
|
+
@parsed = @parsed.merge(tags)
|
47
|
+
.merge(@raw.fetch(:named_tags){ {} })
|
48
|
+
end
|
49
|
+
|
50
|
+
def handle_payload
|
51
|
+
return unless @raw.key? :payload
|
52
|
+
|
53
|
+
@parsed = @parsed.merge(@raw[:payload])
|
54
|
+
end
|
55
|
+
|
56
|
+
def handle_exception
|
57
|
+
return unless @raw.key? :exception
|
58
|
+
|
59
|
+
@parsed[:tag] = "exception"
|
60
|
+
@parsed = @parsed.merge(@raw[:exception])
|
61
|
+
end
|
62
|
+
|
63
|
+
def flatten_log
|
64
|
+
flattened = @parsed.map do |key, value|
|
65
|
+
"#{key}=#{value.to_json}"
|
66
|
+
end
|
67
|
+
|
68
|
+
flattened.join(" ")
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -1,7 +1,8 @@
|
|
1
1
|
begin
|
2
2
|
require "syslog_protocol"
|
3
3
|
rescue LoadError
|
4
|
-
raise LoadError,
|
4
|
+
raise LoadError,
|
5
|
+
'Gem syslog_protocol is required for remote logging using the Syslog protocol. Please add the gem "syslog_protocol" to your Gemfile.'
|
5
6
|
end
|
6
7
|
|
7
8
|
module SemanticLogger
|
@@ -1,7 +1,8 @@
|
|
1
1
|
begin
|
2
2
|
require "syslog_protocol"
|
3
3
|
rescue LoadError
|
4
|
-
raise LoadError,
|
4
|
+
raise LoadError,
|
5
|
+
'Gem syslog_protocol is required for remote logging using the Syslog protocol. Please add the gem "syslog_protocol" to your Gemfile.'
|
5
6
|
end
|
6
7
|
|
7
8
|
module SemanticLogger
|
@@ -1,16 +1,15 @@
|
|
1
1
|
module SemanticLogger
|
2
2
|
module Formatters
|
3
|
-
|
4
|
-
autoload :
|
5
|
-
autoload :
|
6
|
-
autoload :
|
7
|
-
autoload :
|
8
|
-
autoload :
|
9
|
-
autoload :
|
10
|
-
autoload :
|
11
|
-
autoload :
|
12
|
-
autoload :
|
13
|
-
# @formatter:on
|
3
|
+
autoload :Base, "semantic_logger/formatters/base"
|
4
|
+
autoload :Color, "semantic_logger/formatters/color"
|
5
|
+
autoload :Default, "semantic_logger/formatters/default"
|
6
|
+
autoload :Json, "semantic_logger/formatters/json"
|
7
|
+
autoload :Raw, "semantic_logger/formatters/raw"
|
8
|
+
autoload :OneLine, "semantic_logger/formatters/one_line"
|
9
|
+
autoload :Signalfx, "semantic_logger/formatters/signalfx"
|
10
|
+
autoload :Syslog, "semantic_logger/formatters/syslog"
|
11
|
+
autoload :Fluentd, "semantic_logger/formatters/fluentd"
|
12
|
+
autoload :Logfmt, "semantic_logger/formatters/logfmt"
|
14
13
|
|
15
14
|
# Return formatter that responds to call.
|
16
15
|
#
|