class Google::Cloud::Trace::Middleware

# Trace Middleware

A Rack middleware that manages trace context and captures a trace of the request. Specifically, it:

## Installing

To use this middleware, simply install it in your middleware stack. Here is an example Sinatra application that includes the Trace middleware:

“`ruby # Simple sinatra application

require “sinatra” require “google/cloud/trace”

use Google::Cloud::Trace::Middleware

get “/” do

"Hello World!"

end “`

Here is an example `config.ru` file for a web application that uses the standard Rack configuration mechanism.

“`ruby # config.ru for simple Rack application

require “google/cloud/trace” use Google::Cloud::Trace::Middleware

run MyApp “`

If your application uses Ruby On Rails, you may also use the provided {Google::Cloud::Trace::Railtie} for close integration with Rails and ActiveRecord.

## Custom measurements

By default, this middleware creates traces that measure just the http request handling as a whole. If you want to provide more detailed measurements of smaller processes, use the classes provided in this library. Below is a Sinatra example to get you started.

“`ruby # Simple sinatra application

require “sinatra” require “google/cloud/trace”

use Google::Cloud::Trace::Middleware

get “/” do

Google::Cloud::Trace.in_span "Sleeping on the job!" do
  sleep rand
end
"Hello World!"

end “`

## Error handling

An error encountered during the reporting of traces by the middleware can be handled using a Proc set in the `on_error` configuration. (See {Google::Cloud::Trace.configure}.) The Proc must take the error object as the single argument.

“`ruby # Configure error handling

require “sinatra” require “google/cloud/trace” require “google/cloud/error_reporting”

Google::Cloud::Trace.configure do |config|

config.on_error = lambda do |error|
  Google::Cloud::ErrorReporting.report error
end

end

use Google::Cloud::Trace::Middleware

get “/” do

Google::Cloud::Trace.in_span "Sleeping on the job!" do
  sleep rand
end
"Hello World!"

end “`

## Sampling and blacklisting

A sampler makes the decision whether to record a trace for each request (if the decision was not made by the context, e.g. by providing a request header). By default, this sampler is the default {Google::Cloud::Trace::TimeSampler}, which enforces a maximum QPS per process, and blacklists a small number of request paths such as health checks sent by Google App Engine. You may adjust this behavior by providing an alternate sampler. See {Google::Cloud::Trace::TimeSampler}.

Constants

AGENT_NAME

The name of this trace agent as reported to the Stackdriver backend.

Public Class Methods

new(app, service: nil, **kwargs) click to toggle source

Create a new Middleware for traces

@param [Rack Application] app Rack application @param [Google::Cloud::Trace::Service, AsyncReporter] service

The service object to update traces. Optional if running on GCE.

@param [Hash] kwargs Hash of configuration settings. Used for backward

