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
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
require "rspec/expectations"
|
|
2
|
+
|
|
3
|
+
module SemanticLogger
|
|
4
|
+
module Test
|
|
5
|
+
# RSpec matchers and helpers for asserting on Semantic Logger events.
|
|
6
|
+
#
|
|
7
|
+
# These mirror the Minitest helpers in SemanticLogger::Test::Minitest.
|
|
8
|
+
#
|
|
9
|
+
# Enable them once, in spec_helper.rb:
|
|
10
|
+
#
|
|
11
|
+
# require "semantic_logger/test/rspec"
|
|
12
|
+
#
|
|
13
|
+
# RSpec.configure do |config|
|
|
14
|
+
# config.include SemanticLogger::Test::RSpec
|
|
15
|
+
# end
|
|
16
|
+
#
|
|
17
|
+
# Capture the events emitted whilst running a block, then assert on them:
|
|
18
|
+
#
|
|
19
|
+
# events = capture_semantic_logger_events { User.new.enable! }
|
|
20
|
+
# expect(events.first).to be_a_semantic_logger_event(level: :info, message: "User enabled")
|
|
21
|
+
#
|
|
22
|
+
# Or assert directly that a block logs a matching event:
|
|
23
|
+
#
|
|
24
|
+
# expect { User.new.enable! }.to(
|
|
25
|
+
# log_semantic_logger_event(level: :info, message: "User enabled")
|
|
26
|
+
# )
|
|
27
|
+
module RSpec
|
|
28
|
+
# Attributes that may be asserted on a single log event. The keys map
|
|
29
|
+
# directly onto SemanticLogger::Log attributes, except for the
|
|
30
|
+
# *_includes variants which assert a partial (substring / subset) match.
|
|
31
|
+
EVENT_ATTRIBUTES = %i[
|
|
32
|
+
level name message thread_name tags named_tags context
|
|
33
|
+
metric metric_amount dimensions level_index duration time
|
|
34
|
+
exception backtrace payload
|
|
35
|
+
].freeze
|
|
36
|
+
|
|
37
|
+
# Matches a single SemanticLogger::Log against a set of expectations.
|
|
38
|
+
#
|
|
39
|
+
# An expected value may be:
|
|
40
|
+
# * a Class - the actual value must be a kind_of that class
|
|
41
|
+
# * :nil - the actual value must be nil
|
|
42
|
+
# * any value - compared with ==
|
|
43
|
+
class EventMatcher
|
|
44
|
+
include ::RSpec::Matchers::Composable
|
|
45
|
+
|
|
46
|
+
def initialize(message_includes: nil, payload_includes: nil, exception_includes: nil, **expected)
|
|
47
|
+
unknown = expected.keys - EVENT_ATTRIBUTES
|
|
48
|
+
raise ArgumentError, "Unknown log event attribute(s): #{unknown.join(', ')}" unless unknown.empty?
|
|
49
|
+
|
|
50
|
+
@expected = expected
|
|
51
|
+
@message_includes = message_includes
|
|
52
|
+
@payload_includes = payload_includes
|
|
53
|
+
@exception_includes = exception_includes
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def matches?(event)
|
|
57
|
+
@event = event
|
|
58
|
+
return failed("no log event") unless event
|
|
59
|
+
|
|
60
|
+
@expected.each_pair do |name, expected|
|
|
61
|
+
actual = event.public_send(name)
|
|
62
|
+
return failed(mismatch(name, expected, actual)) unless attribute_matches?(expected, actual)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
return failed(includes_failure(:message, @message_includes)) unless message_includes_matches?
|
|
66
|
+
return failed(includes_failure(:payload, @payload_includes)) unless payload_includes_matches?
|
|
67
|
+
return failed(includes_failure(:exception, @exception_includes)) unless exception_includes_matches?
|
|
68
|
+
|
|
69
|
+
true
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def description
|
|
73
|
+
"be a semantic logger event matching #{full_expectations.inspect}"
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def failure_message
|
|
77
|
+
"expected log event to #{description}, but #{@failure}.\n" \
|
|
78
|
+
"Log event: #{event_inspect}"
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def failure_message_when_negated
|
|
82
|
+
"expected log event not to #{description}.\nLog event: #{event_inspect}"
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
private
|
|
86
|
+
|
|
87
|
+
def failed(reason)
|
|
88
|
+
@failure = reason
|
|
89
|
+
false
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def attribute_matches?(expected, actual)
|
|
93
|
+
case expected
|
|
94
|
+
when :nil
|
|
95
|
+
actual.nil?
|
|
96
|
+
when Class
|
|
97
|
+
actual.is_a?(expected)
|
|
98
|
+
else
|
|
99
|
+
expected == actual
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def message_includes_matches?
|
|
104
|
+
return true unless @message_includes
|
|
105
|
+
|
|
106
|
+
@event.message&.include?(@message_includes)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def payload_includes_matches?
|
|
110
|
+
return true unless @payload_includes
|
|
111
|
+
|
|
112
|
+
payload = @event.payload || {}
|
|
113
|
+
@payload_includes.all? { |key, value| payload[key] == value }
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def exception_includes_matches?
|
|
117
|
+
return true unless @exception_includes
|
|
118
|
+
|
|
119
|
+
exception = @event.exception
|
|
120
|
+
return false unless exception
|
|
121
|
+
|
|
122
|
+
@exception_includes.all? { |key, value| exception.public_send(key) == value }
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def mismatch(name, expected, actual)
|
|
126
|
+
"#{name} was #{actual.inspect} (expected #{expected.inspect})"
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def includes_failure(name, expected)
|
|
130
|
+
"#{name} did not include #{expected.inspect}"
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def full_expectations
|
|
134
|
+
@expected.merge(
|
|
135
|
+
{
|
|
136
|
+
message_includes: @message_includes,
|
|
137
|
+
payload_includes: @payload_includes,
|
|
138
|
+
exception_includes: @exception_includes
|
|
139
|
+
}.compact
|
|
140
|
+
)
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def event_inspect
|
|
144
|
+
@event.respond_to?(:to_h) ? @event.to_h.inspect : @event.inspect
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# Matches a block, asserting that it emits at least one log event that
|
|
149
|
+
# matches the supplied expectations.
|
|
150
|
+
class LogEventMatcher
|
|
151
|
+
include ::RSpec::Matchers::Composable
|
|
152
|
+
|
|
153
|
+
def initialize(capture, on:, expected:)
|
|
154
|
+
@capture = capture
|
|
155
|
+
@on = on
|
|
156
|
+
@matcher = EventMatcher.new(**expected)
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def matches?(block)
|
|
160
|
+
@events = @capture.call(@on, &block)
|
|
161
|
+
@events.any? { |event| @matcher.matches?(event) }
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def supports_block_expectations?
|
|
165
|
+
true
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def description
|
|
169
|
+
"log a semantic logger event matching #{@matcher.description}"
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def failure_message
|
|
173
|
+
"expected the block to #{description}.\n" \
|
|
174
|
+
"Captured #{@events.size} event(s):\n#{captured_inspect}"
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def failure_message_when_negated
|
|
178
|
+
"expected the block not to #{description}, but it did.\n" \
|
|
179
|
+
"Captured #{@events.size} event(s):\n#{captured_inspect}"
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
private
|
|
183
|
+
|
|
184
|
+
def captured_inspect
|
|
185
|
+
@events.map { |event| " #{event.to_h.inspect}" }.join("\n")
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
# Returns [Array<SemanticLogger::Log>] the log events captured whilst
|
|
190
|
+
# running the supplied block.
|
|
191
|
+
#
|
|
192
|
+
# Notes:
|
|
193
|
+
# - All log events are captured regardless of the global default log level.
|
|
194
|
+
# - Pass a class to capture only events logged through that class's logger.
|
|
195
|
+
# Otherwise every log event in the process is captured for the duration
|
|
196
|
+
# of the block.
|
|
197
|
+
def capture_semantic_logger_events(klass = nil, silence: :trace, &block)
|
|
198
|
+
logger = SemanticLogger::Test::CaptureLogEvents.new
|
|
199
|
+
|
|
200
|
+
if klass
|
|
201
|
+
allow(klass).to receive(:logger).and_return(logger)
|
|
202
|
+
block.call
|
|
203
|
+
elsif silence
|
|
204
|
+
SemanticLogger.silence(silence) do
|
|
205
|
+
stub_processor(logger, &block)
|
|
206
|
+
end
|
|
207
|
+
else
|
|
208
|
+
stub_processor(logger, &block)
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
logger.events
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
# Matcher for a single captured log event.
|
|
215
|
+
#
|
|
216
|
+
# expect(events.first).to be_a_semantic_logger_event(level: :info, message: "Hi")
|
|
217
|
+
def be_a_semantic_logger_event(**expected)
|
|
218
|
+
EventMatcher.new(**expected)
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
# Composable alias, for use inside other matchers:
|
|
222
|
+
#
|
|
223
|
+
# expect(events).to include(a_semantic_logger_event(message: "Hi"))
|
|
224
|
+
def a_semantic_logger_event(**expected)
|
|
225
|
+
EventMatcher.new(**expected)
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
# Block matcher asserting that the block logs a matching event.
|
|
229
|
+
#
|
|
230
|
+
# expect { User.new.enable! }.to(
|
|
231
|
+
# log_semantic_logger_event(level: :info, message: "User enabled")
|
|
232
|
+
# )
|
|
233
|
+
#
|
|
234
|
+
# Pass `on:` to capture only one class's events.
|
|
235
|
+
def log_semantic_logger_event(on: nil, **expected)
|
|
236
|
+
LogEventMatcher.new(method(:capture_semantic_logger_events), on: on, expected: expected)
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
private
|
|
240
|
+
|
|
241
|
+
def stub_processor(logger)
|
|
242
|
+
allow(SemanticLogger::Logger).to receive(:processor).and_return(logger)
|
|
243
|
+
yield
|
|
244
|
+
ensure
|
|
245
|
+
allow(SemanticLogger::Logger).to receive(:processor).and_call_original
|
|
246
|
+
end
|
|
247
|
+
end
|
|
248
|
+
end
|
|
249
|
+
end
|
|
@@ -1,15 +1,28 @@
|
|
|
1
|
+
require "json"
|
|
2
|
+
|
|
1
3
|
module SemanticLogger
|
|
2
4
|
# Internal-use only utility functions for Semantic Logger.
|
|
3
5
|
# Not intended for public use.
|
|
4
6
|
module Utils
|
|
5
7
|
def self.constantize_symbol(symbol, namespace = "SemanticLogger::Appender")
|
|
6
8
|
klass = "#{namespace}::#{camelize(symbol.to_s)}"
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
9
|
+
constant =
|
|
10
|
+
begin
|
|
11
|
+
Object.const_get(klass)
|
|
12
|
+
rescue NameError
|
|
13
|
+
raise(ArgumentError,
|
|
14
|
+
"Could not convert symbol: #{symbol.inspect} to a class in: #{namespace}. Looking for: #{klass}")
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# The resolved constant is instantiated by the caller, so ensure it is
|
|
18
|
+
# actually a class within the expected namespace rather than some other
|
|
19
|
+
# constant that happens to share the name.
|
|
20
|
+
unless constant.is_a?(Class)
|
|
10
21
|
raise(ArgumentError,
|
|
11
|
-
"Could not convert symbol: #{symbol.inspect} to a class in: #{namespace}.
|
|
22
|
+
"Could not convert symbol: #{symbol.inspect} to a class in: #{namespace}. #{klass} is not a class.")
|
|
12
23
|
end
|
|
24
|
+
|
|
25
|
+
constant
|
|
13
26
|
end
|
|
14
27
|
|
|
15
28
|
# Borrow from Rails, when not running Rails
|
|
@@ -76,5 +89,71 @@ module SemanticLogger
|
|
|
76
89
|
def self.strip_path?(path)
|
|
77
90
|
strip_paths.any? { |exclude| path.start_with?(exclude) }
|
|
78
91
|
end
|
|
92
|
+
|
|
93
|
+
# Serializes the value to JSON, repairing invalid UTF-8 only when necessary.
|
|
94
|
+
#
|
|
95
|
+
# Non UTF-8 data appears in well under 1% of log events, so it is wasteful to
|
|
96
|
+
# walk and reallocate the entire structure (see .encode_utf8) on every call.
|
|
97
|
+
# Instead this attempts `.to_json` directly and only falls back to cleansing
|
|
98
|
+
# when serialization fails because of an encoding problem.
|
|
99
|
+
#
|
|
100
|
+
# The exception raised for non UTF-8 data depends on the json gem version:
|
|
101
|
+
# older versions raise Encoding::UndefinedConversionError (an EncodingError),
|
|
102
|
+
# newer versions wrap it as JSON::GeneratorError, so both are rescued. The
|
|
103
|
+
# retry is attempted only once: if it still fails (for example a
|
|
104
|
+
# JSON::GeneratorError caused by something other than encoding, such as NaN),
|
|
105
|
+
# the error propagates unchanged rather than being swallowed.
|
|
106
|
+
def self.to_json(value)
|
|
107
|
+
value.to_json
|
|
108
|
+
rescue JSON::GeneratorError, EncodingError
|
|
109
|
+
encode_utf8(value).to_json
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Returns a copy of the supplied value with every String converted to valid UTF-8.
|
|
113
|
+
#
|
|
114
|
+
# Recurses through Hash and Array structures, cleansing both keys and values.
|
|
115
|
+
# Strings that are already valid UTF-8 are returned unchanged (the common case),
|
|
116
|
+
# so the fast path allocates nothing. Any other value (Symbol, Numeric, Time, nil,
|
|
117
|
+
# ...) is returned as-is.
|
|
118
|
+
#
|
|
119
|
+
# Used by .to_json on the rare failing path, and directly by formatters that
|
|
120
|
+
# serialize per value or emit to a non-JSON sink (where a single `.to_json`
|
|
121
|
+
# rescue boundary cannot catch an intermediate failure).
|
|
122
|
+
def self.encode_utf8(value)
|
|
123
|
+
case value
|
|
124
|
+
when String
|
|
125
|
+
encode_utf8_string(value)
|
|
126
|
+
when Hash
|
|
127
|
+
value.each_with_object({}) do |(key, val), hash|
|
|
128
|
+
hash[encode_utf8(key)] = encode_utf8(val)
|
|
129
|
+
end
|
|
130
|
+
when Array
|
|
131
|
+
value.map { |element| encode_utf8(element) }
|
|
132
|
+
else
|
|
133
|
+
value
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# Options used when transcoding a string to UTF-8.
|
|
138
|
+
# Invalid byte sequences and characters that cannot be represented in UTF-8 are
|
|
139
|
+
# dropped rather than substituted, matching the preference in issue #180.
|
|
140
|
+
ENCODE_UTF8_OPTIONS = {invalid: :replace, undef: :replace, replace: "".freeze}.freeze
|
|
141
|
+
|
|
142
|
+
# Returns the string converted to valid UTF-8, dropping any invalid bytes.
|
|
143
|
+
def self.encode_utf8_string(string)
|
|
144
|
+
return string if string.encoding == Encoding::UTF_8 && string.valid_encoding?
|
|
145
|
+
|
|
146
|
+
if string.encoding == Encoding::UTF_8
|
|
147
|
+
# Correctly tagged as UTF-8 but contains invalid byte sequences.
|
|
148
|
+
string.scrub("")
|
|
149
|
+
else
|
|
150
|
+
# Different encoding (e.g. ASCII-8BIT / Latin-1): transcode into UTF-8.
|
|
151
|
+
string.encode(Encoding::UTF_8, **ENCODE_UTF8_OPTIONS)
|
|
152
|
+
end
|
|
153
|
+
rescue EncodingError
|
|
154
|
+
# Last resort for encodings without a converter to UTF-8: reinterpret the
|
|
155
|
+
# raw bytes as UTF-8 and drop anything invalid. Logging must never raise.
|
|
156
|
+
string.dup.force_encoding(Encoding::UTF_8).scrub("")
|
|
157
|
+
end
|
|
79
158
|
end
|
|
80
159
|
end
|
data/lib/semantic_logger.rb
CHANGED
|
@@ -12,12 +12,21 @@ require "semantic_logger/loggable"
|
|
|
12
12
|
require "semantic_logger/concerns/compatibility"
|
|
13
13
|
require "semantic_logger/appender"
|
|
14
14
|
require "semantic_logger/appenders"
|
|
15
|
+
require "semantic_logger/queue_processor"
|
|
15
16
|
require "semantic_logger/processor"
|
|
16
17
|
require "semantic_logger/sync_processor"
|
|
17
18
|
require "semantic_logger/logger"
|
|
18
19
|
require "semantic_logger/debug_as_trace_logger"
|
|
19
20
|
require "semantic_logger/semantic_logger"
|
|
20
21
|
|
|
22
|
+
# Automatically reopen appenders in the child process after a fork.
|
|
23
|
+
# Enabled by default; opt out with `SemanticLogger.reopen_on_fork = false`.
|
|
24
|
+
# Skipped on platforms without `Process._fork` (e.g. JRuby), which cannot fork.
|
|
25
|
+
if Process.respond_to?(:_fork)
|
|
26
|
+
require "semantic_logger/core_ext/process"
|
|
27
|
+
Process.singleton_class.prepend(SemanticLogger::CoreExt::Process)
|
|
28
|
+
end
|
|
29
|
+
|
|
21
30
|
# Flush all appenders at exit, waiting for outstanding messages on the queue
|
|
22
31
|
# to be written first.
|
|
23
32
|
at_exit do
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: semantic_logger
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 5.0.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Reid Morrison
|
|
@@ -23,6 +23,9 @@ dependencies:
|
|
|
23
23
|
- - "~>"
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
25
|
version: '1.0'
|
|
26
|
+
description: Semantic Logger is a high-performance, asynchronous structured logging
|
|
27
|
+
framework for Ruby & Rails. It logs to multiple destinations via a background thread,
|
|
28
|
+
preserving structured (semantic) payloads.
|
|
26
29
|
executables: []
|
|
27
30
|
extensions: []
|
|
28
31
|
extra_rdoc_files: []
|
|
@@ -34,10 +37,10 @@ files:
|
|
|
34
37
|
- lib/semantic_logger/ansi_colors.rb
|
|
35
38
|
- lib/semantic_logger/appender.rb
|
|
36
39
|
- lib/semantic_logger/appender/async.rb
|
|
37
|
-
- lib/semantic_logger/appender/async_batch.rb
|
|
38
40
|
- lib/semantic_logger/appender/bugsnag.rb
|
|
39
41
|
- lib/semantic_logger/appender/cloudwatch_logs.rb
|
|
40
42
|
- lib/semantic_logger/appender/elasticsearch.rb
|
|
43
|
+
- lib/semantic_logger/appender/elasticsearch_base.rb
|
|
41
44
|
- lib/semantic_logger/appender/elasticsearch_http.rb
|
|
42
45
|
- lib/semantic_logger/appender/file.rb
|
|
43
46
|
- lib/semantic_logger/appender/graylog.rb
|
|
@@ -51,6 +54,7 @@ files:
|
|
|
51
54
|
- lib/semantic_logger/appender/new_relic.rb
|
|
52
55
|
- lib/semantic_logger/appender/new_relic_logs.rb
|
|
53
56
|
- lib/semantic_logger/appender/open_telemetry.rb
|
|
57
|
+
- lib/semantic_logger/appender/opensearch.rb
|
|
54
58
|
- lib/semantic_logger/appender/rabbitmq.rb
|
|
55
59
|
- lib/semantic_logger/appender/sentry.rb
|
|
56
60
|
- lib/semantic_logger/appender/sentry_ruby.rb
|
|
@@ -63,12 +67,14 @@ files:
|
|
|
63
67
|
- lib/semantic_logger/appenders.rb
|
|
64
68
|
- lib/semantic_logger/base.rb
|
|
65
69
|
- lib/semantic_logger/concerns/compatibility.rb
|
|
70
|
+
- lib/semantic_logger/core_ext/process.rb
|
|
66
71
|
- lib/semantic_logger/core_ext/thread.rb
|
|
67
72
|
- lib/semantic_logger/debug_as_trace_logger.rb
|
|
68
73
|
- lib/semantic_logger/formatters.rb
|
|
69
74
|
- lib/semantic_logger/formatters/base.rb
|
|
70
75
|
- lib/semantic_logger/formatters/color.rb
|
|
71
76
|
- lib/semantic_logger/formatters/default.rb
|
|
77
|
+
- lib/semantic_logger/formatters/ecs.rb
|
|
72
78
|
- lib/semantic_logger/formatters/fluentd.rb
|
|
73
79
|
- lib/semantic_logger/formatters/json.rb
|
|
74
80
|
- lib/semantic_logger/formatters/logfmt.rb
|
|
@@ -76,6 +82,7 @@ files:
|
|
|
76
82
|
- lib/semantic_logger/formatters/new_relic_logs.rb
|
|
77
83
|
- lib/semantic_logger/formatters/one_line.rb
|
|
78
84
|
- lib/semantic_logger/formatters/open_telemetry.rb
|
|
85
|
+
- lib/semantic_logger/formatters/pattern.rb
|
|
79
86
|
- lib/semantic_logger/formatters/raw.rb
|
|
80
87
|
- lib/semantic_logger/formatters/signalfx.rb
|
|
81
88
|
- lib/semantic_logger/formatters/syslog.rb
|
|
@@ -89,6 +96,7 @@ files:
|
|
|
89
96
|
- lib/semantic_logger/metric/signalfx.rb
|
|
90
97
|
- lib/semantic_logger/metric/statsd.rb
|
|
91
98
|
- lib/semantic_logger/processor.rb
|
|
99
|
+
- lib/semantic_logger/queue_processor.rb
|
|
92
100
|
- lib/semantic_logger/reporters/minitest.rb
|
|
93
101
|
- lib/semantic_logger/semantic_logger.rb
|
|
94
102
|
- lib/semantic_logger/subscriber.rb
|
|
@@ -96,6 +104,7 @@ files:
|
|
|
96
104
|
- lib/semantic_logger/sync_processor.rb
|
|
97
105
|
- lib/semantic_logger/test/capture_log_events.rb
|
|
98
106
|
- lib/semantic_logger/test/minitest.rb
|
|
107
|
+
- lib/semantic_logger/test/rspec.rb
|
|
99
108
|
- lib/semantic_logger/utils.rb
|
|
100
109
|
- lib/semantic_logger/version.rb
|
|
101
110
|
homepage: https://logger.rocketjob.io
|
|
@@ -103,8 +112,9 @@ licenses:
|
|
|
103
112
|
- Apache-2.0
|
|
104
113
|
metadata:
|
|
105
114
|
bug_tracker_uri: https://github.com/reidmorrison/semantic_logger/issues
|
|
115
|
+
changelog_uri: https://github.com/reidmorrison/semantic_logger/releases
|
|
106
116
|
documentation_uri: https://logger.rocketjob.io
|
|
107
|
-
source_code_uri: https://github.com/reidmorrison/semantic_logger/tree/
|
|
117
|
+
source_code_uri: https://github.com/reidmorrison/semantic_logger/tree/v5.0.0
|
|
108
118
|
rubygems_mfa_required: 'true'
|
|
109
119
|
rdoc_options: []
|
|
110
120
|
require_paths:
|
|
@@ -113,7 +123,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
113
123
|
requirements:
|
|
114
124
|
- - ">="
|
|
115
125
|
- !ruby/object:Gem::Version
|
|
116
|
-
version: '2
|
|
126
|
+
version: '3.2'
|
|
117
127
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
118
128
|
requirements:
|
|
119
129
|
- - ">="
|
|
@@ -122,6 +132,5 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
122
132
|
requirements: []
|
|
123
133
|
rubygems_version: 3.6.9
|
|
124
134
|
specification_version: 4
|
|
125
|
-
summary:
|
|
126
|
-
loggers.
|
|
135
|
+
summary: High-performance, asynchronous structured logging framework for Ruby & Rails.
|
|
127
136
|
test_files: []
|
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
module SemanticLogger
|
|
2
|
-
module Appender
|
|
3
|
-
# Log asynchronously in batches using a separate thread.
|
|
4
|
-
#
|
|
5
|
-
# Log messages are grouped up and only logged when:
|
|
6
|
-
# * The number of queued messages is exceeded.
|
|
7
|
-
# * Or, the appropriate amount of time has passed since the last batch was sent.
|
|
8
|
-
class AsyncBatch < Async
|
|
9
|
-
attr_accessor :batch_size, :batch_seconds
|
|
10
|
-
attr_reader :signal
|
|
11
|
-
|
|
12
|
-
# Batching Appender proxy for appenders that support batches.
|
|
13
|
-
#
|
|
14
|
-
# Parameters:
|
|
15
|
-
# batch_size: [Integer]
|
|
16
|
-
# Maximum number of messages to batch up before sending.
|
|
17
|
-
# Default: 300
|
|
18
|
-
#
|
|
19
|
-
# batch_seconds: [Integer]
|
|
20
|
-
# Maximum number of seconds between sending batches.
|
|
21
|
-
# Default: 5
|
|
22
|
-
#
|
|
23
|
-
# See SemanticLogger::Appender::Async for other paramaters
|
|
24
|
-
#
|
|
25
|
-
# Note:
|
|
26
|
-
# * `lag_check_interval` is not applicable to batches, since the first message of every batch
|
|
27
|
-
# is the oldest and is always checked to see if the lag interval has been exceeded.
|
|
28
|
-
def initialize(appender:,
|
|
29
|
-
max_queue_size: 10_000,
|
|
30
|
-
lag_threshold_s: 30,
|
|
31
|
-
batch_size: 300,
|
|
32
|
-
batch_seconds: 5)
|
|
33
|
-
@batch_size = batch_size
|
|
34
|
-
@batch_seconds = batch_seconds
|
|
35
|
-
@signal = Concurrent::Event.new
|
|
36
|
-
super(
|
|
37
|
-
appender: appender,
|
|
38
|
-
max_queue_size: max_queue_size,
|
|
39
|
-
lag_threshold_s: lag_threshold_s
|
|
40
|
-
)
|
|
41
|
-
|
|
42
|
-
return if appender.respond_to?(:batch)
|
|
43
|
-
|
|
44
|
-
raise(ArgumentError, "#{appender.class.name} does not support batching. It must implement #batch")
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
# Add log message for processing.
|
|
48
|
-
def log(log)
|
|
49
|
-
result = super
|
|
50
|
-
# Wake up the processing thread since the number of queued messages has been exceeded.
|
|
51
|
-
signal.set if queue.size >= batch_size
|
|
52
|
-
result
|
|
53
|
-
end
|
|
54
|
-
|
|
55
|
-
private
|
|
56
|
-
|
|
57
|
-
# Separate thread for batching up log messages before writing.
|
|
58
|
-
def process_messages
|
|
59
|
-
loop do
|
|
60
|
-
# Wait for batch interval or number of messages to be exceeded.
|
|
61
|
-
signal.wait(batch_seconds)
|
|
62
|
-
|
|
63
|
-
logs = []
|
|
64
|
-
messages = []
|
|
65
|
-
first = true
|
|
66
|
-
message_count = queue.length
|
|
67
|
-
message_count.times do
|
|
68
|
-
# Queue#pop(true) raises an exception when there are no more messages, which is considered expensive.
|
|
69
|
-
message = queue.pop
|
|
70
|
-
if message.is_a?(Log)
|
|
71
|
-
logs << message
|
|
72
|
-
if first
|
|
73
|
-
check_lag(message)
|
|
74
|
-
first = false
|
|
75
|
-
end
|
|
76
|
-
else
|
|
77
|
-
messages << message
|
|
78
|
-
end
|
|
79
|
-
end
|
|
80
|
-
appender.batch(logs) if logs.size.positive?
|
|
81
|
-
messages.each { |message| process_message(message) }
|
|
82
|
-
signal.reset unless queue.size >= batch_size
|
|
83
|
-
end
|
|
84
|
-
end
|
|
85
|
-
|
|
86
|
-
def submit_request(command)
|
|
87
|
-
# Wake up the processing thread to process this command immediately.
|
|
88
|
-
signal.set
|
|
89
|
-
super
|
|
90
|
-
end
|
|
91
|
-
end
|
|
92
|
-
end
|
|
93
|
-
end
|