fluent-plugin-detect-exceptions-with-webflux-support 0.0.13

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,382 @@
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
+ module Fluent
16
+ Struct.new('Rule', :from_states, :pattern, :to_state)
17
+
18
+ # Configuration of the state machine that detects exceptions.
19
+ module ExceptionDetectorConfig
20
+ # Rule for a state transition: if pattern matches go to the given state.
21
+ class RuleTarget
22
+ attr_accessor :pattern, :to_state
23
+
24
+ def initialize(pattern, state)
25
+ @pattern = pattern
26
+ @to_state = state
27
+ end
28
+
29
+ def ==(other)
30
+ other.class == self.class && other.state == state
31
+ end
32
+
33
+ alias eql? ==
34
+
35
+ def hash
36
+ state.hash
37
+ end
38
+
39
+ def state
40
+ [@pattern, @to_state]
41
+ end
42
+ end
43
+
44
+ def self.rule(from_state_or_states, pattern, to_state)
45
+ from_state_or_states = [from_state_or_states] unless
46
+ from_state_or_states.is_a?(Array)
47
+ Struct::Rule.new(from_state_or_states, pattern, to_state)
48
+ end
49
+
50
+ def self.supported
51
+ RULES_BY_LANG.keys
52
+ end
53
+
54
+ JAVA_RULES = [
55
+ rule(%i[start_state java_start_exception],
56
+ /(?:Exception|Error|Throwable|V8 errors stack trace)[:\r\n]/,
57
+ :java_after_exception),
58
+ rule(:java_after_exception, /^[\t ]*nested exception is:[\t ]*/,
59
+ :java_start_exception),
60
+ rule(:java_after_exception, /^[\r\n]*$/, :java_after_exception),
61
+ rule(%i[java_after_exception java], /^[\t ]+(?:eval )?at /, :java),
62
+ rule([:java_after_exception, :java], /^[\t ]+(?:eval )?\*__checkpoint/,
63
+ :java),
64
+
65
+ rule(%i[java_after_exception java],
66
+ # C# nested exception.
67
+ /^[\t ]+--- End of inner exception stack trace ---$/,
68
+ :java),
69
+
70
+ rule(%i[java_after_exception java],
71
+ # C# exception from async code.
72
+ /^--- End of stack trace from previous (?x:
73
+ )location where exception was thrown ---$/,
74
+ :java),
75
+
76
+ rule(%i[java_after_exception java], /^[\t ]*(?:Caused by|Suppressed):/,
77
+ :java_after_exception),
78
+ rule([:java_after_exception, :java],
79
+ /^(?:Error has been observed at the following site\(s\)):/,
80
+ :java),
81
+ rule([:java_after_exception, :java], /^(?:Original Stack Trace):/, :java),
82
+ rule(%i[java_after_exception java],
83
+ /^[\t ]*... \d+ (?:more|common frames omitted)/, :java)
84
+ ].freeze
85
+
86
+ PYTHON_RULES = [
87
+ rule(:start_state, /^Traceback \(most recent call last\):$/, :python),
88
+ rule(:python, /^[\t ]+File /, :python_code),
89
+ rule(:python_code, /[^\t ]/, :python),
90
+ rule(:python, /^(?:[^\s.():]+\.)*[^\s.():]+:/, :start_state)
91
+ ].freeze
92
+
93
+ PHP_RULES = [
94
+ rule(:start_state, /
95
+ (?:PHP\ (?:Notice|Parse\ error|Fatal\ error|Warning):)|
96
+ (?:exception\ '[^']+'\ with\ message\ ')/x, :php_stack_begin),
97
+ rule(:php_stack_begin, /^Stack trace:/, :php_stack_frames),
98
+ rule(:php_stack_frames, /^#\d/, :php_stack_frames),
99
+ rule(:php_stack_frames, /^\s+thrown in /, :start_state)
100
+ ].freeze
101
+
102
+ GO_RULES = [
103
+ rule(:start_state, /\bpanic: /, :go_after_panic),
104
+ rule(:start_state, /http: panic serving/, :go_goroutine),
105
+ rule(:go_after_panic, /^$/, :go_goroutine),
106
+ rule(%i[go_after_panic go_after_signal go_frame_line1],
107
+ /^$/, :go_goroutine),
108
+ rule(:go_after_panic, /^\[signal /, :go_after_signal),
109
+ rule(:go_goroutine, /^goroutine \d+ \[[^\]]+\]:$/, :go_frame_line1),
110
+ rule(:go_frame_line1, /^(?:[^\s.:]+\.)*[^\s.():]+\(|^created by /,
111
+ :go_frame_line2),
112
+ rule(:go_frame_line2, /^\s/, :go_frame_line1)
113
+ ].freeze
114
+
115
+ RUBY_RULES = [
116
+ rule(:start_state, /Error \(.*\):$/, :ruby_before_rails_trace),
117
+ rule(:ruby_before_rails_trace, /^ $/, :ruby),
118
+ rule(:ruby_before_rails_trace, /^[\t ]+.*?\.rb:\d+:in `/, :ruby),
119
+ rule(:ruby, /^[\t ]+.*?\.rb:\d+:in `/, :ruby)
120
+ ].freeze
121
+
122
+ DART_RULES = [
123
+ rule(:start_state, /^Unhandled exception:$/, :dart_exc),
124
+ rule(:dart_exc, /^Instance of/, :dart_stack),
125
+ rule(:dart_exc, /^Exception/, :dart_stack),
126
+ rule(:dart_exc, /^Bad state/, :dart_stack),
127
+ rule(:dart_exc, /^IntegerDivisionByZeroException/, :dart_stack),
128
+ rule(:dart_exc, /^Invalid argument/, :dart_stack),
129
+ rule(:dart_exc, /^RangeError/, :dart_stack),
130
+ rule(:dart_exc, /^Assertion failed/, :dart_stack),
131
+ rule(:dart_exc, /^Cannot instantiate/, :dart_stack),
132
+ rule(:dart_exc, /^Reading static variable/, :dart_stack),
133
+ rule(:dart_exc, /^UnimplementedError/, :dart_stack),
134
+ rule(:dart_exc, /^Unsupported operation/, :dart_stack),
135
+ rule(:dart_exc, /^Concurrent modification/, :dart_stack),
136
+ rule(:dart_exc, /^Out of Memory/, :dart_stack),
137
+ rule(:dart_exc, /^Stack Overflow/, :dart_stack),
138
+ rule(:dart_exc, /^'.+?':.+?$/, :dart_type_err_line1),
139
+ rule(:dart_type_err_line1, /^#\d+\s+.+?\(.+?\)$/, :dart_stack),
140
+ rule(:dart_type_err_line1, /^.+?$/, :dart_type_err_line2),
141
+ rule(:dart_type_err_line2, /^.*?\^.*?$/, :dart_type_err_line3),
142
+ rule(:dart_type_err_line3, /^$/, :dart_type_err_line4),
143
+ rule(:dart_type_err_line4, /^$/, :dart_stack),
144
+ rule(:dart_exc, /^FormatException/, :dart_format_err_line1),
145
+ rule(:dart_format_err_line1, /^#\d+\s+.+?\(.+?\)$/, :dart_stack),
146
+ rule(:dart_format_err_line1, /^./, :dart_format_err_line2),
147
+ rule(:dart_format_err_line2, /^.*?\^/, :dart_format_err_line3),
148
+ rule(:dart_format_err_line3, /^$/, :dart_stack),
149
+ rule(:dart_exc, /^NoSuchMethodError:/, :dart_method_err_line1),
150
+ rule(:dart_method_err_line1, /^Receiver:/, :dart_method_err_line2),
151
+ rule(:dart_method_err_line2, /^Tried calling:/, :dart_method_err_line3),
152
+ rule(:dart_method_err_line3, /^Found:/, :dart_stack),
153
+ rule(:dart_method_err_line3, /^#\d+\s+.+?\(.+?\)$/, :dart_stack),
154
+ rule(:dart_stack, /^#\d+\s+.+?\(.+?\)$/, :dart_stack),
155
+ rule(:dart_stack, /^<asynchronous suspension>$/, :dart_stack)
156
+ ].freeze
157
+
158
+ ALL_RULES = (
159
+ JAVA_RULES + PYTHON_RULES + PHP_RULES + GO_RULES + RUBY_RULES + DART_RULES
160
+ ).freeze
161
+
162
+ RULES_BY_LANG = {
163
+ java: JAVA_RULES,
164
+ javascript: JAVA_RULES,
165
+ js: JAVA_RULES,
166
+ csharp: JAVA_RULES,
167
+ py: PYTHON_RULES,
168
+ python: PYTHON_RULES,
169
+ php: PHP_RULES,
170
+ go: GO_RULES,
171
+ rb: RUBY_RULES,
172
+ ruby: RUBY_RULES,
173
+ dart: DART_RULES,
174
+ all: ALL_RULES
175
+ }.freeze
176
+
177
+ DEFAULT_FIELDS = %w[message log].freeze
178
+ end
179
+
180
+ # State machine that consumes individual log lines and detects
181
+ # multi-line stack traces.
182
+ class ExceptionDetector
183
+ def initialize(*languages)
184
+ @state = :start_state
185
+ @rules = Hash.new { |h, k| h[k] = [] }
186
+
187
+ languages = [:all] if languages.empty?
188
+
189
+ languages.each do |lang|
190
+ rule_config =
191
+ ExceptionDetectorConfig::RULES_BY_LANG.fetch(lang.downcase) do |_k|
192
+ raise ArgumentError, "Unknown language: #{lang}"
193
+ end
194
+
195
+ rule_config.each do |r|
196
+ target = ExceptionDetectorConfig::RuleTarget.new(r[:pattern],
197
+ r[:to_state])
198
+ r[:from_states].each do |from_state|
199
+ @rules[from_state] << target
200
+ end
201
+ end
202
+ end
203
+
204
+ @rules.each_value(&:uniq!)
205
+ end
206
+
207
+ # Updates the state machine and returns the trace detection status:
208
+ # - no_trace: 'line' does not belong to an exception trace,
209
+ # - start_trace: 'line' starts a detected exception trace,
210
+ # - inside: 'line' is part of a detected exception trace,
211
+ # - end: the detected exception trace ends after 'line'.
212
+ def update(line)
213
+ trace_seen_before = transition(line)
214
+ # If the state machine fell back to the start state because there is no
215
+ # defined transition for 'line', trigger another state transition because
216
+ # 'line' may contain the beginning of another exception.
217
+ transition(line) unless trace_seen_before
218
+ new_state = @state
219
+ trace_seen_after = new_state != :start_state
220
+
221
+ case [trace_seen_before, trace_seen_after]
222
+ when [true, true]
223
+ :inside_trace
224
+ when [true, false]
225
+ :end_trace
226
+ when [false, true]
227
+ :start_trace
228
+ else
229
+ :no_trace
230
+ end
231
+ end
232
+
233
+ def reset
234
+ @state = :start_state
235
+ end
236
+
237
+ private
238
+
239
+ # Executes a transition of the state machine for the given line.
240
+ # Returns false if the line does not match any transition rule and the
241
+ # state machine was reset to the initial state.
242
+ def transition(line)
243
+ @rules[@state].each do |r|
244
+ next unless line =~ r.pattern
245
+
246
+ @state = r.to_state
247
+ return true
248
+ end
249
+ @state = :start_state
250
+ false
251
+ end
252
+ end
253
+
254
+ # Buffers and groups log records if they contain exception stack traces.
255
+ class TraceAccumulator
256
+ attr_reader :buffer_start_time
257
+
258
+ # If message_field is nil, the instance is set up to accumulate
259
+ # records that are plain strings (i.e. the whole record is concatenated).
260
+ # Otherwise, the instance accepts records that are dictionaries (usually
261
+ # originating from structured JSON logs) and accumulates just the
262
+ # content of the given message field.
263
+ # message_field may contain the empty string. In this case, the
264
+ # TraceAccumulator 'learns' the field name from the first record by checking
265
+ # for some pre-defined common field names of text logs.
266
+ # The option parameter can be used to pass the following parameters:
267
+ # force_line_breaks adds line breaks when combining exception stacks
268
+ # max_lines and max_bytes limit the maximum amount
269
+ # of data to be buffered. The default value 0 indicates 'no limit'.
270
+ def initialize(message_field, languages, **options, &emit_callback)
271
+ @exception_detector = Fluent::ExceptionDetector.new(*languages)
272
+ @message_field = message_field
273
+ @force_line_breaks = options[:force_line_breaks] || false
274
+ @max_lines = options[:max_lines] || 0
275
+ @max_bytes = options[:max_bytes] || 0
276
+ @emit = emit_callback
277
+ @messages = []
278
+ @buffer_start_time = Time.now
279
+ @buffer_size = 0
280
+ @first_record = nil
281
+ @first_timestamp = nil
282
+ end
283
+
284
+ def push(time_sec, record)
285
+ message = extract_message(record)
286
+ if message.nil?
287
+ @exception_detector.reset
288
+ detection_status = :no_trace
289
+ else
290
+ force_flush if @max_bytes.positive? &&
291
+ @buffer_size + message.length > @max_bytes
292
+ detection_status = @exception_detector.update(message)
293
+ end
294
+
295
+ update_buffer(detection_status, time_sec, record, message)
296
+
297
+ force_flush if @max_lines.positive? && @messages.length == @max_lines
298
+ end
299
+
300
+ def flush
301
+ case @messages.length
302
+ when 0
303
+ return
304
+ when 1
305
+ @emit.call(@first_timestamp, @first_record)
306
+ else
307
+ combined_message = @messages.join
308
+ if @message_field.nil?
309
+ output_record = combined_message
310
+ else
311
+ output_record = @first_record
312
+ output_record[@message_field] = combined_message
313
+ end
314
+ @emit.call(@first_timestamp, output_record)
315
+ end
316
+ @messages = []
317
+ @first_record = nil
318
+ @first_timestamp = nil
319
+ @buffer_size = 0
320
+ end
321
+
322
+ def force_flush
323
+ flush
324
+ @exception_detector.reset
325
+ end
326
+
327
+ private
328
+
329
+ def extract_message(record)
330
+ if !@message_field.nil? && @message_field.empty?
331
+ ExceptionDetectorConfig::DEFAULT_FIELDS.each do |f|
332
+ if record.key?(f)
333
+ @message_field = f
334
+ break
335
+ end
336
+ end
337
+ end
338
+ @message_field.nil? ? record : record[@message_field]
339
+ end
340
+
341
+ def update_buffer(detection_status, time_sec, record, message)
342
+ trigger_emit = %i[no_trace end_trace].include?(detection_status)
343
+ if @messages.empty? && trigger_emit
344
+ @emit.call(time_sec, record)
345
+ return
346
+ end
347
+
348
+ case detection_status
349
+ when :inside_trace
350
+ add(time_sec, record, message)
351
+ when :end_trace
352
+ add(time_sec, record, message)
353
+ flush
354
+ when :no_trace
355
+ flush
356
+ add(time_sec, record, message)
357
+ flush
358
+ when :start_trace
359
+ flush
360
+ add(time_sec, record, message)
361
+ end
362
+ end
363
+
364
+ def add(time_sec, record, message)
365
+ if @messages.empty?
366
+ @first_record = record unless @message_field.nil?
367
+ @first_timestamp = time_sec
368
+ @buffer_start_time = Time.now
369
+ end
370
+ return if message.nil?
371
+
372
+ message_with_line_break =
373
+ if @force_line_breaks && !@messages.empty? && !message.include?("\n")
374
+ "\n#{message}"
375
+ else
376
+ message
377
+ end
378
+ @messages << message_with_line_break
379
+ @buffer_size += message_with_line_break.length
380
+ end
381
+ end
382
+ end
@@ -0,0 +1,142 @@
1
+ #
2
+ # Copyright 2016 Google Inc. All rights reserved.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+
16
+ require 'fluent/plugin/exception_detector'
17
+ require 'fluent/output'
18
+
19
+ module Fluent
20
+ # This output plugin consumes a log stream of JSON objects which contain
21
+ # single-line log messages. If a consecutive sequence of log messages form
22
+ # an exception stack trace, they forwarded as a single, combined JSON
23
+ # object. Otherwise, the input log data is forwarded as is.
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
27
+ desc 'The field which contains the raw message text in the input JSON data.'
28
+ config_param :message, :string, default: ''
29
+ desc 'The interval of flushing the buffer for multiline format.'
30
+ config_param :multiline_flush_interval, :time, default: nil
31
+ desc 'Programming languages for which to detect exceptions. Default: all.'
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
35
+ desc 'Maximum number of lines to flush (0 means no limit). Default: 1000.'
36
+ config_param :max_lines, :integer, default: 1000
37
+ desc 'Maximum number of bytes to flush (0 means no limit). Default: 0.'
38
+ config_param :max_bytes, :integer, default: 0
39
+ desc 'Separate log streams by this field in the input JSON data.'
40
+ config_param :stream, :string, default: ''
41
+
42
+ Fluent::Plugin.register_output('detect_exceptions_with_webflux_support', self)
43
+
44
+ def configure(conf)
45
+ super
46
+
47
+ @check_flush_interval = [multiline_flush_interval * 0.1, 1].max if multiline_flush_interval
48
+
49
+ @languages = languages.map(&:to_sym)
50
+
51
+ # Maps log stream tags to a corresponding TraceAccumulator.
52
+ @accumulators = {}
53
+ end
54
+
55
+ def start
56
+ super
57
+
58
+ return unless multiline_flush_interval
59
+
60
+ @flush_buffer_mutex = Mutex.new
61
+ @stop_check = false
62
+ @thread = Thread.new(&method(:check_flush_loop))
63
+ end
64
+
65
+ def before_shutdown
66
+ flush_buffers
67
+ super if defined?(super)
68
+ end
69
+
70
+ def shutdown
71
+ # Before shutdown is not available in older fluentd versions.
72
+ # Hence, we make sure that we flush the buffers here as well.
73
+ flush_buffers
74
+ @thread.join if @multiline_flush_interval
75
+ super
76
+ end
77
+
78
+ def emit(tag, entries, chain)
79
+ entries.each do |time_sec, record|
80
+ process_record(tag, time_sec, record)
81
+ end
82
+ chain.next
83
+ end
84
+
85
+ private
86
+
87
+ def process_record(tag, time_sec, record)
88
+ synchronize do
89
+ log_id = [tag]
90
+ log_id.push(record.fetch(@stream, '')) unless @stream.empty?
91
+ unless @accumulators.key?(log_id)
92
+ out_tag = tag.sub(/^#{Regexp.escape(@remove_tag_prefix)}\./, '')
93
+ @accumulators[log_id] =
94
+ Fluent::TraceAccumulator.new(
95
+ @message,
96
+ @languages,
97
+ force_line_breaks: @force_line_breaks,
98
+ max_lines: @max_lines,
99
+ max_bytes: @max_bytes
100
+ ) do |t, r|
101
+ router.emit(out_tag, t, r)
102
+ end
103
+ end
104
+
105
+ @accumulators[log_id].push(time_sec, record)
106
+ end
107
+ end
108
+
109
+ def flush_buffers
110
+ synchronize do
111
+ @stop_check = true
112
+ @accumulators.each_value(&:force_flush)
113
+ end
114
+ end
115
+
116
+ def check_flush_loop
117
+ @flush_buffer_mutex.synchronize do
118
+ loop do
119
+ @flush_buffer_mutex.sleep(@check_flush_interval)
120
+ now = Time.now
121
+ break if @stop_check
122
+
123
+ @accumulators.each_value do |acc|
124
+ acc.force_flush if now - acc.buffer_start_time >
125
+ @multiline_flush_interval
126
+ end
127
+ end
128
+ end
129
+ rescue StandardError
130
+ log.error 'error in check_flush_loop', error: $ERROR_INFO.to_s
131
+ log.error_backtrace
132
+ end
133
+
134
+ def synchronize(&block)
135
+ if @multiline_flush_interval
136
+ @flush_buffer_mutex.synchronize(&block)
137
+ else
138
+ yield
139
+ end
140
+ end
141
+ end
142
+ end
data/test/helper.rb ADDED
@@ -0,0 +1,46 @@
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 'rubygems'
16
+ require 'bundler'
17
+ begin
18
+ Bundler.setup(:default, :development)
19
+ rescue Bundler::BundlerError => e
20
+ # rubocop:disable Style/StderrPuts
21
+ $stderr.puts e.message
22
+ $stderr.puts 'Run `bundle install` to install missing gems'
23
+ # rubocop:enable Style/StderrPuts
24
+ exit e.status_code
25
+ end
26
+ require 'test/unit'
27
+
28
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
29
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
30
+ require 'fluent/test'
31
+ unless ENV.key?('VERBOSE')
32
+ nulllogger = Object.new
33
+ nulllogger.instance_eval do |_|
34
+ def respond_to_missing?(_method, _include_private = false)
35
+ true
36
+ end
37
+
38
+ def method_missing(_method, *_args)
39
+ # pass
40
+ end
41
+ end
42
+ # global $log variable is used by fluentd
43
+ $log = nulllogger # rubocop:disable Style/GlobalVars
44
+ end
45
+
46
+ require 'fluent/plugin/out_detect_exceptions_with_webflux_support'
@@ -0,0 +1,73 @@
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 'benchmark'
16
+
17
+ require 'fluent/plugin/exception_detector'
18
+
19
+ size_in_m = 25
20
+ line_length = 50
21
+
22
+ size = size_in_m << 20
23
+
24
+ JAVA_EXC = <<~END_JAVA.freeze
25
+ Jul 09, 2015 3:23:29 PM com.google.devtools.search.cloud.feeder.MakeLog: RuntimeException: Run from this message!
26
+ at com.my.app.Object.do$a1(MakeLog.java:50)
27
+ at java.lang.Thing.call(Thing.java:10)
28
+ at com.my.app.Object.help(MakeLog.java:40)
29
+ at sun.javax.API.method(API.java:100)
30
+ at com.jetty.Framework.main(MakeLog.java:30)
31
+ END_JAVA
32
+
33
+ PYTHON_EXC = <<~END_PYTHON.freeze
34
+ Traceback (most recent call last):
35
+ File "/base/data/home/runtimes/python27/python27_lib/versions/third_party/webapp2-2.5.2/webapp2.py", line 1535, in __call__
36
+ rv = self.handle_exception(request, response, e)
37
+ File "/base/data/home/apps/s~nearfieldspy/1.378705245900539993/nearfieldspy.py", line 17, in start
38
+ return get()
39
+ File "/base/data/home/apps/s~nearfieldspy/1.378705245900539993/nearfieldspy.py", line 5, in get
40
+ raise Exception('spam', 'eggs')
41
+ Exception: ('spam', 'eggs')
42
+ END_PYTHON
43
+
44
+ chars = [('a'..'z'), ('A'..'Z')].map(&:to_a).flatten
45
+
46
+ random_text = (1..(size / line_length)).collect do
47
+ (0...line_length).map { chars[rand(chars.length)] }.join
48
+ end
49
+
50
+ exceptions = {
51
+ java: (JAVA_EXC * (size / JAVA_EXC.length)).lines,
52
+ python: (PYTHON_EXC * (size / PYTHON_EXC.length)).lines
53
+ }
54
+
55
+ puts "Start benchmark. Input size #{size_in_m}M."
56
+ Benchmark.bm do |x|
57
+ languages = Fluent::ExceptionDetectorConfig::RULES_BY_LANG.keys
58
+ languages.each do |lang|
59
+ buffer = Fluent::TraceAccumulator.new(nil, lang) {}
60
+ x.report("#{lang}_detector_random_text") do
61
+ random_text.each { |l| buffer.push(0, l) }
62
+ end
63
+ end
64
+ %i[java python all].each do |detector_lang|
65
+ buffer = Fluent::TraceAccumulator.new(nil, detector_lang) {}
66
+ exc_languages = detector_lang == :all ? exceptions.keys : [detector_lang]
67
+ exc_languages.each do |exc_lang|
68
+ x.report("#{detector_lang}_detector_#{exc_lang}_stacks") do
69
+ exceptions[exc_lang].each { |l| buffer.push(0, l) }
70
+ end
71
+ end
72
+ end
73
+ end