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,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+
5
+ module OpenTracing
6
+ module Instrumentation
7
+ module Common
8
+ # ErrorWriter can be used to write error tag and log to span
9
+ class ErrorWriter
10
+ LOG_ERROR_EVENT = 'error'
11
+ ERROR_TAG = 'error'
12
+
13
+ attr_reader :set_error_tag
14
+ attr_reader :log_error_event
15
+
16
+ # @param set_error_tag [TrueClass,ErrorClass] enable set error tag
17
+ # @param log_error_event [TrueClass, ErrorClass] enable log exception
18
+ def initialize(
19
+ set_error_tag: true,
20
+ log_error_event: true
21
+ )
22
+ @set_error_tag = set_error_tag
23
+ @log_error_event = log_error_event
24
+ end
25
+
26
+ # Write error tag and log error event
27
+ # @param span [OpenTracing::Span] target for tag and log
28
+ # @param exception [Exception] logged to tag
29
+ def write_error(span, exception, event: LOG_ERROR_EVENT)
30
+ tag_error(span)
31
+
32
+ log_error(span, exception, event)
33
+ end
34
+
35
+ private
36
+
37
+ def tag_error(span)
38
+ return unless set_error_tag
39
+
40
+ span.set_tag('error', true)
41
+ end
42
+
43
+ def log_error(span, exception, event)
44
+ return unless log_error_event
45
+
46
+ span.log_kv(
47
+ event: event,
48
+ 'error.kind': exception.class.to_s,
49
+ message: exception.to_s,
50
+ stack: JSON.dump(exception.backtrace),
51
+ )
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OpenTracing
4
+ module Instrumentation
5
+ # Faraday tracing middlewares
6
+ module Faraday
7
+ autoload :TraceMiddleware, 'opentracing/instrumentation/faraday/trace_middleware'
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OpenTracing
4
+ module Instrumentation
5
+ module Faraday
6
+ # ResponseLogger add request and response log_kv to faraday_request span
7
+ class ResponseLogger
8
+ DEFAULT_REQUEST_LOG_HEADERS = %w[].freeze
9
+ DEFAULT_REQUEST_TAG_HEADERS = {
10
+ accept: 'Accept',
11
+ connection: 'Connection',
12
+ content_type: 'Content-Type',
13
+ user_agent: 'User-Agent',
14
+ }.freeze
15
+ DEFAULT_RESPONSE_LOG_HEADERS = %w[
16
+ Content-Length
17
+ ].freeze
18
+ DEFAULT_RESPONSE_TAG_HEADERS = {
19
+ connection: 'Connection',
20
+ content_type: 'Content-Type',
21
+ }.freeze
22
+
23
+ attr_reader :request_tag_headers
24
+ attr_reader :request_log_headers
25
+ attr_reader :response_tag_headers
26
+ attr_reader :response_log_headers
27
+
28
+ def initialize(
29
+ request_log_headers: DEFAULT_REQUEST_LOG_HEADERS,
30
+ request_tag_headers: DEFAULT_REQUEST_TAG_HEADERS,
31
+ response_tag_headers: DEFAULT_RESPONSE_TAG_HEADERS,
32
+ response_log_headers: DEFAULT_RESPONSE_LOG_HEADERS
33
+ )
34
+ @request_tag_headers = request_tag_headers
35
+ @request_log_headers = request_log_headers
36
+ @response_tag_headers = response_tag_headers
37
+ @response_log_headers = response_log_headers
38
+ end
39
+
40
+ def log_request(span, request_headers)
41
+ log_headers(span, request_headers, request_log_headers, 'request')
42
+ tag_headers(span, request_headers, request_tag_headers, 'request')
43
+ end
44
+
45
+ def log_response(span, response_headers)
46
+ log_headers(span, response_headers, response_log_headers, 'response')
47
+ tag_headers(span, response_headers, response_tag_headers, 'response')
48
+ end
49
+
50
+ private
51
+
52
+ def tag_headers(span, headers, tag_headers, event)
53
+ tag_headers.each do |key, header_name|
54
+ header_value = headers[header_name]
55
+ next unless header_value
56
+
57
+ span.set_tag("http.#{event}.#{key}", header_value)
58
+ end
59
+ end
60
+
61
+ def log_headers(span, headers, log_headers, event)
62
+ header_hash =
63
+ log_headers
64
+ .map { |key| [key, headers[key]] }
65
+ .reject { |(_key, value)| value.nil? }
66
+ .to_h
67
+
68
+ span.log_kv(
69
+ event: event,
70
+ headers: JSON.dump(header_hash),
71
+ )
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,202 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'opentracing/instrumentation/faraday/response_logger'
4
+
5
+ module OpenTracing
6
+ module Instrumentation
7
+ module Faraday
8
+ # TraceMiddleware inject tracing header into request and trace request
9
+ #
10
+ # Usage with default config:
11
+ # Faraday.new(url) do |connection|
12
+ # connection.use \
13
+ # OpenTracing::Instrumentation::Faraday::TraceMiddleware
14
+ # end
15
+ #
16
+ # Usage with config block:
17
+ # Faraday.new(url) do |connection|
18
+ # connection.use \
19
+ # OpenTracing::Instrumentation::Faraday::TraceMiddleware do |c|
20
+ # # c is instance of Config
21
+ # c.tracer = tracer
22
+ # end
23
+ # end
24
+ class TraceMiddleware
25
+ extend Forwardable
26
+
27
+ # Config for TraceMiddleware
28
+ class Config
29
+ DEFAULT_COMMAND_NAME = 'faraday_request'
30
+ DEFAULT_COMPONENT = 'faraday'
31
+ DEFAULT_EXPECTED_ERRORS = [StandardError].freeze
32
+
33
+ def initialize
34
+ @tracer = OpenTracing.global_tracer
35
+ @operation_name = DEFAULT_COMMAND_NAME
36
+ @component = DEFAULT_COMPONENT
37
+ @expected_errors = DEFAULT_EXPECTED_ERRORS
38
+ @service_name = nil
39
+ @inject = true
40
+ @response_logger = ResponseLogger.new
41
+ end
42
+
43
+ # Instance of tracer. Should implement OpenTracing::Tracer API.
44
+ #
45
+ # @return [OpenTracing::Tracer]
46
+ attr_accessor :tracer
47
+
48
+ # Operation name of tracing span.
49
+ #
50
+ # @return [String]
51
+ attr_accessor :operation_name
52
+
53
+ # List of handled errors.
54
+ #
55
+ # @return [Array<Class>]
56
+ attr_accessor :expected_errors
57
+
58
+ # Value for component tag
59
+ #
60
+ # @return [String]
61
+ attr_accessor :component
62
+
63
+ # Value for service_name tag.
64
+ #
65
+ # @return [String]
66
+ attr_accessor :service_name
67
+
68
+ # Instance of response logger
69
+ #
70
+ # @return [ResponseLogger]
71
+ attr_accessor :response_logger
72
+
73
+ # Inject trace headers to response
74
+ #
75
+ # @return [Boolean]
76
+ attr_accessor :inject
77
+ end
78
+
79
+ # @param config [Config]
80
+ #
81
+ # @yieldparam config [Config]
82
+ def initialize(
83
+ app,
84
+ config = Config.new
85
+ )
86
+ @app = app
87
+ @config = config
88
+ yield(config) if block_given?
89
+ end
90
+
91
+ # Wrap Faraday request to trace it with OpenTracing
92
+ # @param env [Faraday::Env]
93
+ # @return [Faraday::Response]
94
+ def call(env)
95
+ trace_request(env) do |span|
96
+ inject_tracing(span, env) if inject
97
+ response_logger&.log_request(span, env.request_headers)
98
+ @app.call(env).on_complete do |response|
99
+ set_response_tags(span, response)
100
+ response_logger&.log_response(span, response.response_headers)
101
+ end
102
+ end
103
+ end
104
+
105
+ private
106
+
107
+ def_delegators :@config,
108
+ :tracer,
109
+ :operation_name,
110
+ :component,
111
+ :expected_errors,
112
+ :inject,
113
+ :response_logger,
114
+ :service_name
115
+
116
+ def trace_request(env)
117
+ scope = build_scope(env)
118
+ span = scope.span
119
+
120
+ yield(span)
121
+ rescue *expected_errors => e
122
+ set_exception_tags(span, e)
123
+ raise
124
+ ensure
125
+ scope.close
126
+ end
127
+
128
+ def build_scope(env)
129
+ tracer.start_active_span(
130
+ operation_name,
131
+ tags: request_tags(env),
132
+ )
133
+ end
134
+
135
+ def inject_tracing(span, env)
136
+ tracer.inject(
137
+ span.context,
138
+ OpenTracing::FORMAT_RACK,
139
+ env[:request_headers],
140
+ )
141
+ end
142
+
143
+ def request_tags(env)
144
+ base_tags
145
+ .merge(http_tags(env))
146
+ .merge(faraday_tags(env))
147
+ end
148
+
149
+ def base_tags
150
+ {
151
+ 'span.kind' => 'client',
152
+ 'component' => component,
153
+ 'peer.service_name' => service_name,
154
+ }.compact
155
+ end
156
+
157
+ def http_tags(env)
158
+ {
159
+ 'http.method' => env[:method],
160
+ 'http.url' => env[:url].to_s,
161
+ }
162
+ end
163
+
164
+ def faraday_tags(env)
165
+ {
166
+ 'faraday.adapter' => @app.class.to_s,
167
+ 'faraday.parallel' => env.parallel?,
168
+ 'faraday.parse_body' => env.parse_body?,
169
+ 'faraday.ssl_verify' => env.ssl.verify?,
170
+ }
171
+ end
172
+
173
+ def set_response_tags(span, response)
174
+ span.set_tag('http.status_code', response.status)
175
+
176
+ return if response.success?
177
+
178
+ set_http_error_tags(span, response)
179
+ end
180
+
181
+ def set_http_error_tags(span, response)
182
+ span.set_tag('error', true)
183
+ span.log_kv(
184
+ event: 'error',
185
+ message: response.body.to_s,
186
+ 'error.kind': 'http',
187
+ )
188
+ end
189
+
190
+ def set_exception_tags(span, error)
191
+ span.set_tag('error', true)
192
+ span.log_kv(
193
+ event: 'error',
194
+ message: error.message,
195
+ 'error.kind': error.class.to_s,
196
+ 'stack': error.backtrace,
197
+ )
198
+ end
199
+ end
200
+ end
201
+ end
202
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OpenTracing
4
+ module Instrumentation
5
+ # Mongo driver instrumentation. Support mongo gem version 2.x.
6
+ module Mongo
7
+ autoload :DirectSanitazer, 'opentracing/instrumentation/mongo/direct_sanitazer'
8
+ autoload :TraceSubscriber, 'opentracing/instrumentation/mongo/trace_subscriber'
9
+ autoload :QuerySanitazer, 'opentracing/instrumentation/mongo/query_sanitazer'
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OpenTracing
4
+ module Instrumentation
5
+ module Mongo
6
+ # DirectSanitazer requests.
7
+ class DirectSanitazer
8
+ def sanitaze(command, command_name)
9
+ command = command.dup
10
+ command.delete(command_name)
11
+ command
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bson'
4
+ require 'date'
5
+ require 'opentracing/instrumentation/mongo/direct_sanitazer'
6
+
7
+ module OpenTracing
8
+ module Instrumentation
9
+ module Mongo
10
+ # QuerySanitazer clean private data from requests.
11
+ class QuerySanitazer < DirectSanitazer
12
+ DEFAULT_SAFE_CLASSES = [
13
+ TrueClass,
14
+ FalseClass,
15
+ Numeric,
16
+ Date,
17
+ Time,
18
+ ].freeze
19
+ DEFAULT_EXCLUDE_KEYS = %w[lsid].freeze
20
+
21
+ def initialize(
22
+ safe_classes: DEFAULT_SAFE_CLASSES,
23
+ exclude_keys: DEFAULT_EXCLUDE_KEYS
24
+ )
25
+ @safe_classes = safe_classes
26
+ @exclude_keys = exclude_keys
27
+ end
28
+
29
+ def sanitaze(command, command_name)
30
+ command_without_command_name = super(command, command_name)
31
+ exclude_keys.each do |key|
32
+ command_without_command_name.delete(key)
33
+ end
34
+ sanitaze_value(command_without_command_name)
35
+ end
36
+
37
+ private
38
+
39
+ attr_reader :safe_classes
40
+ attr_reader :exclude_keys
41
+
42
+ OBJECT_ID_PLACEHOLDER = '$oid'
43
+ STRING_PLACEHOLDER = '$string'
44
+ PLACEHOLDER = '?'
45
+
46
+ def sanitaze_value(value)
47
+ case value
48
+ when Hash
49
+ sanitaze_hash(value)
50
+ when Enumerable
51
+ sanitaze_array(value)
52
+ else
53
+ sanitaze_simple(value)
54
+ end
55
+ end
56
+
57
+ def sanitaze_simple(value)
58
+ case value
59
+ when BSON::ObjectId
60
+ OBJECT_ID_PLACEHOLDER
61
+ when String
62
+ STRING_PLACEHOLDER
63
+ when *safe_classes
64
+ value
65
+ else
66
+ PLACEHOLDER
67
+ end
68
+ end
69
+
70
+ def sanitaze_hash(hash)
71
+ hash.transform_values do |value|
72
+ sanitaze_value(value)
73
+ end
74
+ end
75
+
76
+ def sanitaze_array(array)
77
+ array.map do |value|
78
+ sanitaze_value(value)
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end