fluent-plugin-detect-exceptions-with-error 0.0.1a

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,407 @@
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_relative '../helper'
16
+ require 'fluent/plugin/exception_detector'
17
+
18
+ class ExceptionDetectorTest < Test::Unit::TestCase
19
+ JAVA_EXC_PART1 = <<END.freeze
20
+ Jul 09, 2015 3:23:29 PM com.google.devtools.search.cloud.feeder.MakeLog: RuntimeException: Run from this message!
21
+ at com.my.app.Object.do$a1(MakeLog.java:50)
22
+ at java.lang.Thing.call(Thing.java:10)
23
+ END
24
+
25
+ JAVA_EXC_PART2 = <<END.freeze
26
+ at com.my.app.Object.help(MakeLog.java:40)
27
+ at sun.javax.API.method(API.java:100)
28
+ at com.jetty.Framework.main(MakeLog.java:30)
29
+ END
30
+
31
+ JAVA_EXC = (JAVA_EXC_PART1 + JAVA_EXC_PART2).freeze
32
+
33
+ COMPLEX_JAVA_EXC = <<END.freeze
34
+ javax.servlet.ServletException: Something bad happened
35
+ at com.example.myproject.OpenSessionInViewFilter.doFilter(OpenSessionInViewFilter.java:60)
36
+ at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1157)
37
+ at com.example.myproject.ExceptionHandlerFilter.doFilter(ExceptionHandlerFilter.java:28)
38
+ at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1157)
39
+ at com.example.myproject.OutputBufferFilter.doFilter(OutputBufferFilter.java:33)
40
+ at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1157)
41
+ at org.mortbay.jetty.servlet.ServletHandler.handle(ServletHandler.java:388)
42
+ at org.mortbay.jetty.security.SecurityHandler.handle(SecurityHandler.java:216)
43
+ at org.mortbay.jetty.servlet.SessionHandler.handle(SessionHandler.java:182)
44
+ at org.mortbay.jetty.handler.ContextHandler.handle(ContextHandler.java:765)
45
+ at org.mortbay.jetty.webapp.WebAppContext.handle(WebAppContext.java:418)
46
+ at org.mortbay.jetty.handler.HandlerWrapper.handle(HandlerWrapper.java:152)
47
+ at org.mortbay.jetty.Server.handle(Server.java:326)
48
+ at org.mortbay.jetty.HttpConnection.handleRequest(HttpConnection.java:542)
49
+ at org.mortbay.jetty.HttpConnection$RequestHandler.content(HttpConnection.java:943)
50
+ at org.mortbay.jetty.HttpParser.parseNext(HttpParser.java:756)
51
+ at org.mortbay.jetty.HttpParser.parseAvailable(HttpParser.java:218)
52
+ at org.mortbay.jetty.HttpConnection.handle(HttpConnection.java:404)
53
+ at org.mortbay.jetty.bio.SocketConnector$Connection.run(SocketConnector.java:228)
54
+ at org.mortbay.thread.QueuedThreadPool$PoolThread.run(QueuedThreadPool.java:582)
55
+ Caused by: com.example.myproject.MyProjectServletException
56
+ at com.example.myproject.MyServlet.doPost(MyServlet.java:169)
57
+ at javax.servlet.http.HttpServlet.service(HttpServlet.java:727)
58
+ at javax.servlet.http.HttpServlet.service(HttpServlet.java:820)
59
+ at org.mortbay.jetty.servlet.ServletHolder.handle(ServletHolder.java:511)
60
+ at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1166)
61
+ at com.example.myproject.OpenSessionInViewFilter.doFilter(OpenSessionInViewFilter.java:30)
62
+ ... 27 more
63
+ END
64
+
65
+ NODE_JS_EXC = <<END.freeze
66
+ ReferenceError: myArray is not defined
67
+ at next (/app/node_modules/express/lib/router/index.js:256:14)
68
+ at /app/node_modules/express/lib/router/index.js:615:15
69
+ at next (/app/node_modules/express/lib/router/index.js:271:10)
70
+ at Function.process_params (/app/node_modules/express/lib/router/index.js:330:12)
71
+ at /app/node_modules/express/lib/router/index.js:277:22
72
+ at Layer.handle [as handle_request] (/app/node_modules/express/lib/router/layer.js:95:5)
73
+ at Route.dispatch (/app/node_modules/express/lib/router/route.js:112:3)
74
+ at next (/app/node_modules/express/lib/router/route.js:131:13)
75
+ at Layer.handle [as handle_request] (/app/node_modules/express/lib/router/layer.js:95:5)
76
+ at /app/app.js:52:3
77
+ END
78
+
79
+ CLIENT_JS_EXC = <<END.freeze
80
+ Error
81
+ at bls (<anonymous>:3:9)
82
+ at <anonymous>:6:4
83
+ at a_function_name
84
+ at Object.InjectedScript._evaluateOn (http://<anonymous>/file.js?foo=bar:875:140)
85
+ at Object.InjectedScript.evaluate (<anonymous>)
86
+ END
87
+
88
+ V8_JS_EXC = <<END.freeze
89
+ V8 errors stack trace
90
+ eval at Foo.a (eval at Bar.z (myscript.js:10:3))
91
+ at new Contructor.Name (native)
92
+ at new FunctionName (unknown location)
93
+ at Type.functionName [as methodName] (file(copy).js?query='yes':12:9)
94
+ at functionName [as methodName] (native)
95
+ at Type.main(sample(copy).js:6:4)
96
+ END
97
+
98
+ PYTHON_EXC = <<END.freeze
99
+ Traceback (most recent call last):
100
+ File "/base/data/home/runtimes/python27/python27_lib/versions/third_party/webapp2-2.5.2/webapp2.py", line 1535, in __call__
101
+ rv = self.handle_exception(request, response, e)
102
+ File "/base/data/home/apps/s~nearfieldspy/1.378705245900539993/nearfieldspy.py", line 17, in start
103
+ return get()
104
+ File "/base/data/home/apps/s~nearfieldspy/1.378705245900539993/nearfieldspy.py", line 5, in get
105
+ raise Exception('spam', 'eggs')
106
+ Exception: ('spam', 'eggs')
107
+ END
108
+
109
+ PHP_EXC = <<END.freeze
110
+ exception 'Exception' with message 'Custom exception' in /home/joe/work/test-php/test.php:5
111
+ Stack trace:
112
+ #0 /home/joe/work/test-php/test.php(9): func1()
113
+ #1 /home/joe/work/test-php/test.php(13): func2()
114
+ #2 {main}
115
+ END
116
+
117
+ PHP_ON_GAE_EXC = <<END.freeze
118
+ PHP Fatal error: Uncaught exception 'Exception' with message 'message' in /base/data/home/apps/s~crash-example-php/1.388306779641080894/errors.php:60
119
+ Stack trace:
120
+ #0 [internal function]: ErrorEntryGenerator::{closure}()
121
+ #1 /base/data/home/apps/s~crash-example-php/1.388306779641080894/errors.php(20): call_user_func_array(Object(Closure), Array)
122
+ #2 /base/data/home/apps/s~crash-example-php/1.388306779641080894/index.php(36): ErrorEntry->__call('raise', Array)
123
+ #3 /base/data/home/apps/s~crash-example-php/1.388306779641080894/index.php(36): ErrorEntry->raise()
124
+ #4 {main}
125
+ thrown in /base/data/home/apps/s~crash-example-php/1.388306779641080894/errors.php on line 60
126
+ END
127
+
128
+ GO_EXC = <<END.freeze
129
+ panic: runtime error: index out of range
130
+
131
+ goroutine 12 [running]:
132
+ main88989.memoryAccessException()
133
+ crash_example_go.go:58 +0x12a
134
+ main88989.handler(0x2afb7042a408, 0xc01042f880, 0xc0104d3450)
135
+ crash_example_go.go:36 +0x7ec
136
+ net/http.HandlerFunc.ServeHTTP(0x13e5128, 0x2afb7042a408, 0xc01042f880, 0xc0104d3450)
137
+ go/src/net/http/server.go:1265 +0x56
138
+ net/http.(*ServeMux).ServeHTTP(0xc01045cab0, 0x2afb7042a408, 0xc01042f880, 0xc0104d3450)
139
+ go/src/net/http/server.go:1541 +0x1b4
140
+ appengine_internal.executeRequestSafely(0xc01042f880, 0xc0104d3450)
141
+ go/src/appengine_internal/api_prod.go:288 +0xb7
142
+ appengine_internal.(*server).HandleRequest(0x15819b0, 0xc010401560, 0xc0104c8180, 0xc010431380, 0x0, 0x0)
143
+ go/src/appengine_internal/api_prod.go:222 +0x102b
144
+ reflect.Value.call(0x1243fe0, 0x15819b0, 0x113, 0x12c8a20, 0x4, 0xc010485f78, 0x3, 0x3, 0x0, 0x0, ...)
145
+ /tmp/appengine/go/src/reflect/value.go:419 +0x10fd
146
+ reflect.Value.Call(0x1243fe0, 0x15819b0, 0x113, 0xc010485f78, 0x3, 0x3, 0x0, 0x0, 0x0)
147
+ /tmp/ap"
148
+ END
149
+
150
+ CSHARP_EXC = <<END.freeze
151
+ System.Collections.Generic.KeyNotFoundException: The given key was not present in the dictionary.
152
+ at System.Collections.Generic.Dictionary`2[System.String,System.Collections.Generic.Dictionary`2[System.Int32,System.Double]].get_Item (System.String key) [0x00000] in <filename unknown>:0
153
+ at File3.Consolidator_Class.Function5 (System.Collections.Generic.Dictionary`2 names, System.Text.StringBuilder param_4) [0x00007] in /usr/local/google/home/Csharp/another file.csharp:9
154
+ at File3.Consolidator_Class.Function4 (System.Text.StringBuilder param_4, System.Double[,,] array) [0x00013] in /usr/local/google/home/Csharp/another file.csharp:23
155
+ at File3.Consolidator_Class.Function3 (Int32 param_3) [0x0000f] in /usr/local/google/home/Csharp/another file.csharp:27
156
+ at File3.Consolidator_Class.Function3 (System.Text.StringBuilder param_3) [0x00007] in /usr/local/google/home/Csharp/another file.csharp:32
157
+ at File2.Processor.Function2 (System.Int32& param_2, System.Collections.Generic.Stack`1& numbers) [0x00003] in /usr/local/google/home/Csharp/File2.csharp:19
158
+ at File2.Processor.Random2 () [0x00037] in /usr/local/google/home/Csharp/File2.csharp:28
159
+ at File2.Processor.Function1 (Int32 param_1, System.Collections.Generic.Dictionary`2 map) [0x00007] in /usr/local/google/home/Csharp/File2.csharp:34
160
+ at Main.Welcome+<Main>c__AnonStorey0.<>m__0 () [0x00006] in /usr/local/google/home/Csharp/hello.csharp:48
161
+ at System.Threading.Thread.StartInternal () [0x00000] in <filename unknown>:0
162
+ END
163
+
164
+ RUBY_EXC = <<END.freeze
165
+ NoMethodError (undefined method `resursivewordload' for #<BooksController:0x007f8dd9a0c738>):
166
+ app/controllers/books_controller.rb:69:in `recursivewordload'
167
+ app/controllers/books_controller.rb:75:in `loadword'
168
+ app/controllers/books_controller.rb:79:in `loadline'
169
+ app/controllers/books_controller.rb:83:in `loadparagraph'
170
+ app/controllers/books_controller.rb:87:in `loadpage'
171
+ app/controllers/books_controller.rb:91:in `onload'
172
+ app/controllers/books_controller.rb:95:in `loadrecursive'
173
+ app/controllers/books_controller.rb:99:in `requestload'
174
+ app/controllers/books_controller.rb:118:in `generror'
175
+ config/error_reporting_logger.rb:62:in `tagged'
176
+ END
177
+
178
+ ARBITRARY_TEXT = <<END.freeze
179
+ This arbitrary text.
180
+ I am glad it contains no exception.
181
+ END
182
+
183
+ def check_multiline(detector, expected_first, expected_last, multiline)
184
+ lines = multiline.lines
185
+ lines.each_with_index do |line, index|
186
+ action = detector.update(line)
187
+ case index
188
+ when 0
189
+ assert_equal(expected_first, action,
190
+ "unexpected action on first line: #{line}")
191
+ when lines.length - 1
192
+ assert_equal(expected_last, action,
193
+ "unexpected action on last line: #{line}")
194
+ else
195
+ assert_equal(:inside_trace, action, "line not buffered: #{line}")
196
+ end
197
+ end
198
+ end
199
+
200
+ def check_no_multiline(detector, text)
201
+ text.lines.each do |line|
202
+ action = detector.update(line)
203
+ assert_equal(:no_trace, action, "unexpected action on line: #{line}")
204
+ end
205
+ end
206
+
207
+ def check_exception(exception, detects_end)
208
+ detector = Fluent::ExceptionDetector.new
209
+ after_exc = detects_end ? :end_trace : :inside_trace
210
+ before_second_exc = detects_end ? :inside_trace : :start_trace
211
+ check_multiline(detector, :no_trace, :no_trace, 'This is not an exception.')
212
+ check_multiline(detector, :inside_trace, after_exc, exception)
213
+ check_multiline(detector, :no_trace, :no_trace, 'This is not an exception.')
214
+ check_multiline(detector, :inside_trace, after_exc, exception)
215
+ check_multiline(detector, before_second_exc, after_exc, exception)
216
+ end
217
+
218
+ def test_java
219
+ check_exception(JAVA_EXC, false)
220
+ check_exception(COMPLEX_JAVA_EXC, false)
221
+ end
222
+
223
+ def test_js
224
+ check_exception(NODE_JS_EXC, false)
225
+ check_exception(CLIENT_JS_EXC, false)
226
+ check_exception(V8_JS_EXC, false)
227
+ end
228
+
229
+ def test_csharp
230
+ check_exception(CSHARP_EXC, false)
231
+ end
232
+
233
+ def test_python
234
+ check_exception(PYTHON_EXC, true)
235
+ end
236
+
237
+ def test_php
238
+ check_exception(PHP_EXC, false)
239
+ check_exception(PHP_ON_GAE_EXC, true)
240
+ end
241
+
242
+ def test_go
243
+ check_exception(GO_EXC, false)
244
+ end
245
+
246
+ def test_ruby
247
+ check_exception(RUBY_EXC, false)
248
+ end
249
+
250
+ def test_mixed_languages
251
+ check_exception(JAVA_EXC, false)
252
+ check_exception(PYTHON_EXC, true)
253
+ check_exception(COMPLEX_JAVA_EXC, false)
254
+ check_exception(NODE_JS_EXC, false)
255
+ check_exception(PHP_EXC, false)
256
+ check_exception(PHP_ON_GAE_EXC, true)
257
+ check_exception(CLIENT_JS_EXC, false)
258
+ check_exception(GO_EXC, false)
259
+ check_exception(CSHARP_EXC, false)
260
+ check_exception(V8_JS_EXC, false)
261
+ check_exception(RUBY_EXC, false)
262
+ end
263
+
264
+ def test_reset
265
+ detector = Fluent::ExceptionDetector.new
266
+
267
+ check_multiline(detector, :inside_trace, :inside_trace, JAVA_EXC_PART1)
268
+ check_multiline(detector, :inside_trace, :inside_trace, JAVA_EXC_PART2)
269
+
270
+ check_multiline(detector, :start_trace, :inside_trace, JAVA_EXC_PART1)
271
+ detector.reset
272
+ check_no_multiline(detector, JAVA_EXC_PART2)
273
+ end
274
+
275
+ def feed_lines(buffer, *messages)
276
+ messages.each do |m|
277
+ m.each_line do |line|
278
+ buffer.push(0, line)
279
+ end
280
+ buffer.flush
281
+ end
282
+ end
283
+
284
+ Struct.new('TestBufferScenario', :desc, :languages, :input, :expected)
285
+
286
+ def buffer_scenario(desc, languages, input, expected)
287
+ Struct::TestBufferScenario.new(desc, languages, input, expected)
288
+ end
289
+
290
+ def test_buffer
291
+ [
292
+ buffer_scenario('mixed languages',
293
+ [:all],
294
+ [JAVA_EXC, ARBITRARY_TEXT, PYTHON_EXC, GO_EXC],
295
+ [JAVA_EXC] + ARBITRARY_TEXT.lines + [PYTHON_EXC, GO_EXC]),
296
+ buffer_scenario('single language',
297
+ [:go],
298
+ [JAVA_EXC, ARBITRARY_TEXT, GO_EXC],
299
+ JAVA_EXC.lines + ARBITRARY_TEXT.lines + [GO_EXC]),
300
+ buffer_scenario('some exceptions from non-configured languages',
301
+ [:python],
302
+ [JAVA_EXC, PYTHON_EXC, GO_EXC],
303
+ JAVA_EXC.lines + [PYTHON_EXC] + GO_EXC.lines),
304
+ buffer_scenario('all exceptions from non-configured languages',
305
+ [:ruby],
306
+ [JAVA_EXC, PYTHON_EXC, GO_EXC],
307
+ JAVA_EXC.lines + PYTHON_EXC.lines + GO_EXC.lines)
308
+ ].each do |s|
309
+ out = []
310
+ buffer = Fluent::TraceAccumulator.new(nil,
311
+ s.languages) { |_, m| out << m }
312
+ feed_lines(buffer, *s.input)
313
+ assert_equal(s.expected, out, s.desc)
314
+ end
315
+ end
316
+
317
+ def feed_json(buffer, message_field, messages)
318
+ messages.each do |m|
319
+ m.each_line do |line|
320
+ buffer.push(0, message_field => line)
321
+ end
322
+ buffer.flush
323
+ end
324
+ end
325
+
326
+ def expected_json(message_field, messages)
327
+ messages.collect { |m| { message_field => [m].flatten.join } }
328
+ end
329
+
330
+ Struct.new('TestJsonScenario',
331
+ :desc, :configured_field, :actual_field, :input, :output)
332
+
333
+ def json_scenario(desc, configured_field, actual_field, input, output)
334
+ Struct::TestJsonScenario.new(desc, configured_field, actual_field,
335
+ input, output)
336
+ end
337
+
338
+ def test_json_messages
339
+ [
340
+ json_scenario('User-defined message field', 'mydata', 'mydata',
341
+ [PYTHON_EXC, ARBITRARY_TEXT, GO_EXC],
342
+ [PYTHON_EXC] + ARBITRARY_TEXT.lines + [GO_EXC]),
343
+ json_scenario('Default message field "message"', '', 'message',
344
+ [PYTHON_EXC, ARBITRARY_TEXT, GO_EXC],
345
+ [PYTHON_EXC] + ARBITRARY_TEXT.lines + [GO_EXC]),
346
+ json_scenario('Default message field "log"', '', 'log',
347
+ [PYTHON_EXC, ARBITRARY_TEXT, GO_EXC],
348
+ [PYTHON_EXC] + ARBITRARY_TEXT.lines + [GO_EXC]),
349
+ json_scenario('Wrongly defined message field', 'doesnotexist', 'mydata',
350
+ [PYTHON_EXC, ARBITRARY_TEXT, GO_EXC],
351
+ PYTHON_EXC.lines + ARBITRARY_TEXT.lines + GO_EXC.lines),
352
+ json_scenario('Undefined message field', '', 'mydata',
353
+ [PYTHON_EXC, ARBITRARY_TEXT, GO_EXC],
354
+ PYTHON_EXC.lines + ARBITRARY_TEXT.lines + GO_EXC.lines)
355
+ ].each do |s|
356
+ out = []
357
+ buffer = Fluent::TraceAccumulator.new(s.configured_field,
358
+ [:all]) { |_, m| out << m }
359
+ feed_json(buffer, s.actual_field, s.input)
360
+ assert_equal(expected_json(s.actual_field, s.output), out, s.desc)
361
+ end
362
+ end
363
+
364
+ def test_max_lines_limit
365
+ # Limit is equal to the first part of the exception and forces it to be
366
+ # flushed before the rest of the exception is processed.
367
+ max_lines = JAVA_EXC_PART1.lines.length
368
+ out = []
369
+ buffer = Fluent::TraceAccumulator.new(nil,
370
+ [:all],
371
+ max_lines: max_lines) do |_, m|
372
+ out << m
373
+ end
374
+ feed_lines(buffer, JAVA_EXC)
375
+ assert_equal([JAVA_EXC_PART1] + JAVA_EXC_PART2.lines, out)
376
+ end
377
+
378
+ def test_high_max_bytes_limit
379
+ # Limit is just too small to add one more line to the buffered first part of
380
+ # the exception.
381
+ max_bytes = JAVA_EXC_PART1.length + JAVA_EXC_PART2.lines[0].length - 1
382
+ out = []
383
+ buffer = Fluent::TraceAccumulator.new(nil,
384
+ [:all],
385
+ max_bytes: max_bytes) do |_, m|
386
+ out << m
387
+ end
388
+ feed_lines(buffer, JAVA_EXC)
389
+ # Check that the trace is flushed after the first part.
390
+ assert_equal([JAVA_EXC_PART1] + JAVA_EXC_PART2.lines, out)
391
+ end
392
+
393
+ def test_low_max_bytes_limit
394
+ # Limit is exceeded by the character that follows the buffered first part of
395
+ # the exception.
396
+ max_bytes = JAVA_EXC_PART1.length
397
+ out = []
398
+ buffer = Fluent::TraceAccumulator.new(nil,
399
+ [:all],
400
+ max_bytes: max_bytes) do |_, m|
401
+ out << m
402
+ end
403
+ feed_lines(buffer, JAVA_EXC)
404
+ # Check that the trace is flushed after the first part.
405
+ assert_equal([JAVA_EXC_PART1] + JAVA_EXC_PART2.lines, out)
406
+ end
407
+ end
@@ -0,0 +1,207 @@
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_relative '../helper'
16
+ require 'fluent/plugin/out_detect_exceptions'
17
+
18
+ class DetectExceptionsOutputTest < Test::Unit::TestCase
19
+ def setup
20
+ Fluent::Test.setup
21
+ end
22
+
23
+ CONFIG = <<END.freeze
24
+ remove_tag_prefix prefix
25
+ END
26
+
27
+ DEFAULT_TAG = 'prefix.test.tag'.freeze
28
+
29
+ ARBITRARY_TEXT = 'This line is not an exception.'.freeze
30
+
31
+ JAVA_EXC = <<END.freeze
32
+ Exception: foo
33
+ at bar
34
+ END
35
+
36
+ PYTHON_EXC = <<END.freeze
37
+ Traceback (most recent call last):
38
+ File "/base/data/home/runtimes/python27/python27_lib/versions/third_party/webapp2-2.5.2/webapp2.py", line 1535, in __call__
39
+ rv = self.handle_exception(request, response, e)
40
+ Exception: ('spam', 'eggs')
41
+ END
42
+
43
+ def create_driver(conf = CONFIG, tag = DEFAULT_TAG)
44
+ d = Fluent::Test::OutputTestDriver.new(Fluent::DetectExceptionsOutput, tag)
45
+ d.configure(conf)
46
+ d
47
+ end
48
+
49
+ def log_entry(message, count, stream)
50
+ log_entry = { 'message' => message, 'count' => count }
51
+ log_entry['stream'] = stream unless stream.nil?
52
+ log_entry
53
+ end
54
+
55
+ def feed_lines(driver, t, *messages, stream: nil)
56
+ count = 0
57
+ messages.each do |m|
58
+ m.each_line do |line|
59
+ driver.emit(log_entry(line, count, stream), t + count)
60
+ count += 1
61
+ end
62
+ end
63
+ end
64
+
65
+ def run_driver(driver, *messages)
66
+ t = Time.now.to_i
67
+ driver.run do
68
+ feed_lines(driver, t, *messages)
69
+ end
70
+ end
71
+
72
+ def make_logs(t, *messages, stream: nil)
73
+ count = 0
74
+ logs = []
75
+ messages.each do |m|
76
+ logs << [t + count, log_entry(m, count, stream)]
77
+ count += m.lines.count
78
+ end
79
+ logs
80
+ end
81
+
82
+ def test_configure
83
+ assert_nothing_raised do
84
+ create_driver
85
+ end
86
+ end
87
+
88
+ def test_exception_detection
89
+ d = create_driver
90
+ t = Time.now.to_i
91
+ messages = [ARBITRARY_TEXT, JAVA_EXC, ARBITRARY_TEXT]
92
+ d.run do
93
+ feed_lines(d, t, *messages)
94
+ end
95
+ assert_equal(make_logs(t, *messages), d.events)
96
+ end
97
+
98
+ def test_single_language_config
99
+ cfg = 'languages java'
100
+ d = create_driver(cfg)
101
+ t = Time.now.to_i
102
+ d.run do
103
+ feed_lines(d, t, ARBITRARY_TEXT, JAVA_EXC, PYTHON_EXC)
104
+ end
105
+ expected = ARBITRARY_TEXT.lines + [JAVA_EXC] + PYTHON_EXC.lines
106
+ assert_equal(make_logs(t, *expected), d.events)
107
+ end
108
+
109
+ def test_multi_language_config
110
+ cfg = 'languages python, java'
111
+ d = create_driver(cfg)
112
+ t = Time.now.to_i
113
+ d.run do
114
+ feed_lines(d, t, ARBITRARY_TEXT, JAVA_EXC, PYTHON_EXC)
115
+ end
116
+ expected = ARBITRARY_TEXT.lines + [JAVA_EXC] + [PYTHON_EXC]
117
+ assert_equal(make_logs(t, *expected), d.events)
118
+ end
119
+
120
+ def test_split_exception_after_timeout
121
+ cfg = 'multiline_flush_interval 1'
122
+ d = create_driver(cfg)
123
+ t1 = 0
124
+ t2 = 0
125
+ d.run do
126
+ t1 = Time.now.to_i
127
+ feed_lines(d, t1, JAVA_EXC)
128
+ sleep 2
129
+ t2 = Time.now.to_i
130
+ feed_lines(d, t2, " at x\n at y\n")
131
+ end
132
+ assert_equal(make_logs(t1, JAVA_EXC) +
133
+ make_logs(t2, " at x\n", " at y\n"),
134
+ d.events)
135
+ end
136
+
137
+ def test_do_not_split_exception_after_pause
138
+ d = create_driver
139
+ t1 = 0
140
+ t2 = 0
141
+ d.run do
142
+ t1 = Time.now.to_i
143
+ feed_lines(d, t1, JAVA_EXC)
144
+ sleep 1
145
+ t2 = Time.now.to_i
146
+ feed_lines(d, t2, " at x\n at y\n")
147
+ d.instance.before_shutdown
148
+ end
149
+ assert_equal(make_logs(t1, JAVA_EXC + " at x\n at y\n"), d.events)
150
+ end
151
+
152
+ def get_out_tags(remove_tag_prefix, original_tag)
153
+ cfg = "remove_tag_prefix #{remove_tag_prefix}"
154
+ d = create_driver(cfg, original_tag)
155
+ run_driver(d, ARBITRARY_TEXT, JAVA_EXC, ARBITRARY_TEXT)
156
+ d.emits.collect { |e| e[0] }.sort.uniq
157
+ end
158
+
159
+ def test_remove_tag_prefix
160
+ tags = get_out_tags('prefix.plus', 'prefix.plus.rest.of.the.tag')
161
+ assert_equal(['rest.of.the.tag'], tags)
162
+ tags = get_out_tags('prefix.pl', 'prefix.plus.rest.of.the.tag')
163
+ assert_equal(['prefix.plus.rest.of.the.tag'], tags)
164
+ tags = get_out_tags('does.not.occur', 'prefix.plus.rest.of.the.tag')
165
+ assert_equal(['prefix.plus.rest.of.the.tag'], tags)
166
+ end
167
+
168
+ def test_flush_after_max_lines
169
+ cfg = 'max_lines 2'
170
+ d = create_driver(cfg)
171
+ t = Time.now.to_i
172
+ d.run do
173
+ feed_lines(d, t, PYTHON_EXC, JAVA_EXC)
174
+ end
175
+ # Expected: the first two lines of the exception are buffered and combined.
176
+ # Then the max_lines setting kicks in and the rest of the Python exception
177
+ # is logged line-by-line (since it's not an exception stack in itself).
178
+ # Finally, the Java exception is logged in its entirety, since it only
179
+ # has two lines.
180
+ expected =
181
+ [PYTHON_EXC.lines[0..1].join] + PYTHON_EXC.lines[2..-1] + [JAVA_EXC]
182
+ assert_equal(make_logs(t, *expected), d.events)
183
+ end
184
+
185
+ def test_separate_streams
186
+ cfg = 'stream stream'
187
+ d = create_driver(cfg)
188
+ t = Time.now.to_i
189
+ d.run do
190
+ feed_lines(d, t, JAVA_EXC.lines[0], stream: 'java')
191
+ feed_lines(d, t, PYTHON_EXC.lines[0..1].join, stream: 'python')
192
+ feed_lines(d, t, JAVA_EXC.lines[1..-1].join, stream: 'java')
193
+ feed_lines(d, t, JAVA_EXC, stream: 'java')
194
+ feed_lines(d, t, PYTHON_EXC.lines[2..-1].join, stream: 'python')
195
+ feed_lines(d, t, 'something else', stream: 'java')
196
+ end
197
+ # Expected: the Python and the Java exceptions are handled separately
198
+ # because they belong to different streams.
199
+ # Note that the Java exception is only detected when 'something else'
200
+ # is processed.
201
+ expected = make_logs(t, JAVA_EXC, stream: 'java') +
202
+ make_logs(t, PYTHON_EXC, stream: 'python') +
203
+ make_logs(t, JAVA_EXC, stream: 'java') +
204
+ make_logs(t, 'something else', stream: 'java')
205
+ assert_equal(expected, d.events)
206
+ end
207
+ end