fluent-plugin-detect-exceptions-xiniaoyun 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,287 @@
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 'flexmock/test_unit'
16
+ require_relative '../helper'
17
+ require 'fluent/plugin/out_detect_exceptions'
18
+ require 'json'
19
+
20
+ class DetectExceptionsOutputTest < Test::Unit::TestCase
21
+ def setup
22
+ Fluent::Test.setup
23
+ end
24
+
25
+ CONFIG = <<END.freeze
26
+ remove_tag_prefix prefix
27
+ END
28
+
29
+ DEFAULT_TAG = 'prefix.test.tag'.freeze
30
+
31
+ ARBITRARY_TEXT = 'This line is not an exception.'.freeze
32
+
33
+ JAVA_EXC = <<END.freeze
34
+ SomeException: foo
35
+ at bar
36
+ Caused by: org.AnotherException
37
+ at bar2
38
+ at bar3
39
+ END
40
+
41
+ PHP_EXC = <<END.freeze
42
+ exception 'Exception' with message 'Custom exception' in /home/joe/work/test-php/test.php:5
43
+ Stack trace:
44
+ #0 /home/joe/work/test-php/test.php(9): func1()
45
+ #1 /home/joe/work/test-php/test.php(13): func2()
46
+ #2 {main}
47
+ END
48
+
49
+ PYTHON_EXC = <<END.freeze
50
+ Traceback (most recent call last):
51
+ File "/base/data/home/runtimes/python27/python27_lib/versions/third_party/webapp2-2.5.2/webapp2.py", line 1535, in __call__
52
+ rv = self.handle_exception(request, response, e)
53
+ Exception: ('spam', 'eggs')
54
+ END
55
+
56
+ RUBY_EXC = <<END.freeze
57
+ examble.rb:18:in `thrower': An error has occurred. (RuntimeError)
58
+ from examble.rb:14:in `caller'
59
+ from examble.rb:10:in `helper'
60
+ from examble.rb:6:in `writer'
61
+ from examble.rb:2:in `runner'
62
+ from examble.rb:21:in `<main>'
63
+ END
64
+
65
+ def create_driver(conf = CONFIG, tag = DEFAULT_TAG)
66
+ d = Fluent::Test::OutputTestDriver.new(Fluent::DetectExceptionsOutput, tag)
67
+ d.configure(conf)
68
+ d
69
+ end
70
+
71
+ def log_entry(message, count, stream)
72
+ log_entry = { 'message' => message, 'count' => count }
73
+ log_entry['stream'] = stream unless stream.nil?
74
+ log_entry
75
+ end
76
+
77
+ def feed_lines(driver, t, *messages, stream: nil)
78
+ count = 0
79
+ messages.each do |m|
80
+ m.each_line do |line|
81
+ driver.emit(log_entry(line, count, stream), t + count)
82
+ count += 1
83
+ end
84
+ end
85
+ end
86
+
87
+ def run_driver(driver, *messages)
88
+ t = Time.now.to_i
89
+ driver.run do
90
+ feed_lines(driver, t, *messages)
91
+ end
92
+ end
93
+
94
+ def make_logs(t, *messages, stream: nil)
95
+ count = 0
96
+ logs = []
97
+ messages.each do |m|
98
+ logs << [t + count, log_entry(m, count, stream)]
99
+ count += m.lines.count
100
+ end
101
+ logs
102
+ end
103
+
104
+ def test_configure
105
+ assert_nothing_raised do
106
+ create_driver
107
+ end
108
+ end
109
+
110
+ def test_exception_detection
111
+ d = create_driver
112
+ t = Time.now.to_i
113
+ messages = [ARBITRARY_TEXT, JAVA_EXC, ARBITRARY_TEXT]
114
+ d.run do
115
+ feed_lines(d, t, *messages)
116
+ end
117
+ assert_equal(make_logs(t, *messages), d.events)
118
+ end
119
+
120
+ def test_ignore_nested_exceptions
121
+ test_cases = {
122
+ 'php' => PHP_EXC,
123
+ 'python' => PYTHON_EXC,
124
+ 'ruby' => RUBY_EXC
125
+ }
126
+
127
+ test_cases.each do |language, exception|
128
+ cfg = "languages #{language}"
129
+ d = create_driver(cfg)
130
+ t = Time.now.to_i
131
+
132
+ # Convert exception to a single line to simplify the test case.
133
+ single_line_exception = exception.gsub("\n", '\\n')
134
+
135
+ # There is a nested exception within the body, we should ignore those!
136
+ json_line_with_exception = {
137
+ 'timestamp' => {
138
+ 'nanos' => 998_152_494,
139
+ 'seconds' => 1_496_420_064
140
+ },
141
+ 'message' => single_line_exception,
142
+ 'thread' => 139_658_267_147_048,
143
+ 'severity' => 'ERROR'
144
+ }.to_json + "\n"
145
+ json_line_without_exception = {
146
+ 'timestamp' => {
147
+ 'nanos' => 5_990_266,
148
+ 'seconds' => 1_496_420_065
149
+ },
150
+ 'message' => 'next line',
151
+ 'thread' => 139_658_267_147_048,
152
+ 'severity' => 'INFO'
153
+ }.to_json + "\n"
154
+
155
+ router_mock = flexmock('router')
156
+
157
+ # Validate that each line received is emitted separately as expected.
158
+ router_mock.should_receive(:emit)
159
+ .once.with(DEFAULT_TAG, Integer,
160
+ 'message' => json_line_with_exception,
161
+ 'count' => 0)
162
+
163
+ router_mock.should_receive(:emit)
164
+ .once.with(DEFAULT_TAG, Integer,
165
+ 'message' => json_line_without_exception,
166
+ 'count' => 1)
167
+
168
+ d.instance.router = router_mock
169
+
170
+ d.run do
171
+ feed_lines(d, t, json_line_with_exception + json_line_without_exception)
172
+ end
173
+ end
174
+ end
175
+
176
+ def test_single_language_config
177
+ cfg = 'languages java'
178
+ d = create_driver(cfg)
179
+ t = Time.now.to_i
180
+ d.run do
181
+ feed_lines(d, t, ARBITRARY_TEXT, JAVA_EXC, PYTHON_EXC)
182
+ end
183
+ expected = ARBITRARY_TEXT.lines + [JAVA_EXC] + PYTHON_EXC.lines
184
+ assert_equal(make_logs(t, *expected), d.events)
185
+ end
186
+
187
+ def test_multi_language_config
188
+ cfg = 'languages python, java'
189
+ d = create_driver(cfg)
190
+ t = Time.now.to_i
191
+ d.run do
192
+ feed_lines(d, t, ARBITRARY_TEXT, JAVA_EXC, PYTHON_EXC)
193
+ end
194
+ expected = ARBITRARY_TEXT.lines + [JAVA_EXC] + [PYTHON_EXC]
195
+ assert_equal(make_logs(t, *expected), d.events)
196
+ end
197
+
198
+ def test_split_exception_after_timeout
199
+ cfg = 'multiline_flush_interval 1'
200
+ d = create_driver(cfg)
201
+ t1 = 0
202
+ t2 = 0
203
+ d.run do
204
+ t1 = Time.now.to_i
205
+ feed_lines(d, t1, JAVA_EXC)
206
+ sleep 2
207
+ t2 = Time.now.to_i
208
+ feed_lines(d, t2, " at x\n at y\n")
209
+ end
210
+ assert_equal(make_logs(t1, JAVA_EXC) +
211
+ make_logs(t2, " at x\n", " at y\n"),
212
+ d.events)
213
+ end
214
+
215
+ def test_do_not_split_exception_after_pause
216
+ d = create_driver
217
+ t1 = 0
218
+ t2 = 0
219
+ d.run do
220
+ t1 = Time.now.to_i
221
+ feed_lines(d, t1, JAVA_EXC)
222
+ sleep 1
223
+ t2 = Time.now.to_i
224
+ feed_lines(d, t2, " at x\n at y\n")
225
+ d.instance.before_shutdown
226
+ end
227
+ assert_equal(make_logs(t1, JAVA_EXC + " at x\n at y\n"), d.events)
228
+ end
229
+
230
+ def get_out_tags(remove_tag_prefix, original_tag)
231
+ cfg = "remove_tag_prefix #{remove_tag_prefix}"
232
+ d = create_driver(cfg, original_tag)
233
+ run_driver(d, ARBITRARY_TEXT, JAVA_EXC, ARBITRARY_TEXT)
234
+ d.emits.collect { |e| e[0] }.sort.uniq
235
+ end
236
+
237
+ def test_remove_tag_prefix
238
+ tags = get_out_tags('prefix.plus', 'prefix.plus.rest.of.the.tag')
239
+ assert_equal(['rest.of.the.tag'], tags)
240
+ tags = get_out_tags('prefix.pl', 'prefix.plus.rest.of.the.tag')
241
+ assert_equal(['prefix.plus.rest.of.the.tag'], tags)
242
+ tags = get_out_tags('does.not.occur', 'prefix.plus.rest.of.the.tag')
243
+ assert_equal(['prefix.plus.rest.of.the.tag'], tags)
244
+ end
245
+
246
+ def test_flush_after_max_lines
247
+ cfg = 'max_lines 2'
248
+ d = create_driver(cfg)
249
+ t = Time.now.to_i
250
+ d.run do
251
+ feed_lines(d, t, PYTHON_EXC, JAVA_EXC)
252
+ end
253
+ # Expected: the first two lines of the exception are buffered and combined.
254
+ # Then the max_lines setting kicks in and the rest of the Python exception
255
+ # is logged line-by-line (since it's not an exception stack in itself).
256
+ # For the following Java stack trace, the two lines of the first exception
257
+ # are buffered and combined. So are the first two lines of the second
258
+ # exception. Then the rest is logged line-by-line.
259
+ expected = [PYTHON_EXC.lines[0..1].join] + PYTHON_EXC.lines[2..-1] + \
260
+ [JAVA_EXC.lines[0..1].join] + [JAVA_EXC.lines[2..3].join] + \
261
+ JAVA_EXC.lines[4..-1]
262
+ assert_equal(make_logs(t, *expected), d.events)
263
+ end
264
+
265
+ def test_separate_streams
266
+ cfg = 'stream stream'
267
+ d = create_driver(cfg)
268
+ t = Time.now.to_i
269
+ d.run do
270
+ feed_lines(d, t, JAVA_EXC.lines[0], stream: 'java')
271
+ feed_lines(d, t, PYTHON_EXC.lines[0..1].join, stream: 'python')
272
+ feed_lines(d, t, JAVA_EXC.lines[1..-1].join, stream: 'java')
273
+ feed_lines(d, t, JAVA_EXC, stream: 'java')
274
+ feed_lines(d, t, PYTHON_EXC.lines[2..-1].join, stream: 'python')
275
+ feed_lines(d, t, 'something else', stream: 'java')
276
+ end
277
+ # Expected: the Python and the Java exceptions are handled separately
278
+ # because they belong to different streams.
279
+ # Note that the Java exception is only detected when 'something else'
280
+ # is processed.
281
+ expected = make_logs(t, JAVA_EXC, stream: 'java') +
282
+ make_logs(t, PYTHON_EXC, stream: 'python') +
283
+ make_logs(t, JAVA_EXC, stream: 'java') +
284
+ make_logs(t, 'something else', stream: 'java')
285
+ assert_equal(expected, d.events)
286
+ end
287
+ end
metadata ADDED
@@ -0,0 +1,133 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: fluent-plugin-detect-exceptions-xiniaoyun
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Stackdriver Agents
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2019-01-24 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: fluentd
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0.10'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0.10'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.3'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.3'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rubocop
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '='
46
+ - !ruby/object:Gem::Version
47
+ version: 0.42.0
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '='
53
+ - !ruby/object:Gem::Version
54
+ version: 0.42.0
55
+ - !ruby/object:Gem::Dependency
56
+ name: test-unit
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '3.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '3.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: flexmock
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '2.0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '2.0'
83
+ description: |2
84
+ Fluentd output plugin which detects exception stack traces in a stream of
85
+ JSON log messages and combines all single-line messages that belong to the
86
+ same stack trace into one multi-line message.
87
+ This is an official Google Ruby gem.
88
+ email:
89
+ - stackdriver-agents@google.com
90
+ executables: []
91
+ extensions: []
92
+ extra_rdoc_files: []
93
+ files:
94
+ - CONTRIBUTING
95
+ - Gemfile
96
+ - LICENSE
97
+ - README.rdoc
98
+ - Rakefile
99
+ - fluent-plugin-detect-exceptions.gemspec
100
+ - lib/fluent/plugin/exception_detector.rb
101
+ - lib/fluent/plugin/out_detect_exceptions.rb
102
+ - test/helper.rb
103
+ - test/plugin/bench_exception_detector.rb
104
+ - test/plugin/test_exception_detector.rb
105
+ - test/plugin/test_out_detect_exceptions.rb
106
+ homepage: https://github.com/GoogleCloudPlatform/fluent-plugin-detect-exceptions
107
+ licenses:
108
+ - Apache-2.0
109
+ metadata: {}
110
+ post_install_message:
111
+ rdoc_options: []
112
+ require_paths:
113
+ - lib
114
+ required_ruby_version: !ruby/object:Gem::Requirement
115
+ requirements:
116
+ - - ">="
117
+ - !ruby/object:Gem::Version
118
+ version: '2.0'
119
+ required_rubygems_version: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - ">="
122
+ - !ruby/object:Gem::Version
123
+ version: '0'
124
+ requirements: []
125
+ rubygems_version: 3.0.1
126
+ signing_key:
127
+ specification_version: 4
128
+ summary: fluentd output plugin for combining stack traces as multi-line JSON logs
129
+ test_files:
130
+ - test/helper.rb
131
+ - test/plugin/bench_exception_detector.rb
132
+ - test/plugin/test_exception_detector.rb
133
+ - test/plugin/test_out_detect_exceptions.rb