fluent-plugin-detect-exceptions 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CONTRIBUTING +24 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +60 -0
- data/LICENSE +201 -0
- data/README.rdoc +86 -0
- data/Rakefile +38 -0
- data/fluent-plugin-detect-exceptions.gemspec +28 -0
- data/lib/fluent/plugin/exception_detector.rb +287 -0
- data/lib/fluent/plugin/out_detect_exceptions.rb +131 -0
- data/test/helper.rb +46 -0
- data/test/plugin/bench_exception_detector.rb +73 -0
- data/test/plugin/test_exception_detector.rb +353 -0
- data/test/plugin/test_out_detect_exceptions.rb +173 -0
- metadata +127 -0
@@ -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
|