jira_exception_collector 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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