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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a0dd81d1918a7eb930634c65283b9716c753c3721f87659aff1ff471ed40f6db
4
- data.tar.gz: 174b1436664b23d69003d2ad96423e9162b60e62e16ede20173ae2f996c98732
3
+ metadata.gz: 600d4f38d95662574e1cfa1c0e3b743d1537de52b292ae3258411822383ba947
4
+ data.tar.gz: ee436c70b22307b5a3ceeee14057a28a7fac98d71327afe1097537cb105d3c03
5
5
  SHA512:
6
- metadata.gz: 0d95b2391c05acea7d7da3fad911930d9ef2d69cb95f61d6d643685b15c7a1b946c8c89ef4a509efc291d954d3f9e16dcc018a6840e151dd8c6967b2df7bd884
7
- data.tar.gz: 412c45a60b44075a197c714bcbe3ec2b18ae91569ec98393d1e44a4afc467fee7ea81d3e2068bb208290fc4eb714f07e453705738adffaecbfd3e4e2ac122747
6
+ metadata.gz: a34404d3ed44c6dc7deaf970fa7392600d01a09dcfb6543cb17da275896b4b5a41ad01fb87ef3a74330eed802ab1d1147d34f5f0c86403cda9c6b936654a4df4
7
+ data.tar.gz: 9ccbdfa5e831fc0f67c3edd2e0bc78bf18884e2e49988ee0de5ea08785bd0932a179db9fec15046849770f31b9c5d2c870cc2801cac4ad19f7fe5618c4088b3b
@@ -1,2 +1,5 @@
1
1
  ## 0.1.0
2
2
  - Plugin created with the logstash plugin generator
3
+
4
+ ## 0.1.1
5
+ - added `includes` patterns
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
- #### Code
23
- - To get started, you'll need JRuby with the Bundler gem installed.
9
+ ## Documentation
24
10
 
25
- - Create a new plugin or clone and existing from the GitHub [logstash-plugins](https://github.com/logstash-plugins) organization. We also provide [example plugins](https://github.com/logstash-plugins?query=example).
11
+ Goal and principles are described in the project [GitHub pages](https://pismy.github.io/logstash-filter-java_stack_digest/).
26
12
 
27
- - Install dependencies
28
- ```sh
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
- - Update your dependencies
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
- bundle install
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
- bundle exec rspec
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
- ### 2. Running your unpublished Plugin in Logstash
30
+ ## Configuration
47
31
 
48
- #### 2.1 Run in a local Logstash clone
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
- - Edit Logstash `Gemfile` and add the local plugin path, for example:
39
+ Filter configuration example:
51
40
  ```ruby
52
- gem "logstash-filter-awesome", :path => "/your/local/logstash-filter-awesome"
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
- event.set(@target, compute_digest(stack_trace.split("\n"), 0))
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, level)
73
+ def compute_digest(stack_trace)
74
+ md5 = Digest::MD5.new
70
75
 
71
76
  # 1: extract error class from first line
72
- error_class = @error_pattern.match(stack_trace.shift)
73
- puts "Error (#{level}) #{error_class}:" if @debug
74
- md5 = Digest::MD5.new
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
- if stack_trace.first.start_with?(' ') or stack_trace.first.start_with?("\t")
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(stack_trace.first)
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: is it the next error in the stack
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, level+1)
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[2].nil? or stack_element[2].empty?) and (stack_element[3].nil? or stack_element[3].empty?)
122
+ if @exclude_no_source and (stack_element[3].nil? or stack_element[3].empty?)
118
123
  return true
119
124
  end
120
- # 2: Regex based exclusion
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(stack_element[1])
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.0'
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
@@ -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(&lt;generated&gt;)</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(&lt;generated&gt;)</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.0
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-03-30 00:00:00.000000000 Z
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