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.
Files changed (46) hide show
  1. checksums.yaml +7 -0
  2. data/.BUNDLER_VERSION +1 -0
  3. data/.drone.jsonnet +35 -0
  4. data/.gitignore +1 -0
  5. data/.gitlab-ci.yml +80 -0
  6. data/.rubocop.yml +36 -0
  7. data/.ruby-version +1 -0
  8. data/GEM_VERSION +1 -0
  9. data/Gemfile +19 -0
  10. data/Gemfile.lock +101 -0
  11. data/LICENSE.txt +21 -0
  12. data/README.md +39 -0
  13. data/Rakefile +11 -0
  14. data/bin/console +16 -0
  15. data/bin/setup +8 -0
  16. data/lib/opentracing/instrumentation.rb +23 -0
  17. data/lib/opentracing/instrumentation/common.rb +13 -0
  18. data/lib/opentracing/instrumentation/common/error_writer.rb +56 -0
  19. data/lib/opentracing/instrumentation/faraday.rb +10 -0
  20. data/lib/opentracing/instrumentation/faraday/response_logger.rb +76 -0
  21. data/lib/opentracing/instrumentation/faraday/trace_middleware.rb +202 -0
  22. data/lib/opentracing/instrumentation/mongo.rb +12 -0
  23. data/lib/opentracing/instrumentation/mongo/direct_sanitazer.rb +16 -0
  24. data/lib/opentracing/instrumentation/mongo/query_sanitazer.rb +84 -0
  25. data/lib/opentracing/instrumentation/mongo/trace_subscriber.rb +107 -0
  26. data/lib/opentracing/instrumentation/object_wrapper.rb +59 -0
  27. data/lib/opentracing/instrumentation/rack.rb +11 -0
  28. data/lib/opentracing/instrumentation/rack/http_tagger.rb +69 -0
  29. data/lib/opentracing/instrumentation/rack/trace_middleware.rb +94 -0
  30. data/lib/opentracing/instrumentation/redis.rb +18 -0
  31. data/lib/opentracing/instrumentation/redis/config.rb +40 -0
  32. data/lib/opentracing/instrumentation/redis/span_builder.rb +85 -0
  33. data/lib/opentracing/instrumentation/redis/tracing_driver_wrapper.rb +117 -0
  34. data/lib/opentracing/instrumentation/sidekiq.rb +17 -0
  35. data/lib/opentracing/instrumentation/sidekiq/client_middleware.rb +66 -0
  36. data/lib/opentracing/instrumentation/sidekiq/job_tagger.rb +61 -0
  37. data/lib/opentracing/instrumentation/sidekiq/server_middleware.rb +70 -0
  38. data/lib/opentracing/instrumentation/sinatra.rb +11 -0
  39. data/lib/opentracing/instrumentation/sinatra/trace_middleware.rb +64 -0
  40. data/lib/opentracing/instrumentation/thrift.rb +15 -0
  41. data/lib/opentracing/instrumentation/thrift/config.rb +24 -0
  42. data/lib/opentracing/instrumentation/thrift/traced_protocol.rb +145 -0
  43. data/lib/opentracing/instrumentation/thrift/traced_protocol_factory.rb +48 -0
  44. data/lib/opentracing/instrumentation/version.rb +7 -0
  45. data/opentracing-instrumentation.gemspec +40 -0
  46. 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