fluent-plugin-detect-exceptions 0.0.1

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,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.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
32
+
33
+ PYTHON_EXC = <<END.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
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::ExceptionBuffer.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
+ %w(java python all).each do |detector_lang|
65
+ buffer = Fluent::ExceptionBuffer.new(nil, 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
@@ -0,0 +1,353 @@
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
+ PHP Fatal error: Uncaught exception 'Exception' with message 'message' in /base/data/home/apps/s~crash-example-php/1.388306779641080894/errors.php:60
111
+ Stack trace:
112
+ #0 [internal function]: ErrorEntryGenerator::{closure}()
113
+ #1 /base/data/home/apps/s~crash-example-php/1.388306779641080894/errors.php(20): call_user_func_array(Object(Closure), Array)
114
+ #2 /base/data/home/apps/s~crash-example-php/1.388306779641080894/index.php(36): ErrorEntry->__call('raise', Array)
115
+ #3 /base/data/home/apps/s~crash-example-php/1.388306779641080894/index.php(36): ErrorEntry->raise()
116
+ #4 {main}
117
+ thrown in /base/data/home/apps/s~crash-example-php/1.388306779641080894/errors.php on line 60
118
+ END
119
+
120
+ GO_EXC = <<END.freeze
121
+ panic: runtime error: index out of range
122
+
123
+ goroutine 12 [running]:
124
+ main88989.memoryAccessException()
125
+ crash_example_go.go:58 +0x12a
126
+ main88989.handler(0x2afb7042a408, 0xc01042f880, 0xc0104d3450)
127
+ crash_example_go.go:36 +0x7ec
128
+ net/http.HandlerFunc.ServeHTTP(0x13e5128, 0x2afb7042a408, 0xc01042f880, 0xc0104d3450)
129
+ go/src/net/http/server.go:1265 +0x56
130
+ net/http.(*ServeMux).ServeHTTP(0xc01045cab0, 0x2afb7042a408, 0xc01042f880, 0xc0104d3450)
131
+ go/src/net/http/server.go:1541 +0x1b4
132
+ appengine_internal.executeRequestSafely(0xc01042f880, 0xc0104d3450)
133
+ go/src/appengine_internal/api_prod.go:288 +0xb7
134
+ appengine_internal.(*server).HandleRequest(0x15819b0, 0xc010401560, 0xc0104c8180, 0xc010431380, 0x0, 0x0)
135
+ go/src/appengine_internal/api_prod.go:222 +0x102b
136
+ reflect.Value.call(0x1243fe0, 0x15819b0, 0x113, 0x12c8a20, 0x4, 0xc010485f78, 0x3, 0x3, 0x0, 0x0, ...)
137
+ /tmp/appengine/go/src/reflect/value.go:419 +0x10fd
138
+ reflect.Value.Call(0x1243fe0, 0x15819b0, 0x113, 0xc010485f78, 0x3, 0x3, 0x0, 0x0, 0x0)
139
+ /tmp/ap"
140
+ END
141
+
142
+ CSHARP_EXC = <<END.freeze
143
+ System.Collections.Generic.KeyNotFoundException: The given key was not present in the dictionary.
144
+ 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
145
+ 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
146
+ at File3.Consolidator_Class.Function4 (System.Text.StringBuilder param_4, System.Double[,,] array) [0x00013] in /usr/local/google/home/Csharp/another file.csharp:23
147
+ at File3.Consolidator_Class.Function3 (Int32 param_3) [0x0000f] in /usr/local/google/home/Csharp/another file.csharp:27
148
+ at File3.Consolidator_Class.Function3 (System.Text.StringBuilder param_3) [0x00007] in /usr/local/google/home/Csharp/another file.csharp:32
149
+ at File2.Processor.Function2 (System.Int32& param_2, System.Collections.Generic.Stack`1& numbers) [0x00003] in /usr/local/google/home/Csharp/File2.csharp:19
150
+ at File2.Processor.Random2 () [0x00037] in /usr/local/google/home/Csharp/File2.csharp:28
151
+ at File2.Processor.Function1 (Int32 param_1, System.Collections.Generic.Dictionary`2 map) [0x00007] in /usr/local/google/home/Csharp/File2.csharp:34
152
+ at Main.Welcome+<Main>c__AnonStorey0.<>m__0 () [0x00006] in /usr/local/google/home/Csharp/hello.csharp:48
153
+ at System.Threading.Thread.StartInternal () [0x00000] in <filename unknown>:0
154
+ END
155
+
156
+ RUBY_EXC = <<END.freeze
157
+ NoMethodError (undefined method `resursivewordload' for #<BooksController:0x007f8dd9a0c738>):
158
+ app/controllers/books_controller.rb:69:in `recursivewordload'
159
+ app/controllers/books_controller.rb:75:in `loadword'
160
+ app/controllers/books_controller.rb:79:in `loadline'
161
+ app/controllers/books_controller.rb:83:in `loadparagraph'
162
+ app/controllers/books_controller.rb:87:in `loadpage'
163
+ app/controllers/books_controller.rb:91:in `onload'
164
+ app/controllers/books_controller.rb:95:in `loadrecursive'
165
+ app/controllers/books_controller.rb:99:in `requestload'
166
+ app/controllers/books_controller.rb:118:in `generror'
167
+ config/error_reporting_logger.rb:62:in `tagged'
168
+ END
169
+
170
+ ARBITRARY_TEXT = <<END.freeze
171
+ This arbitrary text.
172
+ I am glad it contains no exception.
173
+ END
174
+
175
+ def check_multiline(detector, expected_first, expected_last, multiline)
176
+ lines = multiline.lines
177
+ lines.each_with_index do |line, index|
178
+ action = detector.update(line)
179
+ case index
180
+ when 0
181
+ assert_equal(expected_first, action,
182
+ "unexpected action on first line: #{line}")
183
+ when lines.length - 1
184
+ assert_equal(expected_last, action,
185
+ "unexpected action on last line: #{line}")
186
+ else
187
+ assert_equal(:inside_trace, action, "line not buffered: #{line}")
188
+ end
189
+ end
190
+ end
191
+
192
+ def check_no_multiline(detector, text)
193
+ text.lines.each do |line|
194
+ action = detector.update(line)
195
+ assert_equal(:no_trace, action, "unexpected action on line: #{line}")
196
+ end
197
+ end
198
+
199
+ def check_exception(exception, detects_end)
200
+ detector = Fluent::ExceptionDetector.new
201
+ after_exc = detects_end ? :end_trace : :inside_trace
202
+ before_second_exc = detects_end ? :inside_trace : :start_trace
203
+ check_multiline(detector, :no_trace, :no_trace, 'This is not an exception.')
204
+ check_multiline(detector, :inside_trace, after_exc, exception)
205
+ check_multiline(detector, :no_trace, :no_trace, 'This is not an exception.')
206
+ check_multiline(detector, :inside_trace, after_exc, exception)
207
+ check_multiline(detector, before_second_exc, after_exc, exception)
208
+ end
209
+
210
+ def test_java
211
+ check_exception(JAVA_EXC, false)
212
+ check_exception(COMPLEX_JAVA_EXC, false)
213
+ end
214
+
215
+ def test_js
216
+ check_exception(NODE_JS_EXC, false)
217
+ check_exception(CLIENT_JS_EXC, false)
218
+ check_exception(V8_JS_EXC, false)
219
+ end
220
+
221
+ def test_csharp
222
+ check_exception(CSHARP_EXC, false)
223
+ end
224
+
225
+ def test_python
226
+ check_exception(PYTHON_EXC, true)
227
+ end
228
+
229
+ def test_php
230
+ check_exception(PHP_EXC, true)
231
+ end
232
+
233
+ def test_go
234
+ check_exception(GO_EXC, false)
235
+ end
236
+
237
+ def test_ruby
238
+ check_exception(RUBY_EXC, false)
239
+ end
240
+
241
+ def test_mixed_languages
242
+ check_exception(JAVA_EXC, false)
243
+ check_exception(PYTHON_EXC, true)
244
+ check_exception(COMPLEX_JAVA_EXC, false)
245
+ check_exception(NODE_JS_EXC, false)
246
+ check_exception(PHP_EXC, true)
247
+ check_exception(CLIENT_JS_EXC, false)
248
+ check_exception(GO_EXC, false)
249
+ check_exception(CSHARP_EXC, false)
250
+ check_exception(V8_JS_EXC, false)
251
+ check_exception(RUBY_EXC, false)
252
+ end
253
+
254
+ def test_reset
255
+ detector = Fluent::ExceptionDetector.new
256
+
257
+ check_multiline(detector, :inside_trace, :inside_trace, JAVA_EXC_PART1)
258
+ check_multiline(detector, :inside_trace, :inside_trace, JAVA_EXC_PART2)
259
+
260
+ check_multiline(detector, :start_trace, :inside_trace, JAVA_EXC_PART1)
261
+ detector.reset
262
+ check_no_multiline(detector, JAVA_EXC_PART2)
263
+ end
264
+
265
+ def feed_lines(buffer, *messages)
266
+ messages.each do |m|
267
+ m.each_line do |line|
268
+ buffer.push(0, line)
269
+ end
270
+ buffer.flush
271
+ end
272
+ end
273
+
274
+ Struct.new('TestBufferScenario', :desc, :languages, :input, :expected)
275
+
276
+ def buffer_scenario(desc, languages, input, expected)
277
+ Struct::TestBufferScenario.new(desc, languages, input, expected)
278
+ end
279
+
280
+ def test_buffer
281
+ [
282
+ buffer_scenario('mixed languages',
283
+ [:all],
284
+ [JAVA_EXC, ARBITRARY_TEXT, PYTHON_EXC, GO_EXC],
285
+ [JAVA_EXC] + ARBITRARY_TEXT.lines + [PYTHON_EXC, GO_EXC]),
286
+ buffer_scenario('single language',
287
+ [:go],
288
+ [JAVA_EXC, ARBITRARY_TEXT, GO_EXC],
289
+ JAVA_EXC.lines + ARBITRARY_TEXT.lines + [GO_EXC]),
290
+ buffer_scenario('some exceptions from non-configured languages',
291
+ [:python],
292
+ [JAVA_EXC, PYTHON_EXC, GO_EXC],
293
+ JAVA_EXC.lines + [PYTHON_EXC] + GO_EXC.lines),
294
+ buffer_scenario('all exceptions from non-configured languages',
295
+ [:ruby],
296
+ [JAVA_EXC, PYTHON_EXC, GO_EXC],
297
+ JAVA_EXC.lines + PYTHON_EXC.lines + GO_EXC.lines)
298
+ ].each do |s|
299
+ out = []
300
+ buffer = Fluent::TraceAccumulator.new(nil,
301
+ s.languages) { |_, m| out << m }
302
+ feed_lines(buffer, *s.input)
303
+ assert_equal(s.expected, out, s.desc)
304
+ end
305
+ end
306
+
307
+ def feed_json(buffer, message_field, messages)
308
+ messages.each do |m|
309
+ m.each_line do |line|
310
+ buffer.push(0, message_field => line)
311
+ end
312
+ buffer.flush
313
+ end
314
+ end
315
+
316
+ def expected_json(message_field, messages)
317
+ messages.collect { |m| { message_field => [m].flatten.join } }
318
+ end
319
+
320
+ Struct.new('TestJsonScenario',
321
+ :desc, :configured_field, :actual_field, :input, :output)
322
+
323
+ def json_scenario(desc, configured_field, actual_field, input, output)
324
+ Struct::TestJsonScenario.new(desc, configured_field, actual_field,
325
+ input, output)
326
+ end
327
+
328
+ def test_json_messages
329
+ [
330
+ json_scenario('User-defined message field', 'mydata', 'mydata',
331
+ [PYTHON_EXC, ARBITRARY_TEXT, GO_EXC],
332
+ [PYTHON_EXC] + ARBITRARY_TEXT.lines + [GO_EXC]),
333
+ json_scenario('Default message field "message"', '', 'message',
334
+ [PYTHON_EXC, ARBITRARY_TEXT, GO_EXC],
335
+ [PYTHON_EXC] + ARBITRARY_TEXT.lines + [GO_EXC]),
336
+ json_scenario('Default message field "log"', '', 'log',
337
+ [PYTHON_EXC, ARBITRARY_TEXT, GO_EXC],
338
+ [PYTHON_EXC] + ARBITRARY_TEXT.lines + [GO_EXC]),
339
+ json_scenario('Wrongly defined message field', 'doesnotexist', 'mydata',
340
+ [PYTHON_EXC, ARBITRARY_TEXT, GO_EXC],
341
+ PYTHON_EXC.lines + ARBITRARY_TEXT.lines + GO_EXC.lines),
342
+ json_scenario('Undefined message field', '', 'mydata',
343
+ [PYTHON_EXC, ARBITRARY_TEXT, GO_EXC],
344
+ PYTHON_EXC.lines + ARBITRARY_TEXT.lines + GO_EXC.lines)
345
+ ].each do |s|
346
+ out = []
347
+ buffer = Fluent::TraceAccumulator.new(s.configured_field,
348
+ [:all]) { |_, m| out << m }
349
+ feed_json(buffer, s.actual_field, s.input)
350
+ assert_equal(expected_json(s.actual_field, s.output), out, s.desc)
351
+ end
352
+ end
353
+ end
@@ -0,0 +1,173 @@
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 feed_lines(driver, t, *messages)
50
+ count = 0
51
+ messages.each do |m|
52
+ m.each_line do |line|
53
+ driver.emit({ 'message' => line, 'count' => count }, t + count)
54
+ count += 1
55
+ end
56
+ end
57
+ end
58
+
59
+ def run_driver(driver, *messages)
60
+ t = Time.now.to_i
61
+ driver.run do
62
+ feed_lines(driver, t, *messages)
63
+ end
64
+ end
65
+
66
+ def make_logs(t, *messages)
67
+ count = 0
68
+ logs = []
69
+ messages.each do |m|
70
+ logs << [t + count, { 'message' => m, 'count' => count }]
71
+ count += m.lines.count
72
+ end
73
+ logs
74
+ end
75
+
76
+ def test_configure
77
+ assert_nothing_raised do
78
+ create_driver
79
+ end
80
+ end
81
+
82
+ def test_exception_detection
83
+ d = create_driver
84
+ t = Time.now.to_i
85
+ messages = [ARBITRARY_TEXT, JAVA_EXC, ARBITRARY_TEXT]
86
+ d.run do
87
+ feed_lines(d, t, *messages)
88
+ end
89
+ assert_equal(make_logs(t, *messages), d.events)
90
+ end
91
+
92
+ def test_single_language_config
93
+ cfg = 'languages java'
94
+ d = create_driver(cfg)
95
+ t = Time.now.to_i
96
+ d.run do
97
+ feed_lines(d, t, ARBITRARY_TEXT, JAVA_EXC, PYTHON_EXC)
98
+ end
99
+ expected = ARBITRARY_TEXT.lines + [JAVA_EXC] + PYTHON_EXC.lines
100
+ assert_equal(make_logs(t, *expected), d.events)
101
+ end
102
+
103
+ def test_multi_language_config
104
+ cfg = 'languages python, java'
105
+ d = create_driver(cfg)
106
+ t = Time.now.to_i
107
+ d.run do
108
+ feed_lines(d, t, ARBITRARY_TEXT, JAVA_EXC, PYTHON_EXC)
109
+ end
110
+ expected = ARBITRARY_TEXT.lines + [JAVA_EXC] + [PYTHON_EXC]
111
+ assert_equal(make_logs(t, *expected), d.events)
112
+ end
113
+
114
+ def test_split_exception_after_timeout
115
+ cfg = 'multiline_flush_interval 1'
116
+ d = create_driver(cfg)
117
+ t1 = 0
118
+ t2 = 0
119
+ d.run do
120
+ t1 = Time.now.to_i
121
+ feed_lines(d, t1, JAVA_EXC)
122
+ sleep 2
123
+ t2 = Time.now.to_i
124
+ feed_lines(d, t2, " at x\n at y\n")
125
+ end
126
+ assert_equal(make_logs(t1, JAVA_EXC) +
127
+ make_logs(t2, " at x\n", " at y\n"),
128
+ d.events)
129
+ end
130
+
131
+ def test_do_not_split_exception_after_pause
132
+ d = create_driver
133
+ t1 = 0
134
+ t2 = 0
135
+ d.run do
136
+ t1 = Time.now.to_i
137
+ feed_lines(d, t1, JAVA_EXC)
138
+ sleep 1
139
+ t2 = Time.now.to_i
140
+ feed_lines(d, t2, " at x\n at y\n")
141
+ d.instance.before_shutdown
142
+ end
143
+ assert_equal(make_logs(t1, JAVA_EXC + " at x\n at y\n"), d.events)
144
+ end
145
+
146
+ def get_out_tags(remove_tag_prefix, original_tag)
147
+ cfg = "remove_tag_prefix #{remove_tag_prefix}"
148
+ d = create_driver(cfg, original_tag)
149
+ run_driver(d, ARBITRARY_TEXT, JAVA_EXC, ARBITRARY_TEXT)
150
+ d.emits.collect { |e| e[0] }.sort.uniq
151
+ end
152
+
153
+ def test_remove_tag_prefix
154
+ tags = get_out_tags('prefix.plus', 'prefix.plus.rest.of.the.tag')
155
+ assert_equal(['rest.of.the.tag'], tags)
156
+ tags = get_out_tags('prefix.pl', 'prefix.plus.rest.of.the.tag')
157
+ assert_equal(['prefix.plus.rest.of.the.tag'], tags)
158
+ tags = get_out_tags('does.not.occur', 'prefix.plus.rest.of.the.tag')
159
+ assert_equal(['prefix.plus.rest.of.the.tag'], tags)
160
+ end
161
+
162
+ def test_flush_after_max_lines
163
+ cfg = 'max_lines 2'
164
+ d = create_driver(cfg)
165
+ t = Time.now.to_i
166
+ d.run do
167
+ feed_lines(d, t, PYTHON_EXC, JAVA_EXC)
168
+ end
169
+ expected =
170
+ [PYTHON_EXC.lines[0..2].join] + PYTHON_EXC.lines[3..-1] + [JAVA_EXC]
171
+ assert_equal(make_logs(t, *expected), d.events)
172
+ end
173
+ end