API compatibility. See the {file:INSTRUMENTATION.md Instrumentation
Guide} and [Configuration
Guide](https://googleapis.dev/ruby/stackdriver/latest/file.INSTRUMENTATION_CONFIGURATION.html)
for the prefered way to set configuration parameters.
# File lib/google/cloud/trace/middleware.rb, line 154
def initialize app, service: nil, **kwargs
  @app = app

  load_config(**kwargs)

  if service
    @service = service
  else
    project_id = configuration.project_id

    if project_id
      credentials = configuration.credentials
      tracer = Google::Cloud::Trace.new project_id: project_id,
                                        credentials: credentials
      @service = Google::Cloud::Trace::AsyncReporter.new tracer.service
    end
  end
end

Public Instance Methods

call(env) click to toggle source

Implementation of the trace middleware. Creates a trace for this request, populates it with a root span for the entire request, and ensures it is reported back to Stackdriver.

@param [Hash] env Rack environment hash @return [Rack::Response] The response from downstream Rack app

# File lib/google/cloud/trace/middleware.rb, line 181
def call env
  trace = create_trace env
  begin
    Google::Cloud::Trace.set trace
    Google::Cloud::Trace.in_span "rack-request" do |span|
      configure_span span, env
      result = @app.call env
      configure_result span, result
      result
    end
  ensure
    Google::Cloud::Trace.set nil
    send_trace trace, env
  end
end
configure_result(span, result) click to toggle source

Performs post-request tasks, including adding result-dependent labels to the root span, and adding trace context headers to the HTTP response.

@private @param [Google::Cloud::Trace::TraceSpan] span The root span to

configure.

@param [Array] result The Rack response.

# File lib/google/cloud/trace/middleware.rb, line 373
def configure_result span, result
  if result.is_a?(::Array) && result.size == 3
    span.labels[Google::Cloud::Trace::LabelKey::HTTP_STATUS_CODE] =
      result[0].to_s
    result[1]["X-Cloud-Trace-Context"] =
      span.trace.trace_context.to_string
  end
  result
end
configure_span(span, env) click to toggle source

Configures the root span for this request. This may be called before the request is actually handled because it doesn't depend on the result.

@private @param [Google::Cloud::Trace::TraceSpan] span The root span to

configure.

@param [Hash] env Rack environment hash

# File lib/google/cloud/trace/middleware.rb, line 302
def configure_span span, env
  span.name = get_path env
  set_basic_labels span.labels, env
  set_extended_labels span.labels,
                      span.trace.trace_context.capture_stack?
  span
end
create_trace(env) click to toggle source

Create a new trace for this request.

@private @param [Hash] env The Rack environment.

# File lib/google/cloud/trace/middleware.rb, line 227
def create_trace env
  trace_context = get_trace_context env
  Google::Cloud::Trace::TraceRecord.new \
    @service.project,
    trace_context,
    span_id_generator: configuration.span_id_generator
end
get_host(env) click to toggle source

Gets the URI hostname from the given Rack environment.

@private @param [Hash] env Rack environment hash @return [String] The hostname.

# File lib/google/cloud/trace/middleware.rb, line 271
def get_host env
  env["HTTP_HOST"] || env["SERVER_NAME"]
end
get_path(env) click to toggle source

Gets the URI path from the given Rack environment.

@private @param [Hash] env Rack environment hash @return [String] The URI path.

# File lib/google/cloud/trace/middleware.rb, line 258
def get_path env
  path = "#{env['SCRIPT_NAME']}#{env['PATH_INFO']}"
  path = "/#{path}" unless path.start_with? "/"
  path
end
get_trace_context(env) click to toggle source

Gets the current trace context from the given Rack environment. Makes a sampling decision if one has not been made already.

@private @param [Hash] env Rack environment hash @return [Stackdriver::Core::TraceContext] The trace context.

# File lib/google/cloud/trace/middleware.rb, line 205
def get_trace_context env
  Stackdriver::Core::TraceContext.parse_rack_env env do |tc|
    if tc.sampled?.nil?
      sampler = configuration.sampler ||
                Google::Cloud::Trace::TimeSampler.default
      sampled = sampler.call env
      tc = Stackdriver::Core::TraceContext.new \
        trace_id: tc.trace_id,
        span_id: tc.span_id,
        sampled: sampled,
        capture_stack: sampled && configuration.capture_stack
    end
    tc
  end
end
get_url(env) click to toggle source

Gets the full URL from the given Rack environment.

@private @param [Hash] env Rack environment hash @return [String] The URL.

# File lib/google/cloud/trace/middleware.rb, line 282
def get_url env
  path = get_path env
  host = get_host env
  scheme = env["rack.url_scheme"]
  query_string = env["QUERY_STRING"].to_s
  url = "#{scheme}://#{host}#{path}"
  url = "#{url}?#{query_string}" unless query_string.empty?
  url
end
send_trace(trace, env) click to toggle source

Send the given trace to the trace service, if requested.

@private @param [Google::Cloud::Trace::TraceRecord] trace The trace to send. @param [Hash] env The Rack environment.

# File lib/google/cloud/trace/middleware.rb, line 242
def send_trace trace, env
  return unless @service && trace.trace_context.sampled?
  begin
    @service.patch_traces trace
  rescue StandardError => e
    handle_error e, logger: env["rack.logger"]
  end
end
set_basic_labels(labels, env) click to toggle source

Configures standard labels. @private

# File lib/google/cloud/trace/middleware.rb, line 314
def set_basic_labels labels, env
  set_label labels, Google::Cloud::Trace::LabelKey::AGENT, AGENT_NAME
  set_label labels, Google::Cloud::Trace::LabelKey::HTTP_HOST,
            get_host(env)
  set_label labels, Google::Cloud::Trace::LabelKey::HTTP_METHOD,
            env["REQUEST_METHOD"]
  set_label labels,
            Google::Cloud::Trace::LabelKey::HTTP_CLIENT_PROTOCOL,
            env["SERVER_PROTOCOL"]
  set_label labels, Google::Cloud::Trace::LabelKey::HTTP_USER_AGENT,
            env["HTTP_USER_AGENT"]
  set_label labels, Google::Cloud::Trace::LabelKey::HTTP_URL,
            get_url(env)
  set_label labels, Google::Cloud::Trace::LabelKey::PID,
            ::Process.pid.to_s
  set_label labels, Google::Cloud::Trace::LabelKey::TID,
            ::Thread.current.object_id.to_s
end
set_extended_labels(labels, capture_stack) click to toggle source

Configures stack and gae labels. @private

# File lib/google/cloud/trace/middleware.rb, line 337
def set_extended_labels labels, capture_stack
  if capture_stack
    Google::Cloud::Trace::LabelKey.set_stack_trace labels,
                                                   skip_frames: 3
  end
  if Google::Cloud.env.app_engine?
    set_label labels, Google::Cloud::Trace::LabelKey::GAE_APP_MODULE,
              Google::Cloud.env.app_engine_service_id
    set_label labels,
              Google::Cloud::Trace::LabelKey::GAE_APP_MODULE_VERSION,
              Google::Cloud.env.app_engine_service_version
  end
end
set_label(labels, key, value) click to toggle source

Sets the given label if the given value is a proper string.

@private @param [Hash] labels The labels hash. @param [String] key The key of the label to set. @param [Object] value The value to set.

# File lib/google/cloud/trace/middleware.rb, line 359
def set_label labels, key, value
  labels[key] = value if value.is_a? ::String
end

Private Instance Methods

configuration() click to toggle source

@private Get Google::Cloud::Trace.configure

# File lib/google/cloud/trace/middleware.rb, line 413
def configuration
  Google::Cloud::Trace.configure
end
error_callback() click to toggle source

@private Get the error callback from the configuration. This value is memoized to reduce calls to the configuration.

# File lib/google/cloud/trace/middleware.rb, line 420
def error_callback
  if @error_callback.nil?
    @error_callback = :unset
    configuration_callback = configuration.on_error
    configuration_callback ||= Cloud.configure.on_error
    @error_callback = configuration_callback if configuration_callback
  end

  return nil if @error_callback == :unset
  @error_callback
end
handle_error(error, logger: nil) click to toggle source

@private Handle errors raised when making patch_traces API calls.

# File lib/google/cloud/trace/middleware.rb, line 434
def handle_error error, logger: nil
  # Use on_error from configuration
  if error_callback
    error_callback.call error
  else
    # log error
    msg = "Transmit to Stackdriver Trace failed: #{error.inspect}"
    if logger
      logger.error msg
    else
      warn msg
    end
  end
end
init_default_config() click to toggle source

Fallback to default configuration values if not defined already

# File lib/google/cloud/trace/middleware.rb, line 405
def init_default_config
  configuration.project_id ||= Trace.default_project_id
  configuration.credentials ||= Cloud.configure.credentials
  configuration.capture_stack ||= false
end
load_config(**kwargs) click to toggle source

Consolidate configurations from various sources. Also set instrumentation config parameters to default values if not set already.

# File lib/google/cloud/trace/middleware.rb, line 390
def load_config **kwargs
  capture_stack = kwargs[:capture_stack]
  configuration.capture_stack = capture_stack unless capture_stack.nil?

  sampler = kwargs[:sampler]
  configuration.sampler = sampler unless sampler.nil?

  generator = kwargs[:span_id_generator]
  configuration.span_id_generator = generator unless generator.nil?

  init_default_config
end