opentracing-instrumentation 0.1.0
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/.BUNDLER_VERSION +1 -0
- data/.drone.jsonnet +35 -0
- data/.gitignore +1 -0
- data/.gitlab-ci.yml +80 -0
- data/.rubocop.yml +36 -0
- data/.ruby-version +1 -0
- data/GEM_VERSION +1 -0
- data/Gemfile +19 -0
- data/Gemfile.lock +101 -0
- data/LICENSE.txt +21 -0
- data/README.md +39 -0
- data/Rakefile +11 -0
- data/bin/console +16 -0
- data/bin/setup +8 -0
- data/lib/opentracing/instrumentation.rb +23 -0
- data/lib/opentracing/instrumentation/common.rb +13 -0
- data/lib/opentracing/instrumentation/common/error_writer.rb +56 -0
- data/lib/opentracing/instrumentation/faraday.rb +10 -0
- data/lib/opentracing/instrumentation/faraday/response_logger.rb +76 -0
- data/lib/opentracing/instrumentation/faraday/trace_middleware.rb +202 -0
- data/lib/opentracing/instrumentation/mongo.rb +12 -0
- data/lib/opentracing/instrumentation/mongo/direct_sanitazer.rb +16 -0
- data/lib/opentracing/instrumentation/mongo/query_sanitazer.rb +84 -0
- data/lib/opentracing/instrumentation/mongo/trace_subscriber.rb +107 -0
- data/lib/opentracing/instrumentation/object_wrapper.rb +59 -0
- data/lib/opentracing/instrumentation/rack.rb +11 -0
- data/lib/opentracing/instrumentation/rack/http_tagger.rb +69 -0
- data/lib/opentracing/instrumentation/rack/trace_middleware.rb +94 -0
- data/lib/opentracing/instrumentation/redis.rb +18 -0
- data/lib/opentracing/instrumentation/redis/config.rb +40 -0
- data/lib/opentracing/instrumentation/redis/span_builder.rb +85 -0
- data/lib/opentracing/instrumentation/redis/tracing_driver_wrapper.rb +117 -0
- data/lib/opentracing/instrumentation/sidekiq.rb +17 -0
- data/lib/opentracing/instrumentation/sidekiq/client_middleware.rb +66 -0
- data/lib/opentracing/instrumentation/sidekiq/job_tagger.rb +61 -0
- data/lib/opentracing/instrumentation/sidekiq/server_middleware.rb +70 -0
- data/lib/opentracing/instrumentation/sinatra.rb +11 -0
- data/lib/opentracing/instrumentation/sinatra/trace_middleware.rb +64 -0
- data/lib/opentracing/instrumentation/thrift.rb +15 -0
- data/lib/opentracing/instrumentation/thrift/config.rb +24 -0
- data/lib/opentracing/instrumentation/thrift/traced_protocol.rb +145 -0
- data/lib/opentracing/instrumentation/thrift/traced_protocol_factory.rb +48 -0
- data/lib/opentracing/instrumentation/version.rb +7 -0
- data/opentracing-instrumentation.gemspec +40 -0
- metadata +255 -0
@@ -0,0 +1,107 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
require 'opentracing/instrumentation/mongo/query_sanitazer'
|
5
|
+
|
6
|
+
module OpenTracing
|
7
|
+
module Instrumentation
|
8
|
+
module Mongo
|
9
|
+
# TraceSubscriber trace mongo requests
|
10
|
+
class TraceSubscriber
|
11
|
+
include Forwardable
|
12
|
+
|
13
|
+
DEFAULT_OPERATION_NAME = 'mongo_command'
|
14
|
+
|
15
|
+
def initialize(
|
16
|
+
tracer: OpenTracing.global_tracer,
|
17
|
+
scope_store: {},
|
18
|
+
operation_name: DEFAULT_OPERATION_NAME,
|
19
|
+
sanitazer: QuerySanitazer.new
|
20
|
+
)
|
21
|
+
@tracer = tracer
|
22
|
+
@monitor = Monitor.new
|
23
|
+
@scope_store = scope_store
|
24
|
+
@operation_name = operation_name
|
25
|
+
@sanitazer = sanitazer
|
26
|
+
end
|
27
|
+
|
28
|
+
def started(event)
|
29
|
+
scope = tracer.start_active_span(
|
30
|
+
operation_name,
|
31
|
+
tags: base_tags(event).merge(mongo_tags(event)),
|
32
|
+
)
|
33
|
+
|
34
|
+
store_scope(event.operation_id, scope)
|
35
|
+
end
|
36
|
+
|
37
|
+
def succeeded(event)
|
38
|
+
clean_scope(event.operation_id)
|
39
|
+
end
|
40
|
+
|
41
|
+
def failed(event)
|
42
|
+
tag_error(event.operation_id, event.message, event.failure)
|
43
|
+
clean_scope(event.operation_id)
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
attr_reader :tracer
|
49
|
+
attr_reader :scope_store
|
50
|
+
attr_reader :monitor
|
51
|
+
attr_reader :operation_name
|
52
|
+
attr_reader :sanitazer
|
53
|
+
|
54
|
+
def base_tags(event)
|
55
|
+
{
|
56
|
+
'component' => 'db',
|
57
|
+
|
58
|
+
'db.type' => 'mongo',
|
59
|
+
'db.instance' => event.address.to_s,
|
60
|
+
}
|
61
|
+
end
|
62
|
+
|
63
|
+
def mongo_tags(event)
|
64
|
+
collection_name = event.command[event.command_name]
|
65
|
+
command_args = sanitazer.sanitaze(event.command, event.command_name)
|
66
|
+
|
67
|
+
{
|
68
|
+
'mongo.request_id' => event.request_id,
|
69
|
+
'mongo.operation_id' => event.operation_id,
|
70
|
+
'mongo.database_name' => event.database_name,
|
71
|
+
'mongo.collection_name' => collection_name,
|
72
|
+
'mongo.command_name' => event.command_name,
|
73
|
+
'mongo.command_args' => JSON.dump(command_args),
|
74
|
+
}
|
75
|
+
end
|
76
|
+
|
77
|
+
def store_scope(operation_id, scope)
|
78
|
+
monitor.synchronize do
|
79
|
+
scope_store[operation_id] = scope
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def clean_scope(operation_id)
|
84
|
+
monitor.synchronize do
|
85
|
+
scope_store
|
86
|
+
.delete(operation_id)
|
87
|
+
.close
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
ERROR_TAG = 'error'
|
92
|
+
|
93
|
+
def tag_error(operation_id, message, object)
|
94
|
+
monitor.synchronize do
|
95
|
+
scope_store[operation_id]
|
96
|
+
.span
|
97
|
+
.set_tag(ERROR_TAG, true)
|
98
|
+
.log_kv(
|
99
|
+
'error.kind': message,
|
100
|
+
'error.object': JSON.dump(object),
|
101
|
+
)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module OpenTracing
|
4
|
+
module Instrumentation
|
5
|
+
# ObjectWrapper allow trace call methods on any ruby object.
|
6
|
+
#
|
7
|
+
# Usage:
|
8
|
+
# wrapped_object = OpenTracing::Instrumentation::ObjectWrapper.new(other_object)
|
9
|
+
# wrapped_object.other_object_method
|
10
|
+
class ObjectWrapper
|
11
|
+
DEFAULT_COMMAND_NAME = 'method_call'
|
12
|
+
|
13
|
+
attr_reader :tracer,
|
14
|
+
:command_name
|
15
|
+
|
16
|
+
def initialize(
|
17
|
+
object,
|
18
|
+
tracer: OpenTracing.global_tracer,
|
19
|
+
command_name: DEFAULT_COMMAND_NAME
|
20
|
+
)
|
21
|
+
@object = object
|
22
|
+
@tracer = tracer
|
23
|
+
@command_name = command_name
|
24
|
+
end
|
25
|
+
|
26
|
+
# wrapped object
|
27
|
+
attr_reader :object
|
28
|
+
|
29
|
+
def respond_to_missing?(method_name, *)
|
30
|
+
object.response_to?(method_name)
|
31
|
+
end
|
32
|
+
|
33
|
+
def method_missing(method_name, *args)
|
34
|
+
return super unless object.respond_to?(method_name)
|
35
|
+
|
36
|
+
tags = build_tags(method_name)
|
37
|
+
tracer.start_active_span(command_name, tags: tags) do |scope|
|
38
|
+
call_method(scope.span, method_name, *args)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def call_method(span, method_name, *args)
|
45
|
+
object.send(method_name, *args)
|
46
|
+
rescue StandardError => e
|
47
|
+
span.set_tag('error', true)
|
48
|
+
raise e
|
49
|
+
end
|
50
|
+
|
51
|
+
def build_tags(method)
|
52
|
+
{
|
53
|
+
'object.class' => object.class.to_s,
|
54
|
+
'object.method' => method.to_s,
|
55
|
+
}
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module OpenTracing
|
4
|
+
module Instrumentation
|
5
|
+
# Rack tracing middlewares
|
6
|
+
module Rack
|
7
|
+
autoload :HttpTagger, 'opentracing/instrumentation/rack/http_tagger'
|
8
|
+
autoload :TraceMiddleware, 'opentracing/instrumentation/rack/trace_middleware'
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rack'
|
4
|
+
|
5
|
+
module OpenTracing
|
6
|
+
module Instrumentation
|
7
|
+
module Rack
|
8
|
+
# HttpTagger create addition tags on span
|
9
|
+
class HttpTagger
|
10
|
+
DEFAULT_TAG_REQUEST_HEADERS = {
|
11
|
+
connection: 'Connection',
|
12
|
+
content_type: 'Content-Type',
|
13
|
+
user_agent: 'User-Agent',
|
14
|
+
}.freeze
|
15
|
+
|
16
|
+
DEFAULT_TAG_RESPONSE_HEADERS = {
|
17
|
+
connection: 'Connection',
|
18
|
+
content_type: 'Content-Type',
|
19
|
+
keep_alive: 'Keep-Alive',
|
20
|
+
}.freeze
|
21
|
+
|
22
|
+
def initialize(
|
23
|
+
tag_request_headers: DEFAULT_TAG_REQUEST_HEADERS,
|
24
|
+
tag_response_headers: DEFAULT_TAG_RESPONSE_HEADERS
|
25
|
+
)
|
26
|
+
@tag_request_headers =
|
27
|
+
prepare_request_mapping(tag_request_headers)
|
28
|
+
@tag_response_headers =
|
29
|
+
prepare_response_mapping(tag_response_headers)
|
30
|
+
end
|
31
|
+
|
32
|
+
def request_tags(env)
|
33
|
+
@tag_request_headers
|
34
|
+
.transform_values { |header_name| env[header_name] }
|
35
|
+
.compact
|
36
|
+
end
|
37
|
+
|
38
|
+
def response_tags(headers)
|
39
|
+
header_mapper = lambda do |header_regexp|
|
40
|
+
headers.find do |(header, _value)|
|
41
|
+
header_regexp.match?(header)
|
42
|
+
end&.dig(1)
|
43
|
+
end
|
44
|
+
@tag_response_headers
|
45
|
+
.transform_values(&header_mapper)
|
46
|
+
.compact
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def prepare_request_mapping(source_mapping)
|
52
|
+
source_mapping.map do |key, header|
|
53
|
+
rack_header = 'HTTP_' + header.tr('-', '_').upcase
|
54
|
+
tag_name = 'http.request.' + key.to_s
|
55
|
+
[tag_name, rack_header]
|
56
|
+
end.to_h
|
57
|
+
end
|
58
|
+
|
59
|
+
def prepare_response_mapping(source_mapping)
|
60
|
+
source_mapping.map do |key, header|
|
61
|
+
tag_name = 'http.response.' + key.to_s
|
62
|
+
header_regexp = /^#{header}$/i
|
63
|
+
[tag_name, header_regexp]
|
64
|
+
end.to_h
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rack'
|
4
|
+
|
5
|
+
module OpenTracing
|
6
|
+
module Instrumentation
|
7
|
+
module Rack
|
8
|
+
# TraceMiddleware observer rack requests.
|
9
|
+
class TraceMiddleware
|
10
|
+
def initialize(
|
11
|
+
app,
|
12
|
+
logger: nil,
|
13
|
+
command_name: 'rack',
|
14
|
+
http_tagger: HttpTagger.new,
|
15
|
+
tracer: OpenTracing.global_tracer
|
16
|
+
)
|
17
|
+
@app = app
|
18
|
+
@logger = logger
|
19
|
+
@command_name = command_name
|
20
|
+
@http_tagger = http_tagger
|
21
|
+
@tracer = tracer
|
22
|
+
end
|
23
|
+
|
24
|
+
def call(env)
|
25
|
+
trace_request(env) do |span|
|
26
|
+
catch_error(span) do
|
27
|
+
status, headers, body = app.call(env)
|
28
|
+
|
29
|
+
set_response_tags(span, status, headers)
|
30
|
+
[status, headers, body]
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
attr_reader :app
|
38
|
+
attr_reader :http_tagger
|
39
|
+
attr_reader :tracer
|
40
|
+
attr_reader :logger
|
41
|
+
|
42
|
+
def trace_request(env)
|
43
|
+
extracted_ctx = tracer.extract(OpenTracing::FORMAT_RACK, env)
|
44
|
+
logger&.info('Tracing context extracted') if extracted_ctx
|
45
|
+
tracer.start_active_span(
|
46
|
+
@command_name,
|
47
|
+
child_of: extracted_ctx,
|
48
|
+
tags: request_tags(env),
|
49
|
+
) do |scope|
|
50
|
+
yield(scope.span)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def catch_error(span)
|
55
|
+
yield
|
56
|
+
rescue StandardError => e
|
57
|
+
logger&.error(e)
|
58
|
+
error_tag(span, e)
|
59
|
+
raise
|
60
|
+
end
|
61
|
+
|
62
|
+
REQUEST_URI = 'REQUEST_URI'
|
63
|
+
|
64
|
+
def request_tags(env)
|
65
|
+
{
|
66
|
+
'http.method' => env[::Rack::REQUEST_METHOD],
|
67
|
+
'http.url' => env[REQUEST_URI],
|
68
|
+
'span.kind' => 'server',
|
69
|
+
}.merge(http_tagger.request_tags(env))
|
70
|
+
end
|
71
|
+
|
72
|
+
def set_response_tags(span, status, headers)
|
73
|
+
set_status_tag(span, status)
|
74
|
+
set_header_tags(span, headers)
|
75
|
+
end
|
76
|
+
|
77
|
+
def set_status_tag(span, status)
|
78
|
+
span.set_tag('http.status', status)
|
79
|
+
end
|
80
|
+
|
81
|
+
def set_header_tags(span, headers)
|
82
|
+
http_tagger
|
83
|
+
.response_tags(headers)
|
84
|
+
.each(&span.method(:set_tag))
|
85
|
+
end
|
86
|
+
|
87
|
+
def error_tag(span, error)
|
88
|
+
span.set_tag('error', true)
|
89
|
+
span.log_kv('error.kind': error.class.to_s)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module OpenTracing
|
4
|
+
module Instrumentation
|
5
|
+
# Redis tracing drivers
|
6
|
+
module Redis
|
7
|
+
EVENT_WRITE = 'redis_write'
|
8
|
+
EVENT_READ = 'redis_read'
|
9
|
+
|
10
|
+
autoload :Config,
|
11
|
+
'opentracing/instrumentation/redis/config'
|
12
|
+
autoload :SpanBuilder,
|
13
|
+
'opentracing/instrumentation/redis/span_builder'
|
14
|
+
autoload :TracingDriverWrapper,
|
15
|
+
'opentracing/instrumentation/redis/tracing_driver_wrapper'
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'redis/connection/ruby'
|
4
|
+
|
5
|
+
module OpenTracing
|
6
|
+
module Instrumentation
|
7
|
+
module Redis
|
8
|
+
# Redis tracing mixin config
|
9
|
+
class Config
|
10
|
+
DEFAULT_SPAN_NAME = 'redis_command'
|
11
|
+
# Safe by default
|
12
|
+
DEFAULT_LOG_ARGS = false
|
13
|
+
DEFAULT_LOG_REPLY = false
|
14
|
+
DEFAULT_COMPONENT = 'kv'
|
15
|
+
|
16
|
+
attr_accessor :tracer
|
17
|
+
attr_accessor :span_name
|
18
|
+
attr_accessor :log_args
|
19
|
+
attr_accessor :log_reply
|
20
|
+
attr_accessor :component
|
21
|
+
|
22
|
+
def initialize(
|
23
|
+
tracer: OpenTracing.global_tracer,
|
24
|
+
span_name: DEFAULT_SPAN_NAME,
|
25
|
+
log_args: DEFAULT_LOG_ARGS,
|
26
|
+
log_reply: DEFAULT_LOG_REPLY,
|
27
|
+
component: DEFAULT_COMPONENT
|
28
|
+
)
|
29
|
+
@tracer = tracer
|
30
|
+
@span_name = span_name
|
31
|
+
@log_args = log_args
|
32
|
+
@log_reply = log_reply
|
33
|
+
@component = component
|
34
|
+
|
35
|
+
yield self if block_given?
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
module OpenTracing
|
6
|
+
module Instrumentation
|
7
|
+
module Redis
|
8
|
+
# SpanBuilder create span with tags and logs
|
9
|
+
class SpanBuilder
|
10
|
+
extend Forwardable
|
11
|
+
|
12
|
+
def initialize(
|
13
|
+
config: Config.new,
|
14
|
+
error_writer: Common::ErrorWriter.new
|
15
|
+
)
|
16
|
+
@config = config
|
17
|
+
@error_writer = error_writer
|
18
|
+
end
|
19
|
+
|
20
|
+
def_delegators :@config,
|
21
|
+
:tracer,
|
22
|
+
:span_name,
|
23
|
+
:component,
|
24
|
+
:log_args,
|
25
|
+
:log_reply
|
26
|
+
|
27
|
+
def_delegators :@error_writer,
|
28
|
+
:write_error
|
29
|
+
|
30
|
+
def start_active_scope(connection_class, peer_addr)
|
31
|
+
tags = build_tags(connection_class, peer_addr)
|
32
|
+
tracer.start_active_span(
|
33
|
+
span_name,
|
34
|
+
tags: tags,
|
35
|
+
)
|
36
|
+
end
|
37
|
+
|
38
|
+
def build_tags(connection_class, peer_addr)
|
39
|
+
{
|
40
|
+
'span.kind' => 'client',
|
41
|
+
'component' => component,
|
42
|
+
'peer.address' => peer_addr,
|
43
|
+
'peer.service' => 'redis',
|
44
|
+
'redis.driver' => connection_class.to_s,
|
45
|
+
}.compact
|
46
|
+
end
|
47
|
+
|
48
|
+
REDACTED_ARG = ['*'].freeze
|
49
|
+
|
50
|
+
def write_log_command(span, command)
|
51
|
+
command_name, *args = command
|
52
|
+
args_value = log_args ? JSON.dump(args) : REDACTED_ARG * args.size
|
53
|
+
|
54
|
+
span.log_kv(
|
55
|
+
event: EVENT_WRITE,
|
56
|
+
'redis.command': command_name,
|
57
|
+
'redis.args': args_value,
|
58
|
+
)
|
59
|
+
end
|
60
|
+
|
61
|
+
def write_error_reply(span, reply)
|
62
|
+
return unless reply.is_a?(::Redis::CommandError)
|
63
|
+
|
64
|
+
span.set_tag('error', true)
|
65
|
+
span.log_kv(
|
66
|
+
'error.kind': 'redis_error',
|
67
|
+
message: reply.to_s,
|
68
|
+
event: EVENT_READ,
|
69
|
+
)
|
70
|
+
end
|
71
|
+
|
72
|
+
def write_log_reply(span, reply)
|
73
|
+
write_error_reply(span, reply)
|
74
|
+
|
75
|
+
return unless log_reply
|
76
|
+
|
77
|
+
span.log_kv(
|
78
|
+
event: EVENT_READ,
|
79
|
+
'redis.reply': JSON.dump(reply),
|
80
|
+
)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|