google-cloud-error_reporting 0.21.0

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.
@@ -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