logstash-filter-java_stack_digest 0.1.0 → 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +3 -0
- data/README.md +30 -56
- data/lib/logstash/filters/java_stack_digest.rb +37 -20
- data/logstash-filter-java_stack_digest.gemspec +1 -1
- data/spec/filters/java_stack_digest_spec.rb +19 -0
- data/stack-digest.md +210 -0
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 600d4f38d95662574e1cfa1c0e3b743d1537de52b292ae3258411822383ba947
|
4
|
+
data.tar.gz: ee436c70b22307b5a3ceeee14057a28a7fac98d71327afe1097537cb105d3c03
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a34404d3ed44c6dc7deaf970fa7392600d01a09dcfb6543cb17da275896b4b5a41ad01fb87ef3a74330eed802ab1d1147d34f5f0c86403cda9c6b936654a4df4
|
7
|
+
data.tar.gz: 9ccbdfa5e831fc0f67c3edd2e0bc78bf18884e2e49988ee0de5ea08785bd0932a179db9fec15046849770f31b9c5d2c870cc2801cac4ad19f7fe5618c4088b3b
|
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
@@ -1,79 +1,53 @@
|
|
1
1
|
# Logstash Plugin
|
2
2
|
|
3
|
-
This is a plugin for [Logstash](https://github.com/elastic/logstash)
|
3
|
+
This is a filter plugin for [Logstash](https://github.com/elastic/logstash) that enables computing a *stable* digest
|
4
|
+
(MD5) from a Java stack trace.
|
4
5
|
|
5
6
|
It is fully free and fully open source. The license is Apache 2.0, meaning you are pretty much free to use it however you want in whatever way.
|
6
7
|
|
7
|
-
## Documentation
|
8
|
-
|
9
|
-
Logstash provides infrastructure to automatically generate documentation for this plugin. We use the asciidoc format to write documentation so any comments in the source code will be first converted into asciidoc and then into html. All plugin documentation are placed under one [central location](http://www.elastic.co/guide/en/logstash/current/).
|
10
|
-
|
11
|
-
- For formatting code or config example, you can use the asciidoc `[source,ruby]` directive
|
12
|
-
- For more asciidoc formatting tips, see the excellent reference here https://github.com/elastic/docs#asciidoc-guide
|
13
|
-
|
14
|
-
## Need Help?
|
15
|
-
|
16
|
-
Need help? Try #logstash on freenode IRC or the https://discuss.elastic.co/c/logstash discussion forum.
|
17
|
-
|
18
|
-
## Developing
|
19
|
-
|
20
|
-
### 1. Plugin Developement and Testing
|
21
8
|
|
22
|
-
|
23
|
-
- To get started, you'll need JRuby with the Bundler gem installed.
|
9
|
+
## Documentation
|
24
10
|
|
25
|
-
|
11
|
+
Goal and principles are described in the project [GitHub pages](https://pismy.github.io/logstash-filter-java_stack_digest/).
|
26
12
|
|
27
|
-
-
|
28
|
-
|
29
|
-
bundle install
|
30
|
-
```
|
13
|
+
You'll also find an [online debugger](https://pismy.github.io/logstash-filter-java_stack_digest/debugger.html) to help
|
14
|
+
you configure and debug your plugin options.
|
31
15
|
|
32
|
-
#### Test
|
33
16
|
|
34
|
-
|
17
|
+
## Install in Logstash
|
35
18
|
|
19
|
+
- Download the plugin from [RubyGems](https://rubygems.org/gems/logstash-filter-java_stack_digest).
|
36
20
|
```sh
|
37
|
-
|
21
|
+
gem install logstash-filter-java_stack_digest
|
38
22
|
```
|
39
|
-
|
40
|
-
- Run tests
|
41
|
-
|
23
|
+
- Install the plugin from the Logstash home
|
42
24
|
```sh
|
43
|
-
|
25
|
+
bin/logstash-plugin install logstash-filter-java_stack_digest.gem
|
44
26
|
```
|
27
|
+
- Configure the plugin in Logstash configuration
|
28
|
+
- Start Logstash and proceed to test the plugin
|
45
29
|
|
46
|
-
|
30
|
+
## Configuration
|
47
31
|
|
48
|
-
|
32
|
+
This plugin supports the following configuration options:
|
33
|
+
* `source` (type `string`): the name of the field containing the Java stack trace option (default value: `"stack_trace"`)
|
34
|
+
* `target` (type `string`): the name of the field to assign the computed stack trace digest (default value: `"stack_digest"`)
|
35
|
+
* `exclude_no_source` (type `boolean`): whether stack trace elements without source info (no filename or line number) should be excluded from the digest (default value: `true`)
|
36
|
+
* `includes` (type `array` of `string`): RegExp patterns determining whether stack trace elements should be **included** from digest (defaults to none - _exclusion patterns only_)
|
37
|
+
* `excludes` (type `array` of `string`): RegExp patterns determining whether stack trace elements should be **excluded** from digest (defaults to standard dynamic Java reflection and generated classes patterns)
|
49
38
|
|
50
|
-
|
39
|
+
Filter configuration example:
|
51
40
|
```ruby
|
52
|
-
|
41
|
+
filter {
|
42
|
+
java_stack_digest {
|
43
|
+
source => 'java_stack'
|
44
|
+
target => 'error_digest'
|
45
|
+
exclude_no_source => true
|
46
|
+
includes => ['^com\\.xyz\\.', '^java\\.']
|
47
|
+
excludes => ['\\$\\$FastClassByCGLIB\\$\\$', '\\$\\$EnhancerBySpringCGLIB\\$\\$']
|
48
|
+
}
|
49
|
+
}
|
53
50
|
```
|
54
|
-
- Install plugin
|
55
|
-
```sh
|
56
|
-
bin/logstash-plugin install --no-verify
|
57
|
-
```
|
58
|
-
- Run Logstash with your plugin
|
59
|
-
```sh
|
60
|
-
bin/logstash -e 'filter {awesome {}}'
|
61
|
-
```
|
62
|
-
At this point any modifications to the plugin code will be applied to this local Logstash setup. After modifying the plugin, simply rerun Logstash.
|
63
|
-
|
64
|
-
#### 2.2 Run in an installed Logstash
|
65
|
-
|
66
|
-
You can use the same **2.1** method to run your plugin in an installed Logstash by editing its `Gemfile` and pointing the `:path` to your local plugin development directory or you can build the gem and install it using:
|
67
|
-
|
68
|
-
- Build your plugin gem
|
69
|
-
```sh
|
70
|
-
gem build logstash-filter-awesome.gemspec
|
71
|
-
```
|
72
|
-
- Install the plugin from the Logstash home
|
73
|
-
```sh
|
74
|
-
bin/logstash-plugin install /your/local/plugin/logstash-filter-awesome.gem
|
75
|
-
```
|
76
|
-
- Start Logstash and proceed to test the plugin
|
77
51
|
|
78
52
|
## Contributing
|
79
53
|
|
@@ -17,9 +17,6 @@ class LogStash::Filters::JavaStackDigest < LogStash::Filters::Base
|
|
17
17
|
#
|
18
18
|
config_name "java_stack_digest"
|
19
19
|
|
20
|
-
# activates debug traces
|
21
|
-
config :debug, :validate => :boolean, :default => false
|
22
|
-
|
23
20
|
# The name of the input field supposed to contain the Java stack trace (default 'stack_trace').
|
24
21
|
config :source, :validate => :string, :default => "stack_trace"
|
25
22
|
|
@@ -29,9 +26,12 @@ class LogStash::Filters::JavaStackDigest < LogStash::Filters::Base
|
|
29
26
|
# Determines whether stack trace elements without source info (no filename or line number) should be excluded from the digest (default 'true')
|
30
27
|
config :exclude_no_source, :validate => :boolean, :default => true
|
31
28
|
|
29
|
+
# Array of regular expressions to include stack trace elements
|
30
|
+
# defaults: empty; if non-empty, matching will start with includes, then filter out excludes
|
31
|
+
config :includes, :validate => :array, :default => []
|
32
|
+
|
32
33
|
# Array of regular expressions to exclude stack trace elements
|
33
34
|
# defaults to standard dynamic classes and method invocations
|
34
|
-
# config :excludes, :validate => :array, :default => %w["\\$\\$FastClassByCGLIB\\$\\$", "\\$\\$EnhancerBySpringCGLIB\\$\\$", "^sun\\.reflect\\..*\\.invoke", "^com\\.sun\\.", "^sun\\.net\\.", "^java\\.lang\\.reflect\\.Method\\.invoke", "^net\\.sf\\.cglib\\.proxy\\.MethodProxy\\.invoke", "^java\\.util\\.concurrent\\.ThreadPoolExecutor\\.runWorker","^java\\.lang\\.Thread\\.run$" ]
|
35
35
|
config :excludes, :validate => :array, :default => [/\$\$FastClassByCGLIB\$\$/, /\$\$EnhancerBySpringCGLIB\$\$/, /^sun\.reflect\..*\.invoke/, /^com\.sun\./, /^sun\.net\./, /^java\.lang\.reflect\.Method\.invoke/, /^net\.sf\.cglib\.proxy\.MethodProxy\.invoke/, /^java\.util\.concurrent\.ThreadPoolExecutor\.runWorker/, /^java\.lang\.Thread\.run$/ ]
|
36
36
|
|
37
37
|
public
|
@@ -48,6 +48,9 @@ class LogStash::Filters::JavaStackDigest < LogStash::Filters::Base
|
|
48
48
|
# group 3: line number (optional)
|
49
49
|
@stack_element_pattern = /^\s+at\s+((?:[\w$]+\.){2,}[\w$]+)\((?:([^:]+)(?::(\d+))?)?\)/
|
50
50
|
|
51
|
+
# coerce includes to an array of Regexp
|
52
|
+
@includes = @includes.collect {|pattern| Regexp::new(pattern)}
|
53
|
+
|
51
54
|
# coerce excludes to an array of Regexp
|
52
55
|
@excludes = @excludes.collect {|pattern| Regexp::new(pattern)}
|
53
56
|
end # def register
|
@@ -59,35 +62,35 @@ class LogStash::Filters::JavaStackDigest < LogStash::Filters::Base
|
|
59
62
|
|
60
63
|
return if stack_trace.nil? || stack_trace.empty?
|
61
64
|
|
62
|
-
|
65
|
+
# compute digest add to the event
|
66
|
+
event.set(@target, compute_digest(stack_trace.split("\n")))
|
63
67
|
|
64
68
|
# filter_matched should go in the last line of our successful code
|
65
69
|
filter_matched(event)
|
66
70
|
end # def filter
|
67
71
|
|
68
72
|
# computes a Java stack trace digest
|
69
|
-
def compute_digest(stack_trace
|
73
|
+
def compute_digest(stack_trace)
|
74
|
+
md5 = Digest::MD5.new
|
70
75
|
|
71
76
|
# 1: extract error class from first line
|
72
|
-
|
73
|
-
|
74
|
-
|
77
|
+
cur_stack_trace_line = stack_trace.shift
|
78
|
+
error_class = @error_pattern.match(cur_stack_trace_line)
|
79
|
+
|
75
80
|
# digest: error classname
|
76
81
|
md5.update error_class[1]
|
77
82
|
|
78
83
|
# 2: read all stack trace elements until stack trace is empty or we hit the next error
|
79
84
|
ste_count = 0
|
80
85
|
while not stack_trace.empty?
|
81
|
-
|
86
|
+
cur_stack_trace_line = stack_trace.first
|
87
|
+
if cur_stack_trace_line.start_with?(' ') or cur_stack_trace_line.start_with?("\t")
|
82
88
|
# current line starts with a whitespace: is it a stack trace element ?
|
83
|
-
stack_element = @stack_element_pattern.match(
|
89
|
+
stack_element = @stack_element_pattern.match(cur_stack_trace_line)
|
84
90
|
if stack_element
|
85
91
|
# current line is a stack trace element
|
86
92
|
ste_count+=1
|
87
|
-
if is_excluded?(stack_element)
|
88
|
-
puts " (-) at #{stack_element[1]}(#{stack_element[2]}:#{stack_element[3]})" if @debug
|
89
|
-
else
|
90
|
-
puts " (+) at #{stack_element[1]}(#{stack_element[2]}:#{stack_element[3]})" if @debug
|
93
|
+
if not is_excluded?(stack_element)
|
91
94
|
# digest: STE classname and method
|
92
95
|
md5.update stack_element[1]
|
93
96
|
# digest: line number (if present)
|
@@ -97,29 +100,43 @@ class LogStash::Filters::JavaStackDigest < LogStash::Filters::Base
|
|
97
100
|
end
|
98
101
|
end
|
99
102
|
elsif(ste_count > 0)
|
100
|
-
# current line doesn't start with a whitespace and we've already read stack trace elements:
|
103
|
+
# current line doesn't start with a whitespace and we've already read stack trace elements: it looks like the next error in the stack
|
101
104
|
break
|
102
105
|
end
|
103
106
|
# move to next line
|
104
107
|
stack_trace.shift
|
105
108
|
end
|
106
109
|
|
110
|
+
|
107
111
|
# 3: if stack trace not empty, compute digest for next error
|
108
112
|
if not stack_trace.empty?
|
109
|
-
md5.update compute_digest(stack_trace
|
113
|
+
md5.update compute_digest(stack_trace)
|
110
114
|
end
|
111
115
|
|
112
116
|
return md5.hexdigest
|
113
117
|
end
|
114
118
|
|
119
|
+
# Determines whether the given stack trace element (Regexp match) should be excluded from digest computation
|
115
120
|
def is_excluded?(stack_element)
|
116
121
|
# 1: exclude elements without source info ?
|
117
|
-
if @exclude_no_source and (stack_element[
|
122
|
+
if @exclude_no_source and (stack_element[3].nil? or stack_element[3].empty?)
|
118
123
|
return true
|
119
124
|
end
|
120
|
-
|
125
|
+
|
126
|
+
# 2: Regex based inclusions
|
127
|
+
classname_and_method = stack_element[1]
|
128
|
+
if not @includes.empty?
|
129
|
+
match_idx = @includes.index do |pattern|
|
130
|
+
pattern.match(classname_and_method)
|
131
|
+
end
|
132
|
+
if match_idx.nil?
|
133
|
+
return true
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
# 3: Regex based exclusions
|
121
138
|
@excludes.each do |pattern|
|
122
|
-
if pattern.match(
|
139
|
+
if pattern.match(classname_and_method)
|
123
140
|
return true
|
124
141
|
end
|
125
142
|
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Gem::Specification.new do |s|
|
2
2
|
s.name = 'logstash-filter-java_stack_digest'
|
3
|
-
s.version = '0.1.
|
3
|
+
s.version = '0.1.1'
|
4
4
|
s.licenses = ['Apache-2.0']
|
5
5
|
s.summary = 'Logstash filter that computes a digest of Java stack traces.'
|
6
6
|
s.description = 'Logstash filter that computes a digest of Java stack traces.'
|
@@ -143,6 +143,25 @@ Caused by: java.net.SocketTimeoutException: Read timed out
|
|
143
143
|
end
|
144
144
|
end
|
145
145
|
|
146
|
+
# ---------------------------------------------------------------------
|
147
|
+
describe "Digest with inclusion and exclusion config" do
|
148
|
+
let(:config) do <<-CONFIG
|
149
|
+
filter {
|
150
|
+
java_stack_digest {
|
151
|
+
exclude_no_source => true
|
152
|
+
includes => ['^com\\.xyz\\.']
|
153
|
+
excludes => ['\\$\\$FastClassByCGLIB\\$\\$', '\\$\\$EnhancerBySpringCGLIB\\$\\$']
|
154
|
+
}
|
155
|
+
}
|
156
|
+
CONFIG
|
157
|
+
end
|
158
|
+
|
159
|
+
sample("stack_trace" => stack_trace) do
|
160
|
+
expect(subject).to include("stack_digest")
|
161
|
+
expect(subject.get('stack_digest')).to eq('fa54771601828e9a2964ed0651e8c09c')
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
146
165
|
# ---------------------------------------------------------------------
|
147
166
|
describe "Digest should be computed if stack trace present with non default config" do
|
148
167
|
let(:config) do <<-CONFIG
|
data/stack-digest.md
ADDED
@@ -0,0 +1,210 @@
|
|
1
|
+
# Details about stack digest
|
2
|
+
|
3
|
+
This page gives details about the **stack digest** feature (goal and implementation).
|
4
|
+
|
5
|
+
|
6
|
+
## Why generating stack digests?
|
7
|
+
|
8
|
+
A full Java stack trace is very helpful to analyse and debug a single error, but is not very handy to compare two
|
9
|
+
errors.
|
10
|
+
|
11
|
+
The idea is to turn a full Java stack trace into a **short** and **stable** digest, that could help matching several
|
12
|
+
distinct occurrences of the same type of error:
|
13
|
+
|
14
|
+
* **short** for easing elasticsearch indexing, and take advantage of it (we use a MD5 hash),
|
15
|
+
* **stable** is the tricky part, as the same type of error occurring twice may not generate exactly the same stack trace
|
16
|
+
(see below).
|
17
|
+
|
18
|
+
This done, it becomes easy with elasticsearch or any other logs centralization and indexation system to:
|
19
|
+
|
20
|
+
* **count** distinct type of errors that occur in your code over time,
|
21
|
+
* **count** occurrences and frequency of a given type of error,
|
22
|
+
* **detect** when a (new) type of error occurred for the first time (maybe linking this to a new version being deployed?).
|
23
|
+
|
24
|
+
The stack digest may also become a simple error id that you can link your bug tracker with...
|
25
|
+
|
26
|
+
|
27
|
+
## Stack digest stability challenge by examples
|
28
|
+
|
29
|
+
### Let's consider error stack 1
|
30
|
+
|
31
|
+
*(the stack trace presented here has been cut by half from useless lines)*
|
32
|
+
|
33
|
+
<pre>
|
34
|
+
<b>com.xyz.MyApp$MyClient$MyClientException</b>: <strike>An error occurred while getting Alice's things</strike><sup>(msg)</sup>
|
35
|
+
at <b>com.xyz.MyApp$MyClient.getTheThings(MyApp.java:26)</b>
|
36
|
+
at <b>com.xyz.MyApp$MyService.displayThings(MyApp.java:16)</b>
|
37
|
+
at <strike>com.xyz.MyApp$MyService$$FastClassByCGLIB$$e7645040.invoke()</strike><sup>(aop)</sup>
|
38
|
+
at <i>net.sf.cglib.proxy.MethodProxy.invoke()</i><sup>(aop)</sup>
|
39
|
+
at <i>org.springframework.aop.framework.Cglib2AopProxy$CglibMethodInvocation.invokeJoinpoint()</i><sup>(fwk)</sup>
|
40
|
+
at <i>org.springframework.aop.framework.ReflectiveMethodInvocation.proceed()</i><sup>(fwk)</sup>
|
41
|
+
at <i>org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint.proceed()</i><sup>(fwk)</sup>
|
42
|
+
at <i>sun.reflect.NativeMethodAccessorImpl.invoke0()</i><sup>(aop)</sup>
|
43
|
+
at <i>sun.reflect.NativeMethodAccessorImpl.invoke()</i><sup>(aop)</sup>
|
44
|
+
at <i>sun.reflect.DelegatingMethodAccessorImpl.invoke()</i><sup>(aop)</sup>
|
45
|
+
at <i>java.lang.reflect.Method.invoke()</i><sup>(aop)</sup>
|
46
|
+
at <i>org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethodWithGivenArgs()</i><sup>(fwk)</sup>
|
47
|
+
at <i>org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethod()</i><sup>(fwk)</sup>
|
48
|
+
at <i>org.springframework.aop.aspectj.AspectJAroundAdvice.invoke()</i><sup>(fwk)</sup>
|
49
|
+
at <i>org.springframework.aop.framework.ReflectiveMethodInvocation.proceed()</i><sup>(fwk)</sup>
|
50
|
+
at <i>org.springframework.aop.interceptor.AbstractTraceInterceptor.invoke()</i><sup>(fwk)</sup>
|
51
|
+
at <i>org.springframework.aop.framework.ReflectiveMethodInvocation.proceed()</i><sup>(fwk)</sup>
|
52
|
+
at <i>org.springframework.transaction.interceptor.TransactionInterceptor.invoke()</i><sup>(fwk)</sup>
|
53
|
+
at <i>org.springframework.aop.framework.ReflectiveMethodInvocation.proceed()</i><sup>(fwk)</sup>
|
54
|
+
at <i>org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke()</i><sup>(fwk)</sup>
|
55
|
+
at <i>org.springframework.aop.framework.ReflectiveMethodInvocation.proceed()</i><sup>(fwk)</sup>
|
56
|
+
at <i>org.springframework.aop.framework.Cglib2AopProxy$DynamicAdvisedInterceptor.intercept()</i><sup>(fwk)</sup>
|
57
|
+
at <strike>com.xyz.MyApp$MyService$$EnhancerBySpringCGLIB$$c673c675.displayThings(<generated>)</strike><sup>(aop)</sup>
|
58
|
+
at <strike>sun.reflect.GeneratedMethodAccessor647.invoke(Unknown Source)</strike><sup>(aop)</sup>
|
59
|
+
at <i>sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)</i><sup>(aop)</sup>
|
60
|
+
at <i>java.lang.reflect.Method.invoke(Method.java:498)</i><sup>(aop)</sup>
|
61
|
+
at <i>org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:205)</i><sup>(fwk)</sup>
|
62
|
+
at <i>org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:133)</i><sup>(fwk)</sup>
|
63
|
+
at <i>org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:116)</i><sup>(fwk)</sup>
|
64
|
+
at <i>org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:827)</i><sup>(fwk)</sup>
|
65
|
+
at <i>org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:738)</i><sup>(fwk)</sup>
|
66
|
+
at <i>org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:85)</i><sup>(fwk)</sup>
|
67
|
+
at <i>org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:963)</i><sup>(fwk)</sup>
|
68
|
+
at <i>org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:897)</i><sup>(fwk)</sup>
|
69
|
+
at <i>org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:970)</i><sup>(fwk)</sup>
|
70
|
+
at <i>org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:861)</i><sup>(fwk)</sup>
|
71
|
+
at <i>javax.servlet.http.HttpServlet.service(HttpServlet.java:624)</i><sup>(jee)</sup>
|
72
|
+
at <i>org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:846)</i><sup>(fwk)</sup>
|
73
|
+
at <i>javax.servlet.http.HttpServlet.service(HttpServlet.java:731)</i><sup>(jee)</sup>
|
74
|
+
at <i>org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:303)</i><sup>(jee)</sup>
|
75
|
+
at <i>org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)</i><sup>(jee)</sup>
|
76
|
+
at <i>org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)</i><sup>(jee)</sup>
|
77
|
+
at <i>org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241)</i><sup>(jee)</sup>
|
78
|
+
at <i>org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)</i><sup>(jee)</sup>
|
79
|
+
...
|
80
|
+
at <i>org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331)</i><sup>(fwk)</sup>
|
81
|
+
at <i>org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:214)</i><sup>(fwk)</sup>
|
82
|
+
at <i>org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:177)</i><sup>(fwk)</sup>
|
83
|
+
at <i>org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:346)</i><sup>(fwk)</sup>
|
84
|
+
at <i>org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:262)</i><sup>(fwk)</sup>
|
85
|
+
at <i>org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241)</i><sup>(jee)</sup>
|
86
|
+
at <i>org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)</i><sup>(jee)</sup>
|
87
|
+
...
|
88
|
+
at <i>org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:116)</i><sup>(jee)</sup>
|
89
|
+
at <i>org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:436)</i><sup>(jee)</sup>
|
90
|
+
at <i>org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1078)</i><sup>(jee)</sup>
|
91
|
+
at <i>org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:625)</i><sup>(jee)</sup>
|
92
|
+
at <i>org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:316)</i><sup>(jee)</sup>
|
93
|
+
at <i>java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)</i><sup>(jee)</sup>
|
94
|
+
at <i>java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)</i><sup>(jee)</sup>
|
95
|
+
at <i>org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)</i><sup>(jee)</sup>
|
96
|
+
at <i>java.lang.Thread.run(Thread.java:748)</i><sup>(jee)</sup>
|
97
|
+
...
|
98
|
+
Caused by: <b>com.xyz.MyApp$HttpStack$HttpError</b>: <strike>I/O error on GET http://dummy/user/alice/things</strike><sup>(msg)</sup>
|
99
|
+
at <b>com.xyz.MyApp$HttpStack.get(MyApp.java:40)</b>
|
100
|
+
at <b>com.xyz.MyApp$MyClient.getTheThings(MyApp.java:24)</b>
|
101
|
+
... 23 common frames omitted
|
102
|
+
Caused by: <b>java.net.SocketTimeoutException</b>: <strike>Read timed out</strike><sup>(msg)</sup>
|
103
|
+
at <b>com.xyz.MyApp$HttpStack.get(MyApp.java:38)</b>
|
104
|
+
... 24 common frames omitted
|
105
|
+
</pre>
|
106
|
+
|
107
|
+
---
|
108
|
+
|
109
|
+
<strike>Strike out elements</strike> may vary from one occurrence to the other:
|
110
|
+
|
111
|
+
* <strike>error messages</strike><sup>(msg)</sup> often contain stuff related to the very error occurrence context,
|
112
|
+
* <strike>AOP generated classes</strike><sup>(aop)</sup> may vary from one execution to another.
|
113
|
+
|
114
|
+
*Italic* elements are somewhat not stable, or at least useless (purely technical). Ex:
|
115
|
+
|
116
|
+
* <i>JEE container stuff</i><sup>(jee)</sup>: may change when you upgrade your JEE container version or add/remove/reorganize your servlet filters chain for instance,
|
117
|
+
* <i>Spring Framework</i><sup>(fwk)</sup> underlying stacks (MVC, security) for pretty much the same reason,
|
118
|
+
* <i>AOP and dynamic invocation</i><sup>(aop)</sup>: purely technical, and quite implementation-dependent.
|
119
|
+
|
120
|
+
Only **bolded elements** are supposed to be stable.
|
121
|
+
|
122
|
+
|
123
|
+
### Now let's consider error stack 2
|
124
|
+
|
125
|
+
*(shortened)*
|
126
|
+
|
127
|
+
<pre>
|
128
|
+
<b>com.xyz.MyApp$MyClient$MyClientException</b>: <strike>An error occurred while getting <b>Bob</b>'s things</strike><sup>(msg)</sup>
|
129
|
+
at <b>com.xyz.MyApp$MyClient.getTheThings(MyApp.java:26)</b>
|
130
|
+
at <b>com.xyz.MyApp$MyService.displayThings(MyApp.java:16)</b>
|
131
|
+
at <strike>com.xyz.MyApp$MyService$$FastClassByCGLIB$$<b>07e70d1e</b>.invoke()</strike><sup>(aop)</sup>
|
132
|
+
at <i>net.sf.cglib.proxy.MethodProxy.invoke()</i><sup>(aop)</sup>
|
133
|
+
at <i>org.springframework.aop.framework.Cglib2AopProxy$CglibMethodInvocation.invokeJoinpoint()</i><sup>(fwk)</sup>
|
134
|
+
at <i>org.springframework.aop.framework.ReflectiveMethodInvocation.proceed()</i><sup>(fwk)</sup>
|
135
|
+
at <i>org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint.proceed()</i><sup>(fwk)</sup>
|
136
|
+
at <i>sun.reflect.NativeMethodAccessorImpl.invoke0()</i><sup>(aop)</sup>
|
137
|
+
at <i>sun.reflect.NativeMethodAccessorImpl.invoke()</i><sup>(aop)</sup>
|
138
|
+
at <i>sun.reflect.DelegatingMethodAccessorImpl.invoke()</i><sup>(aop)</sup>
|
139
|
+
at <i>java.lang.reflect.Method.invoke()</i><sup>(aop)</sup>
|
140
|
+
at <i>org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethodWithGivenArgs()</i><sup>(fwk)</sup>
|
141
|
+
at <i>org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethod()</i><sup>(fwk)</sup>
|
142
|
+
at <i>org.springframework.aop.aspectj.AspectJAroundAdvice.invoke()</i><sup>(fwk)</sup>
|
143
|
+
at <i>org.springframework.aop.framework.ReflectiveMethodInvocation.proceed()</i><sup>(fwk)</sup>
|
144
|
+
at <i>org.springframework.aop.interceptor.AbstractTraceInterceptor.invoke()</i><sup>(fwk)</sup>
|
145
|
+
at <i>org.springframework.aop.framework.ReflectiveMethodInvocation.proceed()</i><sup>(fwk)</sup>
|
146
|
+
at <i>org.springframework.transaction.interceptor.TransactionInterceptor.invoke()</i><sup>(fwk)</sup>
|
147
|
+
at <i>org.springframework.aop.framework.ReflectiveMethodInvocation.proceed()</i><sup>(fwk)</sup>
|
148
|
+
at <i>org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke()</i><sup>(fwk)</sup>
|
149
|
+
at <i>org.springframework.aop.framework.ReflectiveMethodInvocation.proceed()</i><sup>(fwk)</sup>
|
150
|
+
at <i>org.springframework.aop.framework.Cglib2AopProxy$DynamicAdvisedInterceptor.intercept()</i><sup>(fwk)</sup>
|
151
|
+
at <strike>com.xyz.MyApp$MyService$$EnhancerBySpringCGLIB$$<b>e3f570b1</b>.displayThings(<generated>)</strike><sup>(aop)</sup>
|
152
|
+
at <strike>sun.reflect.<b>GeneratedMethodAccessor737</b>.invoke(Unknown Source)</strike><sup>(aop)</sup>
|
153
|
+
...
|
154
|
+
Caused by: <b>com.xyz.MyApp$HttpStack$HttpError</b>: <strike>I/O error on GET http://dummy/user/<b>bob</b>/things</strike><sup>(msg)</sup>
|
155
|
+
at <b>com.xyz.MyApp$HttpStack.get(MyApp.java:40)</b>
|
156
|
+
at <b>com.xyz.MyApp$MyClient.getTheThings(MyApp.java:24)</b>
|
157
|
+
... 23 common frames omitted
|
158
|
+
Caused by: <b>java.net.SocketTimeoutException</b>: <strike>Read timed out</strike><sup>(msg)</sup>
|
159
|
+
at <b>com.xyz.MyApp$HttpStack.get(MyApp.java:38)</b>
|
160
|
+
... 24 common frames omitted
|
161
|
+
</pre>
|
162
|
+
|
163
|
+
---
|
164
|
+
|
165
|
+
You may see in this example that most of the <strike>strike elements have slight <b>differences</b></strike> from error stack
|
166
|
+
1 (messages and generated classes names).
|
167
|
+
|
168
|
+
Nevertheless it is the same exact error (despite the context is different as it applies to another user), and the goal
|
169
|
+
here is to be able to count them as *two occurrences of the same error*.
|
170
|
+
|
171
|
+
### Now let's consider error stack 3
|
172
|
+
|
173
|
+
*(shortened)*
|
174
|
+
|
175
|
+
<pre>
|
176
|
+
<b>com.xyz.MyApp$MyClient$MyClientException</b>: <strike>An error occurred while getting Alice's things</strike><sup>(msg)</sup>
|
177
|
+
at <b>com.xyz.MyApp$MyClient.getTheThings(MyApp.java:26)</b>
|
178
|
+
at <b>com.xyz.MyApp$MyService.displayThings(MyApp.java:16)</b>
|
179
|
+
at <strike>com.xyz.MyApp$MyService$$FastClassByCGLIB$$e7645040.invoke()</strike><sup>(aop)</sup>
|
180
|
+
at <i>net.sf.cglib.proxy.MethodProxy.invoke()</i><sup>(aop)</sup>
|
181
|
+
at <i>org.springframework.aop.framework.Cglib2AopProxy$CglibMethodInvocation.invokeJoinpoint()</i><sup>(fwk)</sup>
|
182
|
+
at <i>org.springframework.aop.framework.ReflectiveMethodInvocation.proceed()</i><sup>(fwk)</sup>
|
183
|
+
at <i>org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint.proceed()</i><sup>(fwk)</sup>
|
184
|
+
at <i>sun.reflect.NativeMethodAccessorImpl.invoke0()</i><sup>(aop)</sup>
|
185
|
+
at <i>sun.reflect.NativeMethodAccessorImpl.invoke()</i><sup>(aop)</sup>
|
186
|
+
at <i>sun.reflect.DelegatingMethodAccessorImpl.invoke()</i><sup>(aop)</sup>
|
187
|
+
at <i>java.lang.reflect.Method.invoke()</i><sup>(aop)</sup>
|
188
|
+
...
|
189
|
+
Caused by: <b>com.xyz.MyApp$HttpStack$HttpError</b>: <strike>I/O error on GET http://dummy/user/alice/things</strike><sup>(msg)</sup>
|
190
|
+
at <b>com.xyz.MyApp$HttpStack.get(MyApp.java:40)</b>
|
191
|
+
at <b>com.xyz.MyApp$MyClient.getTheThings(MyApp.java:24)</b>
|
192
|
+
... 23 common frames omitted
|
193
|
+
Caused by: <b>javax.net.ssl.SSLException</b>: <strike>Connection has been shutdown: javax.net.ssl.SSLHandshakeException: Received fatal alert: certificate_unknown</strike><sup>(msg)</sup>
|
194
|
+
at <b>com.sun.net.ssl.internal.ssl.SSLSocketImpl.checkEOF(SSLSocketImpl.java:1172)</b>
|
195
|
+
... 24 common frames omitted
|
196
|
+
</pre>
|
197
|
+
|
198
|
+
---
|
199
|
+
|
200
|
+
Here, you can see that the first and second errors are the same as in error stack 1, but the root cause is different (`SSLException` instead of `SocketTimeoutException`).
|
201
|
+
|
202
|
+
So in that case we don't want the top error digest computed for error stack 3 to be the same as for error stack 1.
|
203
|
+
|
204
|
+
## Stack digest computation rules
|
205
|
+
|
206
|
+
As a conclusion, stack digest computation applies the following rules:
|
207
|
+
|
208
|
+
1. a stack digest shall **not compute with the error message**
|
209
|
+
2. a stack digest shall **compute with it's parent cause** (recurses)
|
210
|
+
3. in order to stabilize the stack digest (over time and space), it's recommended to **exclude non-stable elements**
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: logstash-filter-java_stack_digest
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Pierre Smeyers
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-
|
11
|
+
date: 2018-04-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
requirement: !ruby/object:Gem::Requirement
|
@@ -54,6 +54,7 @@ files:
|
|
54
54
|
- logstash-filter-java_stack_digest.gemspec
|
55
55
|
- spec/filters/java_stack_digest_spec.rb
|
56
56
|
- spec/spec_helper.rb
|
57
|
+
- stack-digest.md
|
57
58
|
homepage: https://github.com/pismy/logstash-filter-java_stack_digest
|
58
59
|
licenses:
|
59
60
|
- Apache-2.0
|