Class: Google::Cloud::ErrorReporting::Middleware

Inherits:
Object
  • Object
show all
Defined in:
lib/google/cloud/error_reporting/middleware.rb

Overview

Middleware

Google::Cloud::ErrorReporting::Middleware defines a Rack Middleware that can automatically catch upstream exceptions and report them to Stackdriver Error Reporting.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(app, error_reporting: nil, project_id: nil, service_name: nil, service_version: nil, ignore_classes: nil) ⇒ Object

Construct a new instance of Middleware

Parameters:

  • app (Rack Application)

    The Rack application

  • error_reporting (Google::Cloud::ErrorReporting::V1beta1::ReportErrorsServiceApi)

    A ErrorReporting::V1beta1::ReportErrorsServiceApi object to for reporting exceptions

  • project_id (String)

    Name of GCP project. Default to ENV["ERROR_REPORTING_PROJECT"] then ENV["GOOGLE_CLOUD_PROJECT"]. Automatically discovered if on GAE

  • service_name (String)

    Name of the service. Default to ENV["ERROR_REPORTING_SERVICE"] then "ruby". Automatically discovered if on GAE

  • service_version (String)

    Version of the service. Optional. ENV["ERROR_REPORTING_VERSION"]. Automatically discovered if on GAE

  • ignore_classes (Array<Class>)

    A single or an array of Exception classes to ignore

Raises:

  • (ArgumentError)


52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
# File 'lib/google/cloud/error_reporting/middleware.rb', line 52

def initialize app, error_reporting: nil, project_id: nil,
               service_name: nil, service_version: nil,
               ignore_classes: nil
  @app = app
  @error_reporting = error_reporting ||
    Google::Cloud::ErrorReporting::V1beta1::ReportErrorsServiceApi.new

  @service_name = service_name ||
                  ENV["ERROR_REPORTING_SERVICE"] ||
                  Google::Cloud::Core::Environment.gae_module_id ||
                  "ruby"
  @service_version = service_version ||
                     ENV["ERROR_REPORTING_VERSION"] ||
                     Google::Cloud::Core::Environment.gae_module_version
  @ignore_classes = Array(ignore_classes)
  @project_id = project_id ||
                ENV["ERROR_REPORTING_PROJECT"] ||
                ENV["GOOGLE_CLOUD_PROJECT"] ||
                Google::Cloud::Core::Environment.project_id

  raise ArgumentError, "project_id is required" if @project_id.nil?
end

Instance Attribute Details

#error_reportingObject (readonly)

Returns the value of attribute error_reporting



29
30
31
# File 'lib/google/cloud/error_reporting/middleware.rb', line 29

def error_reporting
  @error_reporting
end

#ignore_classesObject (readonly)

Returns the value of attribute ignore_classes



29
30
31
# File 'lib/google/cloud/error_reporting/middleware.rb', line 29

def ignore_classes
  @ignore_classes
end

#project_idObject (readonly)

Returns the value of attribute project_id



29
30
31
# File 'lib/google/cloud/error_reporting/middleware.rb', line 29

def project_id
  @project_id
end

#service_nameObject (readonly)

Returns the value of attribute service_name



29
30
31
# File 'lib/google/cloud/error_reporting/middleware.rb', line 29

def service_name
  @service_name
end

#service_versionObject (readonly)

Returns the value of attribute service_version



29
30
31
# File 'lib/google/cloud/error_reporting/middleware.rb', line 29

def service_version
  @service_version
end

Instance Method Details

#build_error_event_from_exception(env, exception) ⇒ Google::Devtools::Clouderrorreporting::V1beta1::ReportedErrorEvent

Creates a GRPC ErrorEvent based on the exception. Fill in the HttpRequestContext section of the ErrorEvent based on the HTTP Request headers.

When used in Rails environment. It replies on ActionDispatch::ExceptionWrapper class to derive a HTTP status code based on the exception's class.

Parameters:

  • env (Hash)

    Rack environment hash

  • exception (Exception)

    Exception to convert from

Returns:



