jira_exception_collector 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,19 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+
19
+ config.ru
data/.travis.yml ADDED
@@ -0,0 +1,7 @@
1
+ rvm:
2
+ - 1.8.7
3
+ - 1.9.2
4
+ - 1.9.3
5
+ - jruby
6
+ - ruby-head
7
+ - ree
data/Gemfile ADDED
@@ -0,0 +1,18 @@
1
+ source 'http://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in jira_notifier.gemspec
4
+ gemspec
5
+
6
+ group :test do
7
+ gem 'rake'
8
+ gem 'test-unit'
9
+ gem 'fakeweb'
10
+ gem 'bundler', '>= 1.0.0'
11
+ gem 'rack'
12
+ if RUBY_VERSION =~ /^1\.9/
13
+ gem 'ruby-debug19'
14
+ else
15
+ gem 'ruby-debug'
16
+ end
17
+ end
18
+
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ Copyright (c) 2011 Rich Manalang
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
+
data/README.md ADDED
@@ -0,0 +1,66 @@
1
+ # JIRA Exception Collector
2
+
3
+ A gem that logs your exceptions as a JIRA issue. Once it's in JIRA, you can route it to
4
+ the appropriate developer to fix the issue using JIRA's workflow engine.
5
+
6
+ ![Moneyshot](https://img.skitch.com/20111130-b3f5rj9q95wh9txjrygyeaexn4.png)
7
+
8
+ ## Installation
9
+
10
+ JIRA Exception Collector is a gem. To install:
11
+
12
+ gem install jira-exception-collector
13
+
14
+ ... or, add this to your Gemfile:
15
+
16
+ gem jira-exception-collector
17
+
18
+ ... then:
19
+
20
+ bundle install
21
+
22
+
23
+ ## Usage
24
+
25
+ This gem utilizes the [JIRA Issue Collector plugin](https://plugins.atlassian.com/583856).
26
+ Make sure your JIRA instance has this plugin installed before using this gem.
27
+
28
+ ## Setup an Issue Collector in JIRA
29
+
30
+ In JIRA, go to Administration > Issue Collectors. You'll need to be a JIRA administrator
31
+ to do this. Add a new Issue Collector pointing to the project you want the exceptions to
32
+ go to. Also, if you don't already have an Issue Type for exceptions, you might want to
33
+ create one so that you have a better way of organizing the issues in your project.
34
+
35
+ Once you create the Issue Collector, copy the Collector URL (found on the Issue Collector's
36
+ page).
37
+
38
+ ## Configure your Rack app with the Collector URL
39
+
40
+ This gem works with any Rack based app. To configure it, first crack open your config.ru
41
+ and add this before the `run` statement:
42
+
43
+ ````ruby
44
+ use Rack::JiraExceptionCollector, "[collector url]"
45
+ ````
46
+
47
+ By default, JIRA Exception Collector is enabled under production and staging environments.
48
+ To modify this, just supply an array of the environments you want exceptions to be logged:
49
+
50
+ ````ruby
51
+ use Rack::JiraExceptionCollector, "[collector url]", %w(prod1 prod2 stage deploy)
52
+ ````
53
+
54
+ ## Compatibility with JIRA
55
+
56
+ Because this gem relies on the [JIRA Issue Collector plugin](https://plugins.atlassian.com/583856),
57
+ this gem is only compatible with the JIRA versions that the JIRA Issue Collector supports.
58
+
59
+ ## Contributing
60
+
61
+ This is a pushmepullyou type of project. Fork it hard, push in your awesome sauce, then
62
+ send in your pull request. Oh, don't forget to add tests.
63
+
64
+ ## Copyright
65
+
66
+ Copyright (c) Rich Manalang and Atlassian, Inc. See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
3
+ require "rake/testtask"
4
+
5
+ Rake::TestTask.new do |t|
6
+ t.libs << "test"
7
+ t.test_files = FileList['test/test_*.rb']
8
+ t.verbose = true
9
+ end
10
+
11
+ task :default => :test
@@ -0,0 +1,23 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/rack/jira_exception_collector_version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.name = "jira_exception_collector"
6
+ gem.authors = ["Rich Manalang"]
7
+ gem.email = ["rmanalang@atlassian.com"]
8
+ gem.description = %q{A basic exception logger that logs to a JIRA instance}
9
+ gem.summary = %q{jira_notifier will log your exceptions to a JIRA instance}
10
+ gem.homepage = "https://github.com/manalang/jira_exception_collector"
11
+
12
+ gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
13
+ gem.files = `git ls-files`.split("\n")
14
+ gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
15
+ gem.require_paths = ["lib"]
16
+ gem.version = Rack::JiraExceptionCollector::VERSION
17
+ gem.add_dependency 'rack'
18
+ gem.extra_rdoc_files = ['README.md','LICENSE']
19
+
20
+ gem.add_development_dependency 'rake'
21
+ gem.add_development_dependency 'test-unit'
22
+ gem.add_development_dependency 'fakeweb'
23
+ end
@@ -0,0 +1,53 @@
1
+ {code}
2
+ Server Environment:
3
+ ===================
4
+
5
+ Project root: <%=project_root %>
6
+ Environment name: <%=framework_env %>
7
+
8
+
9
+ Error:
10
+ ======
11
+
12
+ Class: <%=error.class.name %>
13
+ <%- if error.respond_to?(:message) -%>
14
+ Message: <%=error.message %>
15
+ <%- end -%>
16
+
17
+ <%- if backtrace.respond_to?(:each) -%>
18
+ <%- backtrace.each do |line| -%>
19
+ <%=line.file %> [<%=line.method %>] (<%=line.number %>)
20
+ <%- end -%>
21
+ <%- end -%>
22
+
23
+ Request:
24
+ ========
25
+
26
+ URL: <%=url %>
27
+
28
+ <%- if params && params.any? -%>
29
+ Request Parameters:
30
+ -------------------
31
+
32
+ <%- params.each do |key, value| -%>
33
+ <%=key %>: <%=value %>
34
+ <%- end -%>
35
+
36
+ <%- end -%>
37
+ <%- if session && session.any? -%>
38
+ Session:
39
+ ========
40
+
41
+ <%- session.each do |key, value| -%>
42
+ <%=key %>: <%=value %>
43
+ <%- end -%>
44
+
45
+ <%- end -%>
46
+ <%- if environment && environment.any? -%>
47
+ Environment:
48
+ ============
49
+ <%- environment.each do |key,value| -%>
50
+ <%=key %>: <%=value %>
51
+ <%- end -%>
52
+ <%- end -%>
53
+ {code}
@@ -0,0 +1,195 @@
1
+ require 'rack/jira_exception_collector_version'
2
+ require 'net/http'
3
+ require 'net/https'
4
+ require 'uri'
5
+ require 'erb'
6
+ require 'ostruct'
7
+
8
+ module Rack
9
+ class JiraExceptionCollector
10
+
11
+ FILTER_REPLACEMENT = "[FILTERED]"
12
+
13
+ class Error < StandardError; end
14
+
15
+ def initialize(app, collector_url = nil, report_under = %w(staging production),
16
+ rack_environment = 'RACK_ENV')
17
+ @app = app
18
+ @collector_url = collector_url
19
+ @report_under = report_under
20
+ @rack_environment = rack_environment
21
+ @filters = %w(AWS_ACCESS_KEY AWS_SECRET_ACCESS_KEY AWS_ACCOUNT SSH_AUTH_SOCK)
22
+ @failsafe = $stderr
23
+ yield self if block_given?
24
+ end
25
+
26
+ def call(env)
27
+ status, headers, body =
28
+ begin
29
+ @app.call(env)
30
+ rescue StandardError, LoadError, SyntaxError => boom
31
+ notified = send_exception boom, env
32
+ raise
33
+ end
34
+ send_exception env['rack.exception'], env if env['rack.exception']
35
+ [status, headers, body]
36
+ end
37
+
38
+ def environment_filter_keys
39
+ @filters.flatten
40
+ end
41
+
42
+ def environment_filter_regexps
43
+ environment_filter_keys.map do |key|
44
+ "^#{Regexp.escape(wrapped_key_for(key))}$"
45
+ end
46
+ end
47
+ private
48
+ def report?
49
+ @report_under.include?(rack_env) || rack_env == "test"
50
+ end
51
+
52
+ def send_exception(exception, env)
53
+ return true unless report?
54
+ request = Rack::Request.new(env)
55
+ env['rack.session'] = {:a => 1}
56
+
57
+ options = {
58
+ :url => env['REQUEST_URI'],
59
+ :params => request.params,
60
+ :framework_env => rack_env,
61
+ :notifier_name => 'Rack::JiraExceptionCollector',
62
+ :notifier_version => VERSION,
63
+ :environment => environment_data_for(env),
64
+ :session => env['rack.session']
65
+ }
66
+
67
+ if result = post_to_jira(exception, options)
68
+ if result.code == "200"
69
+ env['jira.notified'] = true
70
+ else
71
+ raise Error, "Status: #{result.code} #{result.body.inspect}"
72
+ end
73
+ else
74
+ raise Error, "No response from JIRA"
75
+ end
76
+ rescue Exception => e
77
+ return unless @failsafe
78
+ @failsafe.puts "Fail safe error caught: #{e.class}: #{e.message}"
79
+ @failsafe.puts e.backtrace
80
+ @failsafe.puts "Exception is #{exception.class}: #{exception.message}"
81
+ @failsafe.puts exception.backtrace
82
+ false
83
+ end
84
+
85
+ def rack_env
86
+ ENV[@rack_environment] || 'development'
87
+ end
88
+
89
+ def document_defaults(error)
90
+ {
91
+ :error => error,
92
+ :environment => ENV.to_hash,
93
+ :backtrace => backtrace_for(error),
94
+ :url => nil,
95
+ :request => nil,
96
+ :params => nil,
97
+ :notifier_version => VERSION,
98
+ :session => {},
99
+ :framework_env => ENV['RACK_ENV'] || 'development',
100
+ :project_root => Dir.pwd
101
+ }
102
+ end
103
+
104
+ def document_data(error, options)
105
+ data = document_defaults(error).merge(options)
106
+ [:params, :session, :environment].each{|n| data[n] = clean(data[n]) if data[n] }
107
+ data
108
+ end
109
+
110
+ def document_for(exception, options={})
111
+ data = document_data(exception, options)
112
+ scope = OpenStruct.new(data).extend(ERB::Util)
113
+ scope.instance_eval ERB.new(notice_template, nil, '-').src
114
+ end
115
+
116
+ def notice_template
117
+ ::File.read(::File.join(::File.dirname(__FILE__), 'exception.erb'))
118
+ end
119
+
120
+ BacktraceLine = Struct.new(:file, :number, :method)
121
+
122
+ def backtrace_for(error)
123
+ return "" unless error.respond_to? :backtrace
124
+ lines = Array(error.backtrace).map {|l| backtrace_line(l)}
125
+ if lines.empty?
126
+ lines << BacktraceLine.new("no-backtrace", "1", nil)
127
+ end
128
+ lines
129
+ end
130
+
131
+ def backtrace_line(line)
132
+ if match = line.match(%r{^(.+):(\d+)(?::in `([^']+)')?$})
133
+ BacktraceLine.new(*match.captures)
134
+ else
135
+ BacktraceLine.new(line, "1", nil)
136
+ end
137
+ end
138
+
139
+ def post_to_jira(exception,options={})
140
+ uri = URI.parse(@collector_url)
141
+ http = Net::HTTP.new(uri.host, uri.port)
142
+ http.use_ssl = true if uri.scheme == "https"
143
+ request = Net::HTTP::Post.new(uri.request_uri)
144
+ request['X-JIRA-Client-Name'] = "Rack::JiraExceptionCollector"
145
+ error = document_for(exception, options)
146
+ request.set_form_data({
147
+ "description" => "#{exception.class.name}: #{exception.message}",
148
+ "webInfo" => error
149
+ })
150
+ response = http.request(request)
151
+ end
152
+
153
+ def environment_data_for(env)
154
+ data = {}
155
+ ENV.each do |key,value|
156
+ data[wrapped_key_for(key)] = value.inspect
157
+ end
158
+ env.each do |key,value|
159
+ data["rack[#{key.inspect}]"] = value.inspect
160
+ end
161
+ data
162
+ end
163
+
164
+ def clean(hash)
165
+ hash.inject({}) do |acc, (k, v)|
166
+ acc[k] = (v.is_a?(Hash) ? clean(v) : filtered_value(k,v))
167
+ acc
168
+ end
169
+ end
170
+
171
+ def filters
172
+ environment_filter_keys.flatten.compact
173
+ end
174
+
175
+ def filtered_value(key, value)
176
+ if filters.any? {|f| key.to_s =~ Regexp.new(f)}
177
+ FILTER_REPLACEMENT
178
+ else
179
+ value.to_s
180
+ end
181
+ end
182
+
183
+ def wrapped_key_for(key)
184
+ "ENV[#{key.inspect}]"
185
+ end
186
+
187
+ def extract_body(env)
188
+ if io = env['rack.input']
189
+ io.rewind if io.respond_to?(:rewind)
190
+ io.read
191
+ end
192
+ end
193
+
194
+ end
195
+ end
@@ -0,0 +1,5 @@
1
+ module Rack
2
+ class JiraExceptionCollector
3
+ VERSION = "0.0.1"
4
+ end
5
+ end
data/test/helper.rb ADDED
@@ -0,0 +1,9 @@
1
+ require 'rubygems'
2
+ require 'bundler/setup'
3
+ Bundler.require(:test)
4
+ $:.push File.join(File.dirname(__FILE__), '..', 'lib')
5
+
6
+ require 'test/unit'
7
+ require 'rack/mock'
8
+ require 'rack/lobster'
9
+ require 'rack/jira_exception_collector'
@@ -0,0 +1,28 @@
1
+ require 'helper'
2
+
3
+ class Rack::JiraExceptionCollector::TestLogException < Test::Unit::TestCase
4
+ def setup
5
+ @collector_url = "http://example.com/rest/collectors/1.0/template/form/ac29dace"
6
+ FakeWeb.register_uri(:post, @collector_url,
7
+ :body => "Thanks for providing your feedback", :status => ["200", "OK"])
8
+
9
+ @app = Rack::Lobster.new
10
+ @collector = Rack::JiraExceptionCollector.new @app, @collector_url
11
+ @request = Rack::MockRequest.new @collector
12
+ end
13
+
14
+ def test_exception_was_sent
15
+ ENV['RACK_ENV'] = 'test'
16
+ env = Rack::MockRequest.env_for('/?flip=crash', :method => 'GET')
17
+ assert_raise(RuntimeError) { @collector.call(env) }
18
+ assert_true env['jira.notified'], "JIRA exception created"
19
+ end
20
+
21
+ def test_exception_can_be_sent_from_a_custom_env
22
+ ENV['RACK_ENV'] = "my_custom_env"
23
+ env = Rack::MockRequest.env_for('/?flip=crash', :method => 'GET')
24
+ collector = Rack::JiraExceptionCollector.new @app, @collector_url, "my_custom_env"
25
+ assert_raise(RuntimeError) { collector.call(env) }
26
+ assert_true env['jira.notified'], "JIRA exception created"
27
+ end
28
+ end
metadata ADDED
@@ -0,0 +1,134 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: jira_exception_collector
3
+ version: !ruby/object:Gem::Version
4
+ hash: 29
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 1
10
+ version: 0.0.1
11
+ platform: ruby
12
+ authors:
13
+ - Rich Manalang
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-11-30 00:00:00 Z
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: rack
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ hash: 3
29
+ segments:
30
+ - 0
31
+ version: "0"
32
+ type: :runtime
33
+ version_requirements: *id001
34
+ - !ruby/object:Gem::Dependency
35
+ name: rake
36
+ prerelease: false
37
+ requirement: &id002 !ruby/object:Gem::Requirement
38
+ none: false
39
+ requirements:
40
+ - - ">="
41
+ - !ruby/object:Gem::Version
42
+ hash: 3
43
+ segments:
44
+ - 0
45
+ version: "0"
46
+ type: :development
47
+ version_requirements: *id002
48
+ - !ruby/object:Gem::Dependency
49
+ name: test-unit
50
+ prerelease: false
51
+ requirement: &id003 !ruby/object:Gem::Requirement
52
+ none: false
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ hash: 3
57
+ segments:
58
+ - 0
59
+ version: "0"
60
+ type: :development
61
+ version_requirements: *id003
62
+ - !ruby/object:Gem::Dependency
63
+ name: fakeweb
64
+ prerelease: false
65
+ requirement: &id004 !ruby/object:Gem::Requirement
66
+ none: false
67
+ requirements:
68
+ - - ">="
69
+ - !ruby/object:Gem::Version
70
+ hash: 3
71
+ segments:
72
+ - 0
73
+ version: "0"
74
+ type: :development
75
+ version_requirements: *id004
76
+ description: A basic exception logger that logs to a JIRA instance
77
+ email:
78
+ - rmanalang@atlassian.com
79
+ executables: []
80
+
81
+ extensions: []
82
+
83
+ extra_rdoc_files:
84
+ - README.md
85
+ - LICENSE
86
+ files:
87
+ - .gitignore
88
+ - .travis.yml
89
+ - Gemfile
90
+ - LICENSE
91
+ - README.md
92
+ - Rakefile
93
+ - jira_exception_collector.gemspec
94
+ - lib/rack/exception.erb
95
+ - lib/rack/jira_exception_collector.rb
96
+ - lib/rack/jira_exception_collector_version.rb
97
+ - test/helper.rb
98
+ - test/test_log_exception.rb
99
+ homepage: https://github.com/manalang/jira_exception_collector
100
+ licenses: []
101
+
102
+ post_install_message:
103
+ rdoc_options: []
104
+
105
+ require_paths:
106
+ - lib
107
+ required_ruby_version: !ruby/object:Gem::Requirement
108
+ none: false
109
+ requirements:
110
+ - - ">="
111
+ - !ruby/object:Gem::Version
112
+ hash: 3
113
+ segments:
114
+ - 0
115
+ version: "0"
116
+ required_rubygems_version: !ruby/object:Gem::Requirement
117
+ none: false
118
+ requirements:
119
+ - - ">="
120
+ - !ruby/object:Gem::Version
121
+ hash: 3
122
+ segments:
123
+ - 0
124
+ version: "0"
125
+ requirements: []
126
+
127
+ rubyforge_project:
128
+ rubygems_version: 1.8.10
129
+ signing_key:
130
+ specification_version: 3
131
+ summary: jira_notifier will log your exceptions to a JIRA instance
132
+ test_files:
133
+ - test/helper.rb
134
+ - test/test_log_exception.rb