fluent-plugin-detect-exceptions 0.0.13 → 0.0.14
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.rdoc +16 -6
- data/fluent-plugin-detect-exceptions.gemspec +1 -1
- data/lib/fluent/plugin/exception_detector.rb +16 -8
- data/lib/fluent/plugin/out_detect_exceptions.rb +11 -5
- data/test/plugin/test_out_detect_exceptions.rb +71 -8
- metadata +3 -4
- data/Gemfile.lock +0 -66
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f846941463ccc37acd71280e182d5ae7b0ffd0c95c609bfbfe67de64f86ff478
|
|
4
|
+
data.tar.gz: 1454c4ddae5ba4efa214972f2109f0a71d4984e87b49cefa3541cb87d92d6cc7
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 585f0de72a4f845f39c0f7e753ca3661ad335b10c18d26930afc19bab246b205cb9d68d1fa506c87e5ea674fc9b1ce51b04c278aed5590d997875ea9b50b21d8
|
|
7
|
+
data.tar.gz: 987c453e5667c1736507500859a1f46612a56451f791afaf194574380afd31527da73f9ebd87de53faf2c40e5929ce0cdd0f2c3fbd0c9d4c89a2137bad40a9a1
|
data/README.rdoc
CHANGED
|
@@ -42,6 +42,16 @@ will also install and configure the gem.
|
|
|
42
42
|
|
|
43
43
|
The plugin supports the following parameters:
|
|
44
44
|
|
|
45
|
+
=== Required
|
|
46
|
+
|
|
47
|
+
[remove_tag_prefix] The prefix to remove from the input tag when outputting
|
|
48
|
+
a record. A prefix has to be a complete tag part.
|
|
49
|
+
Example: If remove_tag_prefix is set to 'foo', the input
|
|
50
|
+
tag foo.bar.baz is transformed to bar.baz and the input tag
|
|
51
|
+
'foofoo.bar' is not modified.
|
|
52
|
+
|
|
53
|
+
=== Optional
|
|
54
|
+
|
|
45
55
|
[message] Name of the field in the JSON record that contains the
|
|
46
56
|
single-line log messages that shall be scanned for exceptions.
|
|
47
57
|
If this is set to '', the plugin will try 'message' and 'log',
|
|
@@ -49,12 +59,6 @@ The plugin supports the following parameters:
|
|
|
49
59
|
This parameter is only applicable to structured (JSON) log streams.
|
|
50
60
|
Default: ''.
|
|
51
61
|
|
|
52
|
-
[remove_tag_prefix] The prefix to remove from the input tag when outputting
|
|
53
|
-
a record. A prefix has to be a complete tag part.
|
|
54
|
-
Example: If remove_tag_prefix is set to 'foo', the input
|
|
55
|
-
tag foo.bar.baz is transformed to bar.baz and the input tag
|
|
56
|
-
'foofoo.bar' is not modified. Default: empty string.
|
|
57
|
-
|
|
58
62
|
[languages] A list of language for which exception stack traces shall be
|
|
59
63
|
detected. The values in the list can be separated by commas or
|
|
60
64
|
written as JSON list.
|
|
@@ -66,6 +70,12 @@ The plugin supports the following parameters:
|
|
|
66
70
|
forwarded. If not set, incomplete exceptions stacks
|
|
67
71
|
are not flushed.
|
|
68
72
|
|
|
73
|
+
[force_line_breaks] Force line breaks between each lines when comibining exception stacks.
|
|
74
|
+
This is useful if your exception is formatted
|
|
75
|
+
as a single line. i.e., logs retrieved from the docker's
|
|
76
|
+
logging driver don't have any line break.
|
|
77
|
+
Default: false.
|
|
78
|
+
|
|
69
79
|
[max_lines] Maximum number of lines in a detected exception stack trace.
|
|
70
80
|
If this maximum number is exceeded, the exception stack trace
|
|
71
81
|
that has been detected so far will be output as a single
|
|
@@ -11,7 +11,7 @@ eos
|
|
|
11
11
|
gem.homepage = \
|
|
12
12
|
'https://github.com/GoogleCloudPlatform/fluent-plugin-detect-exceptions'
|
|
13
13
|
gem.license = 'Apache-2.0'
|
|
14
|
-
gem.version = '0.0.
|
|
14
|
+
gem.version = '0.0.14'
|
|
15
15
|
gem.authors = ['Stackdriver Agents']
|
|
16
16
|
gem.email = ['stackdriver-agents@google.com']
|
|
17
17
|
gem.required_ruby_version = Gem::Requirement.new('>= 2.0')
|
|
@@ -256,20 +256,22 @@ module Fluent
|
|
|
256
256
|
# message_field may contain the empty string. In this case, the
|
|
257
257
|
# TraceAccumulator 'learns' the field name from the first record by checking
|
|
258
258
|
# for some pre-defined common field names of text logs.
|
|
259
|
-
# The
|
|
259
|
+
# The option parameter can be used to pass the following parameters:
|
|
260
|
+
# force_line_breaks adds line breaks when combining exception stacks
|
|
261
|
+
# max_lines and max_bytes limit the maximum amount
|
|
260
262
|
# of data to be buffered. The default value 0 indicates 'no limit'.
|
|
261
|
-
def initialize(message_field, languages,
|
|
262
|
-
&emit_callback)
|
|
263
|
+
def initialize(message_field, languages, **options, &emit_callback)
|
|
263
264
|
@exception_detector = Fluent::ExceptionDetector.new(*languages)
|
|
264
|
-
@max_lines = max_lines
|
|
265
|
-
@max_bytes = max_bytes
|
|
266
265
|
@message_field = message_field
|
|
266
|
+
@force_line_breaks = options[:force_line_breaks] || false
|
|
267
|
+
@max_lines = options[:max_lines] || 0
|
|
268
|
+
@max_bytes = options[:max_bytes] || 0
|
|
269
|
+
@emit = emit_callback
|
|
267
270
|
@messages = []
|
|
268
271
|
@buffer_start_time = Time.now
|
|
269
272
|
@buffer_size = 0
|
|
270
273
|
@first_record = nil
|
|
271
274
|
@first_timestamp = nil
|
|
272
|
-
@emit = emit_callback
|
|
273
275
|
end
|
|
274
276
|
|
|
275
277
|
def push(time_sec, record)
|
|
@@ -360,8 +362,14 @@ module Fluent
|
|
|
360
362
|
@buffer_start_time = Time.now
|
|
361
363
|
end
|
|
362
364
|
unless message.nil?
|
|
363
|
-
|
|
364
|
-
|
|
365
|
+
message_with_line_break =
|
|
366
|
+
if @force_line_breaks && !@messages.empty? && !message.include?("\n")
|
|
367
|
+
"\n" + message
|
|
368
|
+
else
|
|
369
|
+
message
|
|
370
|
+
end
|
|
371
|
+
@messages << message_with_line_break
|
|
372
|
+
@buffer_size += message_with_line_break.length
|
|
365
373
|
end
|
|
366
374
|
end
|
|
367
375
|
end
|
|
@@ -22,14 +22,16 @@ module Fluent
|
|
|
22
22
|
# an exception stack trace, they forwarded as a single, combined JSON
|
|
23
23
|
# object. Otherwise, the input log data is forwarded as is.
|
|
24
24
|
class DetectExceptionsOutput < Output
|
|
25
|
+
desc 'The prefix to be removed from the input tag when outputting a record.'
|
|
26
|
+
config_param :remove_tag_prefix, :string
|
|
25
27
|
desc 'The field which contains the raw message text in the input JSON data.'
|
|
26
28
|
config_param :message, :string, default: ''
|
|
27
|
-
desc 'The prefix to be removed from the input tag when outputting a record.'
|
|
28
|
-
config_param :remove_tag_prefix, :string, default: ''
|
|
29
29
|
desc 'The interval of flushing the buffer for multiline format.'
|
|
30
30
|
config_param :multiline_flush_interval, :time, default: nil
|
|
31
31
|
desc 'Programming languages for which to detect exceptions. Default: all.'
|
|
32
32
|
config_param :languages, :array, value_type: :string, default: []
|
|
33
|
+
desc 'Force live breaks when combining exception stacks. Default: false.'
|
|
34
|
+
config_param :force_line_breaks, :bool, default: false
|
|
33
35
|
desc 'Maximum number of lines to flush (0 means no limit). Default: 1000.'
|
|
34
36
|
config_param :max_lines, :integer, default: 1000
|
|
35
37
|
desc 'Maximum number of bytes to flush (0 means no limit). Default: 0.'
|
|
@@ -91,9 +93,13 @@ module Fluent
|
|
|
91
93
|
unless @accumulators.key?(log_id)
|
|
92
94
|
out_tag = tag.sub(/^#{Regexp.escape(@remove_tag_prefix)}\./, '')
|
|
93
95
|
@accumulators[log_id] =
|
|
94
|
-
Fluent::TraceAccumulator.new(
|
|
95
|
-
|
|
96
|
-
|
|
96
|
+
Fluent::TraceAccumulator.new(
|
|
97
|
+
@message,
|
|
98
|
+
@languages,
|
|
99
|
+
force_line_breaks: @force_line_breaks,
|
|
100
|
+
max_lines: @max_lines,
|
|
101
|
+
max_bytes: @max_bytes
|
|
102
|
+
) do |t, r|
|
|
97
103
|
router.emit(out_tag, t, r)
|
|
98
104
|
end
|
|
99
105
|
end
|
|
@@ -28,6 +28,8 @@ END
|
|
|
28
28
|
|
|
29
29
|
DEFAULT_TAG = 'prefix.test.tag'.freeze
|
|
30
30
|
|
|
31
|
+
DEFAULT_TAG_STRIPPED = 'test.tag'.freeze
|
|
32
|
+
|
|
31
33
|
ARBITRARY_TEXT = 'This line is not an exception.'.freeze
|
|
32
34
|
|
|
33
35
|
JAVA_EXC = <<END.freeze
|
|
@@ -74,6 +76,17 @@ END
|
|
|
74
76
|
log_entry
|
|
75
77
|
end
|
|
76
78
|
|
|
79
|
+
def feed_lines_without_line_breaks(driver, t, *messages, stream: nil)
|
|
80
|
+
count = 0
|
|
81
|
+
messages.each do |m|
|
|
82
|
+
m.each_line do |line|
|
|
83
|
+
line.delete!("\n")
|
|
84
|
+
driver.emit(log_entry(line, count, stream), t + count)
|
|
85
|
+
count += 1
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
77
90
|
def feed_lines(driver, t, *messages, stream: nil)
|
|
78
91
|
count = 0
|
|
79
92
|
messages.each do |m|
|
|
@@ -125,7 +138,9 @@ END
|
|
|
125
138
|
}
|
|
126
139
|
|
|
127
140
|
test_cases.each do |language, exception|
|
|
128
|
-
cfg =
|
|
141
|
+
cfg = %(
|
|
142
|
+
#{CONFIG}
|
|
143
|
+
languages #{language})
|
|
129
144
|
d = create_driver(cfg)
|
|
130
145
|
t = Time.now.to_i
|
|
131
146
|
|
|
@@ -156,12 +171,12 @@ END
|
|
|
156
171
|
|
|
157
172
|
# Validate that each line received is emitted separately as expected.
|
|
158
173
|
router_mock.should_receive(:emit)
|
|
159
|
-
.once.with(
|
|
174
|
+
.once.with(DEFAULT_TAG_STRIPPED, Integer,
|
|
160
175
|
'message' => json_line_with_exception,
|
|
161
176
|
'count' => 0)
|
|
162
177
|
|
|
163
178
|
router_mock.should_receive(:emit)
|
|
164
|
-
.once.with(
|
|
179
|
+
.once.with(DEFAULT_TAG_STRIPPED, Integer,
|
|
165
180
|
'message' => json_line_without_exception,
|
|
166
181
|
'count' => 1)
|
|
167
182
|
|
|
@@ -174,7 +189,9 @@ END
|
|
|
174
189
|
end
|
|
175
190
|
|
|
176
191
|
def test_single_language_config
|
|
177
|
-
cfg =
|
|
192
|
+
cfg = %(
|
|
193
|
+
#{CONFIG}
|
|
194
|
+
languages java)
|
|
178
195
|
d = create_driver(cfg)
|
|
179
196
|
t = Time.now.to_i
|
|
180
197
|
d.run do
|
|
@@ -185,7 +202,9 @@ END
|
|
|
185
202
|
end
|
|
186
203
|
|
|
187
204
|
def test_multi_language_config
|
|
188
|
-
cfg =
|
|
205
|
+
cfg = %(
|
|
206
|
+
#{CONFIG}
|
|
207
|
+
languages python, java)
|
|
189
208
|
d = create_driver(cfg)
|
|
190
209
|
t = Time.now.to_i
|
|
191
210
|
d.run do
|
|
@@ -196,7 +215,9 @@ END
|
|
|
196
215
|
end
|
|
197
216
|
|
|
198
217
|
def test_split_exception_after_timeout
|
|
199
|
-
cfg =
|
|
218
|
+
cfg = %(
|
|
219
|
+
#{CONFIG}
|
|
220
|
+
multiline_flush_interval 1)
|
|
200
221
|
d = create_driver(cfg)
|
|
201
222
|
t1 = 0
|
|
202
223
|
t2 = 0
|
|
@@ -227,6 +248,12 @@ END
|
|
|
227
248
|
assert_equal(make_logs(t1, JAVA_EXC + " at x\n at y\n"), d.events)
|
|
228
249
|
end
|
|
229
250
|
|
|
251
|
+
def test_remove_tag_prefix_is_required
|
|
252
|
+
cfg = ''
|
|
253
|
+
e = assert_raises(Fluent::ConfigError) { create_driver(cfg) }
|
|
254
|
+
assert_match(/remove_tag_prefix/, e.message)
|
|
255
|
+
end
|
|
256
|
+
|
|
230
257
|
def get_out_tags(remove_tag_prefix, original_tag)
|
|
231
258
|
cfg = "remove_tag_prefix #{remove_tag_prefix}"
|
|
232
259
|
d = create_driver(cfg, original_tag)
|
|
@@ -243,8 +270,42 @@ END
|
|
|
243
270
|
assert_equal(['prefix.plus.rest.of.the.tag'], tags)
|
|
244
271
|
end
|
|
245
272
|
|
|
273
|
+
def test_force_line_breaks_false
|
|
274
|
+
cfg = %(
|
|
275
|
+
#{CONFIG}
|
|
276
|
+
force_line_breaks true)
|
|
277
|
+
d = create_driver(cfg)
|
|
278
|
+
t = Time.now.to_i
|
|
279
|
+
d.run do
|
|
280
|
+
feed_lines(d, t, JAVA_EXC)
|
|
281
|
+
end
|
|
282
|
+
expected = JAVA_EXC
|
|
283
|
+
assert_equal(make_logs(t, *expected), d.events)
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
def test_force_line_breaks_true
|
|
287
|
+
cfg = %(
|
|
288
|
+
#{CONFIG}
|
|
289
|
+
force_line_breaks true)
|
|
290
|
+
d = create_driver(cfg)
|
|
291
|
+
t = Time.now.to_i
|
|
292
|
+
d.run do
|
|
293
|
+
feed_lines_without_line_breaks(d, t, JAVA_EXC)
|
|
294
|
+
end
|
|
295
|
+
# Expected: the first two lines of the exception are buffered and combined.
|
|
296
|
+
# Then the max_lines setting kicks in and the rest of the Python exception
|
|
297
|
+
# is logged line-by-line (since it's not an exception stack in itself).
|
|
298
|
+
# For the following Java stack trace, the two lines of the first exception
|
|
299
|
+
# are buffered and combined. So are the first two lines of the second
|
|
300
|
+
# exception. Then the rest is logged line-by-line.
|
|
301
|
+
expected = JAVA_EXC.chomp
|
|
302
|
+
assert_equal(make_logs(t, *expected), d.events)
|
|
303
|
+
end
|
|
304
|
+
|
|
246
305
|
def test_flush_after_max_lines
|
|
247
|
-
cfg =
|
|
306
|
+
cfg = %(
|
|
307
|
+
#{CONFIG}
|
|
308
|
+
max_lines 2)
|
|
248
309
|
d = create_driver(cfg)
|
|
249
310
|
t = Time.now.to_i
|
|
250
311
|
d.run do
|
|
@@ -263,7 +324,9 @@ END
|
|
|
263
324
|
end
|
|
264
325
|
|
|
265
326
|
def test_separate_streams
|
|
266
|
-
cfg =
|
|
327
|
+
cfg = %(
|
|
328
|
+
#{CONFIG}
|
|
329
|
+
stream stream)
|
|
267
330
|
d = create_driver(cfg)
|
|
268
331
|
t = Time.now.to_i
|
|
269
332
|
d.run do
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: fluent-plugin-detect-exceptions
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.0.
|
|
4
|
+
version: 0.0.14
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Stackdriver Agents
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2021-09-07 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: fluentd
|
|
@@ -93,7 +93,6 @@ extra_rdoc_files: []
|
|
|
93
93
|
files:
|
|
94
94
|
- CONTRIBUTING
|
|
95
95
|
- Gemfile
|
|
96
|
-
- Gemfile.lock
|
|
97
96
|
- LICENSE
|
|
98
97
|
- README.rdoc
|
|
99
98
|
- Rakefile
|
|
@@ -123,7 +122,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
123
122
|
- !ruby/object:Gem::Version
|
|
124
123
|
version: '0'
|
|
125
124
|
requirements: []
|
|
126
|
-
rubygems_version: 3.0.
|
|
125
|
+
rubygems_version: 3.0.8
|
|
127
126
|
signing_key:
|
|
128
127
|
specification_version: 4
|
|
129
128
|
summary: fluentd output plugin for combining stack traces as multi-line JSON logs
|
data/Gemfile.lock
DELETED
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
PATH
|
|
2
|
-
remote: .
|
|
3
|
-
specs:
|
|
4
|
-
fluent-plugin-detect-exceptions (0.0.13)
|
|
5
|
-
fluentd (>= 0.10)
|
|
6
|
-
|
|
7
|
-
GEM
|
|
8
|
-
remote: https://rubygems.org/
|
|
9
|
-
specs:
|
|
10
|
-
ast (2.4.0)
|
|
11
|
-
concurrent-ruby (1.1.5)
|
|
12
|
-
cool.io (1.5.4)
|
|
13
|
-
dig_rb (1.0.1)
|
|
14
|
-
flexmock (2.3.6)
|
|
15
|
-
fluentd (1.7.4)
|
|
16
|
-
cool.io (>= 1.4.5, < 2.0.0)
|
|
17
|
-
dig_rb (~> 1.0.0)
|
|
18
|
-
http_parser.rb (>= 0.5.1, < 0.7.0)
|
|
19
|
-
msgpack (>= 1.2.0, < 2.0.0)
|
|
20
|
-
serverengine (>= 2.0.4, < 3.0.0)
|
|
21
|
-
sigdump (~> 0.2.2)
|
|
22
|
-
strptime (>= 0.2.2, < 1.0.0)
|
|
23
|
-
tzinfo (~> 2.0)
|
|
24
|
-
tzinfo-data (~> 1.0)
|
|
25
|
-
yajl-ruby (~> 1.0)
|
|
26
|
-
http_parser.rb (0.6.0)
|
|
27
|
-
msgpack (1.3.1)
|
|
28
|
-
parser (2.6.5.0)
|
|
29
|
-
ast (~> 2.4.0)
|
|
30
|
-
power_assert (1.1.5)
|
|
31
|
-
powerpack (0.1.2)
|
|
32
|
-
rainbow (2.2.2)
|
|
33
|
-
rake
|
|
34
|
-
rake (10.5.0)
|
|
35
|
-
rubocop (0.42.0)
|
|
36
|
-
parser (>= 2.3.1.1, < 3.0)
|
|
37
|
-
powerpack (~> 0.1)
|
|
38
|
-
rainbow (>= 1.99.1, < 3.0)
|
|
39
|
-
ruby-progressbar (~> 1.7)
|
|
40
|
-
unicode-display_width (~> 1.0, >= 1.0.1)
|
|
41
|
-
ruby-progressbar (1.10.1)
|
|
42
|
-
serverengine (2.1.1)
|
|
43
|
-
sigdump (~> 0.2.2)
|
|
44
|
-
sigdump (0.2.4)
|
|
45
|
-
strptime (0.2.3)
|
|
46
|
-
test-unit (3.3.4)
|
|
47
|
-
power_assert
|
|
48
|
-
tzinfo (2.0.0)
|
|
49
|
-
concurrent-ruby (~> 1.0)
|
|
50
|
-
tzinfo-data (1.2019.3)
|
|
51
|
-
tzinfo (>= 1.0.0)
|
|
52
|
-
unicode-display_width (1.6.0)
|
|
53
|
-
yajl-ruby (1.4.1)
|
|
54
|
-
|
|
55
|
-
PLATFORMS
|
|
56
|
-
ruby
|
|
57
|
-
|
|
58
|
-
DEPENDENCIES
|
|
59
|
-
flexmock (~> 2.0)
|
|
60
|
-
fluent-plugin-detect-exceptions!
|
|
61
|
-
rake (~> 10.3)
|
|
62
|
-
rubocop (= 0.42.0)
|
|
63
|
-
test-unit (~> 3.0)
|
|
64
|
-
|
|
65
|
-
BUNDLED WITH
|
|
66
|
-
2.0.2
|