141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
# File 'lib/google/cloud/error_reporting/middleware.rb', line 141

def build_error_event_from_exception env, exception
  # Build service_context hash
  service_context = {
                      service: service_name,
                      version: service_version
                    }.delete_if { |k,v| v.nil? }

  # Build error message and source_location hash
  if exception.backtrace.nil? || exception.backtrace.empty?
    message = exception.message
    report_location = nil
  else
    message = "#{exception.backtrace.first}: #{exception.message} " \
              "(#{exception.class})\n\t" +
              exception.backtrace.drop(1).join("\n\t")
    file_path, line_number, function_name =
      exception.backtrace.first.split(":")
    function_name = function_name.to_s[/`(.*)'/, 1]
    report_location = {
                        file_path: file_path,
                        function_name: function_name,
                        line_number: line_number.to_i
                      }.delete_if { |k,v| v.nil? }
  end

  # Build http_request_context hash
  rack_request = Rack::Request.new env
  http_method = rack_request.request_method
  http_url = rack_request.url
  http_user_agent = rack_request.user_agent
  http_referrer = rack_request.referrer
  http_status = get_http_status exception
  http_remote_ip = rack_request.ip
  http_request_context = {
                           method: http_method,
                           url: http_url,
                           user_agent: http_user_agent,
                           referrer: http_referrer,
                           response_status_code: http_status,
                           remote_ip: http_remote_ip
                         }.delete_if { |k,v| v.nil? }

  # Build error_context hash
  error_context = {
                    http_request: http_request_context,
                    user: ENV["USER"],
                    report_location: report_location,
                  }.delete_if { |k,v| v.nil? }

  # Build error_event hash
  t = Time.now
  error_event = {
    event_time: {
      seconds: t.to_i,
      nanos: t.nsec
    },
    service_context: service_context,
    message: message,
    context: error_context
  }.delete_if { |k,v| v.nil? }

  # Finally build and return GRPC ErrorEvent
  Google::Devtools::Clouderrorreporting::V1beta1::ReportedErrorEvent.decode_json \
    error_event.to_json
end

#call(env) ⇒ Object

Implements the mandatory Rack Middleware call method.

Catch all Exceptions from upstream and report them to Stackdriver Error Reporting. Unless the exception's class is defined to be ignored by this Middleware.

Parameters:

  • env (Hash)

    Rack environment hash



84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
# File 'lib/google/cloud/error_reporting/middleware.rb', line 84

def call env
  response = @app.call env

  # sinatra doesn't always raise the Exception, but it saves it in
  # env['sinatra.error']
  if env["sinatra.error"].is_a? Exception
    report_exception env, env["sinatra.error"]
  end

  response
rescue Exception => exception
  report_exception env, exception

  # Always raise exception backup
  raise exception
end

#full_project_idObject

Build full ReportErrorsServiceApi project_path from project_id, which is in "projects/##project_id" format.



210
211
212
# File 'lib/google/cloud/error_reporting/middleware.rb', line 210

def full_project_id
  Google::Cloud::ErrorReporting::V1beta1::ReportErrorsServiceApi.project_path project_id
end

#report_exception(env, exception) ⇒ Object

Report an given exception to Stackdriver Error Reporting.

While it reports most of the exceptions. Certain Rails exceptions that maps to a HTTP status code less than 500 will be treated as not the app fault and ignored.

Parameters:

  • env (Hash)

    Rack environment hash

  • exception (Exception)

    The Ruby exception to report.



111
112
113
114
115
116
117
118
119
120
121
122
123
124
# File 'lib/google/cloud/error_reporting/middleware.rb', line 111

def report_exception env, exception
  # Do not any exceptions that's specified by the ignore_classes list.
  return if ignore_classes.include? exception.class

  error_event = build_error_event_from_exception env,
                                                 exception

  # If this exception maps to a HTTP status code less than 500, do
  # not report it.
  return if
    error_event.context.http_request.response_status_code.to_i < 500

  error_reporting.report_error_event full_project_id, error_event
end