Migrate correlation and tracing code to LabKit
This change is a fairly straightforward refactor to extract the tracing and correlation-id code from the gitlab rails codebase into the new LabKit-Ruby project. The corresponding import into LabKit-Ruby was in https://gitlab.com/gitlab-org/labkit-ruby/merge_requests/1 The code itself remains very similar for now. Extracting it allows us to reuse it in other projects, such as Gitaly-Ruby. This will give us the advantages of correlation-ids and distributed tracing in that project too.
This commit is contained in:
parent
d9e5edf198
commit
4f4de36cac
41 changed files with 47 additions and 1197 deletions
9
Gemfile
9
Gemfile
|
@ -274,6 +274,9 @@ gem 'sentry-raven', '~> 2.7'
|
|||
|
||||
gem 'premailer-rails', '~> 1.9.7'
|
||||
|
||||
# LabKit: Tracing and Correlation
|
||||
gem 'gitlab-labkit', '~> 0.1.2'
|
||||
|
||||
# I18n
|
||||
gem 'ruby_parser', '~> 3.8', require: false
|
||||
gem 'rails-i18n', '~> 5.1'
|
||||
|
@ -301,12 +304,6 @@ group :metrics do
|
|||
gem 'raindrops', '~> 0.18'
|
||||
end
|
||||
|
||||
group :tracing do
|
||||
# OpenTracing
|
||||
gem 'opentracing', '~> 0.4.3'
|
||||
gem 'jaeger-client', '~> 0.10.0'
|
||||
end
|
||||
|
||||
group :development do
|
||||
gem 'foreman', '~> 0.84.0'
|
||||
gem 'brakeman', '~> 4.2', require: false
|
||||
|
|
11
Gemfile.lock
11
Gemfile.lock
|
@ -286,6 +286,12 @@ GEM
|
|||
github-markup (1.7.0)
|
||||
gitlab-default_value_for (3.1.1)
|
||||
activerecord (>= 3.2.0, < 6.0)
|
||||
gitlab-labkit (0.1.2)
|
||||
actionpack (~> 5)
|
||||
activesupport (~> 5)
|
||||
grpc (~> 1.15)
|
||||
jaeger-client (~> 0.10)
|
||||
opentracing (~> 0.4)
|
||||
gitlab-markup (1.7.0)
|
||||
gitlab-sidekiq-fetcher (0.4.0)
|
||||
sidekiq (~> 5)
|
||||
|
@ -571,7 +577,7 @@ GEM
|
|||
validate_email
|
||||
validate_url
|
||||
webfinger (>= 1.0.1)
|
||||
opentracing (0.4.3)
|
||||
opentracing (0.5.0)
|
||||
optimist (3.0.0)
|
||||
org-ruby (0.9.12)
|
||||
rubypants (~> 0.2)
|
||||
|
@ -1050,6 +1056,7 @@ DEPENDENCIES
|
|||
gitaly-proto (~> 1.19.0)
|
||||
github-markup (~> 1.7.0)
|
||||
gitlab-default_value_for (~> 3.1.1)
|
||||
gitlab-labkit (~> 0.1.2)
|
||||
gitlab-markup (~> 1.7.0)
|
||||
gitlab-sidekiq-fetcher (~> 0.4.0)
|
||||
gitlab-styles (~> 2.5)
|
||||
|
@ -1076,7 +1083,6 @@ DEPENDENCIES
|
|||
httparty (~> 0.16.4)
|
||||
icalendar
|
||||
influxdb (~> 0.2)
|
||||
jaeger-client (~> 0.10.0)
|
||||
jira-ruby (~> 1.4)
|
||||
js_regex (~> 3.1)
|
||||
json-schema (~> 2.8.0)
|
||||
|
@ -1117,7 +1123,6 @@ DEPENDENCIES
|
|||
omniauth-twitter (~> 1.4)
|
||||
omniauth-ultraauth (~> 0.0.1)
|
||||
omniauth_crowd (~> 2.2.0)
|
||||
opentracing (~> 0.4.3)
|
||||
org-ruby (~> 0.9.12)
|
||||
peek (~> 1.0.1)
|
||||
peek-gc (~> 0.0.2)
|
||||
|
|
|
@ -128,7 +128,7 @@ class ApplicationController < ActionController::Base
|
|||
|
||||
payload[:ua] = request.env["HTTP_USER_AGENT"]
|
||||
payload[:remote_ip] = request.remote_ip
|
||||
payload[Gitlab::CorrelationId::LOG_KEY] = Gitlab::CorrelationId.current_id
|
||||
payload[Labkit::Correlation::CorrelationId::LOG_KEY] = Labkit::Correlation::CorrelationId.current_id
|
||||
|
||||
logged_user = auth_user
|
||||
|
||||
|
|
5
changelogs/unreleased/an-use-labkit.yml
Normal file
5
changelogs/unreleased/an-use-labkit.yml
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Migrate correlation and tracing code to LabKit
|
||||
merge_request: 25379
|
||||
author:
|
||||
type: other
|
|
@ -35,7 +35,7 @@ unless Sidekiq.server?
|
|||
end
|
||||
|
||||
payload[:response] = event.payload[:response] if event.payload[:response]
|
||||
payload[Gitlab::CorrelationId::LOG_KEY] = Gitlab::CorrelationId.current_id
|
||||
payload[Labkit::Correlation::CorrelationId::LOG_KEY] = Labkit::Correlation::CorrelationId.current_id
|
||||
|
||||
payload
|
||||
end
|
||||
|
|
|
@ -19,7 +19,7 @@ Peek.into Peek::Views::Gitaly
|
|||
Peek.into Peek::Views::Rblineprof
|
||||
Peek.into Peek::Views::Redis
|
||||
Peek.into Peek::Views::GC
|
||||
Peek.into Peek::Views::Tracing if Gitlab::Tracing.tracing_url_enabled?
|
||||
Peek.into Peek::Views::Tracing if Labkit::Tracing.tracing_url_enabled?
|
||||
|
||||
# rubocop:disable Naming/ClassAndModuleCamelCase
|
||||
class PEEK_DB_CLIENT
|
||||
|
|
|
@ -1,16 +1,14 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
if Gitlab::Tracing.enabled?
|
||||
require 'opentracing'
|
||||
|
||||
if Labkit::Tracing.enabled?
|
||||
Rails.application.configure do |config|
|
||||
config.middleware.insert_after Gitlab::Middleware::CorrelationId, ::Gitlab::Tracing::RackMiddleware
|
||||
config.middleware.insert_after Gitlab::Middleware::CorrelationId, ::Labkit::Tracing::RackMiddleware
|
||||
end
|
||||
|
||||
# Instrument the Sidekiq client
|
||||
Sidekiq.configure_client do |config|
|
||||
config.client_middleware do |chain|
|
||||
chain.add Gitlab::Tracing::Sidekiq::ClientMiddleware
|
||||
chain.add Labkit::Tracing::Sidekiq::ClientMiddleware
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -18,20 +16,20 @@ if Gitlab::Tracing.enabled?
|
|||
if Sidekiq.server?
|
||||
Sidekiq.configure_server do |config|
|
||||
config.server_middleware do |chain|
|
||||
chain.add Gitlab::Tracing::Sidekiq::ServerMiddleware
|
||||
chain.add Labkit::Tracing::Sidekiq::ServerMiddleware
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Instrument Rails
|
||||
Gitlab::Tracing::Rails::ActiveRecordSubscriber.instrument
|
||||
Gitlab::Tracing::Rails::ActionViewSubscriber.instrument
|
||||
Labkit::Tracing::Rails::ActiveRecordSubscriber.instrument
|
||||
Labkit::Tracing::Rails::ActionViewSubscriber.instrument
|
||||
|
||||
# In multi-processed clustered architectures (puma, unicorn) don't
|
||||
# start tracing until the worker processes are spawned. This works
|
||||
# around issues when the opentracing implementation spawns threads
|
||||
Gitlab::Cluster::LifecycleEvents.on_worker_start do
|
||||
tracer = Gitlab::Tracing::Factory.create_tracer(Gitlab.process_name, Gitlab::Tracing.connection_string)
|
||||
tracer = Labkit::Tracing::Factory.create_tracer(Gitlab.process_name, Labkit::Tracing.connection_string)
|
||||
OpenTracing.global_tracer = tracer if tracer
|
||||
end
|
||||
end
|
||||
|
|
|
@ -52,9 +52,9 @@ module Gitlab
|
|||
end
|
||||
|
||||
def self.interceptors
|
||||
return [] unless Gitlab::Tracing.enabled?
|
||||
return [] unless Labkit::Tracing.enabled?
|
||||
|
||||
[Gitlab::Tracing::GRPCInterceptor.instance]
|
||||
[Labkit::Tracing::GRPCInterceptor.instance]
|
||||
end
|
||||
private_class_method :interceptors
|
||||
|
||||
|
@ -218,7 +218,7 @@ module Gitlab
|
|||
feature = feature_stack && feature_stack[0]
|
||||
metadata['call_site'] = feature.to_s if feature
|
||||
metadata['gitaly-servers'] = address_metadata(remote_storage) if remote_storage
|
||||
metadata['x-gitlab-correlation-id'] = Gitlab::CorrelationId.current_id if Gitlab::CorrelationId.current_id
|
||||
metadata['x-gitlab-correlation-id'] = Labkit::Correlation::CorrelationId.current_id if Labkit::Correlation::CorrelationId.current_id
|
||||
|
||||
metadata.merge!(server_feature_flags)
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ module Gitlab
|
|||
module Loggers
|
||||
class CorrelationIdLogger < ::GrapeLogging::Loggers::Base
|
||||
def parameters(_, _)
|
||||
{ Gitlab::CorrelationId::LOG_KEY => Gitlab::CorrelationId.current_id }
|
||||
{ Labkit::Correlation::CorrelationId::LOG_KEY => Labkit::Correlation::CorrelationId.current_id }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -10,7 +10,7 @@ module Gitlab
|
|||
data = {}
|
||||
data[:severity] = severity
|
||||
data[:time] = timestamp.utc.iso8601(3)
|
||||
data[Gitlab::CorrelationId::LOG_KEY] = Gitlab::CorrelationId.current_id
|
||||
data[Labkit::Correlation::CorrelationId::LOG_KEY] = Labkit::Correlation::CorrelationId.current_id
|
||||
|
||||
case message
|
||||
when String
|
||||
|
|
|
@ -12,7 +12,7 @@ module Gitlab
|
|||
end
|
||||
|
||||
def call(env)
|
||||
::Gitlab::CorrelationId.use_id(correlation_id(env)) do
|
||||
::Labkit::Correlation::CorrelationId.use_id(correlation_id(env)) do
|
||||
@app.call(env)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -45,7 +45,7 @@ module Gitlab
|
|||
context # Make sure we've set everything we know in the context
|
||||
|
||||
tags = {
|
||||
Gitlab::CorrelationId::LOG_KEY.to_sym => Gitlab::CorrelationId.current_id
|
||||
Labkit::Correlation::CorrelationId::LOG_KEY.to_sym => Labkit::Correlation::CorrelationId.current_id
|
||||
}
|
||||
|
||||
Raven.capture_exception(exception, tags: tags, extra: extra)
|
||||
|
|
|
@ -4,8 +4,8 @@ module Gitlab
|
|||
module SidekiqMiddleware
|
||||
class CorrelationInjector
|
||||
def call(worker_class, job, queue, redis_pool)
|
||||
job[Gitlab::CorrelationId::LOG_KEY] ||=
|
||||
Gitlab::CorrelationId.current_or_new_id
|
||||
job[Labkit::Correlation::CorrelationId::LOG_KEY] ||=
|
||||
Labkit::Correlation::CorrelationId.current_or_new_id
|
||||
|
||||
yield
|
||||
end
|
||||
|
|
|
@ -4,9 +4,9 @@ module Gitlab
|
|||
module SidekiqMiddleware
|
||||
class CorrelationLogger
|
||||
def call(worker, job, queue)
|
||||
correlation_id = job[Gitlab::CorrelationId::LOG_KEY]
|
||||
correlation_id = job[Labkit::Correlation::CorrelationId::LOG_KEY]
|
||||
|
||||
Gitlab::CorrelationId.use_id(correlation_id) do
|
||||
Labkit::Correlation::CorrelationId.use_id(correlation_id) do
|
||||
yield
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,69 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'opentracing'
|
||||
|
||||
module Gitlab
|
||||
module Tracing
|
||||
module Common
|
||||
def tracer
|
||||
OpenTracing.global_tracer
|
||||
end
|
||||
|
||||
# Convience method for running a block with a span
|
||||
def in_tracing_span(operation_name:, tags:, child_of: nil)
|
||||
scope = tracer.start_active_span(
|
||||
operation_name,
|
||||
child_of: child_of,
|
||||
tags: tags
|
||||
)
|
||||
span = scope.span
|
||||
|
||||
# Add correlation details to the span if we have them
|
||||
correlation_id = Gitlab::CorrelationId.current_id
|
||||
if correlation_id
|
||||
span.set_tag('correlation_id', correlation_id)
|
||||
end
|
||||
|
||||
begin
|
||||
yield span
|
||||
rescue => e
|
||||
log_exception_on_span(span, e)
|
||||
raise e
|
||||
ensure
|
||||
scope.close
|
||||
end
|
||||
end
|
||||
|
||||
def postnotify_span(operation_name, start_time, end_time, tags: nil, child_of: nil, exception: nil)
|
||||
span = OpenTracing.start_span(operation_name, start_time: start_time, tags: tags, child_of: child_of)
|
||||
|
||||
log_exception_on_span(span, exception) if exception
|
||||
|
||||
span.finish(end_time: end_time)
|
||||
end
|
||||
|
||||
def log_exception_on_span(span, exception)
|
||||
span.set_tag('error', true)
|
||||
span.log_kv(kv_tags_for_exception(exception))
|
||||
end
|
||||
|
||||
def kv_tags_for_exception(exception)
|
||||
case exception
|
||||
when Exception
|
||||
{
|
||||
'event': 'error',
|
||||
'error.kind': exception.class.to_s,
|
||||
'message': Gitlab::UrlSanitizer.sanitize(exception.message),
|
||||
'stack': exception.backtrace&.join("\n")
|
||||
}
|
||||
else
|
||||
{
|
||||
'event': 'error',
|
||||
'error.kind': exception.class.to_s,
|
||||
'error.object': Gitlab::UrlSanitizer.sanitize(exception.to_s)
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,61 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "cgi"
|
||||
|
||||
module Gitlab
|
||||
module Tracing
|
||||
class Factory
|
||||
OPENTRACING_SCHEME = "opentracing"
|
||||
|
||||
def self.create_tracer(service_name, connection_string)
|
||||
return unless connection_string.present?
|
||||
|
||||
begin
|
||||
opentracing_details = parse_connection_string(connection_string)
|
||||
driver_name = opentracing_details[:driver_name]
|
||||
|
||||
case driver_name
|
||||
when "jaeger"
|
||||
JaegerFactory.create_tracer(service_name, opentracing_details[:options])
|
||||
else
|
||||
raise "Unknown driver: #{driver_name}"
|
||||
end
|
||||
rescue => e
|
||||
# Can't create the tracer? Warn and continue sans tracer
|
||||
warn "Unable to instantiate tracer: #{e}"
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
def self.parse_connection_string(connection_string)
|
||||
parsed = URI.parse(connection_string)
|
||||
|
||||
unless valid_uri?(parsed)
|
||||
raise "Invalid tracing connection string"
|
||||
end
|
||||
|
||||
{
|
||||
driver_name: parsed.host,
|
||||
options: parse_query(parsed.query)
|
||||
}
|
||||
end
|
||||
private_class_method :parse_connection_string
|
||||
|
||||
def self.parse_query(query)
|
||||
return {} unless query
|
||||
|
||||
CGI.parse(query).symbolize_keys.transform_values(&:first)
|
||||
end
|
||||
private_class_method :parse_query
|
||||
|
||||
def self.valid_uri?(uri)
|
||||
return false unless uri
|
||||
|
||||
uri.scheme == OPENTRACING_SCHEME &&
|
||||
uri.host.to_s =~ /^[a-z0-9_]+$/ &&
|
||||
uri.path.empty?
|
||||
end
|
||||
private_class_method :valid_uri?
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,54 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'opentracing'
|
||||
require 'grpc'
|
||||
|
||||
module Gitlab
|
||||
module Tracing
|
||||
class GRPCInterceptor < GRPC::ClientInterceptor
|
||||
include Common
|
||||
include Singleton
|
||||
|
||||
def request_response(request:, call:, method:, metadata:)
|
||||
wrap_with_tracing(method, 'unary', metadata) do
|
||||
yield
|
||||
end
|
||||
end
|
||||
|
||||
def client_streamer(requests:, call:, method:, metadata:)
|
||||
wrap_with_tracing(method, 'client_stream', metadata) do
|
||||
yield
|
||||
end
|
||||
end
|
||||
|
||||
def server_streamer(request:, call:, method:, metadata:)
|
||||
wrap_with_tracing(method, 'server_stream', metadata) do
|
||||
yield
|
||||
end
|
||||
end
|
||||
|
||||
def bidi_streamer(requests:, call:, method:, metadata:)
|
||||
wrap_with_tracing(method, 'bidi_stream', metadata) do
|
||||
yield
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def wrap_with_tracing(method, grpc_type, metadata)
|
||||
tags = {
|
||||
'component' => 'grpc',
|
||||
'span.kind' => 'client',
|
||||
'grpc.method' => method,
|
||||
'grpc.type' => grpc_type
|
||||
}
|
||||
|
||||
in_tracing_span(operation_name: "grpc:#{method}", tags: tags) do |span|
|
||||
OpenTracing.inject(span.context, OpenTracing::FORMAT_TEXT_MAP, metadata)
|
||||
|
||||
yield
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,97 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'jaeger/client'
|
||||
|
||||
module Gitlab
|
||||
module Tracing
|
||||
class JaegerFactory
|
||||
# When the probabilistic sampler is used, by default 0.1% of requests will be traced
|
||||
DEFAULT_PROBABILISTIC_RATE = 0.001
|
||||
|
||||
# The default port for the Jaeger agent UDP listener
|
||||
DEFAULT_UDP_PORT = 6831
|
||||
|
||||
# Reduce this from default of 10 seconds as the Ruby jaeger
|
||||
# client doesn't have overflow control, leading to very large
|
||||
# messages which fail to send over UDP (max packet = 64k)
|
||||
# Flush more often, with smaller packets
|
||||
FLUSH_INTERVAL = 5
|
||||
|
||||
def self.create_tracer(service_name, options)
|
||||
kwargs = {
|
||||
service_name: service_name,
|
||||
sampler: get_sampler(options[:sampler], options[:sampler_param]),
|
||||
reporter: get_reporter(service_name, options[:http_endpoint], options[:udp_endpoint])
|
||||
}.compact
|
||||
|
||||
extra_params = options.except(:sampler, :sampler_param, :http_endpoint, :udp_endpoint, :strict_parsing, :debug) # rubocop: disable CodeReuse/ActiveRecord
|
||||
if extra_params.present?
|
||||
message = "jaeger tracer: invalid option: #{extra_params.keys.join(", ")}"
|
||||
|
||||
if options[:strict_parsing]
|
||||
raise message
|
||||
else
|
||||
warn message
|
||||
end
|
||||
end
|
||||
|
||||
Jaeger::Client.build(kwargs)
|
||||
end
|
||||
|
||||
def self.get_sampler(sampler_type, sampler_param)
|
||||
case sampler_type
|
||||
when "probabilistic"
|
||||
sampler_rate = sampler_param ? sampler_param.to_f : DEFAULT_PROBABILISTIC_RATE
|
||||
Jaeger::Samplers::Probabilistic.new(rate: sampler_rate)
|
||||
when "const"
|
||||
const_value = sampler_param == "1"
|
||||
Jaeger::Samplers::Const.new(const_value)
|
||||
else
|
||||
nil
|
||||
end
|
||||
end
|
||||
private_class_method :get_sampler
|
||||
|
||||
def self.get_reporter(service_name, http_endpoint, udp_endpoint)
|
||||
encoder = Jaeger::Encoders::ThriftEncoder.new(service_name: service_name)
|
||||
|
||||
if http_endpoint.present?
|
||||
sender = get_http_sender(encoder, http_endpoint)
|
||||
elsif udp_endpoint.present?
|
||||
sender = get_udp_sender(encoder, udp_endpoint)
|
||||
else
|
||||
return
|
||||
end
|
||||
|
||||
Jaeger::Reporters::RemoteReporter.new(
|
||||
sender: sender,
|
||||
flush_interval: FLUSH_INTERVAL
|
||||
)
|
||||
end
|
||||
private_class_method :get_reporter
|
||||
|
||||
def self.get_http_sender(encoder, address)
|
||||
Jaeger::HttpSender.new(
|
||||
url: address,
|
||||
encoder: encoder,
|
||||
logger: Logger.new(STDOUT)
|
||||
)
|
||||
end
|
||||
private_class_method :get_http_sender
|
||||
|
||||
def self.get_udp_sender(encoder, address)
|
||||
pair = address.split(":", 2)
|
||||
host = pair[0]
|
||||
port = pair[1] ? pair[1].to_i : DEFAULT_UDP_PORT
|
||||
|
||||
Jaeger::UdpSender.new(
|
||||
host: host,
|
||||
port: port,
|
||||
encoder: encoder,
|
||||
logger: Logger.new(STDOUT)
|
||||
)
|
||||
end
|
||||
private_class_method :get_udp_sender
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,46 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'opentracing'
|
||||
|
||||
module Gitlab
|
||||
module Tracing
|
||||
class RackMiddleware
|
||||
include Common
|
||||
|
||||
REQUEST_METHOD = 'REQUEST_METHOD'
|
||||
|
||||
def initialize(app)
|
||||
@app = app
|
||||
end
|
||||
|
||||
def call(env)
|
||||
method = env[REQUEST_METHOD]
|
||||
|
||||
context = tracer.extract(OpenTracing::FORMAT_RACK, env)
|
||||
tags = {
|
||||
'component' => 'rack',
|
||||
'span.kind' => 'server',
|
||||
'http.method' => method,
|
||||
'http.url' => self.class.build_sanitized_url_from_env(env)
|
||||
}
|
||||
|
||||
in_tracing_span(operation_name: "http:#{method}", child_of: context, tags: tags) do |span|
|
||||
@app.call(env).tap do |status_code, _headers, _body|
|
||||
span.set_tag('http.status_code', status_code)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Generate a sanitized (safe) request URL from the rack environment
|
||||
def self.build_sanitized_url_from_env(env)
|
||||
request = ActionDispatch::Request.new(env)
|
||||
|
||||
original_url = request.original_url
|
||||
uri = URI.parse(original_url)
|
||||
uri.query = request.filtered_parameters.to_query if uri.query.present?
|
||||
|
||||
uri.to_s
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,75 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module Tracing
|
||||
module Rails
|
||||
class ActionViewSubscriber
|
||||
include RailsCommon
|
||||
|
||||
COMPONENT_TAG = 'ActionView'
|
||||
RENDER_TEMPLATE_NOTIFICATION_TOPIC = 'render_template.action_view'
|
||||
RENDER_COLLECTION_NOTIFICATION_TOPIC = 'render_collection.action_view'
|
||||
RENDER_PARTIAL_NOTIFICATION_TOPIC = 'render_partial.action_view'
|
||||
|
||||
# Instruments Rails ActionView events for opentracing.
|
||||
# Returns a lambda, which, when called will unsubscribe from the notifications
|
||||
def self.instrument
|
||||
subscriber = new
|
||||
|
||||
subscriptions = [
|
||||
ActiveSupport::Notifications.subscribe(RENDER_TEMPLATE_NOTIFICATION_TOPIC) do |_, start, finish, _, payload|
|
||||
subscriber.notify_render_template(start, finish, payload)
|
||||
end,
|
||||
ActiveSupport::Notifications.subscribe(RENDER_COLLECTION_NOTIFICATION_TOPIC) do |_, start, finish, _, payload|
|
||||
subscriber.notify_render_collection(start, finish, payload)
|
||||
end,
|
||||
ActiveSupport::Notifications.subscribe(RENDER_PARTIAL_NOTIFICATION_TOPIC) do |_, start, finish, _, payload|
|
||||
subscriber.notify_render_partial(start, finish, payload)
|
||||
end
|
||||
]
|
||||
|
||||
create_unsubscriber subscriptions
|
||||
end
|
||||
|
||||
# For more information on the payloads: https://guides.rubyonrails.org/active_support_instrumentation.html
|
||||
def notify_render_template(start, finish, payload)
|
||||
generate_span_for_notification("render_template", start, finish, payload, tags_for_render_template(payload))
|
||||
end
|
||||
|
||||
def notify_render_collection(start, finish, payload)
|
||||
generate_span_for_notification("render_collection", start, finish, payload, tags_for_render_collection(payload))
|
||||
end
|
||||
|
||||
def notify_render_partial(start, finish, payload)
|
||||
generate_span_for_notification("render_partial", start, finish, payload, tags_for_render_partial(payload))
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def tags_for_render_template(payload)
|
||||
{
|
||||
'component' => COMPONENT_TAG,
|
||||
'template.id' => payload[:identifier],
|
||||
'template.layout' => payload[:layout]
|
||||
}
|
||||
end
|
||||
|
||||
def tags_for_render_collection(payload)
|
||||
{
|
||||
'component' => COMPONENT_TAG,
|
||||
'template.id' => payload[:identifier],
|
||||
'template.count' => payload[:count] || 0,
|
||||
'template.cache.hits' => payload[:cache_hits] || 0
|
||||
}
|
||||
end
|
||||
|
||||
def tags_for_render_partial(payload)
|
||||
{
|
||||
'component' => COMPONENT_TAG,
|
||||
'template.id' => payload[:identifier]
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,49 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module Tracing
|
||||
module Rails
|
||||
class ActiveRecordSubscriber
|
||||
include RailsCommon
|
||||
|
||||
ACTIVE_RECORD_NOTIFICATION_TOPIC = 'sql.active_record'
|
||||
OPERATION_NAME_PREFIX = 'active_record:'
|
||||
DEFAULT_OPERATION_NAME = 'sqlquery'
|
||||
|
||||
# Instruments Rails ActiveRecord events for opentracing.
|
||||
# Returns a lambda, which, when called will unsubscribe from the notifications
|
||||
def self.instrument
|
||||
subscriber = new
|
||||
|
||||
subscription = ActiveSupport::Notifications.subscribe(ACTIVE_RECORD_NOTIFICATION_TOPIC) do |_, start, finish, _, payload|
|
||||
subscriber.notify(start, finish, payload)
|
||||
end
|
||||
|
||||
create_unsubscriber [subscription]
|
||||
end
|
||||
|
||||
# For more information on the payloads: https://guides.rubyonrails.org/active_support_instrumentation.html
|
||||
def notify(start, finish, payload)
|
||||
generate_span_for_notification(notification_name(payload), start, finish, payload, tags_for_notification(payload))
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def notification_name(payload)
|
||||
OPERATION_NAME_PREFIX + (payload[:name].presence || DEFAULT_OPERATION_NAME)
|
||||
end
|
||||
|
||||
def tags_for_notification(payload)
|
||||
{
|
||||
'component' => 'ActiveRecord',
|
||||
'span.kind' => 'client',
|
||||
'db.type' => 'sql',
|
||||
'db.connection_id' => payload[:connection_id],
|
||||
'db.cached' => payload[:cached] || false,
|
||||
'db.statement' => payload[:sql]
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,24 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module Tracing
|
||||
module Rails
|
||||
module RailsCommon
|
||||
extend ActiveSupport::Concern
|
||||
include Gitlab::Tracing::Common
|
||||
|
||||
class_methods do
|
||||
def create_unsubscriber(subscriptions)
|
||||
-> { subscriptions.each { |subscriber| ActiveSupport::Notifications.unsubscribe(subscriber) } }
|
||||
end
|
||||
end
|
||||
|
||||
def generate_span_for_notification(operation_name, start, finish, payload, tags)
|
||||
exception = payload[:exception]
|
||||
|
||||
postnotify_span(operation_name, start, finish, tags: tags, exception: exception)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,26 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'opentracing'
|
||||
|
||||
module Gitlab
|
||||
module Tracing
|
||||
module Sidekiq
|
||||
class ClientMiddleware
|
||||
include SidekiqCommon
|
||||
|
||||
SPAN_KIND = 'client'
|
||||
|
||||
def call(worker_class, job, queue, redis_pool)
|
||||
in_tracing_span(
|
||||
operation_name: "sidekiq:#{job['class']}",
|
||||
tags: tags_from_job(job, SPAN_KIND)) do |span|
|
||||
# Inject the details directly into the job
|
||||
tracer.inject(span.context, OpenTracing::FORMAT_TEXT_MAP, job)
|
||||
|
||||
yield
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,26 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'opentracing'
|
||||
|
||||
module Gitlab
|
||||
module Tracing
|
||||
module Sidekiq
|
||||
class ServerMiddleware
|
||||
include SidekiqCommon
|
||||
|
||||
SPAN_KIND = 'server'
|
||||
|
||||
def call(worker, job, queue)
|
||||
context = tracer.extract(OpenTracing::FORMAT_TEXT_MAP, job)
|
||||
|
||||
in_tracing_span(
|
||||
operation_name: "sidekiq:#{job['class']}",
|
||||
child_of: context,
|
||||
tags: tags_from_job(job, SPAN_KIND)) do |span|
|
||||
yield
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,22 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module Tracing
|
||||
module Sidekiq
|
||||
module SidekiqCommon
|
||||
include Gitlab::Tracing::Common
|
||||
|
||||
def tags_from_job(job, kind)
|
||||
{
|
||||
'component' => 'sidekiq',
|
||||
'span.kind' => kind,
|
||||
'sidekiq.queue' => job['queue'],
|
||||
'sidekiq.jid' => job['jid'],
|
||||
'sidekiq.retry' => job['retry'].to_s,
|
||||
'sidekiq.args' => job['args']&.join(", ")
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -4,9 +4,9 @@ module Peek
|
|||
module Views
|
||||
class Tracing < View
|
||||
def results
|
||||
{
|
||||
tracing_url: Gitlab::Tracing.tracing_url
|
||||
}
|
||||
tracing_url = Labkit::Tracing.tracing_url(Gitlab.process_name)
|
||||
|
||||
{ tracing_url: tracing_url }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -462,7 +462,7 @@ describe ApplicationController do
|
|||
end
|
||||
|
||||
it 'does log correlation id' do
|
||||
Gitlab::CorrelationId.use_id('new-id') do
|
||||
Labkit::Correlation::CorrelationId.use_id('new-id') do
|
||||
get :index
|
||||
end
|
||||
|
||||
|
|
|
@ -1,77 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'fast_spec_helper'
|
||||
|
||||
describe Gitlab::CorrelationId do
|
||||
describe '.use_id' do
|
||||
it 'yields when executed' do
|
||||
expect { |blk| described_class.use_id('id', &blk) }.to yield_control
|
||||
end
|
||||
|
||||
it 'stacks correlation ids' do
|
||||
described_class.use_id('id1') do
|
||||
described_class.use_id('id2') do |current_id|
|
||||
expect(current_id).to eq('id2')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it 'for missing correlation id it generates random one' do
|
||||
described_class.use_id('id1') do
|
||||
described_class.use_id(nil) do |current_id|
|
||||
expect(current_id).not_to be_empty
|
||||
expect(current_id).not_to eq('id1')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '.current_id' do
|
||||
subject { described_class.current_id }
|
||||
|
||||
it 'returns last correlation id' do
|
||||
described_class.use_id('id1') do
|
||||
described_class.use_id('id2') do
|
||||
is_expected.to eq('id2')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '.current_or_new_id' do
|
||||
subject { described_class.current_or_new_id }
|
||||
|
||||
context 'when correlation id is set' do
|
||||
it 'returns last correlation id' do
|
||||
described_class.use_id('id1') do
|
||||
is_expected.to eq('id1')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when correlation id is missing' do
|
||||
it 'returns a new correlation id' do
|
||||
expect(described_class).to receive(:new_id)
|
||||
.and_call_original
|
||||
|
||||
is_expected.not_to be_empty
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '.ids' do
|
||||
subject { described_class.send(:ids) }
|
||||
|
||||
it 'returns empty list if not correlation is used' do
|
||||
is_expected.to be_empty
|
||||
end
|
||||
|
||||
it 'returns list if correlation ids are used' do
|
||||
described_class.use_id('id1') do
|
||||
described_class.use_id('id2') do
|
||||
is_expected.to eq(%w(id1 id2))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -8,7 +8,7 @@ describe Gitlab::JsonLogger do
|
|||
|
||||
describe '#format_message' do
|
||||
before do
|
||||
allow(Gitlab::CorrelationId).to receive(:current_id).and_return('new-correlation-id')
|
||||
allow(Labkit::Correlation::CorrelationId).to receive(:current_id).and_return('new-correlation-id')
|
||||
end
|
||||
|
||||
it 'formats strings' do
|
||||
|
|
|
@ -27,7 +27,7 @@ describe Gitlab::Sentry do
|
|||
context 'when exceptions should not be raised' do
|
||||
before do
|
||||
allow(described_class).to receive(:should_raise_for_dev?).and_return(false)
|
||||
allow(Gitlab::CorrelationId).to receive(:current_id).and_return('cid')
|
||||
allow(Labkit::Correlation::CorrelationId).to receive(:current_id).and_return('cid')
|
||||
end
|
||||
|
||||
it 'logs the exception with all attributes passed' do
|
||||
|
@ -65,7 +65,7 @@ describe Gitlab::Sentry do
|
|||
|
||||
before do
|
||||
allow(described_class).to receive(:enabled?).and_return(true)
|
||||
allow(Gitlab::CorrelationId).to receive(:current_id).and_return('cid')
|
||||
allow(Labkit::Correlation::CorrelationId).to receive(:current_id).and_return('cid')
|
||||
end
|
||||
|
||||
it 'calls Raven.capture_exception' do
|
||||
|
|
|
@ -30,7 +30,7 @@ describe Gitlab::SidekiqMiddleware::CorrelationInjector do
|
|||
it 'injects into payload the correlation id' do
|
||||
expect_any_instance_of(described_class).to receive(:call).and_call_original
|
||||
|
||||
Gitlab::CorrelationId.use_id('new-correlation-id') do
|
||||
Labkit::Correlation::CorrelationId.use_id('new-correlation-id') do
|
||||
TestWorker.perform_async(1234)
|
||||
end
|
||||
|
||||
|
|
|
@ -23,7 +23,7 @@ describe Gitlab::SidekiqMiddleware::CorrelationLogger do
|
|||
expect_any_instance_of(described_class).to receive(:call).and_call_original
|
||||
|
||||
expect_any_instance_of(TestWorker).to receive(:perform).with(1234) do
|
||||
expect(Gitlab::CorrelationId.current_id).to eq('new-correlation-id')
|
||||
expect(Labkit::Correlation::CorrelationId.current_id).to eq('new-correlation-id')
|
||||
end
|
||||
|
||||
Sidekiq::Client.push(
|
||||
|
|
|
@ -1,43 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'fast_spec_helper'
|
||||
|
||||
describe Gitlab::Tracing::Factory do
|
||||
describe '.create_tracer' do
|
||||
let(:service_name) { 'rspec' }
|
||||
|
||||
context "when tracing is not configured" do
|
||||
it 'ignores null connection strings' do
|
||||
expect(described_class.create_tracer(service_name, nil)).to be_nil
|
||||
end
|
||||
|
||||
it 'ignores empty connection strings' do
|
||||
expect(described_class.create_tracer(service_name, '')).to be_nil
|
||||
end
|
||||
|
||||
it 'ignores unknown implementations' do
|
||||
expect(described_class.create_tracer(service_name, 'opentracing://invalid_driver')).to be_nil
|
||||
end
|
||||
|
||||
it 'ignores invalid connection strings' do
|
||||
expect(described_class.create_tracer(service_name, 'open?tracing')).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context "when tracing is configured with jaeger" do
|
||||
let(:mock_tracer) { double('tracer') }
|
||||
|
||||
it 'processes default connections' do
|
||||
expect(Gitlab::Tracing::JaegerFactory).to receive(:create_tracer).with(service_name, {}).and_return(mock_tracer)
|
||||
|
||||
expect(described_class.create_tracer(service_name, 'opentracing://jaeger')).to be(mock_tracer)
|
||||
end
|
||||
|
||||
it 'processes connections with parameters' do
|
||||
expect(Gitlab::Tracing::JaegerFactory).to receive(:create_tracer).with(service_name, { a: '1', b: '2', c: '3' }).and_return(mock_tracer)
|
||||
|
||||
expect(described_class.create_tracer(service_name, 'opentracing://jaeger?a=1&b=2&c=3')).to be(mock_tracer)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,47 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'fast_spec_helper'
|
||||
|
||||
describe Gitlab::Tracing::GRPCInterceptor do
|
||||
subject { described_class.instance }
|
||||
|
||||
shared_examples_for "a grpc interceptor method" do
|
||||
let(:custom_error) { Class.new(StandardError) }
|
||||
|
||||
it 'yields' do
|
||||
expect { |b| method.call(kwargs, &b) }.to yield_control
|
||||
end
|
||||
|
||||
it 'propagates exceptions' do
|
||||
expect { method.call(kwargs) { raise custom_error } }.to raise_error(custom_error)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#request_response' do
|
||||
let(:method) { subject.method(:request_response) }
|
||||
let(:kwargs) { { request: {}, call: {}, method: 'grc_method', metadata: {} } }
|
||||
|
||||
it_behaves_like 'a grpc interceptor method'
|
||||
end
|
||||
|
||||
describe '#client_streamer' do
|
||||
let(:method) { subject.method(:client_streamer) }
|
||||
let(:kwargs) { { requests: [], call: {}, method: 'grc_method', metadata: {} } }
|
||||
|
||||
it_behaves_like 'a grpc interceptor method'
|
||||
end
|
||||
|
||||
describe '#server_streamer' do
|
||||
let(:method) { subject.method(:server_streamer) }
|
||||
let(:kwargs) { { request: {}, call: {}, method: 'grc_method', metadata: {} } }
|
||||
|
||||
it_behaves_like 'a grpc interceptor method'
|
||||
end
|
||||
|
||||
describe '#bidi_streamer' do
|
||||
let(:method) { subject.method(:bidi_streamer) }
|
||||
let(:kwargs) { { requests: [], call: {}, method: 'grc_method', metadata: {} } }
|
||||
|
||||
it_behaves_like 'a grpc interceptor method'
|
||||
end
|
||||
end
|
|
@ -1,71 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'fast_spec_helper'
|
||||
|
||||
describe Gitlab::Tracing::JaegerFactory do
|
||||
describe '.create_tracer' do
|
||||
let(:service_name) { 'rspec' }
|
||||
|
||||
shared_examples_for 'a jaeger tracer' do
|
||||
it 'responds to active_span methods' do
|
||||
expect(tracer).to respond_to(:active_span)
|
||||
end
|
||||
|
||||
it 'yields control' do
|
||||
expect { |b| tracer.start_active_span('operation_name', &b) }.to yield_control
|
||||
end
|
||||
end
|
||||
|
||||
context 'processes default connections' do
|
||||
it_behaves_like 'a jaeger tracer' do
|
||||
let(:tracer) { described_class.create_tracer(service_name, {}) }
|
||||
end
|
||||
end
|
||||
|
||||
context 'handles debug options' do
|
||||
it_behaves_like 'a jaeger tracer' do
|
||||
let(:tracer) { described_class.create_tracer(service_name, { debug: "1" }) }
|
||||
end
|
||||
end
|
||||
|
||||
context 'handles const sampler' do
|
||||
it_behaves_like 'a jaeger tracer' do
|
||||
let(:tracer) { described_class.create_tracer(service_name, { sampler: "const", sampler_param: "1" }) }
|
||||
end
|
||||
end
|
||||
|
||||
context 'handles probabilistic sampler' do
|
||||
it_behaves_like 'a jaeger tracer' do
|
||||
let(:tracer) { described_class.create_tracer(service_name, { sampler: "probabilistic", sampler_param: "0.5" }) }
|
||||
end
|
||||
end
|
||||
|
||||
context 'handles http_endpoint configurations' do
|
||||
it_behaves_like 'a jaeger tracer' do
|
||||
let(:tracer) { described_class.create_tracer(service_name, { http_endpoint: "http://localhost:1234" }) }
|
||||
end
|
||||
end
|
||||
|
||||
context 'handles udp_endpoint configurations' do
|
||||
it_behaves_like 'a jaeger tracer' do
|
||||
let(:tracer) { described_class.create_tracer(service_name, { udp_endpoint: "localhost:4321" }) }
|
||||
end
|
||||
end
|
||||
|
||||
context 'ignores invalid parameters' do
|
||||
it_behaves_like 'a jaeger tracer' do
|
||||
let(:tracer) { described_class.create_tracer(service_name, { invalid: "true" }) }
|
||||
end
|
||||
end
|
||||
|
||||
context 'accepts the debug parameter when strict_parser is set' do
|
||||
it_behaves_like 'a jaeger tracer' do
|
||||
let(:tracer) { described_class.create_tracer(service_name, { debug: "1", strict_parsing: "1" }) }
|
||||
end
|
||||
end
|
||||
|
||||
it 'rejects invalid parameters when strict_parser is set' do
|
||||
expect { described_class.create_tracer(service_name, { invalid: "true", strict_parsing: "1" }) }.to raise_error(StandardError)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,62 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
describe Gitlab::Tracing::RackMiddleware do
|
||||
using RSpec::Parameterized::TableSyntax
|
||||
|
||||
describe '#call' do
|
||||
context 'for normal middleware flow' do
|
||||
let(:fake_app) { -> (env) { fake_app_response } }
|
||||
subject { described_class.new(fake_app) }
|
||||
let(:request) { }
|
||||
|
||||
context 'for 200 responses' do
|
||||
let(:fake_app_response) { [200, { 'Content-Type': 'text/plain' }, ['OK']] }
|
||||
|
||||
it 'delegates correctly' do
|
||||
expect(subject.call(Rack::MockRequest.env_for("/"))).to eq(fake_app_response)
|
||||
end
|
||||
end
|
||||
|
||||
context 'for 500 responses' do
|
||||
let(:fake_app_response) { [500, { 'Content-Type': 'text/plain' }, ['Error']] }
|
||||
|
||||
it 'delegates correctly' do
|
||||
expect(subject.call(Rack::MockRequest.env_for("/"))).to eq(fake_app_response)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when an application is raising an exception' do
|
||||
let(:custom_error) { Class.new(StandardError) }
|
||||
let(:fake_app) { ->(env) { raise custom_error } }
|
||||
|
||||
subject { described_class.new(fake_app) }
|
||||
|
||||
it 'delegates propagates exceptions correctly' do
|
||||
expect { subject.call(Rack::MockRequest.env_for("/")) }.to raise_error(custom_error)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '.build_sanitized_url_from_env' do
|
||||
def env_for_url(url)
|
||||
env = Rack::MockRequest.env_for(input_url)
|
||||
env['action_dispatch.parameter_filter'] = [/token/]
|
||||
|
||||
env
|
||||
end
|
||||
|
||||
where(:input_url, :output_url) do
|
||||
'/gitlab-org/gitlab-ce' | 'http://example.org/gitlab-org/gitlab-ce'
|
||||
'/gitlab-org/gitlab-ce?safe=1' | 'http://example.org/gitlab-org/gitlab-ce?safe=1'
|
||||
'/gitlab-org/gitlab-ce?private_token=secret' | 'http://example.org/gitlab-org/gitlab-ce?private_token=%5BFILTERED%5D'
|
||||
'/gitlab-org/gitlab-ce?mixed=1&private_token=secret' | 'http://example.org/gitlab-org/gitlab-ce?mixed=1&private_token=%5BFILTERED%5D'
|
||||
end
|
||||
|
||||
with_them do
|
||||
it { expect(described_class.build_sanitized_url_from_env(env_for_url(input_url))).to eq(output_url) }
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,147 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'fast_spec_helper'
|
||||
require 'rspec-parameterized'
|
||||
|
||||
describe Gitlab::Tracing::Rails::ActionViewSubscriber do
|
||||
using RSpec::Parameterized::TableSyntax
|
||||
|
||||
shared_examples 'an actionview notification' do
|
||||
it 'notifies the tracer when the hash contains null values' do
|
||||
expect(subject).to receive(:postnotify_span).with(notification_name, start, finish, tags: expected_tags, exception: exception)
|
||||
|
||||
subject.public_send(notify_method, start, finish, payload)
|
||||
end
|
||||
|
||||
it 'notifies the tracer when the payload is missing values' do
|
||||
expect(subject).to receive(:postnotify_span).with(notification_name, start, finish, tags: expected_tags, exception: exception)
|
||||
|
||||
subject.public_send(notify_method, start, finish, payload.compact)
|
||||
end
|
||||
|
||||
it 'does not throw exceptions when with the default tracer' do
|
||||
expect { subject.public_send(notify_method, start, finish, payload) }.not_to raise_error
|
||||
end
|
||||
end
|
||||
|
||||
describe '.instrument' do
|
||||
it 'is unsubscribeable' do
|
||||
unsubscribe = described_class.instrument
|
||||
|
||||
expect(unsubscribe).not_to be_nil
|
||||
expect { unsubscribe.call }.not_to raise_error
|
||||
end
|
||||
end
|
||||
|
||||
describe '#notify_render_template' do
|
||||
subject { described_class.new }
|
||||
let(:start) { Time.now }
|
||||
let(:finish) { Time.now }
|
||||
let(:notification_name) { 'render_template' }
|
||||
let(:notify_method) { :notify_render_template }
|
||||
|
||||
where(:identifier, :layout, :exception) do
|
||||
nil | nil | nil
|
||||
"" | nil | nil
|
||||
"show.haml" | nil | nil
|
||||
nil | "" | nil
|
||||
nil | "layout.haml" | nil
|
||||
nil | nil | StandardError.new
|
||||
end
|
||||
|
||||
with_them do
|
||||
let(:payload) do
|
||||
{
|
||||
exception: exception,
|
||||
identifier: identifier,
|
||||
layout: layout
|
||||
}
|
||||
end
|
||||
|
||||
let(:expected_tags) do
|
||||
{
|
||||
'component' => 'ActionView',
|
||||
'template.id' => identifier,
|
||||
'template.layout' => layout
|
||||
}
|
||||
end
|
||||
|
||||
it_behaves_like 'an actionview notification'
|
||||
end
|
||||
end
|
||||
|
||||
describe '#notify_render_collection' do
|
||||
subject { described_class.new }
|
||||
let(:start) { Time.now }
|
||||
let(:finish) { Time.now }
|
||||
let(:notification_name) { 'render_collection' }
|
||||
let(:notify_method) { :notify_render_collection }
|
||||
|
||||
where(
|
||||
:identifier, :count, :expected_count, :cache_hits, :expected_cache_hits, :exception) do
|
||||
nil | nil | 0 | nil | 0 | nil
|
||||
"" | nil | 0 | nil | 0 | nil
|
||||
"show.haml" | nil | 0 | nil | 0 | nil
|
||||
nil | 0 | 0 | nil | 0 | nil
|
||||
nil | 1 | 1 | nil | 0 | nil
|
||||
nil | nil | 0 | 0 | 0 | nil
|
||||
nil | nil | 0 | 1 | 1 | nil
|
||||
nil | nil | 0 | nil | 0 | StandardError.new
|
||||
end
|
||||
|
||||
with_them do
|
||||
let(:payload) do
|
||||
{
|
||||
exception: exception,
|
||||
identifier: identifier,
|
||||
count: count,
|
||||
cache_hits: cache_hits
|
||||
}
|
||||
end
|
||||
|
||||
let(:expected_tags) do
|
||||
{
|
||||
'component' => 'ActionView',
|
||||
'template.id' => identifier,
|
||||
'template.count' => expected_count,
|
||||
'template.cache.hits' => expected_cache_hits
|
||||
}
|
||||
end
|
||||
|
||||
it_behaves_like 'an actionview notification'
|
||||
end
|
||||
end
|
||||
|
||||
describe '#notify_render_partial' do
|
||||
subject { described_class.new }
|
||||
let(:start) { Time.now }
|
||||
let(:finish) { Time.now }
|
||||
let(:notification_name) { 'render_partial' }
|
||||
let(:notify_method) { :notify_render_partial }
|
||||
|
||||
where(:identifier, :exception) do
|
||||
nil | nil
|
||||
"" | nil
|
||||
"show.haml" | nil
|
||||
nil | StandardError.new
|
||||
end
|
||||
|
||||
with_them do
|
||||
let(:payload) do
|
||||
{
|
||||
exception: exception,
|
||||
identifier: identifier
|
||||
}
|
||||
end
|
||||
|
||||
let(:expected_tags) do
|
||||
{
|
||||
'component' => 'ActionView',
|
||||
'template.id' => identifier
|
||||
}
|
||||
end
|
||||
|
||||
it_behaves_like 'an actionview notification'
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,73 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'fast_spec_helper'
|
||||
require 'rspec-parameterized'
|
||||
|
||||
describe Gitlab::Tracing::Rails::ActiveRecordSubscriber do
|
||||
using RSpec::Parameterized::TableSyntax
|
||||
|
||||
describe '.instrument' do
|
||||
it 'is unsubscribeable' do
|
||||
unsubscribe = described_class.instrument
|
||||
|
||||
expect(unsubscribe).not_to be_nil
|
||||
expect { unsubscribe.call }.not_to raise_error
|
||||
end
|
||||
end
|
||||
|
||||
describe '#notify' do
|
||||
subject { described_class.new }
|
||||
let(:start) { Time.now }
|
||||
let(:finish) { Time.now }
|
||||
|
||||
where(:name, :operation_name, :exception, :connection_id, :cached, :cached_response, :sql) do
|
||||
nil | "active_record:sqlquery" | nil | nil | nil | false | nil
|
||||
"" | "active_record:sqlquery" | nil | nil | nil | false | nil
|
||||
"User Load" | "active_record:User Load" | nil | nil | nil | false | nil
|
||||
"Repo Load" | "active_record:Repo Load" | StandardError.new | nil | nil | false | nil
|
||||
nil | "active_record:sqlquery" | nil | 123 | nil | false | nil
|
||||
nil | "active_record:sqlquery" | nil | nil | false | false | nil
|
||||
nil | "active_record:sqlquery" | nil | nil | true | true | nil
|
||||
nil | "active_record:sqlquery" | nil | nil | true | true | "SELECT * FROM users"
|
||||
end
|
||||
|
||||
with_them do
|
||||
def payload
|
||||
{
|
||||
name: name,
|
||||
exception: exception,
|
||||
connection_id: connection_id,
|
||||
cached: cached,
|
||||
sql: sql
|
||||
}
|
||||
end
|
||||
|
||||
def expected_tags
|
||||
{
|
||||
"component" => "ActiveRecord",
|
||||
"span.kind" => "client",
|
||||
"db.type" => "sql",
|
||||
"db.connection_id" => connection_id,
|
||||
"db.cached" => cached_response,
|
||||
"db.statement" => sql
|
||||
}
|
||||
end
|
||||
|
||||
it 'notifies the tracer when the hash contains null values' do
|
||||
expect(subject).to receive(:postnotify_span).with(operation_name, start, finish, tags: expected_tags, exception: exception)
|
||||
|
||||
subject.notify(start, finish, payload)
|
||||
end
|
||||
|
||||
it 'notifies the tracer when the payload is missing values' do
|
||||
expect(subject).to receive(:postnotify_span).with(operation_name, start, finish, tags: expected_tags, exception: exception)
|
||||
|
||||
subject.notify(start, finish, payload.compact)
|
||||
end
|
||||
|
||||
it 'does not throw exceptions when with the default tracer' do
|
||||
expect { subject.notify(start, finish, payload) }.not_to raise_error
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,43 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'fast_spec_helper'
|
||||
|
||||
describe Gitlab::Tracing::Sidekiq::ClientMiddleware do
|
||||
describe '#call' do
|
||||
let(:worker_class) { 'test_worker_class' }
|
||||
let(:job) do
|
||||
{
|
||||
'class' => "jobclass",
|
||||
'queue' => "jobqueue",
|
||||
'retry' => 0,
|
||||
'args' => %w{1 2 3}
|
||||
}
|
||||
end
|
||||
let(:queue) { 'test_queue' }
|
||||
let(:redis_pool) { double("redis_pool") }
|
||||
let(:custom_error) { Class.new(StandardError) }
|
||||
let(:span) { OpenTracing.start_span('test', ignore_active_scope: true) }
|
||||
|
||||
subject { described_class.new }
|
||||
|
||||
it 'yields' do
|
||||
expect(subject).to receive(:in_tracing_span).with(
|
||||
operation_name: "sidekiq:jobclass",
|
||||
tags: {
|
||||
"component" => "sidekiq",
|
||||
"span.kind" => "client",
|
||||
"sidekiq.queue" => "jobqueue",
|
||||
"sidekiq.jid" => nil,
|
||||
"sidekiq.retry" => "0",
|
||||
"sidekiq.args" => "1, 2, 3"
|
||||
}
|
||||
).and_yield(span)
|
||||
|
||||
expect { |b| subject.call(worker_class, job, queue, redis_pool, &b) }.to yield_control
|
||||
end
|
||||
|
||||
it 'propagates exceptions' do
|
||||
expect { subject.call(worker_class, job, queue, redis_pool) { raise custom_error } }.to raise_error(custom_error)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,43 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'fast_spec_helper'
|
||||
|
||||
describe Gitlab::Tracing::Sidekiq::ServerMiddleware do
|
||||
describe '#call' do
|
||||
let(:worker_class) { 'test_worker_class' }
|
||||
let(:job) do
|
||||
{
|
||||
'class' => "jobclass",
|
||||
'queue' => "jobqueue",
|
||||
'retry' => 0,
|
||||
'args' => %w{1 2 3}
|
||||
}
|
||||
end
|
||||
let(:queue) { 'test_queue' }
|
||||
let(:custom_error) { Class.new(StandardError) }
|
||||
let(:span) { OpenTracing.start_span('test', ignore_active_scope: true) }
|
||||
subject { described_class.new }
|
||||
|
||||
it 'yields' do
|
||||
expect(subject).to receive(:in_tracing_span).with(
|
||||
hash_including(
|
||||
operation_name: "sidekiq:jobclass",
|
||||
tags: {
|
||||
"component" => "sidekiq",
|
||||
"span.kind" => "server",
|
||||
"sidekiq.queue" => "jobqueue",
|
||||
"sidekiq.jid" => nil,
|
||||
"sidekiq.retry" => "0",
|
||||
"sidekiq.args" => "1, 2, 3"
|
||||
}
|
||||
)
|
||||
).and_yield(span)
|
||||
|
||||
expect { |b| subject.call(worker_class, job, queue, &b) }.to yield_control
|
||||
end
|
||||
|
||||
it 'propagates exceptions' do
|
||||
expect { subject.call(worker_class, job, queue) { raise custom_error } }.to raise_error(custom_error)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -251,7 +251,7 @@ describe API::Helpers do
|
|||
correlation_id: 'new-correlation-id'
|
||||
}, extra: {})
|
||||
|
||||
Gitlab::CorrelationId.use_id('new-correlation-id') do
|
||||
Labkit::Correlation::CorrelationId.use_id('new-correlation-id') do
|
||||
handle_api_exception(exception)
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue