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

Sign up to get free protection for your applications and to get access to all the features.
@@ -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