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