gitlab-labkit 0.0.1
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.
- checksums.yaml +7 -0
- data/lib/labkit.rb +15 -0
- data/lib/labkit/correlation.rb +7 -0
- data/lib/labkit/correlation/correlation_id.rb +42 -0
- data/lib/labkit/logging.rb +7 -0
- data/lib/labkit/logging/sanitizer.rb +26 -0
- data/lib/labkit/tracing.rb +53 -0
- data/lib/labkit/tracing/common.rb +59 -0
- data/lib/labkit/tracing/factory.rb +55 -0
- data/lib/labkit/tracing/grpc_interceptor.rb +41 -0
- data/lib/labkit/tracing/jaeger_factory.rb +82 -0
- data/lib/labkit/tracing/rack_middleware.rb +40 -0
- data/lib/labkit/tracing/rails/action_view_subscriber.rb +68 -0
- data/lib/labkit/tracing/rails/active_record_subscriber.rb +50 -0
- data/lib/labkit/tracing/rails/rails_common.rb +26 -0
- data/lib/labkit/tracing/sidekiq/client_middleware.rb +25 -0
- data/lib/labkit/tracing/sidekiq/server_middleware.rb +21 -0
- data/lib/labkit/tracing/sidekiq/sidekiq_common.rb +22 -0
- metadata +229 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: b0e98b5b882ecd840a8fd9cc638c6c6c8802249ac7a8c501b6fba56deb1170dc
|
4
|
+
data.tar.gz: 46c8894fc4cd432bc07a08c64f3090976fa74476d21b1bf482ead09c93cb27f8
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: d8c5fd9a8309b00b3c62652b62fbac2112dc023a5061d85731127c519ba6cb77c475ff405142cac74fb9d5eb30239b17c0d23978998ca8dca79f4f352b950897
|
7
|
+
data.tar.gz: b7fb1d3b8c683295a58054192f1326ce985714698991c7e9f304631cb3ba4b0fd92e49b282f587ad9396b7f1846e0550cd9278fa992e254dc447bb898b81d208
|
data/lib/labkit.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Labkit
|
4
|
+
autoload :Correlation, 'labkit/correlation'
|
5
|
+
autoload :Tracing, 'labkit/tracing'
|
6
|
+
autoload :Logging, 'labkit/logging'
|
7
|
+
|
8
|
+
def self.process_name
|
9
|
+
return 'sidekiq' if Sidekiq.server?
|
10
|
+
return 'console' if defined?(Rails::Console)
|
11
|
+
return 'test' if Rails.env.test?
|
12
|
+
|
13
|
+
'web'
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Labkit
|
4
|
+
module Correlation
|
5
|
+
module CorrelationId
|
6
|
+
LOG_KEY = 'correlation_id'
|
7
|
+
|
8
|
+
class << self
|
9
|
+
def use_id(correlation_id, &blk)
|
10
|
+
# always generate a id if null is passed
|
11
|
+
correlation_id ||= new_id
|
12
|
+
|
13
|
+
ids.push(correlation_id || new_id)
|
14
|
+
|
15
|
+
begin
|
16
|
+
yield(current_id)
|
17
|
+
ensure
|
18
|
+
ids.pop
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def current_id
|
23
|
+
ids.last
|
24
|
+
end
|
25
|
+
|
26
|
+
def current_or_new_id
|
27
|
+
current_id || new_id
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def ids
|
33
|
+
Thread.current[:correlation_id] ||= []
|
34
|
+
end
|
35
|
+
|
36
|
+
def new_id
|
37
|
+
SecureRandom.uuid
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Labkit
|
4
|
+
module Logging
|
5
|
+
class Sanitizer
|
6
|
+
ALLOWED_SCHEMES = %w[http https ssh git].freeze
|
7
|
+
|
8
|
+
def self.sanitize_field(content)
|
9
|
+
regexp = URI::DEFAULT_PARSER.make_regexp(ALLOWED_SCHEMES)
|
10
|
+
|
11
|
+
content.gsub(regexp) { |url| masked_url(url) }
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.masked_url(url)
|
15
|
+
url = url.to_s.strip
|
16
|
+
url = Addressable::URI.parse(url)
|
17
|
+
|
18
|
+
url.password = '*****' if url.password.present?
|
19
|
+
url.user = '*****' if url.user.present?
|
20
|
+
url.to_s
|
21
|
+
rescue Addressable::URI::InvalidURIError
|
22
|
+
''
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'active_support/all'
|
4
|
+
|
5
|
+
module Labkit
|
6
|
+
module Tracing
|
7
|
+
autoload :Common, 'labkit/tracing/common'
|
8
|
+
autoload :Factory, 'labkit/tracing/factory'
|
9
|
+
autoload :GRPCInterceptor, 'labkit/tracing/grpc_interceptor'
|
10
|
+
autoload :JaegerFactory, 'labkit/tracing/jaeger_factory'
|
11
|
+
autoload :RackMiddleware, 'labkit/tracing/rack_middleware'
|
12
|
+
|
13
|
+
module Rails
|
14
|
+
autoload :ActionViewSubscriber, 'labkit/tracing/rails/action_view_subscriber.rb'
|
15
|
+
autoload :ActiveRecordSubscriber, 'labkit/tracing/rails/active_record_subscriber.rb'
|
16
|
+
autoload :RailsCommon, 'labkit/tracing/rails/rails_common'
|
17
|
+
end
|
18
|
+
|
19
|
+
module Sidekiq
|
20
|
+
autoload :ClientMiddleware, 'labkit/tracing/sidekiq/client_middleware'
|
21
|
+
autoload :ServerMiddleware, 'labkit/tracing/sidekiq/server_middleware'
|
22
|
+
autoload :SidekiqCommon, 'labkit/tracing/sidekiq/sidekiq_common'
|
23
|
+
end
|
24
|
+
|
25
|
+
# Only enable tracing when the `GITLAB_TRACING` env var is configured. Note that we avoid using ApplicationSettings since
|
26
|
+
# the same environment variable needs to be configured for Workhorse, Gitaly and any other components which
|
27
|
+
# emit tracing. Since other components may start before Rails, and may not have access to ApplicationSettings,
|
28
|
+
# an env var makes more sense.
|
29
|
+
def self.enabled?
|
30
|
+
connection_string.present?
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.connection_string
|
34
|
+
ENV['GITLAB_TRACING']
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.tracing_url_template
|
38
|
+
ENV['GITLAB_TRACING_URL']
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.tracing_url_enabled?
|
42
|
+
enabled? && tracing_url_template.present?
|
43
|
+
end
|
44
|
+
|
45
|
+
# This will provide a link into the distributed tracing for the current trace,
|
46
|
+
# if it has been captured.
|
47
|
+
def self.tracing_url
|
48
|
+
return unless tracing_url_enabled?
|
49
|
+
|
50
|
+
tracing_url_template.to_s % { correlation_id: Labkit::Correlation::CorrelationId.current_id.to_s, service: Labkit.process_name }
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'opentracing'
|
4
|
+
|
5
|
+
module Labkit
|
6
|
+
module Tracing
|
7
|
+
module Common
|
8
|
+
def tracer
|
9
|
+
OpenTracing.global_tracer
|
10
|
+
end
|
11
|
+
|
12
|
+
# Convience method for running a block with a span
|
13
|
+
def in_tracing_span(operation_name:, tags:, child_of: nil)
|
14
|
+
scope = tracer.start_active_span(operation_name, child_of: child_of, tags: tags)
|
15
|
+
span = scope.span
|
16
|
+
|
17
|
+
# Add correlation details to the span if we have them
|
18
|
+
correlation_id = Labkit::Correlation::CorrelationId.current_id
|
19
|
+
span.set_tag('correlation_id', correlation_id) if correlation_id
|
20
|
+
|
21
|
+
begin
|
22
|
+
yield span
|
23
|
+
rescue StandardError => e
|
24
|
+
log_exception_on_span(span, e)
|
25
|
+
raise e
|
26
|
+
ensure
|
27
|
+
scope.close
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def postnotify_span(operation_name, start_time, end_time, tags: nil, child_of: nil, exception: nil)
|
32
|
+
span = OpenTracing.start_span(operation_name, start_time: start_time, tags: tags, child_of: child_of)
|
33
|
+
|
34
|
+
log_exception_on_span(span, exception) if exception
|
35
|
+
|
36
|
+
span.finish(end_time: end_time)
|
37
|
+
end
|
38
|
+
|
39
|
+
def log_exception_on_span(span, exception)
|
40
|
+
span.set_tag('error', true)
|
41
|
+
span.log_kv(kv_tags_for_exception(exception))
|
42
|
+
end
|
43
|
+
|
44
|
+
def kv_tags_for_exception(exception)
|
45
|
+
case exception
|
46
|
+
when Exception
|
47
|
+
{
|
48
|
+
:"event" => 'error',
|
49
|
+
:"error.kind" => exception.class.to_s,
|
50
|
+
:"message" => Labkit::Logging::Sanitizer.sanitize_field(exception.message),
|
51
|
+
:"stack" => exception.backtrace&.join('\n')
|
52
|
+
}
|
53
|
+
else
|
54
|
+
{ :"event" => 'error', :"error.kind" => exception.class.to_s, :"error.object" => Labkit::Logging::Sanitizer.sanitize_field(exception.to_s) }
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'cgi'
|
4
|
+
|
5
|
+
module Labkit
|
6
|
+
module Tracing
|
7
|
+
class Factory
|
8
|
+
OPENTRACING_SCHEME = 'opentracing'
|
9
|
+
|
10
|
+
def self.create_tracer(service_name, connection_string)
|
11
|
+
return unless connection_string.present?
|
12
|
+
|
13
|
+
begin
|
14
|
+
opentracing_details = parse_connection_string(connection_string)
|
15
|
+
driver_name = opentracing_details[:driver_name]
|
16
|
+
|
17
|
+
case driver_name
|
18
|
+
when 'jaeger'
|
19
|
+
JaegerFactory.create_tracer(service_name, opentracing_details[:options])
|
20
|
+
else
|
21
|
+
raise "Unknown driver: #{driver_name}"
|
22
|
+
end
|
23
|
+
|
24
|
+
# Can't create the tracer? Warn and continue sans tracer
|
25
|
+
rescue StandardError => e
|
26
|
+
warn "Unable to instantiate tracer: #{e}"
|
27
|
+
nil
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.parse_connection_string(connection_string)
|
32
|
+
parsed = URI.parse(connection_string)
|
33
|
+
|
34
|
+
raise 'Invalid tracing connection string' unless valid_uri?(parsed)
|
35
|
+
|
36
|
+
{ driver_name: parsed.host, options: parse_query(parsed.query) }
|
37
|
+
end
|
38
|
+
private_class_method :parse_connection_string
|
39
|
+
|
40
|
+
def self.parse_query(query)
|
41
|
+
return {} unless query
|
42
|
+
|
43
|
+
CGI.parse(query).symbolize_keys.transform_values(&:first)
|
44
|
+
end
|
45
|
+
private_class_method :parse_query
|
46
|
+
|
47
|
+
def self.valid_uri?(uri)
|
48
|
+
return false unless uri
|
49
|
+
|
50
|
+
uri.scheme == OPENTRACING_SCHEME && uri.host.to_s =~ /^[a-z0-9_]+$/ && uri.path.empty?
|
51
|
+
end
|
52
|
+
private_class_method :valid_uri?
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'opentracing'
|
4
|
+
require 'grpc'
|
5
|
+
|
6
|
+
module Labkit
|
7
|
+
module Tracing
|
8
|
+
class GRPCInterceptor < GRPC::ClientInterceptor
|
9
|
+
include Common
|
10
|
+
include Singleton
|
11
|
+
|
12
|
+
def request_response(request:, call:, method:, metadata:)
|
13
|
+
wrap_with_tracing(method, 'unary', metadata) { yield }
|
14
|
+
end
|
15
|
+
|
16
|
+
def client_streamer(requests:, call:, method:, metadata:)
|
17
|
+
wrap_with_tracing(method, 'client_stream', metadata) { yield }
|
18
|
+
end
|
19
|
+
|
20
|
+
def server_streamer(request:, call:, method:, metadata:)
|
21
|
+
wrap_with_tracing(method, 'server_stream', metadata) { yield }
|
22
|
+
end
|
23
|
+
|
24
|
+
def bidi_streamer(requests:, call:, method:, metadata:)
|
25
|
+
wrap_with_tracing(method, 'bidi_stream', metadata) { yield }
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def wrap_with_tracing(method, grpc_type, metadata)
|
31
|
+
tags = { 'component' => 'grpc', 'span.kind' => 'client', 'grpc.method' => method, 'grpc.type' => grpc_type }
|
32
|
+
|
33
|
+
in_tracing_span(operation_name: "grpc:#{method}", tags: tags) do |span|
|
34
|
+
OpenTracing.inject(span.context, OpenTracing::FORMAT_TEXT_MAP, metadata)
|
35
|
+
|
36
|
+
yield
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'jaeger/client'
|
4
|
+
|
5
|
+
module Labkit
|
6
|
+
module Tracing
|
7
|
+
class JaegerFactory
|
8
|
+
# When the probabilistic sampler is used, by default 0.1% of requests will be traced
|
9
|
+
DEFAULT_PROBABILISTIC_RATE = 0.001
|
10
|
+
|
11
|
+
# The default port for the Jaeger agent UDP listener
|
12
|
+
DEFAULT_UDP_PORT = 6831
|
13
|
+
|
14
|
+
# Reduce this from default of 10 seconds as the Ruby jaeger
|
15
|
+
# client doesn't have overflow control, leading to very large
|
16
|
+
# messages which fail to send over UDP (max packet = 64k)
|
17
|
+
# Flush more often, with smaller packets
|
18
|
+
FLUSH_INTERVAL = 5
|
19
|
+
|
20
|
+
def self.create_tracer(service_name, options)
|
21
|
+
kwargs = {
|
22
|
+
service_name: service_name,
|
23
|
+
sampler: get_sampler(options[:sampler], options[:sampler_param]),
|
24
|
+
reporter: get_reporter(service_name, options[:http_endpoint], options[:udp_endpoint])
|
25
|
+
}
|
26
|
+
.compact
|
27
|
+
|
28
|
+
extra_params = options.except(:sampler, :sampler_param, :http_endpoint, :udp_endpoint, :strict_parsing, :debug)
|
29
|
+
if extra_params.present?
|
30
|
+
message = "jaeger tracer: invalid option: #{extra_params.keys.join(', ')}"
|
31
|
+
|
32
|
+
raise message if options[:strict_parsing]
|
33
|
+
|
34
|
+
warn message
|
35
|
+
end
|
36
|
+
|
37
|
+
Jaeger::Client.build(kwargs)
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.get_sampler(sampler_type, sampler_param)
|
41
|
+
case sampler_type
|
42
|
+
when 'probabilistic'
|
43
|
+
sampler_rate = sampler_param ? sampler_param.to_f : DEFAULT_PROBABILISTIC_RATE
|
44
|
+
Jaeger::Samplers::Probabilistic.new(rate: sampler_rate)
|
45
|
+
when 'const'
|
46
|
+
const_value = sampler_param == '1'
|
47
|
+
Jaeger::Samplers::Const.new(const_value)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
private_class_method :get_sampler
|
51
|
+
|
52
|
+
def self.get_reporter(service_name, http_endpoint, udp_endpoint)
|
53
|
+
encoder = Jaeger::Encoders::ThriftEncoder.new(service_name: service_name)
|
54
|
+
|
55
|
+
if http_endpoint.present?
|
56
|
+
sender = get_http_sender(encoder, http_endpoint)
|
57
|
+
elsif udp_endpoint.present?
|
58
|
+
sender = get_udp_sender(encoder, udp_endpoint)
|
59
|
+
else
|
60
|
+
return nil
|
61
|
+
end
|
62
|
+
|
63
|
+
Jaeger::Reporters::RemoteReporter.new(sender: sender, flush_interval: FLUSH_INTERVAL)
|
64
|
+
end
|
65
|
+
private_class_method :get_reporter
|
66
|
+
|
67
|
+
def self.get_http_sender(encoder, address)
|
68
|
+
Jaeger::HttpSender.new(url: address, encoder: encoder, logger: Logger.new(STDOUT))
|
69
|
+
end
|
70
|
+
private_class_method :get_http_sender
|
71
|
+
|
72
|
+
def self.get_udp_sender(encoder, address)
|
73
|
+
pair = address.split(':', 2)
|
74
|
+
host = pair[0]
|
75
|
+
port = pair[1] ? pair[1].to_i : DEFAULT_UDP_PORT
|
76
|
+
|
77
|
+
Jaeger::UdpSender.new(host: host, port: port, encoder: encoder, logger: Logger.new(STDOUT))
|
78
|
+
end
|
79
|
+
private_class_method :get_udp_sender
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'opentracing'
|
4
|
+
require 'rails'
|
5
|
+
|
6
|
+
module Labkit
|
7
|
+
module Tracing
|
8
|
+
class RackMiddleware
|
9
|
+
include Common
|
10
|
+
|
11
|
+
REQUEST_METHOD = 'REQUEST_METHOD'
|
12
|
+
|
13
|
+
def initialize(app)
|
14
|
+
@app = app
|
15
|
+
end
|
16
|
+
|
17
|
+
def call(env)
|
18
|
+
method = env[REQUEST_METHOD]
|
19
|
+
|
20
|
+
context = tracer.extract(OpenTracing::FORMAT_RACK, env)
|
21
|
+
tags = { 'component' => 'rack', 'span.kind' => 'server', 'http.method' => method, 'http.url' => self.class.build_sanitized_url_from_env(env) }
|
22
|
+
|
23
|
+
in_tracing_span(operation_name: "http:#{method}", child_of: context, tags: tags) do |span|
|
24
|
+
@app.call(env).tap { |status_code, _headers, _body| span.set_tag('http.status_code', status_code) }
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# Generate a sanitized (safe) request URL from the rack environment
|
29
|
+
def self.build_sanitized_url_from_env(env)
|
30
|
+
request = ::ActionDispatch::Request.new(env)
|
31
|
+
|
32
|
+
original_url = request.original_url
|
33
|
+
uri = URI.parse(original_url)
|
34
|
+
uri.query = request.filtered_parameters.to_query if uri.query.present?
|
35
|
+
|
36
|
+
uri.to_s
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Labkit
|
4
|
+
module Tracing
|
5
|
+
module Rails
|
6
|
+
class ActionViewSubscriber
|
7
|
+
include RailsCommon
|
8
|
+
|
9
|
+
COMPONENT_TAG = 'ActionView'
|
10
|
+
RENDER_TEMPLATE_NOTIFICATION_TOPIC = 'render_template.action_view'
|
11
|
+
RENDER_COLLECTION_NOTIFICATION_TOPIC = 'render_collection.action_view'
|
12
|
+
RENDER_PARTIAL_NOTIFICATION_TOPIC = 'render_partial.action_view'
|
13
|
+
|
14
|
+
# Instruments Rails ActionView events for opentracing.
|
15
|
+
# Returns a lambda, which, when called will unsubscribe from the notifications
|
16
|
+
def self.instrument
|
17
|
+
subscriber = new
|
18
|
+
|
19
|
+
subscriptions = [
|
20
|
+
ActiveSupport::Notifications.subscribe(RENDER_TEMPLATE_NOTIFICATION_TOPIC) do |_, start, finish, _, payload|
|
21
|
+
subscriber.notify_render_template(start, finish, payload)
|
22
|
+
end,
|
23
|
+
ActiveSupport::Notifications.subscribe(RENDER_COLLECTION_NOTIFICATION_TOPIC) do |_, start, finish, _, payload|
|
24
|
+
subscriber.notify_render_collection(start, finish, payload)
|
25
|
+
end,
|
26
|
+
ActiveSupport::Notifications.subscribe(RENDER_PARTIAL_NOTIFICATION_TOPIC) do |_, start, finish, _, payload|
|
27
|
+
subscriber.notify_render_partial(start, finish, payload)
|
28
|
+
end
|
29
|
+
]
|
30
|
+
|
31
|
+
create_unsubscriber subscriptions
|
32
|
+
end
|
33
|
+
|
34
|
+
# For more information on the payloads: https://guides.rubyonrails.org/active_support_instrumentation.html
|
35
|
+
def notify_render_template(start, finish, payload)
|
36
|
+
generate_span_for_notification('render_template', start, finish, payload, tags_for_render_template(payload))
|
37
|
+
end
|
38
|
+
|
39
|
+
def notify_render_collection(start, finish, payload)
|
40
|
+
generate_span_for_notification('render_collection', start, finish, payload, tags_for_render_collection(payload))
|
41
|
+
end
|
42
|
+
|
43
|
+
def notify_render_partial(start, finish, payload)
|
44
|
+
generate_span_for_notification('render_partial', start, finish, payload, tags_for_render_partial(payload))
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def tags_for_render_template(payload)
|
50
|
+
{ 'component' => COMPONENT_TAG, 'template.id' => payload[:identifier], 'template.layout' => payload[:layout] }
|
51
|
+
end
|
52
|
+
|
53
|
+
def tags_for_render_collection(payload)
|
54
|
+
{
|
55
|
+
'component' => COMPONENT_TAG,
|
56
|
+
'template.id' => payload[:identifier],
|
57
|
+
'template.count' => payload[:count] || 0,
|
58
|
+
'template.cache.hits' => payload[:cache_hits] || 0
|
59
|
+
}
|
60
|
+
end
|
61
|
+
|
62
|
+
def tags_for_render_partial(payload)
|
63
|
+
{ 'component' => COMPONENT_TAG, 'template.id' => payload[:identifier] }
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Labkit
|
4
|
+
module Tracing
|
5
|
+
module Rails
|
6
|
+
class ActiveRecordSubscriber
|
7
|
+
include RailsCommon
|
8
|
+
|
9
|
+
ACTIVE_RECORD_NOTIFICATION_TOPIC = 'sql.active_record'
|
10
|
+
OPERATION_NAME_PREFIX = 'active_record:'
|
11
|
+
DEFAULT_OPERATION_NAME = 'sqlquery'
|
12
|
+
|
13
|
+
# Instruments Rails ActiveRecord events for opentracing.
|
14
|
+
# Returns a lambda, which, when called will unsubscribe from the notifications
|
15
|
+
def self.instrument
|
16
|
+
subscriber = new
|
17
|
+
|
18
|
+
subscription =
|
19
|
+
ActiveSupport::Notifications.subscribe(ACTIVE_RECORD_NOTIFICATION_TOPIC) do |_, start, finish, _, payload|
|
20
|
+
subscriber.notify(start, finish, payload)
|
21
|
+
end
|
22
|
+
|
23
|
+
create_unsubscriber [subscription]
|
24
|
+
end
|
25
|
+
|
26
|
+
# For more information on the payloads: https://guides.rubyonrails.org/active_support_instrumentation.html
|
27
|
+
def notify(start, finish, payload)
|
28
|
+
generate_span_for_notification(notification_name(payload), start, finish, payload, tags_for_notification(payload))
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def notification_name(payload)
|
34
|
+
OPERATION_NAME_PREFIX + (payload[:name].presence || DEFAULT_OPERATION_NAME)
|
35
|
+
end
|
36
|
+
|
37
|
+
def tags_for_notification(payload)
|
38
|
+
{
|
39
|
+
'component' => 'ActiveRecord',
|
40
|
+
'span.kind' => 'client',
|
41
|
+
'db.type' => 'sql',
|
42
|
+
'db.connection_id' => payload[:connection_id],
|
43
|
+
'db.cached' => payload[:cached] || false,
|
44
|
+
'db.statement' => payload[:sql]
|
45
|
+
}
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'active_support/all'
|
4
|
+
|
5
|
+
module Labkit
|
6
|
+
module Tracing
|
7
|
+
module Rails
|
8
|
+
module RailsCommon
|
9
|
+
extend ActiveSupport::Concern
|
10
|
+
include Labkit::Tracing::Common
|
11
|
+
|
12
|
+
class_methods do
|
13
|
+
def create_unsubscriber(subscriptions)
|
14
|
+
-> { subscriptions.each { |subscriber| ActiveSupport::Notifications.unsubscribe(subscriber) } }
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def generate_span_for_notification(operation_name, start, finish, payload, tags)
|
19
|
+
exception = payload[:exception]
|
20
|
+
|
21
|
+
postnotify_span(operation_name, start, finish, tags: tags, exception: exception)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'opentracing'
|
4
|
+
|
5
|
+
module Labkit
|
6
|
+
module Tracing
|
7
|
+
module Sidekiq
|
8
|
+
class ClientMiddleware
|
9
|
+
include SidekiqCommon
|
10
|
+
|
11
|
+
SPAN_KIND = 'client'
|
12
|
+
|
13
|
+
def call(worker_class, job, queue, redis_pool)
|
14
|
+
in_tracing_span(operation_name: "sidekiq:#{job['class']}", tags: tags_from_job(job, SPAN_KIND)) do |span|
|
15
|
+
# Inject the details directly into the job
|
16
|
+
tracer
|
17
|
+
.inject(span.context, OpenTracing::FORMAT_TEXT_MAP, job)
|
18
|
+
|
19
|
+
yield
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'opentracing'
|
4
|
+
|
5
|
+
module Labkit
|
6
|
+
module Tracing
|
7
|
+
module Sidekiq
|
8
|
+
class ServerMiddleware
|
9
|
+
include SidekiqCommon
|
10
|
+
|
11
|
+
SPAN_KIND = 'server'
|
12
|
+
|
13
|
+
def call(worker, job, queue)
|
14
|
+
context = tracer.extract(OpenTracing::FORMAT_TEXT_MAP, job)
|
15
|
+
|
16
|
+
in_tracing_span(operation_name: "sidekiq:#{job['class']}", child_of: context, tags: tags_from_job(job, SPAN_KIND)) { |span| yield }
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Labkit
|
4
|
+
module Tracing
|
5
|
+
module Sidekiq
|
6
|
+
module SidekiqCommon
|
7
|
+
include Labkit::Tracing::Common
|
8
|
+
|
9
|
+
def tags_from_job(job, kind)
|
10
|
+
{
|
11
|
+
'component' => 'sidekiq',
|
12
|
+
'span.kind' => kind,
|
13
|
+
'sidekiq.queue' => job['queue'],
|
14
|
+
'sidekiq.jid' => job['jid'],
|
15
|
+
'sidekiq.retry' => job['retry'].to_s,
|
16
|
+
'sidekiq.args' => job['args']&.join(', ')
|
17
|
+
}
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
metadata
ADDED
@@ -0,0 +1,229 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: gitlab-labkit
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Andrew Newdigate
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2019-02-18 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: activesupport
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '5'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '5'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: grpc
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.16'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.16'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: jaeger-client
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0.10'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0.10'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: opentracing
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0.4'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0.4'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rack
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '2.0'
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '2.0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rails
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '5.0'
|
90
|
+
type: :runtime
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '5.0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: gitlab-styles
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '2.4'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '2.4'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: rake
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - "~>"
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '12.3'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - "~>"
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '12.3'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: rspec
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - "~>"
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: 3.6.0
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - "~>"
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: 3.6.0
|
139
|
+
- !ruby/object:Gem::Dependency
|
140
|
+
name: rspec-parameterized
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - "~>"
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: '0.4'
|
146
|
+
type: :development
|
147
|
+
prerelease: false
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - "~>"
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: '0.4'
|
153
|
+
- !ruby/object:Gem::Dependency
|
154
|
+
name: rubocop
|
155
|
+
requirement: !ruby/object:Gem::Requirement
|
156
|
+
requirements:
|
157
|
+
- - "~>"
|
158
|
+
- !ruby/object:Gem::Version
|
159
|
+
version: 0.54.0
|
160
|
+
type: :development
|
161
|
+
prerelease: false
|
162
|
+
version_requirements: !ruby/object:Gem::Requirement
|
163
|
+
requirements:
|
164
|
+
- - "~>"
|
165
|
+
- !ruby/object:Gem::Version
|
166
|
+
version: 0.54.0
|
167
|
+
- !ruby/object:Gem::Dependency
|
168
|
+
name: rubocop-rspec
|
169
|
+
requirement: !ruby/object:Gem::Requirement
|
170
|
+
requirements:
|
171
|
+
- - "~>"
|
172
|
+
- !ruby/object:Gem::Version
|
173
|
+
version: 1.22.1
|
174
|
+
type: :development
|
175
|
+
prerelease: false
|
176
|
+
version_requirements: !ruby/object:Gem::Requirement
|
177
|
+
requirements:
|
178
|
+
- - "~>"
|
179
|
+
- !ruby/object:Gem::Version
|
180
|
+
version: 1.22.1
|
181
|
+
description:
|
182
|
+
email:
|
183
|
+
- andrew@gitlab.com
|
184
|
+
executables: []
|
185
|
+
extensions: []
|
186
|
+
extra_rdoc_files: []
|
187
|
+
files:
|
188
|
+
- lib/labkit.rb
|
189
|
+
- lib/labkit/correlation.rb
|
190
|
+
- lib/labkit/correlation/correlation_id.rb
|
191
|
+
- lib/labkit/logging.rb
|
192
|
+
- lib/labkit/logging/sanitizer.rb
|
193
|
+
- lib/labkit/tracing.rb
|
194
|
+
- lib/labkit/tracing/common.rb
|
195
|
+
- lib/labkit/tracing/factory.rb
|
196
|
+
- lib/labkit/tracing/grpc_interceptor.rb
|
197
|
+
- lib/labkit/tracing/jaeger_factory.rb
|
198
|
+
- lib/labkit/tracing/rack_middleware.rb
|
199
|
+
- lib/labkit/tracing/rails/action_view_subscriber.rb
|
200
|
+
- lib/labkit/tracing/rails/active_record_subscriber.rb
|
201
|
+
- lib/labkit/tracing/rails/rails_common.rb
|
202
|
+
- lib/labkit/tracing/sidekiq/client_middleware.rb
|
203
|
+
- lib/labkit/tracing/sidekiq/server_middleware.rb
|
204
|
+
- lib/labkit/tracing/sidekiq/sidekiq_common.rb
|
205
|
+
homepage: http://about.gitlab.com
|
206
|
+
licenses:
|
207
|
+
- MIT
|
208
|
+
metadata: {}
|
209
|
+
post_install_message:
|
210
|
+
rdoc_options: []
|
211
|
+
require_paths:
|
212
|
+
- lib
|
213
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
214
|
+
requirements:
|
215
|
+
- - ">="
|
216
|
+
- !ruby/object:Gem::Version
|
217
|
+
version: '0'
|
218
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
219
|
+
requirements:
|
220
|
+
- - ">="
|
221
|
+
- !ruby/object:Gem::Version
|
222
|
+
version: '0'
|
223
|
+
requirements: []
|
224
|
+
rubyforge_project:
|
225
|
+
rubygems_version: 2.7.8
|
226
|
+
signing_key:
|
227
|
+
specification_version: 4
|
228
|
+
summary: Instrumentation for GitLab
|
229
|
+
test_files: []
|