fluent-plugin-detect-exceptions 0.0.13 → 0.0.15
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/Gemfile.lock +33 -25
- data/README.rdoc +16 -6
- data/Rakefile +2 -1
- data/fluent-plugin-detect-exceptions.gemspec +6 -6
- data/lib/fluent/plugin/exception_detector.rb +53 -45
- data/lib/fluent/plugin/out_detect_exceptions.rb +21 -16
- data/test/helper.rb +5 -5
- data/test/plugin/bench_exception_detector.rb +19 -19
- data/test/plugin/test_exception_detector.rb +510 -508
- data/test/plugin/test_out_detect_exceptions.rb +120 -55
- metadata +20 -20
@@ -22,45 +22,47 @@ class DetectExceptionsOutputTest < Test::Unit::TestCase
|
|
22
22
|
Fluent::Test.setup
|
23
23
|
end
|
24
24
|
|
25
|
-
CONFIG =
|
26
|
-
remove_tag_prefix prefix
|
27
|
-
|
25
|
+
CONFIG = <<~END_CONFIG.freeze
|
26
|
+
remove_tag_prefix prefix
|
27
|
+
END_CONFIG
|
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
|
-
JAVA_EXC =
|
34
|
-
SomeException: foo
|
35
|
-
|
36
|
-
Caused by: org.AnotherException
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
PHP_EXC =
|
42
|
-
exception 'Exception' with message 'Custom exception' in /home/joe/work/test-php/test.php:5
|
43
|
-
Stack trace:
|
44
|
-
#0 /home/joe/work/test-php/test.php(9): func1()
|
45
|
-
#1 /home/joe/work/test-php/test.php(13): func2()
|
46
|
-
#2 {main}
|
47
|
-
|
48
|
-
|
49
|
-
PYTHON_EXC =
|
50
|
-
Traceback (most recent call last):
|
51
|
-
|
52
|
-
|
53
|
-
Exception: ('spam', 'eggs')
|
54
|
-
|
55
|
-
|
56
|
-
RUBY_EXC =
|
57
|
-
examble.rb:18:in `thrower': An error has occurred. (RuntimeError)
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
35
|
+
JAVA_EXC = <<~END_JAVA.freeze
|
36
|
+
SomeException: foo
|
37
|
+
at bar
|
38
|
+
Caused by: org.AnotherException
|
39
|
+
at bar2
|
40
|
+
at bar3
|
41
|
+
END_JAVA
|
42
|
+
|
43
|
+
PHP_EXC = <<~END_PHP.freeze
|
44
|
+
exception 'Exception' with message 'Custom exception' in /home/joe/work/test-php/test.php:5
|
45
|
+
Stack trace:
|
46
|
+
#0 /home/joe/work/test-php/test.php(9): func1()
|
47
|
+
#1 /home/joe/work/test-php/test.php(13): func2()
|
48
|
+
#2 {main}
|
49
|
+
END_PHP
|
50
|
+
|
51
|
+
PYTHON_EXC = <<~END_PYTHON.freeze
|
52
|
+
Traceback (most recent call last):
|
53
|
+
File "/base/data/home/runtimes/python27/python27_lib/versions/third_party/webapp2-2.5.2/webapp2.py", line 1535, in __call__
|
54
|
+
rv = self.handle_exception(request, response, e)
|
55
|
+
Exception: ('spam', 'eggs')
|
56
|
+
END_PYTHON
|
57
|
+
|
58
|
+
RUBY_EXC = <<~END_RUBY.freeze
|
59
|
+
examble.rb:18:in `thrower': An error has occurred. (RuntimeError)
|
60
|
+
from examble.rb:14:in `caller'
|
61
|
+
from examble.rb:10:in `helper'
|
62
|
+
from examble.rb:6:in `writer'
|
63
|
+
from examble.rb:2:in `runner'
|
64
|
+
from examble.rb:21:in `<main>'
|
65
|
+
END_RUBY
|
64
66
|
|
65
67
|
def create_driver(conf = CONFIG, tag = DEFAULT_TAG)
|
66
68
|
d = Fluent::Test::OutputTestDriver.new(Fluent::DetectExceptionsOutput, tag)
|
@@ -74,11 +76,22 @@ END
|
|
74
76
|
log_entry
|
75
77
|
end
|
76
78
|
|
77
|
-
def
|
79
|
+
def feed_lines_without_line_breaks(driver, timestamp, *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), timestamp + count)
|
85
|
+
count += 1
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def feed_lines(driver, timestamp, *messages, stream: nil)
|
78
91
|
count = 0
|
79
92
|
messages.each do |m|
|
80
93
|
m.each_line do |line|
|
81
|
-
driver.emit(log_entry(line, count, stream),
|
94
|
+
driver.emit(log_entry(line, count, stream), timestamp + count)
|
82
95
|
count += 1
|
83
96
|
end
|
84
97
|
end
|
@@ -91,11 +104,11 @@ END
|
|
91
104
|
end
|
92
105
|
end
|
93
106
|
|
94
|
-
def make_logs(
|
107
|
+
def make_logs(timestamp, *messages, stream: nil)
|
95
108
|
count = 0
|
96
109
|
logs = []
|
97
110
|
messages.each do |m|
|
98
|
-
logs << [
|
111
|
+
logs << [timestamp + count, log_entry(m, count, stream)]
|
99
112
|
count += m.lines.count
|
100
113
|
end
|
101
114
|
logs
|
@@ -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
|
|
@@ -133,7 +148,7 @@ END
|
|
133
148
|
single_line_exception = exception.gsub("\n", '\\n')
|
134
149
|
|
135
150
|
# There is a nested exception within the body, we should ignore those!
|
136
|
-
|
151
|
+
json_with_exception = {
|
137
152
|
'timestamp' => {
|
138
153
|
'nanos' => 998_152_494,
|
139
154
|
'seconds' => 1_496_420_064
|
@@ -141,8 +156,9 @@ END
|
|
141
156
|
'message' => single_line_exception,
|
142
157
|
'thread' => 139_658_267_147_048,
|
143
158
|
'severity' => 'ERROR'
|
144
|
-
}
|
145
|
-
|
159
|
+
}
|
160
|
+
json_line_with_exception = "#{json_with_exception.to_json}\n"
|
161
|
+
json_without_exception = {
|
146
162
|
'timestamp' => {
|
147
163
|
'nanos' => 5_990_266,
|
148
164
|
'seconds' => 1_496_420_065
|
@@ -150,18 +166,19 @@ END
|
|
150
166
|
'message' => 'next line',
|
151
167
|
'thread' => 139_658_267_147_048,
|
152
168
|
'severity' => 'INFO'
|
153
|
-
}
|
169
|
+
}
|
170
|
+
json_line_without_exception = "#{json_without_exception.to_json}\n"
|
154
171
|
|
155
172
|
router_mock = flexmock('router')
|
156
173
|
|
157
174
|
# Validate that each line received is emitted separately as expected.
|
158
175
|
router_mock.should_receive(:emit)
|
159
|
-
.once.with(
|
176
|
+
.once.with(DEFAULT_TAG_STRIPPED, Integer,
|
160
177
|
'message' => json_line_with_exception,
|
161
178
|
'count' => 0)
|
162
179
|
|
163
180
|
router_mock.should_receive(:emit)
|
164
|
-
.once.with(
|
181
|
+
.once.with(DEFAULT_TAG_STRIPPED, Integer,
|
165
182
|
'message' => json_line_without_exception,
|
166
183
|
'count' => 1)
|
167
184
|
|
@@ -174,7 +191,9 @@ END
|
|
174
191
|
end
|
175
192
|
|
176
193
|
def test_single_language_config
|
177
|
-
cfg =
|
194
|
+
cfg = %(
|
195
|
+
#{CONFIG}
|
196
|
+
languages java)
|
178
197
|
d = create_driver(cfg)
|
179
198
|
t = Time.now.to_i
|
180
199
|
d.run do
|
@@ -185,7 +204,9 @@ END
|
|
185
204
|
end
|
186
205
|
|
187
206
|
def test_multi_language_config
|
188
|
-
cfg =
|
207
|
+
cfg = %(
|
208
|
+
#{CONFIG}
|
209
|
+
languages python, java)
|
189
210
|
d = create_driver(cfg)
|
190
211
|
t = Time.now.to_i
|
191
212
|
d.run do
|
@@ -196,7 +217,9 @@ END
|
|
196
217
|
end
|
197
218
|
|
198
219
|
def test_split_exception_after_timeout
|
199
|
-
cfg =
|
220
|
+
cfg = %(
|
221
|
+
#{CONFIG}
|
222
|
+
multiline_flush_interval 1)
|
200
223
|
d = create_driver(cfg)
|
201
224
|
t1 = 0
|
202
225
|
t2 = 0
|
@@ -224,7 +247,13 @@ END
|
|
224
247
|
feed_lines(d, t2, " at x\n at y\n")
|
225
248
|
d.instance.before_shutdown
|
226
249
|
end
|
227
|
-
assert_equal(make_logs(t1, JAVA_EXC
|
250
|
+
assert_equal(make_logs(t1, "#{JAVA_EXC} at x\n at y\n"), d.events)
|
251
|
+
end
|
252
|
+
|
253
|
+
def test_remove_tag_prefix_is_required
|
254
|
+
cfg = ''
|
255
|
+
e = assert_raises(Fluent::ConfigError) { create_driver(cfg) }
|
256
|
+
assert_match(/remove_tag_prefix/, e.message)
|
228
257
|
end
|
229
258
|
|
230
259
|
def get_out_tags(remove_tag_prefix, original_tag)
|
@@ -243,8 +272,42 @@ END
|
|
243
272
|
assert_equal(['prefix.plus.rest.of.the.tag'], tags)
|
244
273
|
end
|
245
274
|
|
275
|
+
def test_force_line_breaks_false
|
276
|
+
cfg = %(
|
277
|
+
#{CONFIG}
|
278
|
+
force_line_breaks true)
|
279
|
+
d = create_driver(cfg)
|
280
|
+
t = Time.now.to_i
|
281
|
+
d.run do
|
282
|
+
feed_lines(d, t, JAVA_EXC)
|
283
|
+
end
|
284
|
+
expected = JAVA_EXC
|
285
|
+
assert_equal(make_logs(t, *expected), d.events)
|
286
|
+
end
|
287
|
+
|
288
|
+
def test_force_line_breaks_true
|
289
|
+
cfg = %(
|
290
|
+
#{CONFIG}
|
291
|
+
force_line_breaks true)
|
292
|
+
d = create_driver(cfg)
|
293
|
+
t = Time.now.to_i
|
294
|
+
d.run do
|
295
|
+
feed_lines_without_line_breaks(d, t, JAVA_EXC)
|
296
|
+
end
|
297
|
+
# Expected: the first two lines of the exception are buffered and combined.
|
298
|
+
# Then the max_lines setting kicks in and the rest of the Python exception
|
299
|
+
# is logged line-by-line (since it's not an exception stack in itself).
|
300
|
+
# For the following Java stack trace, the two lines of the first exception
|
301
|
+
# are buffered and combined. So are the first two lines of the second
|
302
|
+
# exception. Then the rest is logged line-by-line.
|
303
|
+
expected = JAVA_EXC.chomp
|
304
|
+
assert_equal(make_logs(t, *expected), d.events)
|
305
|
+
end
|
306
|
+
|
246
307
|
def test_flush_after_max_lines
|
247
|
-
cfg =
|
308
|
+
cfg = %(
|
309
|
+
#{CONFIG}
|
310
|
+
max_lines 2)
|
248
311
|
d = create_driver(cfg)
|
249
312
|
t = Time.now.to_i
|
250
313
|
d.run do
|
@@ -256,22 +319,24 @@ END
|
|
256
319
|
# For the following Java stack trace, the two lines of the first exception
|
257
320
|
# are buffered and combined. So are the first two lines of the second
|
258
321
|
# exception. Then the rest is logged line-by-line.
|
259
|
-
expected = [PYTHON_EXC.lines[0..1].join] + PYTHON_EXC.lines[2
|
322
|
+
expected = [PYTHON_EXC.lines[0..1].join] + PYTHON_EXC.lines[2..] + \
|
260
323
|
[JAVA_EXC.lines[0..1].join] + [JAVA_EXC.lines[2..3].join] + \
|
261
|
-
JAVA_EXC.lines[4
|
324
|
+
JAVA_EXC.lines[4..]
|
262
325
|
assert_equal(make_logs(t, *expected), d.events)
|
263
326
|
end
|
264
327
|
|
265
328
|
def test_separate_streams
|
266
|
-
cfg =
|
329
|
+
cfg = %(
|
330
|
+
#{CONFIG}
|
331
|
+
stream stream)
|
267
332
|
d = create_driver(cfg)
|
268
333
|
t = Time.now.to_i
|
269
334
|
d.run do
|
270
335
|
feed_lines(d, t, JAVA_EXC.lines[0], stream: 'java')
|
271
336
|
feed_lines(d, t, PYTHON_EXC.lines[0..1].join, stream: 'python')
|
272
|
-
feed_lines(d, t, JAVA_EXC.lines[1
|
337
|
+
feed_lines(d, t, JAVA_EXC.lines[1..].join, stream: 'java')
|
273
338
|
feed_lines(d, t, JAVA_EXC, stream: 'java')
|
274
|
-
feed_lines(d, t, PYTHON_EXC.lines[2
|
339
|
+
feed_lines(d, t, PYTHON_EXC.lines[2..].join, stream: 'python')
|
275
340
|
feed_lines(d, t, 'something else', stream: 'java')
|
276
341
|
end
|
277
342
|
# Expected: the Python and the Java exceptions are handled separately
|
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.15
|
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: 2023-03-21 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: fluentd
|
@@ -25,61 +25,61 @@ dependencies:
|
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '0.10'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
|
-
name:
|
28
|
+
name: flexmock
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
31
|
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: '
|
33
|
+
version: '2.0'
|
34
34
|
type: :development
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version: '
|
40
|
+
version: '2.0'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
|
-
name:
|
42
|
+
name: rake
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
|
-
- -
|
45
|
+
- - "~>"
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version:
|
47
|
+
version: '10.3'
|
48
48
|
type: :development
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
|
-
- -
|
52
|
+
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
|
-
version:
|
54
|
+
version: '10.3'
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
|
-
name:
|
56
|
+
name: rubocop
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
58
58
|
requirements:
|
59
|
-
- -
|
59
|
+
- - '='
|
60
60
|
- !ruby/object:Gem::Version
|
61
|
-
version:
|
61
|
+
version: 1.48.1
|
62
62
|
type: :development
|
63
63
|
prerelease: false
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
65
65
|
requirements:
|
66
|
-
- -
|
66
|
+
- - '='
|
67
67
|
- !ruby/object:Gem::Version
|
68
|
-
version:
|
68
|
+
version: 1.48.1
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
|
-
name:
|
70
|
+
name: test-unit
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
72
72
|
requirements:
|
73
73
|
- - "~>"
|
74
74
|
- !ruby/object:Gem::Version
|
75
|
-
version: '
|
75
|
+
version: '3.0'
|
76
76
|
type: :development
|
77
77
|
prerelease: false
|
78
78
|
version_requirements: !ruby/object:Gem::Requirement
|
79
79
|
requirements:
|
80
80
|
- - "~>"
|
81
81
|
- !ruby/object:Gem::Version
|
82
|
-
version: '
|
82
|
+
version: '3.0'
|
83
83
|
description: |2
|
84
84
|
Fluentd output plugin which detects exception stack traces in a stream of
|
85
85
|
JSON log messages and combines all single-line messages that belong to the
|
@@ -116,14 +116,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
116
116
|
requirements:
|
117
117
|
- - ">="
|
118
118
|
- !ruby/object:Gem::Version
|
119
|
-
version: '2.
|
119
|
+
version: '2.6'
|
120
120
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
121
121
|
requirements:
|
122
122
|
- - ">="
|
123
123
|
- !ruby/object:Gem::Version
|
124
124
|
version: '0'
|
125
125
|
requirements: []
|
126
|
-
rubygems_version: 3.
|
126
|
+
rubygems_version: 3.1.6
|
127
127
|
signing_key:
|
128
128
|
specification_version: 4
|
129
129
|
summary: fluentd output plugin for combining stack traces as multi-line JSON logs
|