google-cloud-error_reporting 0.21.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 5600f55b4c5304420859f9c425895392f0e8f8e9
4
+ data.tar.gz: cb684753b01da03c48ca26056318635ecddf65ec
5
+ SHA512:
6
+ metadata.gz: 8b0ccadbb064b195a2a9c40f4dd97a822c01bfd2932b2dc9a792e416c3763ae52c1940180870409845db7102b765cbcce397b5ad0398108a2506d5710e34fcff
7
+ data.tar.gz: 67b194caaecf90cc12912fc8b2c178e19a67187579a9ac99d546562324d7e4439ce2a5a7807a2668b06baf6f3a1880d14d57e7d06f3ecf7b1fb7b813277d45c2
@@ -0,0 +1,82 @@
1
+ Stackdriver Clouderrorreporting API for Ruby
2
+ =================================================
3
+
4
+ google-cloud-error_reporting uses [Google API extensions][google-gax] to provide an
5
+ easy-to-use client library for the [Stackdriver Clouderrorreporting API][] (v1beta1) defined in the [googleapis][] git repository
6
+
7
+
8
+ [googleapis]: https://github.com/googleapis/googleapis/tree/master/google/google/devtools/clouderrorreporting/v1beta1
9
+ [google-gax]: https://github.com/googleapis/gax-ruby
10
+ [Stackdriver Clouderrorreporting API]: https://developers.google.com/apis-explorer/#p/clouderrorreporting/v1beta1/
11
+
12
+ Getting started
13
+ ---------------
14
+
15
+ google-cloud-error_reporting will allow you to connect to the [Stackdriver Clouderrorreporting API][] and access all its methods.
16
+
17
+ In order to achieve so you need to set up authentication as well as install the library locally.
18
+
19
+
20
+ Setup Authentication
21
+ --------------------
22
+
23
+ To authenticate all your API calls, first install and setup the [Google Cloud SDK][].
24
+ Once done, you can then run the following command in your terminal:
25
+
26
+ $ gcloud beta auth application-default login
27
+
28
+ or
29
+
30
+ $ gcloud auth login
31
+
32
+ Please see [[gcloud beta auth application-default login][] document for the difference between these commands.
33
+
34
+ [Google Cloud SDK]: https://cloud.google.com/sdk/
35
+ [gcloud beta auth application-default login]: https://cloud.google.com/sdk/gcloud/reference/beta/auth/application-default/login
36
+
37
+
38
+ Installation
39
+ -------------------
40
+
41
+ Install this library using gem:
42
+
43
+ $ [sudo] gem install google-cloud-error_reporting
44
+
45
+
46
+ Rails Integration
47
+ ---------------
48
+
49
+ This library also provides a built in Railtie for Ruby on Rails integration. To do this, simply add this line to config/application.rb:
50
+ ```ruby
51
+ require "google/cloud/error_reporting/rails"
52
+ ```
53
+ Then the library can be configured through this set of Rails parameters in config/environments/*.rb:
54
+ ```ruby
55
+ # Sharing authentication parameters
56
+ config.google_cloud.project_id = "gcp-project-id"
57
+ config.google_cloud.keyfile = "/path/to/gcp/secret.json"
58
+ # Or more specificly for ErrorReporting
59
+ config.google_cloud.error_reporting.project_id = "gcp-project-id"
60
+ config.google_cloud.error_reporting.keyfile = "/path/to/gcp/sercret.json"
61
+
62
+ # Explicitly enable or disable ErrorReporting
63
+ config.google_cloud.use_error_reporting = true
64
+
65
+ # Set Stackdriver Error Reporting service context
66
+ config.google_cloud.error_reporting.service_name = "my-app-name"
67
+ config.google_cloud.error_reporting.service_version = "my-app-version"
68
+ ```
69
+
70
+ Alternatively, check out [stackdriver](../stackdriver) gem, which includes this Railtie by default.
71
+
72
+ Rack Integration
73
+ ---------------
74
+
75
+ Other Rack base framework can also directly leverage the built-in Middleware.
76
+ ```ruby
77
+ require "google/cloud/error_reporting/v1beta1"
78
+
79
+ use Google::Cloud::ErrorReporting::Middleware
80
+ ```
81
+
82
+ At this point you are all set to continue.
@@ -0,0 +1,72 @@
1
+ # Copyright 2016 Google Inc. All rights reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+
16
+ require "bundler/setup"
17
+ require "bundler/gem_tasks"
18
+
19
+ task :test do
20
+ $LOAD_PATH.unshift "lib", "test"
21
+ Dir.glob("test/**/*_test.rb").each { |file| require_relative file }
22
+ end
23
+
24
+ task :rubocop do
25
+ end
26
+
27
+ namespace :test do
28
+ desc "Runs tests with coverage."
29
+ task :coverage do
30
+ end
31
+ end
32
+
33
+ # Acceptance tests
34
+ desc "Runs the error_reporting acceptance tests."
35
+ task :acceptance, :project, :keyfile do |_, args|
36
+ end
37
+
38
+ namespace :acceptance do
39
+ desc "Runs acceptance tests with coverage."
40
+ task :coverage, :project, :keyfile do |t, args|
41
+ end
42
+
43
+
44
+ desc "Runs acceptance cleanup."
45
+ task :cleanup do
46
+ end
47
+ end
48
+
49
+ desc "Runs yard-doctest example tests."
50
+ task :doctest do
51
+ end
52
+
53
+ desc "Start an interactive shell."
54
+ task :console do
55
+ end
56
+
57
+ require "yard"
58
+ require "yard/rake/yardoc_task"
59
+ YARD::Rake::YardocTask.new
60
+
61
+ desc "Generates JSON output from google-cloud-error_reporting .yardoc"
62
+ task :jsondoc => :yard do
63
+ require "rubygems"
64
+ require "gcloud/jsondoc"
65
+
66
+ registry = YARD::Registry.load! ".yardoc"
67
+ generator = Gcloud::Jsondoc::Generator.new registry, "google-cloud-error_reporting"
68
+ generator.write_to "jsondoc"
69
+ cp ["docs/toc.json"], "jsondoc", verbose: true
70
+ end
71
+
72
+ task :default => :test
@@ -0,0 +1,242 @@
1
+ # Copyright 2016 Google Inc. All rights reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ require "rack"
16
+ require "rack/request"
17
+
18
+ module Google
19
+ module Cloud
20
+ module ErrorReporting
21
+ ##
22
+ # Middleware
23
+ #
24
+ # Google::Cloud::ErrorReporting::Middleware defines a Rack Middleware
25
+ # that can automatically catch upstream exceptions and report them
26
+ # to Stackdriver Error Reporting.
27
+ #
28
+ class Middleware
29
+ attr_reader :error_reporting, :ignore_classes, :project_id,
30
+ :service_name, :service_version
31
+
32
+ ##
33
+ # Construct a new instance of Middleware
34
+ #
35
+ # @param [Rack Application] app The Rack application
36
+ # @param [Google::Cloud::ErrorReporting::V1beta1::ReportErrorsServiceApi]
37
+ # error_reporting A ErrorReporting::V1beta1::ReportErrorsServiceApi
38
+ # object to for reporting exceptions
39
+ # @param [String] project_id Name of GCP project. Default to
40
+ # ENV["ERROR_REPORTING_PROJECT"] then ENV["GOOGLE_CLOUD_PROJECT"].
41
+ # Automatically discovered if on GAE
42
+ # @param [String] service_name Name of the service. Default to
43
+ # ENV["ERROR_REPORTING_SERVICE"] then "ruby". Automatically discovered
44
+ # if on GAE
45
+ # @param [String] service_version Version of the service. Optional.
46
+ # ENV["ERROR_REPORTING_VERSION"]. Automatically discovered if on GAE
47
+ # @param [Array<Class>] ignore_classes A single or an array of Exception
48
+ # classes to ignore
49
+ #
50
+ # @return A new instance of Middleware
51
+ #
52
+ def initialize app, error_reporting: nil, project_id: nil,
53
+ service_name: nil, service_version: nil,
54
+ ignore_classes: nil
55
+ @app = app
56
+ @error_reporting = error_reporting ||
57
+ Google::Cloud::ErrorReporting::V1beta1::ReportErrorsServiceApi.new
58
+
59
+ @service_name = service_name ||
60
+ ENV["ERROR_REPORTING_SERVICE"] ||
61
+ Google::Cloud::Core::Environment.gae_module_id ||
62
+ "ruby"
63
+ @service_version = service_version ||
64
+ ENV["ERROR_REPORTING_VERSION"] ||
65
+ Google::Cloud::Core::Environment.gae_module_version
66
+ @ignore_classes = Array(ignore_classes)
67
+ @project_id = project_id ||
68
+ ENV["ERROR_REPORTING_PROJECT"] ||
69
+ ENV["GOOGLE_CLOUD_PROJECT"] ||
70
+ Google::Cloud::Core::Environment.project_id
71
+
72
+ raise ArgumentError, "project_id is required" if @project_id.nil?
73
+ end
74
+
75
+ ##
76
+ # Implements the mandatory Rack Middleware call method.
77
+ #
78
+ # Catch all Exceptions from upstream and report them to Stackdriver
79
+ # Error Reporting. Unless the exception's class is defined to be ignored
80
+ # by this Middleware.
81
+ #
82
+ # @param [Hash] env Rack environment hash
83
+ #
84
+ def call env
85
+ response = @app.call env
86
+
87
+ # sinatra doesn't always raise the Exception, but it saves it in
88
+ # env['sinatra.error']
89
+ if env["sinatra.error"].is_a? Exception
90
+ report_exception env, env["sinatra.error"]
91
+ end
92
+
93
+ response
94
+ rescue Exception => exception
95
+ report_exception env, exception
96
+
97
+ # Always raise exception backup
98
+ raise exception
99
+ end
100
+
101
+ ##
102
+ # Report an given exception to Stackdriver Error Reporting.
103
+ #
104
+ # While it reports most of the exceptions. Certain Rails exceptions that
105
+ # maps to a HTTP status code less than 500 will be treated as not the
106
+ # app fault and ignored.
107
+ #
108
+ # @param [Hash] env Rack environment hash
109
+ # @param [Exception] exception The Ruby exception to report.
110
+ #
111
+ def report_exception env, exception
112
+ # Do not any exceptions that's specified by the ignore_classes list.
113
+ return if ignore_classes.include? exception.class
114
+
115
+ error_event = build_error_event_from_exception env,
116
+ exception
117
+
118
+ # If this exception maps to a HTTP status code less than 500, do
119
+ # not report it.
120
+ return if
121
+ error_event.context.http_request.response_status_code.to_i < 500
122
+
123
+ error_reporting.report_error_event full_project_id, error_event
124
+ end
125
+
126
+ ##
127
+ # Creates a GRPC ErrorEvent based on the exception. Fill in the
128
+ # HttpRequestContext section of the ErrorEvent based on the HTTP Request
129
+ # headers.
130
+ #
131
+ # When used in Rails environment. It replies on
132
+ # ActionDispatch::ExceptionWrapper class to derive a HTTP status code
133
+ # based on the exception's class.
134
+ #
135
+ # @param [Hash] env Rack environment hash
136
+ # @param [Exception] exception Exception to convert from
137
+ #
138
+ # @return [Google::Devtools::Clouderrorreporting::V1beta1::ReportedErrorEvent]
139
+ # The gRPC ReportedErrorEvent object that's based on given exception
140
+ #
141
+ def build_error_event_from_exception env, exception
142
+ # Build service_context hash
143
+ service_context = {
144
+ service: service_name,
145
+ version: service_version
146
+ }.delete_if { |k,v| v.nil? }
147
+
148
+ # Build error message and source_location hash
149
+ if exception.backtrace.nil? || exception.backtrace.empty?
150
+ message = exception.message
151
+ report_location = nil
152
+ else
153
+ message = "#{exception.backtrace.first}: #{exception.message} " \
154
+ "(#{exception.class})\n\t" +
155
+ exception.backtrace.drop(1).join("\n\t")
156
+ file_path, line_number, function_name =
157
+ exception.backtrace.first.split(":")
158
+ function_name = function_name.to_s[/`(.*)'/, 1]
159
+ report_location = {
160
+ file_path: file_path,
161
+ function_name: function_name,
162
+ line_number: line_number.to_i
163
+ }.delete_if { |k,v| v.nil? }
164
+ end
165
+
166
+ # Build http_request_context hash
167
+ rack_request = Rack::Request.new env
168
+ http_method = rack_request.request_method
169
+ http_url = rack_request.url
170
+ http_user_agent = rack_request.user_agent
171
+ http_referrer = rack_request.referrer
172
+ http_status = get_http_status exception
173
+ http_remote_ip = rack_request.ip
174
+ http_request_context = {
175
+ method: http_method,
176
+ url: http_url,
177
+ user_agent: http_user_agent,
178
+ referrer: http_referrer,
179
+ response_status_code: http_status,
180
+ remote_ip: http_remote_ip
181
+ }.delete_if { |k,v| v.nil? }
182
+
183
+ # Build error_context hash
184
+ error_context = {
185
+ http_request: http_request_context,
186
+ user: ENV["USER"],
187
+ report_location: report_location,
188
+ }.delete_if { |k,v| v.nil? }
189
+
190
+ # Build error_event hash
191
+ t = Time.now
192
+ error_event = {
193
+ event_time: {
194
+ seconds: t.to_i,
195
+ nanos: t.nsec
196
+ },
197
+ service_context: service_context,
198
+ message: message,
199
+ context: error_context
200
+ }.delete_if { |k,v| v.nil? }
201
+
202
+ # Finally build and return GRPC ErrorEvent
203
+ Google::Devtools::Clouderrorreporting::V1beta1::ReportedErrorEvent.decode_json \
204
+ error_event.to_json
205
+ end
206
+
207
+ ##
208
+ # Build full ReportErrorsServiceApi project_path from project_id, which
209
+ # is in "projects/#{project_id}" format.
210
+ def full_project_id
211
+ Google::Cloud::ErrorReporting::V1beta1::ReportErrorsServiceApi.project_path project_id
212
+ end
213
+
214
+ private
215
+
216
+ ##
217
+ # Helper method to derive HTTP status code base on exception class in
218
+ # Rails. Returns nil if not in Rails environment
219
+ #
220
+ # @param [Exception] exception An Ruby exception
221
+ #
222
+ # @return [Integer] A number that represents HTTP status code or nil if
223
+ # status code can't be determined
224
+ #
225
+ def get_http_status exception
226
+ http_status = nil
227
+ if defined?(ActionDispatch::ExceptionWrapper) &&
228
+ ActionDispatch::ExceptionWrapper.respond_to?(
229
+ :status_code_for_exception
230
+ )
231
+ http_status =
232
+ ActionDispatch::ExceptionWrapper.status_code_for_exception(
233
+ exception.class.name
234
+ )
235
+ end
236
+
237
+ http_status
238
+ end
239
+ end
240
+ end
241
+ end
242
+ end
@@ -0,0 +1,173 @@
1
+ # Copyright 2016 Google Inc. All rights reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+
16
+ require "google/cloud/error_reporting/v1beta1"
17
+ require "google/cloud/credentials"
18
+
19
+ module Google
20
+ module Cloud
21
+ module ErrorReporting
22
+ ##
23
+ # Railtie
24
+ #
25
+ # Google::Cloud::ErrorReporting::Railtie automatically add the
26
+ # Google::Cloud::ErrorReporting::Middleware to Rack in a Rails environment.
27
+ # It will automatically capture Exceptions from the Rails app and report
28
+ # them to Stackdriver Error Reporting.
29
+ #
30
+ # The Middleware is only added when certain conditions are met. See
31
+ # {Railtie.use_error_reporting?} for detail.
32
+ #
33
+ # When loaded, the Google::Cloud::ErrorReporting::Middleware will be
34
+ # inserted after ::ActionDispatch::DebugExceptions Middleware, which allows
35
+ # it to actually rescue all Exceptions and throw it back up. The middleware
36
+ # should also be initialized with correct gcp project_id, keyfile,
37
+ # service_name, and service_version if they are defined in
38
+ # Rails environment files as follow:
39
+ # config.google_cloud.project_id = "my-gcp-project"
40
+ # config.google_cloud.keyfile = "/path/to/secret.json"
41
+ # or
42
+ # config.google_cloud.error_reporting.project_id = "my-gcp-project"
43
+ # config.google_cloud.error_reporting.keyfile = "/path/to/secret.json"
44
+ # config.google_cloud.error_reporting.service_name = "my-service-name"
45
+ # config.google_cloud.error_reporting.service_version = "v1"
46
+ #
47
+ class Railtie < ::Rails::Railtie
48
+ config.google_cloud = ActiveSupport::OrderedOptions.new unless
49
+ config.respond_to? :google_cloud
50
+ config.google_cloud.error_reporting = ActiveSupport::OrderedOptions.new
51
+
52
+ initializer "Google.Cloud.ErrorReporting" do |app|
53
+ if self.class.use_error_reporting? app.config
54
+ gcp_config = app.config.google_cloud
55
+ er_config = gcp_config.error_reporting
56
+
57
+ project_id = er_config.project_id || gcp_config.project_id
58
+ keyfile = er_config.keyfile || gcp_config.keyfile
59
+
60
+ channel = self.class.grpc_channel keyfile
61
+ service_name = er_config.service_name ||
62
+ error_reporting.class.default_service_name
63
+ service_version = er_config.service_version ||
64
+ error_reporting.class.default_service_version
65
+
66
+ error_reporting =
67
+ Google::Cloud::ErrorReporting::V1beta1::ReportErrorsServiceApi.new channel: channel,
68
+ app_name: service_name,
69
+ app_version: service_version
70
+
71
+ # In later versions of Rails, ActionDispatch::DebugExceptions is
72
+ # responsible for catching exceptions. But it didn't exist until
73
+ # Rails 3.2. So we use ShowExceptions as pivot for earlier Rails.
74
+ rails_exception_middleware =
75
+ if defined? ::ActionDispatch::DebugExceptions
76
+ ::ActionDispatch::DebugExceptions
77
+ else
78
+ ::ActionDispatch::ShowExceptions
79
+ end
80
+
81
+ app.middleware.insert_after rails_exception_middleware,
82
+ Google::Cloud::ErrorReporting::Middleware,
83
+ project_id: project_id,
84
+ error_reporting: error_reporting,
85
+ service_name: service_name,
86
+ service_version: service_version
87
+ end
88
+ end
89
+
90
+ ##
91
+ # Construct a gRPC Channel object from given keyfile
92
+ #
93
+ # @param [String, Hash] keyfile Keyfile downloaded from Google Cloud. If
94
+ # file path the file must be readable.
95
+ #
96
+ # @return [GRPC::Core::Channel] gRPC Channel object based on given
97
+ # keyfile
98
+ #
99
+ def self.grpc_channel keyfile = nil
100
+ require "grpc"
101
+
102
+ scopes = Google::Cloud::ErrorReporting::V1beta1::ReportErrorsServiceApi::ALL_SCOPES
103
+ credentials = if keyfile.nil?
104
+ Google::Cloud::Credentials.default(
105
+ scope: scopes)
106
+ else
107
+ Google::Cloud::Credentials.new(
108
+ keyfile, scope: scopes)
109
+ end
110
+
111
+ # Return nil if :this_channel_is_insecure
112
+ return nil if credentials == :this_channel_is_insecure
113
+
114
+ channel_cred = GRPC::Core::ChannelCredentials.new.compose \
115
+ GRPC::Core::CallCredentials.new credentials.client.updater_proc
116
+ host = Google::Cloud::ErrorReporting::V1beta1::ReportErrorsServiceApi::SERVICE_ADDRESS
117
+
118
+ GRPC::Core::Channel.new host, nil, channel_cred
119
+ end
120
+
121
+ ##
122
+ # Determine whether to use Stackdriver Error Reporting or not.
123
+ #
124
+ # Returns true if valid GCP project_id and keyfile are provided and
125
+ # either Rails is in "production" environment or \
126
+ # config.google_cloud.use_error_reporting is explicitly true. Otherwise
127
+ # false.
128
+ #
129
+ # @param config The Rails.application.config
130
+ #
131
+ # @return true or false
132
+ #
133
+ def self.use_error_reporting? config
134
+ gcp_config = config.google_cloud
135
+ er_config = gcp_config.error_reporting
136
+
137
+ # Return false if config.google_cloud.use_error_reporting is
138
+ # explicitly false
139
+ return false if gcp_config.key?(:use_error_reporting) &&
140
+ !gcp_config.use_error_reporting
141
+
142
+ # Check credentialing. Returns false if authorization errors are
143
+ # rescued.
144
+ keyfile = er_config.keyfile || gcp_config.keyfile
145
+ begin
146
+ grpc_channel keyfile
147
+ rescue Exception => e
148
+ Rails.logger.warn "Google::Cloud::ErrorReporting is not " \
149
+ "activated due to authorization error: #{e.message}"
150
+ return false
151
+ end
152
+
153
+ project_id = er_config.project_id ||
154
+ gcp_config.project_id ||
155
+ ENV["ERROR_REPORTING_PROJECT"] ||
156
+ ENV["GOOGLE_CLOUD_PROJECT"] ||
157
+ Google::Cloud::Core::Environment.project_id
158
+ if project_id.to_s.empty?
159
+ Rails.logger.warn "Google::Cloud::ErrorReporting is not " \
160
+ "activated due to empty project_id"
161
+ return false
162
+ end
163
+
164
+ # Otherwise return true if Rails is running in production or
165
+ # config.google_cloud.use_error_reporting is explicitly true
166
+ Rails.env.production? ||
167
+ (gcp_config.key?(:use_error_reporting) &&
168
+ gcp_config.use_error_reporting)
169
+ end
170
+ end
171
+ end
172
+ end
173
+ end