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.
@@ -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