fluent-plugin-detect-exceptions 0.0.13 → 0.0.15
Sign up to get free protection for your applications and to get access to all the features.
- 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
|