fluent-plugin-detect-exceptions-with-webflux-support 0.0.13
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 +7 -0
- data/CONTRIBUTING +24 -0
- data/Gemfile +3 -0
- data/LICENSE +201 -0
- data/README.rdoc +153 -0
- data/Rakefile +49 -0
- data/fluent-plugin-detect-exceptions-with-webflux-support-0.0.12.gem +0 -0
- data/fluent-plugin-detect-exceptions.gemspec +30 -0
- data/lib/fluent/plugin/exception_detector.rb +382 -0
- data/lib/fluent/plugin/out_detect_exceptions_with_webflux_support.rb +142 -0
- data/test/helper.rb +46 -0
- data/test/plugin/bench_exception_detector.rb +73 -0
- data/test/plugin/test_exception_detector.rb +990 -0
- data/test/plugin/test_out_detect_exceptions_with_webflux_support.rb +352 -0
- metadata +136 -0
@@ -0,0 +1,352 @@
|
|
1
|
+
# Copyright 2016 Google Inc. All rights reserved.
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
require 'flexmock/test_unit'
|
16
|
+
require_relative '../helper'
|
17
|
+
require 'fluent/plugin/out_detect_exceptions'
|
18
|
+
require 'json'
|
19
|
+
|
20
|
+
class DetectExceptionsOutputTest < Test::Unit::TestCase
|
21
|
+
def setup
|
22
|
+
Fluent::Test.setup
|
23
|
+
end
|
24
|
+
|
25
|
+
CONFIG = <<~END_CONFIG.freeze
|
26
|
+
remove_tag_prefix prefix
|
27
|
+
END_CONFIG
|
28
|
+
|
29
|
+
DEFAULT_TAG = 'prefix.test.tag'.freeze
|
30
|
+
|
31
|
+
DEFAULT_TAG_STRIPPED = 'test.tag'.freeze
|
32
|
+
|
33
|
+
ARBITRARY_TEXT = 'This line is not an exception.'.freeze
|
34
|
+
|
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
|
66
|
+
|
67
|
+
def create_driver(conf = CONFIG, tag = DEFAULT_TAG)
|
68
|
+
d = Fluent::Test::OutputTestDriver.new(Fluent::DetectExceptionsOutput, tag)
|
69
|
+
d.configure(conf)
|
70
|
+
d
|
71
|
+
end
|
72
|
+
|
73
|
+
def log_entry(message, count, stream)
|
74
|
+
log_entry = { 'message' => message, 'count' => count }
|
75
|
+
log_entry['stream'] = stream unless stream.nil?
|
76
|
+
log_entry
|
77
|
+
end
|
78
|
+
|
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)
|
91
|
+
count = 0
|
92
|
+
messages.each do |m|
|
93
|
+
m.each_line do |line|
|
94
|
+
driver.emit(log_entry(line, count, stream), timestamp + count)
|
95
|
+
count += 1
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def run_driver(driver, *messages)
|
101
|
+
t = Time.now.to_i
|
102
|
+
driver.run do
|
103
|
+
feed_lines(driver, t, *messages)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def make_logs(timestamp, *messages, stream: nil)
|
108
|
+
count = 0
|
109
|
+
logs = []
|
110
|
+
messages.each do |m|
|
111
|
+
logs << [timestamp + count, log_entry(m, count, stream)]
|
112
|
+
count += m.lines.count
|
113
|
+
end
|
114
|
+
logs
|
115
|
+
end
|
116
|
+
|
117
|
+
def test_configure
|
118
|
+
assert_nothing_raised do
|
119
|
+
create_driver
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def test_exception_detection
|
124
|
+
d = create_driver
|
125
|
+
t = Time.now.to_i
|
126
|
+
messages = [ARBITRARY_TEXT, JAVA_EXC, ARBITRARY_TEXT]
|
127
|
+
d.run do
|
128
|
+
feed_lines(d, t, *messages)
|
129
|
+
end
|
130
|
+
assert_equal(make_logs(t, *messages), d.events)
|
131
|
+
end
|
132
|
+
|
133
|
+
def test_ignore_nested_exceptions
|
134
|
+
test_cases = {
|
135
|
+
'php' => PHP_EXC,
|
136
|
+
'python' => PYTHON_EXC,
|
137
|
+
'ruby' => RUBY_EXC
|
138
|
+
}
|
139
|
+
|
140
|
+
test_cases.each do |language, exception|
|
141
|
+
cfg = %(
|
142
|
+
#{CONFIG}
|
143
|
+
languages #{language})
|
144
|
+
d = create_driver(cfg)
|
145
|
+
t = Time.now.to_i
|
146
|
+
|
147
|
+
# Convert exception to a single line to simplify the test case.
|
148
|
+
single_line_exception = exception.gsub("\n", '\\n')
|
149
|
+
|
150
|
+
# There is a nested exception within the body, we should ignore those!
|
151
|
+
json_with_exception = {
|
152
|
+
'timestamp' => {
|
153
|
+
'nanos' => 998_152_494,
|
154
|
+
'seconds' => 1_496_420_064
|
155
|
+
},
|
156
|
+
'message' => single_line_exception,
|
157
|
+
'thread' => 139_658_267_147_048,
|
158
|
+
'severity' => 'ERROR'
|
159
|
+
}
|
160
|
+
json_line_with_exception = "#{json_with_exception.to_json}\n"
|
161
|
+
json_without_exception = {
|
162
|
+
'timestamp' => {
|
163
|
+
'nanos' => 5_990_266,
|
164
|
+
'seconds' => 1_496_420_065
|
165
|
+
},
|
166
|
+
'message' => 'next line',
|
167
|
+
'thread' => 139_658_267_147_048,
|
168
|
+
'severity' => 'INFO'
|
169
|
+
}
|
170
|
+
json_line_without_exception = "#{json_without_exception.to_json}\n"
|
171
|
+
|
172
|
+
router_mock = flexmock('router')
|
173
|
+
|
174
|
+
# Validate that each line received is emitted separately as expected.
|
175
|
+
router_mock.should_receive(:emit)
|
176
|
+
.once.with(DEFAULT_TAG_STRIPPED, Integer,
|
177
|
+
'message' => json_line_with_exception,
|
178
|
+
'count' => 0)
|
179
|
+
|
180
|
+
router_mock.should_receive(:emit)
|
181
|
+
.once.with(DEFAULT_TAG_STRIPPED, Integer,
|
182
|
+
'message' => json_line_without_exception,
|
183
|
+
'count' => 1)
|
184
|
+
|
185
|
+
d.instance.router = router_mock
|
186
|
+
|
187
|
+
d.run do
|
188
|
+
feed_lines(d, t, json_line_with_exception + json_line_without_exception)
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
def test_single_language_config
|
194
|
+
cfg = %(
|
195
|
+
#{CONFIG}
|
196
|
+
languages java)
|
197
|
+
d = create_driver(cfg)
|
198
|
+
t = Time.now.to_i
|
199
|
+
d.run do
|
200
|
+
feed_lines(d, t, ARBITRARY_TEXT, JAVA_EXC, PYTHON_EXC)
|
201
|
+
end
|
202
|
+
expected = ARBITRARY_TEXT.lines + [JAVA_EXC] + PYTHON_EXC.lines
|
203
|
+
assert_equal(make_logs(t, *expected), d.events)
|
204
|
+
end
|
205
|
+
|
206
|
+
def test_multi_language_config
|
207
|
+
cfg = %(
|
208
|
+
#{CONFIG}
|
209
|
+
languages python, java)
|
210
|
+
d = create_driver(cfg)
|
211
|
+
t = Time.now.to_i
|
212
|
+
d.run do
|
213
|
+
feed_lines(d, t, ARBITRARY_TEXT, JAVA_EXC, PYTHON_EXC)
|
214
|
+
end
|
215
|
+
expected = ARBITRARY_TEXT.lines + [JAVA_EXC] + [PYTHON_EXC]
|
216
|
+
assert_equal(make_logs(t, *expected), d.events)
|
217
|
+
end
|
218
|
+
|
219
|
+
def test_split_exception_after_timeout
|
220
|
+
cfg = %(
|
221
|
+
#{CONFIG}
|
222
|
+
multiline_flush_interval 1)
|
223
|
+
d = create_driver(cfg)
|
224
|
+
t1 = 0
|
225
|
+
t2 = 0
|
226
|
+
d.run do
|
227
|
+
t1 = Time.now.to_i
|
228
|
+
feed_lines(d, t1, JAVA_EXC)
|
229
|
+
sleep 2
|
230
|
+
t2 = Time.now.to_i
|
231
|
+
feed_lines(d, t2, " at x\n at y\n")
|
232
|
+
end
|
233
|
+
assert_equal(make_logs(t1, JAVA_EXC) +
|
234
|
+
make_logs(t2, " at x\n", " at y\n"),
|
235
|
+
d.events)
|
236
|
+
end
|
237
|
+
|
238
|
+
def test_do_not_split_exception_after_pause
|
239
|
+
d = create_driver
|
240
|
+
t1 = 0
|
241
|
+
t2 = 0
|
242
|
+
d.run do
|
243
|
+
t1 = Time.now.to_i
|
244
|
+
feed_lines(d, t1, JAVA_EXC)
|
245
|
+
sleep 1
|
246
|
+
t2 = Time.now.to_i
|
247
|
+
feed_lines(d, t2, " at x\n at y\n")
|
248
|
+
d.instance.before_shutdown
|
249
|
+
end
|
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)
|
257
|
+
end
|
258
|
+
|
259
|
+
def get_out_tags(remove_tag_prefix, original_tag)
|
260
|
+
cfg = "remove_tag_prefix #{remove_tag_prefix}"
|
261
|
+
d = create_driver(cfg, original_tag)
|
262
|
+
run_driver(d, ARBITRARY_TEXT, JAVA_EXC, ARBITRARY_TEXT)
|
263
|
+
d.emits.collect { |e| e[0] }.sort.uniq
|
264
|
+
end
|
265
|
+
|
266
|
+
def test_remove_tag_prefix
|
267
|
+
tags = get_out_tags('prefix.plus', 'prefix.plus.rest.of.the.tag')
|
268
|
+
assert_equal(['rest.of.the.tag'], tags)
|
269
|
+
tags = get_out_tags('prefix.pl', 'prefix.plus.rest.of.the.tag')
|
270
|
+
assert_equal(['prefix.plus.rest.of.the.tag'], tags)
|
271
|
+
tags = get_out_tags('does.not.occur', 'prefix.plus.rest.of.the.tag')
|
272
|
+
assert_equal(['prefix.plus.rest.of.the.tag'], tags)
|
273
|
+
end
|
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
|
+
|
307
|
+
def test_flush_after_max_lines
|
308
|
+
cfg = %(
|
309
|
+
#{CONFIG}
|
310
|
+
max_lines 2)
|
311
|
+
d = create_driver(cfg)
|
312
|
+
t = Time.now.to_i
|
313
|
+
d.run do
|
314
|
+
feed_lines(d, t, PYTHON_EXC, JAVA_EXC)
|
315
|
+
end
|
316
|
+
# Expected: the first two lines of the exception are buffered and combined.
|
317
|
+
# Then the max_lines setting kicks in and the rest of the Python exception
|
318
|
+
# is logged line-by-line (since it's not an exception stack in itself).
|
319
|
+
# For the following Java stack trace, the two lines of the first exception
|
320
|
+
# are buffered and combined. So are the first two lines of the second
|
321
|
+
# exception. Then the rest is logged line-by-line.
|
322
|
+
expected = [PYTHON_EXC.lines[0..1].join] + PYTHON_EXC.lines[2..] + \
|
323
|
+
[JAVA_EXC.lines[0..1].join] + [JAVA_EXC.lines[2..3].join] + \
|
324
|
+
JAVA_EXC.lines[4..]
|
325
|
+
assert_equal(make_logs(t, *expected), d.events)
|
326
|
+
end
|
327
|
+
|
328
|
+
def test_separate_streams
|
329
|
+
cfg = %(
|
330
|
+
#{CONFIG}
|
331
|
+
stream stream)
|
332
|
+
d = create_driver(cfg)
|
333
|
+
t = Time.now.to_i
|
334
|
+
d.run do
|
335
|
+
feed_lines(d, t, JAVA_EXC.lines[0], stream: 'java')
|
336
|
+
feed_lines(d, t, PYTHON_EXC.lines[0..1].join, stream: 'python')
|
337
|
+
feed_lines(d, t, JAVA_EXC.lines[1..].join, stream: 'java')
|
338
|
+
feed_lines(d, t, JAVA_EXC, stream: 'java')
|
339
|
+
feed_lines(d, t, PYTHON_EXC.lines[2..].join, stream: 'python')
|
340
|
+
feed_lines(d, t, 'something else', stream: 'java')
|
341
|
+
end
|
342
|
+
# Expected: the Python and the Java exceptions are handled separately
|
343
|
+
# because they belong to different streams.
|
344
|
+
# Note that the Java exception is only detected when 'something else'
|
345
|
+
# is processed.
|
346
|
+
expected = make_logs(t, JAVA_EXC, stream: 'java') +
|
347
|
+
make_logs(t, PYTHON_EXC, stream: 'python') +
|
348
|
+
make_logs(t, JAVA_EXC, stream: 'java') +
|
349
|
+
make_logs(t, 'something else', stream: 'java')
|
350
|
+
assert_equal(expected, d.events)
|
351
|
+
end
|
352
|
+
end
|
metadata
ADDED
@@ -0,0 +1,136 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: fluent-plugin-detect-exceptions-with-webflux-support
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.13
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Stackdriver Agents
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2023-05-08 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: fluentd
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0.10'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0.10'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: flexmock
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '2.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '2.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '10.3'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '10.3'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rubocop
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - '='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 1.48.1
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - '='
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 1.48.1
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: test-unit
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '3.0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '3.0'
|
83
|
+
description: |2
|
84
|
+
Fluentd output plugin which detects exception stack traces in a stream of
|
85
|
+
JSON log messages and combines all single-line messages that belong to the
|
86
|
+
same stack trace into one multi-line message.
|
87
|
+
This is an official Google Ruby gem.
|
88
|
+
NOTE: This is a fork that tries to support spring webflux stack
|
89
|
+
email:
|
90
|
+
- stackdriver-agents@google.com
|
91
|
+
executables: []
|
92
|
+
extensions: []
|
93
|
+
extra_rdoc_files: []
|
94
|
+
files:
|
95
|
+
- CONTRIBUTING
|
96
|
+
- Gemfile
|
97
|
+
- LICENSE
|
98
|
+
- README.rdoc
|
99
|
+
- Rakefile
|
100
|
+
- fluent-plugin-detect-exceptions-with-webflux-support-0.0.12.gem
|
101
|
+
- fluent-plugin-detect-exceptions.gemspec
|
102
|
+
- lib/fluent/plugin/exception_detector.rb
|
103
|
+
- lib/fluent/plugin/out_detect_exceptions_with_webflux_support.rb
|
104
|
+
- test/helper.rb
|
105
|
+
- test/plugin/bench_exception_detector.rb
|
106
|
+
- test/plugin/test_exception_detector.rb
|
107
|
+
- test/plugin/test_out_detect_exceptions_with_webflux_support.rb
|
108
|
+
homepage: https://github.com/faisaltheparttimecoder/fluent-plugin-detect-exceptions_with_webflux_support
|
109
|
+
licenses:
|
110
|
+
- Apache-2.0
|
111
|
+
metadata: {}
|
112
|
+
post_install_message:
|
113
|
+
rdoc_options: []
|
114
|
+
require_paths:
|
115
|
+
- lib
|
116
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
117
|
+
requirements:
|
118
|
+
- - ">="
|
119
|
+
- !ruby/object:Gem::Version
|
120
|
+
version: '2.6'
|
121
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
122
|
+
requirements:
|
123
|
+
- - ">="
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
version: '0'
|
126
|
+
requirements: []
|
127
|
+
rubygems_version: 3.2.3
|
128
|
+
signing_key:
|
129
|
+
specification_version: 4
|
130
|
+
summary: fluentd output plugin for combining stack traces as multi-line JSON logs
|
131
|
+
with webflux support
|
132
|
+
test_files:
|
133
|
+
- test/helper.rb
|
134
|
+
- test/plugin/bench_exception_detector.rb
|
135
|
+
- test/plugin/test_exception_detector.rb
|
136
|
+
- test/plugin/test_out_detect_exceptions_with_webflux_support.rb
|