Last active
May 13, 2020 02:30
-
-
Save fzakaria/b09b124359583a98c50eb5aa01d93d45 to your computer and use it in GitHub Desktop.
Zipkin JRuby
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class RequestFilter | |
def initialize(app, tracer) | |
@app = app | |
@tracer = tracer | |
end | |
EXCLUDED_PATH_PREFIXES = %w[ | |
... | |
].freeze | |
def call(env) | |
# if the current request is one we should exclude, then immediately return | |
# so we can avoid any cost associated with setting up a trace. | |
path = RequestFilter.get_path(env) | |
if path.start_with?(*EXCLUDED_PATH_PREFIXES) | |
return @app.call(env) | |
end | |
req = Rack::Request.new(env) | |
# Collect just a few HTTP headers from the request that might affect Tracing | |
# X-B3-TraceId, X-B3-SpanId or X-B3-Sampled | |
# https://github.com/openzipkin/b3-propagation/blob/master/README.md | |
# | |
# 1. Rack prefixes the headers in the env variable with HTTP_ so lets strip it | |
# 2. Rack annoyingly converts hyphens to underscores; so lets revert that. | |
# 3. Let's also capitalize for good measure | |
headers = Hash[*env.select {|k, _| k.start_with? 'HTTP_'} | |
.map {|k, v| [k.sub(/^HTTP_/, ''), v]} | |
.map {|k, v| [k.split('_').map(&:capitalize).join('-'), v]} | |
.flatten] | |
span = @tracer.extract_from_headers(headers) | |
# returns the path of the request without the query string, domain and any relative root | |
span.name(path) | |
# We will now set a bunch of common labels | |
# similar to https://github.com/googleapis/google-cloud-ruby/blob/master/google-cloud-trace/lib/google/cloud/trace/middleware.rb | |
span.tag(Label::HTTP_HOST, RequestFilter.get_host(env)) | |
span.tag(Label::HTTP_METHOD, req.request_method) | |
span.tag(Label::HTTP_CLIENT_PROTOCOL, env["SERVER_PROTOCOL"]) | |
span.tag(Label::HTTP_USER_AGENT, req.user_agent) | |
span.tag(Label::HTTP_URL, RequestFilter.get_url(env)) | |
span.tag(Label::PID, ::Process.pid.to_s) | |
span.tag(Label::TID, ::Thread.current.object_id.to_s) | |
span.tag(Label::HTTP_REQUEST_SIZE, req.content_length) unless req.content_length.nil? | |
span.tag("developer", ENV["USER"]) if development? && ENV["USER"] | |
span.start | |
# At this point we need to mark the span as active so it's in thread-local storage | |
status, headers, body = @tracer.with_span_in_scope(span) do | |
@app.call(env) | |
end | |
# to reduce the cardinality of the span names; lets prefer the generic routes. | |
# for instance: /users/:user_id | |
# we have to set this after the call, because it's only present once the sinatra router handles the request | |
if env['sinatra.route'].present? | |
raw_sinatra_route = env['sinatra.route'] | |
span.name(raw_sinatra_route) | |
end | |
# Add the propagation information | |
@tracer.inject_into_headers(span, headers) | |
span.tag(Label::HTTP_STATUS_CODE, status.to_s) | |
# We cannot take the content length of a stream; otherwise it will consume the body! | |
# This is a potential gotcha; so be wary changing this if condition. | |
unless body.is_a?(Sinatra::Helpers::Stream) | |
response = Rack::Response.new(body, status, headers) | |
span.tag(HLabel::HTTP_RESPONSE_SIZE, response.content_length) if response.content_length.present? | |
end | |
span.finish | |
[status, headers, body] | |
end | |
# https://sequel.jeremyevans.net/rdoc/classes/Sequel/Plugins.html | |
# https://github.com/jeremyevans/sequel/blob/master/doc/model_plugins.rdoc | |
module Sequel | |
module Plugins | |
# Sequel::Model instrumentation for Zipkin | |
module ZipkinTracing | |
# | |
# Meta-programming for creating method tracers for the Sequel::Model plugin. | |
module MethodWrapping | |
# Install a method named +method_name+ that will trace execution | |
# with a metric name derived from +operation_name+ (or +method_name+ if +operation_name+ | |
# isn't specified). | |
def wrap_sequel_method(method_name, operation_name = method_name) | |
define_method(method_name) do |*args, &block| | |
klass = self.is_a?(Class) ? self : self.class | |
model = self.try(:model)&.name || klass.name | |
sql = args.first | |
# In 4.37.0, sql was converted to a prepared statement object | |
sql = sql.is_a?(String) ? sql : sql.prepared_sql | |
# We don't want the sequel methods as the root spans. | |
# We prefer those that have a parent | |
parent_span = @tracer.current_span | |
if parent_span.nil? | |
return super(*args, &block) | |
end | |
# lets turn the operation name from `sequel.query` into something like | |
# `sequel.query(User)` | |
# This also removes any module at the prefix | |
model = model.split('::').last | |
operation_name = "#{operation_name}(#{model})" if model.present? | |
@tracer.trace(operation_name, parent: parent_span) do |span| | |
span.tag("/sql", sql) | |
super(*args, &block) | |
end | |
end | |
end | |
end | |
# This is the catch all for all other dataset methods that are accessed | |
# Inspiration from: | |
# https://github.com/newrelic/rpm/blob/abfff6456e/lib/sequel/plugins/newrelic_instrumentation.rb | |
# https://github.com/lightstep/ls-trace-rb/blob/master/lib/ddtrace/contrib/sequel/dataset.rb | |
# https://github.com/foodiefm/sequel-opentracing/blob/master/lib/sequel/opentracing/dataset.rb | |
module DatasetMethods | |
extend Sequel::Plugins::ZipkinTracing::MethodWrapping | |
wrap_sequel_method :execute, "sequel.query" | |
wrap_sequel_method :execute_ddl, "sequel.ddl" | |
wrap_sequel_method :execute_dui, "sequel.dui" | |
wrap_sequel_method :execute_insert, "sequel.insert" | |
end | |
end | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment