elastic-apm 2.1.2 → 2.2.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.
Potentially problematic release.
This version of elastic-apm might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/.rubocop.yml +6 -0
- data/CHANGELOG.md +17 -0
- data/Gemfile +1 -0
- data/docs/advanced.asciidoc +9 -1
- data/docs/api.asciidoc +8 -2
- data/docs/configuration.asciidoc +44 -1
- data/docs/getting-started-rack.asciidoc +6 -0
- data/docs/getting-started-rails.asciidoc +6 -0
- data/docs/index.asciidoc +1 -0
- data/docs/introduction.asciidoc +5 -5
- data/docs/opentracing.asciidoc +94 -0
- data/docs/supported-technologies.asciidoc +5 -0
- data/lib/elastic_apm.rb +53 -11
- data/lib/elastic_apm/agent.rb +17 -8
- data/lib/elastic_apm/config.rb +18 -1
- data/lib/elastic_apm/context.rb +2 -1
- data/lib/elastic_apm/context/user.rb +21 -7
- data/lib/elastic_apm/context_builder.rb +13 -6
- data/lib/elastic_apm/instrumenter.rb +41 -33
- data/lib/elastic_apm/middleware.rb +4 -6
- data/lib/elastic_apm/opentracing.rb +346 -0
- data/lib/elastic_apm/span.rb +26 -29
- data/lib/elastic_apm/span/context.rb +4 -2
- data/lib/elastic_apm/spies/faraday.rb +2 -3
- data/lib/elastic_apm/spies/http.rb +4 -7
- data/lib/elastic_apm/spies/net_http.rb +2 -5
- data/lib/elastic_apm/sql_summarizer.rb +1 -1
- data/lib/elastic_apm/{traceparent.rb → trace_context.rb} +31 -11
- data/lib/elastic_apm/transaction.rb +18 -16
- data/lib/elastic_apm/transport/filters/secrets_filter.rb +1 -0
- data/lib/elastic_apm/transport/serializers/context_serializer.rb +1 -1
- data/lib/elastic_apm/transport/serializers/span_serializer.rb +4 -5
- data/lib/elastic_apm/version.rb +1 -1
- metadata +6 -5
data/lib/elastic_apm/agent.rb
CHANGED
@@ -49,12 +49,14 @@ module ElasticAPM
|
|
49
49
|
def initialize(config)
|
50
50
|
@config = config
|
51
51
|
|
52
|
-
@transport = Transport::Base.new(config)
|
53
|
-
@instrumenter = Instrumenter.new(config) { |event| enqueue event }
|
54
|
-
|
55
52
|
@stacktrace_builder = StacktraceBuilder.new(config)
|
56
|
-
@context_builder = ContextBuilder.new(
|
53
|
+
@context_builder = ContextBuilder.new(config)
|
57
54
|
@error_builder = ErrorBuilder.new(self)
|
55
|
+
|
56
|
+
@transport = Transport::Base.new(config)
|
57
|
+
@instrumenter = Instrumenter.new(
|
58
|
+
config, stacktrace_builder: stacktrace_builder
|
59
|
+
) { |event| enqueue event }
|
58
60
|
end
|
59
61
|
|
60
62
|
attr_reader :config, :transport, :instrumenter,
|
@@ -106,13 +108,13 @@ module ElasticAPM
|
|
106
108
|
name = nil,
|
107
109
|
type = nil,
|
108
110
|
context: nil,
|
109
|
-
|
111
|
+
trace_context: nil
|
110
112
|
)
|
111
113
|
instrumenter.start_transaction(
|
112
114
|
name,
|
113
115
|
type,
|
114
116
|
context: context,
|
115
|
-
|
117
|
+
trace_context: trace_context
|
116
118
|
)
|
117
119
|
end
|
118
120
|
|
@@ -120,12 +122,19 @@ module ElasticAPM
|
|
120
122
|
instrumenter.end_transaction(result)
|
121
123
|
end
|
122
124
|
|
123
|
-
def start_span(
|
125
|
+
def start_span(
|
126
|
+
name = nil,
|
127
|
+
type = nil,
|
128
|
+
backtrace: nil,
|
129
|
+
context: nil,
|
130
|
+
trace_context: nil
|
131
|
+
)
|
124
132
|
instrumenter.start_span(
|
125
133
|
name,
|
126
134
|
type,
|
127
135
|
backtrace: backtrace,
|
128
|
-
context: context
|
136
|
+
context: context,
|
137
|
+
trace_context: trace_context
|
129
138
|
)
|
130
139
|
end
|
131
140
|
|
data/lib/elastic_apm/config.rb
CHANGED
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
require 'logger'
|
4
4
|
require 'yaml'
|
5
|
+
require 'erb'
|
5
6
|
|
6
7
|
require 'elastic_apm/util/prefixed_logger'
|
7
8
|
require 'elastic_apm/config/duration'
|
@@ -21,6 +22,9 @@ module ElasticAPM
|
|
21
22
|
api_buffer_size: 256,
|
22
23
|
api_request_size: '750kb',
|
23
24
|
api_request_time: '10s',
|
25
|
+
capture_body: true,
|
26
|
+
capture_headers: true,
|
27
|
+
capture_env: true,
|
24
28
|
current_user_email_method: :email,
|
25
29
|
current_user_id_method: :id,
|
26
30
|
current_user_username_method: :username,
|
@@ -57,6 +61,9 @@ module ElasticAPM
|
|
57
61
|
'ELASTIC_APM_API_BUFFER_SIZE' => [:int, 'api_buffer_size'],
|
58
62
|
'ELASTIC_APM_API_REQUEST_SIZE' => [:int, 'api_request_size'],
|
59
63
|
'ELASTIC_APM_API_REQUEST_TIME' => 'api_request_time',
|
64
|
+
'ELASTIC_APM_CAPTURE_BODY' => [:bool, 'capture_body'],
|
65
|
+
'ELASTIC_APM_CAPTURE_HEADERS' => [:bool, 'capture_headers'],
|
66
|
+
'ELASTIC_APM_CAPTURE_ENV' => [:bool, 'capture_env'],
|
60
67
|
'ELASTIC_APM_CUSTOM_KEY_FILTERS' => [:list, 'custom_key_filters'],
|
61
68
|
'ELASTIC_APM_DEFAULT_TAGS' => [:dict, 'default_tags'],
|
62
69
|
'ELASTIC_APM_DISABLED_SPIES' => [:list, 'disabled_spies'],
|
@@ -118,6 +125,9 @@ module ElasticAPM
|
|
118
125
|
attr_accessor :api_buffer_size
|
119
126
|
attr_accessor :api_request_size
|
120
127
|
attr_accessor :api_request_time
|
128
|
+
attr_accessor :capture_body
|
129
|
+
attr_accessor :capture_headers
|
130
|
+
attr_accessor :capture_env
|
121
131
|
attr_accessor :current_user_email_method
|
122
132
|
attr_accessor :current_user_id_method
|
123
133
|
attr_accessor :current_user_method
|
@@ -155,6 +165,9 @@ module ElasticAPM
|
|
155
165
|
attr_accessor :view_paths
|
156
166
|
attr_accessor :root_path
|
157
167
|
|
168
|
+
alias :capture_body? :capture_body
|
169
|
+
alias :capture_headers? :capture_headers
|
170
|
+
alias :capture_env? :capture_env
|
158
171
|
alias :disable_send? :disable_send
|
159
172
|
alias :http_compression? :http_compression
|
160
173
|
alias :instrument? :instrument
|
@@ -229,6 +242,10 @@ module ElasticAPM
|
|
229
242
|
@span_frames_min_duration_us = duration * 1_000_000
|
230
243
|
end
|
231
244
|
|
245
|
+
def span_frames_min_duration?
|
246
|
+
span_frames_min_duration != 0
|
247
|
+
end
|
248
|
+
|
232
249
|
DEPRECATED_OPTIONS = %i[
|
233
250
|
compression_level=
|
234
251
|
compression_minimum_size=
|
@@ -307,7 +324,7 @@ module ElasticAPM
|
|
307
324
|
|
308
325
|
def set_from_config_file
|
309
326
|
return unless File.exist?(config_file)
|
310
|
-
assign(YAML.
|
327
|
+
assign(YAML.safe_load(ERB.new(File.read(config_file)).result) || {})
|
311
328
|
rescue ConfigError => e
|
312
329
|
alert_logger.warn format(
|
313
330
|
'Failed to configure from config file: %s',
|
data/lib/elastic_apm/context.rb
CHANGED
@@ -12,9 +12,10 @@ module ElasticAPM
|
|
12
12
|
attr_accessor :request, :response, :user
|
13
13
|
attr_reader :custom, :tags
|
14
14
|
|
15
|
-
def initialize(custom: {}, tags: {})
|
15
|
+
def initialize(custom: {}, tags: {}, user: nil)
|
16
16
|
@custom = custom
|
17
17
|
@tags = tags
|
18
|
+
@user = user || User.new
|
18
19
|
end
|
19
20
|
end
|
20
21
|
end
|
@@ -4,20 +4,34 @@ module ElasticAPM
|
|
4
4
|
class Context
|
5
5
|
# @api private
|
6
6
|
class User
|
7
|
-
def initialize(
|
7
|
+
def initialize(id: nil, email: nil, username: nil)
|
8
|
+
@id = id
|
9
|
+
@email = email
|
10
|
+
@username = username
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.infer(config, record)
|
8
14
|
return unless record
|
9
15
|
|
10
|
-
|
11
|
-
|
12
|
-
|
16
|
+
new(
|
17
|
+
id: safe_get(record, config.current_user_id_method)&.to_s,
|
18
|
+
email: safe_get(record, config.current_user_email_method),
|
19
|
+
username: safe_get(record, config.current_user_username_method)
|
20
|
+
)
|
13
21
|
end
|
14
22
|
|
15
23
|
attr_accessor :id, :email, :username
|
16
24
|
|
17
|
-
|
25
|
+
def empty?
|
26
|
+
!id && !email && !username
|
27
|
+
end
|
28
|
+
|
29
|
+
class << self
|
30
|
+
private
|
18
31
|
|
19
|
-
|
20
|
-
|
32
|
+
def safe_get(record, method_name)
|
33
|
+
record.respond_to?(method_name) ? record.send(method_name) : nil
|
34
|
+
end
|
21
35
|
end
|
22
36
|
end
|
23
37
|
end
|
@@ -1,10 +1,13 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module ElasticAPM
|
4
|
-
# TODO: Move to txn.add_request ?
|
5
4
|
# @api private
|
6
5
|
class ContextBuilder
|
7
|
-
def initialize(
|
6
|
+
def initialize(config)
|
7
|
+
@config = config
|
8
|
+
end
|
9
|
+
|
10
|
+
attr_reader :config
|
8
11
|
|
9
12
|
def build(rack_env)
|
10
13
|
context = Context.new
|
@@ -14,7 +17,7 @@ module ElasticAPM
|
|
14
17
|
|
15
18
|
private
|
16
19
|
|
17
|
-
# rubocop:disable Metrics/AbcSize
|
20
|
+
# rubocop:disable Metrics/MethodLength, Metrics/AbcSize
|
18
21
|
def apply_to_request(context, rack_env)
|
19
22
|
req = rails_req?(rack_env) ? rack_env : Rack::Request.new(rack_env)
|
20
23
|
|
@@ -25,12 +28,16 @@ module ElasticAPM
|
|
25
28
|
request.http_version = build_http_version rack_env
|
26
29
|
request.method = req.request_method
|
27
30
|
request.url = Context::Request::Url.new(req)
|
28
|
-
|
29
|
-
request.body = get_body(req)
|
31
|
+
|
32
|
+
request.body = get_body(req) if config.capture_body?
|
33
|
+
|
34
|
+
headers, env = get_headers_and_env(rack_env)
|
35
|
+
request.headers = headers if config.capture_headers?
|
36
|
+
request.env = env if config.capture_env?
|
30
37
|
|
31
38
|
context
|
32
39
|
end
|
33
|
-
# rubocop:enable Metrics/AbcSize
|
40
|
+
# rubocop:enable Metrics/MethodLength, Metrics/AbcSize
|
34
41
|
|
35
42
|
def get_body(req)
|
36
43
|
return req.POST if req.form_data?
|
@@ -1,5 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'elastic_apm/trace_context'
|
3
4
|
require 'elastic_apm/span'
|
4
5
|
require 'elastic_apm/transaction'
|
5
6
|
|
@@ -7,16 +8,16 @@ module ElasticAPM
|
|
7
8
|
# rubocop:disable Metrics/ClassLength
|
8
9
|
# @api private
|
9
10
|
class Instrumenter
|
10
|
-
|
11
|
+
TRANSACTION_KEY = :__elastic_instrumenter_transaction_key
|
12
|
+
SPAN_KEY = :__elastic_instrumenter_spans_key
|
11
13
|
|
12
|
-
|
13
|
-
SPAN_KEY = :__elastic_span_key
|
14
|
+
include Logging
|
14
15
|
|
15
16
|
# @api private
|
16
17
|
class Current
|
17
18
|
def initialize
|
18
19
|
self.transaction = nil
|
19
|
-
self.
|
20
|
+
self.spans = []
|
20
21
|
end
|
21
22
|
|
22
23
|
def transaction
|
@@ -27,23 +28,25 @@ module ElasticAPM
|
|
27
28
|
Thread.current[TRANSACTION_KEY] = transaction
|
28
29
|
end
|
29
30
|
|
30
|
-
def
|
31
|
-
Thread.current[SPAN_KEY]
|
31
|
+
def spans
|
32
|
+
Thread.current[SPAN_KEY] ||= []
|
32
33
|
end
|
33
34
|
|
34
|
-
def
|
35
|
-
Thread.current[SPAN_KEY]
|
35
|
+
def spans=(spans)
|
36
|
+
Thread.current[SPAN_KEY] ||= []
|
37
|
+
Thread.current[SPAN_KEY] = spans
|
36
38
|
end
|
37
39
|
end
|
38
40
|
|
39
|
-
def initialize(config, &enqueue)
|
41
|
+
def initialize(config, stacktrace_builder:, &enqueue)
|
40
42
|
@config = config
|
43
|
+
@stacktrace_builder = stacktrace_builder
|
41
44
|
@enqueue = enqueue
|
42
45
|
|
43
46
|
@current = Current.new
|
44
47
|
end
|
45
48
|
|
46
|
-
attr_reader :config, :enqueue
|
49
|
+
attr_reader :config, :stacktrace_builder, :enqueue
|
47
50
|
|
48
51
|
def start
|
49
52
|
debug 'Starting instrumenter'
|
@@ -53,7 +56,7 @@ module ElasticAPM
|
|
53
56
|
debug 'Stopping instrumenter'
|
54
57
|
|
55
58
|
self.current_transaction = nil
|
56
|
-
|
59
|
+
current_spans.pop until current_spans.empty?
|
57
60
|
|
58
61
|
@subscriber.unregister! if @subscriber
|
59
62
|
end
|
@@ -78,7 +81,7 @@ module ElasticAPM
|
|
78
81
|
name = nil,
|
79
82
|
type = nil,
|
80
83
|
context: nil,
|
81
|
-
|
84
|
+
trace_context: nil
|
82
85
|
)
|
83
86
|
return nil unless config.instrument?
|
84
87
|
|
@@ -87,14 +90,14 @@ module ElasticAPM
|
|
87
90
|
"Transactions may not be nested.\nAlready inside #{transaction}"
|
88
91
|
end
|
89
92
|
|
90
|
-
sampled =
|
93
|
+
sampled = trace_context ? trace_context.recorded? : random_sample?
|
91
94
|
|
92
95
|
transaction =
|
93
96
|
Transaction.new(
|
94
97
|
name,
|
95
98
|
type,
|
96
99
|
context: context,
|
97
|
-
|
100
|
+
trace_context: trace_context,
|
98
101
|
sampled: sampled
|
99
102
|
)
|
100
103
|
|
@@ -118,16 +121,23 @@ module ElasticAPM
|
|
118
121
|
|
119
122
|
# spans
|
120
123
|
|
121
|
-
def
|
122
|
-
@current.
|
124
|
+
def current_spans
|
125
|
+
@current.spans
|
123
126
|
end
|
124
127
|
|
125
|
-
def current_span
|
126
|
-
|
128
|
+
def current_span
|
129
|
+
current_spans.last
|
127
130
|
end
|
128
131
|
|
129
132
|
# rubocop:disable Metrics/MethodLength, Metrics/CyclomaticComplexity
|
130
|
-
|
133
|
+
# rubocop:disable Metrics/AbcSize, Metrics/PerceivedComplexity
|
134
|
+
def start_span(
|
135
|
+
name,
|
136
|
+
type = nil,
|
137
|
+
backtrace: nil,
|
138
|
+
context: nil,
|
139
|
+
trace_context: nil
|
140
|
+
)
|
131
141
|
return unless (transaction = current_transaction)
|
132
142
|
return unless transaction.sampled?
|
133
143
|
|
@@ -138,32 +148,34 @@ module ElasticAPM
|
|
138
148
|
return
|
139
149
|
end
|
140
150
|
|
151
|
+
parent = current_span || transaction
|
152
|
+
|
141
153
|
span = Span.new(
|
142
154
|
name,
|
143
155
|
type,
|
144
|
-
|
145
|
-
|
146
|
-
context: context
|
156
|
+
transaction_id: transaction.id,
|
157
|
+
parent_id: parent.id,
|
158
|
+
context: context,
|
159
|
+
stacktrace_builder: stacktrace_builder,
|
160
|
+
trace_context: trace_context || parent.trace_context.child
|
147
161
|
)
|
148
162
|
|
149
|
-
if backtrace && span_frames_min_duration?
|
163
|
+
if backtrace && config.span_frames_min_duration?
|
150
164
|
span.original_backtrace = backtrace
|
151
165
|
end
|
152
166
|
|
153
|
-
|
167
|
+
current_spans.push span
|
154
168
|
|
155
169
|
span.start
|
156
170
|
end
|
171
|
+
# rubocop:enable Metrics/AbcSize, Metrics/PerceivedComplexity
|
157
172
|
# rubocop:enable Metrics/MethodLength, Metrics/CyclomaticComplexity
|
158
173
|
|
159
174
|
def end_span
|
160
|
-
return unless (span =
|
175
|
+
return unless (span = current_spans.pop)
|
161
176
|
|
162
177
|
span.done
|
163
178
|
|
164
|
-
self.current_span =
|
165
|
-
span.parent&.is_a?(Span) && span.parent || nil
|
166
|
-
|
167
179
|
enqueue.call span
|
168
180
|
|
169
181
|
span
|
@@ -185,7 +197,7 @@ module ElasticAPM
|
|
185
197
|
|
186
198
|
def set_user(user)
|
187
199
|
return unless current_transaction
|
188
|
-
current_transaction.context.user = Context::User.
|
200
|
+
current_transaction.context.user = Context::User.infer(config, user)
|
189
201
|
end
|
190
202
|
|
191
203
|
def inspect
|
@@ -199,10 +211,6 @@ module ElasticAPM
|
|
199
211
|
def random_sample?
|
200
212
|
rand <= config.transaction_sample_rate
|
201
213
|
end
|
202
|
-
|
203
|
-
def span_frames_min_duration?
|
204
|
-
config.span_frames_min_duration != 0
|
205
|
-
end
|
206
214
|
end
|
207
215
|
# rubocop:enable Metrics/ClassLength
|
208
216
|
end
|
@@ -1,8 +1,6 @@
|
|
1
1
|
#
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
|
-
require 'elastic_apm/traceparent'
|
5
|
-
|
6
4
|
module ElasticAPM
|
7
5
|
# @api private
|
8
6
|
class Middleware
|
@@ -53,13 +51,13 @@ module ElasticAPM
|
|
53
51
|
def start_transaction(env)
|
54
52
|
ElasticAPM.start_transaction 'Rack', 'request',
|
55
53
|
context: ElasticAPM.build_context(env),
|
56
|
-
|
54
|
+
trace_context: trace_context(env)
|
57
55
|
end
|
58
56
|
|
59
|
-
def
|
57
|
+
def trace_context(env)
|
60
58
|
return unless (header = env['HTTP_ELASTIC_APM_TRACEPARENT'])
|
61
|
-
|
62
|
-
rescue
|
59
|
+
TraceContext.parse(header)
|
60
|
+
rescue TraceContext::InvalidTraceparentHeader
|
63
61
|
warn "Couldn't parse invalid traceparent header: #{header.inspect}"
|
64
62
|
nil
|
65
63
|
end
|
@@ -0,0 +1,346 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'elastic_apm'
|
4
|
+
require 'opentracing'
|
5
|
+
|
6
|
+
module ElasticAPM
|
7
|
+
module OpenTracing
|
8
|
+
# @api private
|
9
|
+
class Span
|
10
|
+
def initialize(elastic_span, span_context)
|
11
|
+
@elastic_span = elastic_span
|
12
|
+
@span_context = span_context
|
13
|
+
end
|
14
|
+
|
15
|
+
attr_reader :elastic_span
|
16
|
+
|
17
|
+
def operation_name=(name)
|
18
|
+
elastic_span.name = name
|
19
|
+
end
|
20
|
+
|
21
|
+
def context
|
22
|
+
@span_context
|
23
|
+
end
|
24
|
+
|
25
|
+
# rubocop:disable Metrics/MethodLength
|
26
|
+
def set_tag(key, val)
|
27
|
+
if elastic_span.is_a?(Transaction)
|
28
|
+
case key.to_s
|
29
|
+
when 'type'
|
30
|
+
elastic_span.type = val
|
31
|
+
when 'result'
|
32
|
+
elastic_span.result = val
|
33
|
+
when /user\.(\w+)/
|
34
|
+
set_user_value($1, val)
|
35
|
+
else
|
36
|
+
elastic_span.context.tags[key] = val
|
37
|
+
end
|
38
|
+
else
|
39
|
+
elastic_span.context.tags[key] = val
|
40
|
+
end
|
41
|
+
end
|
42
|
+
# rubocop:enable Metrics/MethodLength
|
43
|
+
|
44
|
+
def set_baggage_item(_key, _value)
|
45
|
+
ElasticAPM.agent.config.logger.warn(
|
46
|
+
'Baggage is not supported by ElasticAPM'
|
47
|
+
)
|
48
|
+
end
|
49
|
+
|
50
|
+
def get_baggage_item(_key)
|
51
|
+
ElasticAPM.agent.config.logger.warn(
|
52
|
+
'Baggage is not supported by ElasticAPM'
|
53
|
+
)
|
54
|
+
|
55
|
+
nil
|
56
|
+
end
|
57
|
+
|
58
|
+
# rubocop:disable Lint/UnusedMethodArgument
|
59
|
+
def log_kv(timestamp: nil, **fields)
|
60
|
+
if (exception = fields[:'error.object'])
|
61
|
+
ElasticAPM.report exception
|
62
|
+
elsif (message = fields[:message])
|
63
|
+
ElasticAPM.report_message message
|
64
|
+
end
|
65
|
+
end
|
66
|
+
# rubocop:enable Lint/UnusedMethodArgument
|
67
|
+
|
68
|
+
def finish(end_time: Time.now)
|
69
|
+
return unless (instrumenter = ElasticAPM.agent&.instrumenter)
|
70
|
+
|
71
|
+
elastic_span.done end_time: Util.micros(end_time)
|
72
|
+
|
73
|
+
case elastic_span
|
74
|
+
when ElasticAPM::Transaction
|
75
|
+
instrumenter.current_transaction = nil
|
76
|
+
when ElasticAPM::Span
|
77
|
+
instrumenter.current_spans.delete(elastic_span)
|
78
|
+
end
|
79
|
+
|
80
|
+
instrumenter.enqueue.call elastic_span
|
81
|
+
end
|
82
|
+
|
83
|
+
private
|
84
|
+
|
85
|
+
def set_user_value(key, value)
|
86
|
+
return unless elastic_span.is_a?(Transaction)
|
87
|
+
|
88
|
+
setter = :"#{key}="
|
89
|
+
return unless elastic_span.context.user.respond_to?(setter)
|
90
|
+
elastic_span.context.user.send(setter, value)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
# @api private
|
95
|
+
class SpanContext
|
96
|
+
def initialize(id:, trace_id:, baggage: nil)
|
97
|
+
if baggage
|
98
|
+
ElasticAPM.agent.config.logger.warn(
|
99
|
+
'Baggage is not supported by ElasticAPM'
|
100
|
+
)
|
101
|
+
end
|
102
|
+
|
103
|
+
@id = id
|
104
|
+
@trace_id = trace_id
|
105
|
+
@trace_context =
|
106
|
+
ElasticAPM::TraceContext.new(trace_id: trace_id, span_id: id)
|
107
|
+
end
|
108
|
+
|
109
|
+
attr_accessor :id, :trace_id, :trace_context
|
110
|
+
|
111
|
+
def self.from_trace_context(trace_context)
|
112
|
+
new(
|
113
|
+
trace_id: trace_context.trace_id,
|
114
|
+
id: trace_context.span_id
|
115
|
+
).tap do |span_context|
|
116
|
+
span_context.trace_context = trace_context
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
# @api private
|
122
|
+
class Scope
|
123
|
+
def initialize(span, scope_stack, finish_on_close:)
|
124
|
+
@span = span
|
125
|
+
@scope_stack = scope_stack
|
126
|
+
@finish_on_close = finish_on_close
|
127
|
+
end
|
128
|
+
|
129
|
+
attr_reader :span
|
130
|
+
|
131
|
+
def elastic_span
|
132
|
+
span.elastic_span
|
133
|
+
end
|
134
|
+
|
135
|
+
def close
|
136
|
+
@span.finish if @finish_on_close
|
137
|
+
@scope_stack.pop
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
# @api private
|
142
|
+
class ScopeStack
|
143
|
+
KEY = :__elastic_apm_ot_scope_stack
|
144
|
+
|
145
|
+
def push(scope)
|
146
|
+
scopes << scope
|
147
|
+
end
|
148
|
+
|
149
|
+
def pop
|
150
|
+
scopes.pop
|
151
|
+
end
|
152
|
+
|
153
|
+
def last
|
154
|
+
scopes.last
|
155
|
+
end
|
156
|
+
|
157
|
+
private
|
158
|
+
|
159
|
+
def scopes
|
160
|
+
Thread.current[KEY] ||= []
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
# @api private
|
165
|
+
class ScopeManager
|
166
|
+
def initialize
|
167
|
+
@scope_stack = ScopeStack.new
|
168
|
+
end
|
169
|
+
|
170
|
+
def activate(span, finish_on_close: true)
|
171
|
+
return active if active && active.span == span
|
172
|
+
|
173
|
+
scope = Scope.new(span, @scope_stack, finish_on_close: finish_on_close)
|
174
|
+
@scope_stack.push scope
|
175
|
+
scope
|
176
|
+
end
|
177
|
+
|
178
|
+
def active
|
179
|
+
@scope_stack.last
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
# rubocop:disable Metrics/ClassLength
|
184
|
+
# A custom tracer to use the OpenTracing API with ElasticAPM
|
185
|
+
class Tracer
|
186
|
+
def initialize
|
187
|
+
@scope_manager = ScopeManager.new
|
188
|
+
end
|
189
|
+
|
190
|
+
attr_reader :scope_manager
|
191
|
+
|
192
|
+
def active_span
|
193
|
+
scope_manager.active&.span
|
194
|
+
end
|
195
|
+
|
196
|
+
# rubocop:disable Metrics/MethodLength, Metrics/ParameterLists
|
197
|
+
def start_active_span(
|
198
|
+
operation_name,
|
199
|
+
child_of: nil,
|
200
|
+
references: nil,
|
201
|
+
start_time: Time.now,
|
202
|
+
tags: {},
|
203
|
+
ignore_active_scope: false,
|
204
|
+
finish_on_close: true,
|
205
|
+
**
|
206
|
+
)
|
207
|
+
span = start_span(
|
208
|
+
operation_name,
|
209
|
+
child_of: child_of,
|
210
|
+
references: references,
|
211
|
+
start_time: start_time,
|
212
|
+
tags: tags,
|
213
|
+
ignore_active_scope: ignore_active_scope
|
214
|
+
)
|
215
|
+
scope = scope_manager.activate(span, finish_on_close: finish_on_close)
|
216
|
+
|
217
|
+
if block_given?
|
218
|
+
begin
|
219
|
+
yield scope
|
220
|
+
ensure
|
221
|
+
scope.close
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
scope
|
226
|
+
end
|
227
|
+
# rubocop:enable Metrics/MethodLength, Metrics/ParameterLists
|
228
|
+
|
229
|
+
# rubocop:disable Metrics/MethodLength, Metrics/ParameterLists
|
230
|
+
# rubocop:disable Metrics/AbcSize
|
231
|
+
def start_span(
|
232
|
+
operation_name,
|
233
|
+
child_of: nil,
|
234
|
+
references: nil,
|
235
|
+
start_time: Time.now,
|
236
|
+
tags: {},
|
237
|
+
ignore_active_scope: false,
|
238
|
+
**
|
239
|
+
)
|
240
|
+
span_context = prepare_span_context(
|
241
|
+
child_of: child_of,
|
242
|
+
references: references,
|
243
|
+
ignore_active_scope: ignore_active_scope
|
244
|
+
)
|
245
|
+
|
246
|
+
if span_context
|
247
|
+
trace_context =
|
248
|
+
span_context &&
|
249
|
+
span_context.respond_to?(:trace_context) &&
|
250
|
+
span_context.trace_context
|
251
|
+
end
|
252
|
+
|
253
|
+
elastic_span =
|
254
|
+
if ElasticAPM.current_transaction
|
255
|
+
ElasticAPM.start_span(
|
256
|
+
operation_name,
|
257
|
+
trace_context: trace_context
|
258
|
+
)
|
259
|
+
else
|
260
|
+
ElasticAPM.start_transaction(
|
261
|
+
operation_name,
|
262
|
+
trace_context: trace_context
|
263
|
+
)
|
264
|
+
end
|
265
|
+
|
266
|
+
# if no Elastic APM agent is running or transaction not sampled
|
267
|
+
unless elastic_span
|
268
|
+
return ::OpenTracing::Span::NOOP_INSTANCE
|
269
|
+
end
|
270
|
+
|
271
|
+
span_context ||=
|
272
|
+
SpanContext.from_trace_context(elastic_span.trace_context)
|
273
|
+
|
274
|
+
tags.each do |key, value|
|
275
|
+
elastic_span.context.tags[key] = value
|
276
|
+
end
|
277
|
+
|
278
|
+
elastic_span.start Util.micros(start_time)
|
279
|
+
|
280
|
+
Span.new(elastic_span, span_context)
|
281
|
+
end
|
282
|
+
# rubocop:enable Metrics/AbcSize
|
283
|
+
# rubocop:enable Metrics/MethodLength, Metrics/ParameterLists
|
284
|
+
|
285
|
+
def inject(span_context, format, carrier)
|
286
|
+
case format
|
287
|
+
when ::OpenTracing::FORMAT_RACK
|
288
|
+
carrier['elastic-apm-traceparent'] = span_context.to_header
|
289
|
+
else
|
290
|
+
warn 'Only injection via HTTP headers and Rack is available'
|
291
|
+
end
|
292
|
+
end
|
293
|
+
|
294
|
+
def extract(format, carrier)
|
295
|
+
case format
|
296
|
+
when ::OpenTracing::FORMAT_RACK
|
297
|
+
ElasticAPM::TraceContext
|
298
|
+
.parse(carrier['HTTP_ELASTIC_APM_TRACEPARENT'])
|
299
|
+
else
|
300
|
+
warn 'Only extraction from HTTP headers via Rack is available'
|
301
|
+
nil
|
302
|
+
end
|
303
|
+
rescue ElasticAPM::TraceContext::InvalidTraceparentHeader
|
304
|
+
nil
|
305
|
+
end
|
306
|
+
|
307
|
+
private
|
308
|
+
|
309
|
+
def prepare_span_context(
|
310
|
+
child_of:,
|
311
|
+
references:,
|
312
|
+
ignore_active_scope:
|
313
|
+
)
|
314
|
+
context_from_child_of(child_of) ||
|
315
|
+
context_from_references(references) ||
|
316
|
+
context_from_active_scope(ignore_active_scope)
|
317
|
+
end
|
318
|
+
|
319
|
+
def context_from_child_of(child_of)
|
320
|
+
return unless child_of
|
321
|
+
child_of.respond_to?(:context) ? child_of.context : child_of
|
322
|
+
end
|
323
|
+
|
324
|
+
def context_from_references(references)
|
325
|
+
return if !references || references.none?
|
326
|
+
|
327
|
+
child_of = references.find do |reference|
|
328
|
+
reference.type == ::OpenTracing::Reference::CHILD_OF
|
329
|
+
end
|
330
|
+
|
331
|
+
(child_of || references.first).context
|
332
|
+
end
|
333
|
+
|
334
|
+
def context_from_active_scope(ignore_active_scope)
|
335
|
+
if ignore_active_scope
|
336
|
+
ElasticAPM.agent&.config&.logger&.warn(
|
337
|
+
'ignore_active_scope might lead to unexpeced results'
|
338
|
+
)
|
339
|
+
return
|
340
|
+
end
|
341
|
+
@scope_manager.active&.span&.context
|
342
|
+
end
|
343
|
+
end
|
344
|
+
# rubocop:enable Metrics/ClassLength
|
345
|
+
end
|
346
|
+
end
|