splunk-tracer 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/.circleci/config.yml +31 -0
- data/.gitignore +12 -0
- data/.rspec +2 -0
- data/.rubocop.yml +48 -0
- data/CHANGELOG.md +4 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +52 -0
- data/LICENSE.txt +21 -0
- data/Makefile +24 -0
- data/README.md +74 -0
- data/Rakefile +6 -0
- data/benchmark.rb +138 -0
- data/benchmark/bench.rb +60 -0
- data/benchmark/threading/thread_test.rb +54 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/circle.yml +3 -0
- data/example.rb +33 -0
- data/examples/fork_children/main.rb +54 -0
- data/examples/rack/hello.rb +22 -0
- data/examples/rack/inject_extract.rb +68 -0
- data/lib/splunktracing.rb +50 -0
- data/lib/splunktracing/global_tracer.rb +32 -0
- data/lib/splunktracing/reporter.rb +119 -0
- data/lib/splunktracing/scope.rb +23 -0
- data/lib/splunktracing/scope_manager.rb +54 -0
- data/lib/splunktracing/span.rb +191 -0
- data/lib/splunktracing/span_context.rb +13 -0
- data/lib/splunktracing/tracer.rb +321 -0
- data/lib/splunktracing/transport/base.rb +10 -0
- data/lib/splunktracing/transport/callback.rb +16 -0
- data/lib/splunktracing/transport/http_json.rb +144 -0
- data/lib/splunktracing/transport/nil.rb +9 -0
- data/lib/splunktracing/version.rb +3 -0
- data/scripts/version.rb +5 -0
- data/splunktracing.gemspec +31 -0
- metadata +193 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
module SplunkTracing
|
|
2
|
+
# Scope represents an OpenTracing Scope
|
|
3
|
+
#
|
|
4
|
+
# See http://www.opentracing.io for more information.
|
|
5
|
+
class Scope
|
|
6
|
+
attr_reader :span
|
|
7
|
+
|
|
8
|
+
def initialize(manager:, span:, finish_on_close: true)
|
|
9
|
+
@manager = manager
|
|
10
|
+
@span = span
|
|
11
|
+
@finish_on_close = finish_on_close
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# Mark the end of the active period for the current thread and Scope,
|
|
15
|
+
# updating the ScopeManager#active in the process.
|
|
16
|
+
def close
|
|
17
|
+
raise(SplunkTracing::Error, 'already closed') if @closed
|
|
18
|
+
@closed = true
|
|
19
|
+
@span.finish if @finish_on_close
|
|
20
|
+
@manager.deactivate
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
module SplunkTracing
|
|
2
|
+
# ScopeManager represents an OpenTracing ScopeManager
|
|
3
|
+
#
|
|
4
|
+
# See http://www.opentracing.io for more information.
|
|
5
|
+
#
|
|
6
|
+
# The ScopeManager interface abstracts both the activation of Span instances
|
|
7
|
+
# via ScopeManager#activate and access to an active Span/Scope via
|
|
8
|
+
# ScopeManager#active
|
|
9
|
+
#
|
|
10
|
+
class ScopeManager
|
|
11
|
+
# Make a span instance active.
|
|
12
|
+
#
|
|
13
|
+
# @param span [Span] the Span that should become active
|
|
14
|
+
# @param finish_on_close [Boolean] whether the Span should automatically be
|
|
15
|
+
# finished when Scope#close is called
|
|
16
|
+
# @return [Scope] instance to control the end of the active period for the
|
|
17
|
+
# Span. It is a programming error to neglect to call Scope#close on the
|
|
18
|
+
# returned instance.
|
|
19
|
+
def activate(span:, finish_on_close: true)
|
|
20
|
+
return active if active && active.span == span
|
|
21
|
+
SplunkTracing::Scope.new(manager: self, span: span, finish_on_close: finish_on_close).tap do |scope|
|
|
22
|
+
add_scope(scope)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# @return [Scope] the currently active Scope which can be used to access the
|
|
27
|
+
# currently active Span.
|
|
28
|
+
#
|
|
29
|
+
# If there is a non-null Scope, its wrapped Span becomes an implicit parent
|
|
30
|
+
# (as Reference#CHILD_OF) of any newly-created Span at Tracer#start_active_span
|
|
31
|
+
# or Tracer#start_span time.
|
|
32
|
+
def active
|
|
33
|
+
scopes.last
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def deactivate
|
|
37
|
+
scopes.pop
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
|
|
42
|
+
def scopes
|
|
43
|
+
Thread.current[object_id.to_s] || []
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def add_scope(scope)
|
|
47
|
+
if Thread.current[object_id.to_s].nil?
|
|
48
|
+
Thread.current[object_id.to_s] = [scope]
|
|
49
|
+
else
|
|
50
|
+
Thread.current[object_id.to_s] << scope
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
require 'concurrent'
|
|
2
|
+
require 'splunktracing/span_context'
|
|
3
|
+
|
|
4
|
+
module SplunkTracing
|
|
5
|
+
# Span represents an OpenTracer Span
|
|
6
|
+
#
|
|
7
|
+
# See http://www.opentracing.io for more information.
|
|
8
|
+
class Span
|
|
9
|
+
# Part of the OpenTracing API
|
|
10
|
+
attr_writer :operation_name
|
|
11
|
+
|
|
12
|
+
# Internal use only
|
|
13
|
+
# @private
|
|
14
|
+
attr_reader :start_micros, :end_micros, :tags, :operation_name, :context
|
|
15
|
+
|
|
16
|
+
# To keep backwards compatibility
|
|
17
|
+
alias_method :span_context, :context
|
|
18
|
+
|
|
19
|
+
# Creates a new {Span}
|
|
20
|
+
#
|
|
21
|
+
# @param tracer [Tracer] the tracer that created this span
|
|
22
|
+
# @param operation_name [String] the operation name of this span. If it's
|
|
23
|
+
# not a String it will be encoded with to_s.
|
|
24
|
+
# @param child_of [SpanContext] the parent SpanContext (per child_of)
|
|
25
|
+
# @param references [Array<SpanContext>] An array of SpanContexts
|
|
26
|
+
# that identify what Spans this Span follows from causally. Presently
|
|
27
|
+
# only one reference is supported, and cannot be provided in addition to
|
|
28
|
+
# a child_of.
|
|
29
|
+
# @param start_micros [Numeric] start time of the span in microseconds
|
|
30
|
+
# @param tags [Hash] initial key:value tags (per set_tag) for the Span
|
|
31
|
+
# @param max_log_records [Numeric] maximum allowable number of log records
|
|
32
|
+
# for the Span
|
|
33
|
+
# @return [Span] a started Span
|
|
34
|
+
def initialize(
|
|
35
|
+
tracer:,
|
|
36
|
+
operation_name:,
|
|
37
|
+
child_of: nil,
|
|
38
|
+
references: [],
|
|
39
|
+
start_micros:,
|
|
40
|
+
tags: nil,
|
|
41
|
+
max_log_records:
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
@tags = Concurrent::Hash.new
|
|
45
|
+
@tags.update(tags.each { |k, v| tags[k] = v.to_s }) unless tags.nil?
|
|
46
|
+
@log_records = Concurrent::Array.new
|
|
47
|
+
@dropped_logs = Concurrent::AtomicFixnum.new
|
|
48
|
+
@max_log_records = max_log_records
|
|
49
|
+
|
|
50
|
+
@tracer = tracer
|
|
51
|
+
self.operation_name = operation_name.to_s
|
|
52
|
+
self.start_micros = start_micros
|
|
53
|
+
|
|
54
|
+
ref = child_of ? child_of : references
|
|
55
|
+
ref = ref[0] if (Array === ref)
|
|
56
|
+
ref = ref.context if (Span === ref)
|
|
57
|
+
|
|
58
|
+
if SpanContext === ref
|
|
59
|
+
@context = SpanContext.new(id: SplunkTracing.guid, trace_id: ref.trace_id, parent_id: ref.id)
|
|
60
|
+
set_baggage(ref.baggage)
|
|
61
|
+
# set_tag(:parent_span_guid, ref.id)
|
|
62
|
+
else
|
|
63
|
+
@context = SpanContext.new(id: SplunkTracing.guid, trace_id: SplunkTracing.guid)
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Set a tag value on this span
|
|
68
|
+
# @param key [String] the key of the tag
|
|
69
|
+
# @param value [String] the value of the tag. If it's not a String
|
|
70
|
+
# it will be encoded with to_s
|
|
71
|
+
def set_tag(key, value)
|
|
72
|
+
tags[key] = value.to_s
|
|
73
|
+
self
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# TODO(ngauthier@gmail.com) baggage keys have a restricted format according
|
|
77
|
+
# to the spec: http://opentracing.io/documentation/pages/spec#baggage-vs-span-tags
|
|
78
|
+
|
|
79
|
+
# Set a baggage item on the span
|
|
80
|
+
# @param key [String] the key of the baggage item
|
|
81
|
+
# @param value [String] the value of the baggage item
|
|
82
|
+
def set_baggage_item(key, value)
|
|
83
|
+
@context = SpanContext.new(
|
|
84
|
+
id: context.id,
|
|
85
|
+
trace_id: context.trace_id,
|
|
86
|
+
parent_id: context.parent_id,
|
|
87
|
+
baggage: context.baggage.merge({key => value})
|
|
88
|
+
)
|
|
89
|
+
self
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Set all baggage at once. This will reset the baggage to the given param.
|
|
93
|
+
# @param baggage [Hash] new baggage for the span
|
|
94
|
+
def set_baggage(baggage = {})
|
|
95
|
+
@context = SpanContext.new(
|
|
96
|
+
id: context.id,
|
|
97
|
+
trace_id: context.trace_id,
|
|
98
|
+
parent_id: context.parent_id,
|
|
99
|
+
baggage: baggage
|
|
100
|
+
)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Get a baggage item
|
|
104
|
+
# @param key [String] the key of the baggage item
|
|
105
|
+
# @return Value of the baggage item
|
|
106
|
+
def get_baggage_item(key)
|
|
107
|
+
context.baggage[key]
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# @deprecated Use {#log_kv} instead.
|
|
111
|
+
# Add a log entry to this span
|
|
112
|
+
# @param event [String] event name for the log
|
|
113
|
+
# @param timestamp [Time] time of the log
|
|
114
|
+
# @param fields [Hash] Additional information to log
|
|
115
|
+
def log(event: nil, timestamp: Time.now, **fields)
|
|
116
|
+
warn 'Span#log is deprecated. Please use Span#log_kv instead.'
|
|
117
|
+
return unless tracer.enabled?
|
|
118
|
+
|
|
119
|
+
fields = {} if fields.nil?
|
|
120
|
+
unless event.nil?
|
|
121
|
+
fields[:event] = event.to_s
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
log_kv(timestamp: timestamp, **fields)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def log_kv(timestamp: Time.now, **fields)
|
|
128
|
+
return unless tracer.enabled?
|
|
129
|
+
|
|
130
|
+
fields = {} if fields.nil?
|
|
131
|
+
record = {
|
|
132
|
+
timestamp_micros: SplunkTracing.micros(timestamp),
|
|
133
|
+
fields: fields,
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
log_records.push(record)
|
|
137
|
+
if log_records.size > @max_log_records
|
|
138
|
+
log_records.shift
|
|
139
|
+
dropped_logs.increment
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# Finish the {Span}
|
|
144
|
+
# @param end_time [Time] custom end time, if not now
|
|
145
|
+
def finish(end_time: Time.now)
|
|
146
|
+
if end_micros.nil?
|
|
147
|
+
self.end_micros = SplunkTracing.micros(end_time)
|
|
148
|
+
end
|
|
149
|
+
tracer.finish_span(self)
|
|
150
|
+
self
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# Hash representation of a span
|
|
154
|
+
def to_h
|
|
155
|
+
if end_micros.nil?
|
|
156
|
+
self.end_micros = SplunkTracing.micros(Time.now)
|
|
157
|
+
end
|
|
158
|
+
{
|
|
159
|
+
guid: tracer.guid,
|
|
160
|
+
span_id: context.id,
|
|
161
|
+
trace_id: context.trace_id,
|
|
162
|
+
parent_span_id: context.parent_id,
|
|
163
|
+
operation_name: operation_name,
|
|
164
|
+
tags: tags,
|
|
165
|
+
timestamp: start_micros/1000000.0,
|
|
166
|
+
duration: end_micros-start_micros,
|
|
167
|
+
error_flag: false,
|
|
168
|
+
dropped_logs: dropped_logs_count,
|
|
169
|
+
log_records: log_records,
|
|
170
|
+
baggage: context.baggage
|
|
171
|
+
}
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
# Internal use only
|
|
175
|
+
# @private
|
|
176
|
+
def dropped_logs_count
|
|
177
|
+
dropped_logs.value
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
# Internal use only
|
|
181
|
+
# @private
|
|
182
|
+
def logs_count
|
|
183
|
+
log_records.size
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
private
|
|
187
|
+
|
|
188
|
+
attr_reader :tracer, :dropped_logs, :log_records
|
|
189
|
+
attr_writer :start_micros, :end_micros
|
|
190
|
+
end
|
|
191
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
module SplunkTracing
|
|
2
|
+
# SpanContext holds the data for a span that gets inherited to child spans
|
|
3
|
+
class SpanContext
|
|
4
|
+
attr_reader :id, :trace_id, :parent_id, :baggage
|
|
5
|
+
|
|
6
|
+
def initialize(id:, trace_id:, parent_id: nil, baggage: {})
|
|
7
|
+
@id = id.freeze
|
|
8
|
+
@trace_id = trace_id.freeze
|
|
9
|
+
@parent_id = parent_id.freeze
|
|
10
|
+
@baggage = baggage.freeze
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
require 'json'
|
|
2
|
+
require 'concurrent'
|
|
3
|
+
|
|
4
|
+
require 'opentracing'
|
|
5
|
+
|
|
6
|
+
require 'splunktracing/span'
|
|
7
|
+
require 'splunktracing/reporter'
|
|
8
|
+
require 'splunktracing/transport/http_json'
|
|
9
|
+
require 'splunktracing/transport/nil'
|
|
10
|
+
require 'splunktracing/transport/callback'
|
|
11
|
+
|
|
12
|
+
module SplunkTracing
|
|
13
|
+
class Tracer
|
|
14
|
+
class Error < SplunkTracing::Error; end
|
|
15
|
+
class ConfigurationError < SplunkTracing::Tracer::Error; end
|
|
16
|
+
|
|
17
|
+
attr_reader :access_token, :guid
|
|
18
|
+
|
|
19
|
+
# Initialize a new tracer. Either an access_token or a transport must be
|
|
20
|
+
# provided. A component_name is always required.
|
|
21
|
+
# @param component_name [String] Component name to use for the tracer
|
|
22
|
+
# @param access_token [String] The project access token when pushing to SplunkTracing
|
|
23
|
+
# @param transport [SplunkTracing::Transport] How the data should be transported
|
|
24
|
+
# @param tags [Hash] Tracer-level tags
|
|
25
|
+
# @return SplunkTracing::Tracer
|
|
26
|
+
# @raise SplunkTracing::ConfigurationError if the group name or access token is not a valid string.
|
|
27
|
+
def initialize(component_name:, access_token: nil, transport: nil, tags: {})
|
|
28
|
+
configure(component_name: component_name, access_token: access_token, transport: transport, tags: tags)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def max_log_records
|
|
32
|
+
@max_log_records ||= DEFAULT_MAX_LOG_RECORDS
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def max_log_records=(max)
|
|
36
|
+
@max_log_records = [MIN_MAX_LOG_RECORDS, max].max
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def max_span_records
|
|
40
|
+
@max_span_records ||= DEFAULT_MAX_SPAN_RECORDS
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def max_span_records=(max)
|
|
44
|
+
@max_span_records = [MIN_MAX_SPAN_RECORDS, max].max
|
|
45
|
+
@reporter.max_span_records = @max_span_records
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Set the report flushing period. If set to 0, no flushing will be done, you
|
|
49
|
+
# must manually call flush.
|
|
50
|
+
def report_period_seconds=(seconds)
|
|
51
|
+
@reporter.period = seconds
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# TODO(bhs): Support FollowsFrom and multiple references
|
|
55
|
+
|
|
56
|
+
# Creates a scope manager or returns the already-created one.
|
|
57
|
+
#
|
|
58
|
+
# @return [ScopeManager] the current ScopeManager, which may be a no-op but
|
|
59
|
+
# may not be nil.
|
|
60
|
+
def scope_manager
|
|
61
|
+
@scope_manager ||= SplunkTracing::ScopeManager.new
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Returns a newly started and activated Scope.
|
|
65
|
+
#
|
|
66
|
+
# If ScopeManager#active is not nil, no explicit references are provided,
|
|
67
|
+
# and `ignore_active_scope` is false, then an inferred References#CHILD_OF
|
|
68
|
+
# reference is created to the ScopeManager#active's SpanContext when
|
|
69
|
+
# start_active_span is invoked.
|
|
70
|
+
#
|
|
71
|
+
# @param operation_name [String] The operation name for the Span
|
|
72
|
+
# @param child_of [SpanContext, Span] SpanContext that acts as a parent to
|
|
73
|
+
# the newly-started Span. If a Span instance is provided, its
|
|
74
|
+
# context is automatically substituted. See [Reference] for more
|
|
75
|
+
# information.
|
|
76
|
+
#
|
|
77
|
+
# If specified, the `references` parameter must be omitted.
|
|
78
|
+
# @param references [Array<Reference>] An array of reference
|
|
79
|
+
# objects that identify one or more parent SpanContexts.
|
|
80
|
+
# @param start_time [Time] When the Span started, if not now
|
|
81
|
+
# @param tags [Hash] Tags to assign to the Span at start time
|
|
82
|
+
# @param ignore_active_scope [Boolean] whether to create an implicit
|
|
83
|
+
# References#CHILD_OF reference to the ScopeManager#active.
|
|
84
|
+
# @param finish_on_close [Boolean] whether span should automatically be
|
|
85
|
+
# finished when Scope#close is called
|
|
86
|
+
# @yield [Scope] If an optional block is passed to start_active it will
|
|
87
|
+
# yield the newly-started Scope. If `finish_on_close` is true then the
|
|
88
|
+
# Span will be finished automatically after the block is executed.
|
|
89
|
+
# @return [Scope] The newly-started and activated Scope
|
|
90
|
+
def start_active_span(operation_name,
|
|
91
|
+
child_of: nil,
|
|
92
|
+
references: nil,
|
|
93
|
+
start_time: Time.now,
|
|
94
|
+
tags: nil,
|
|
95
|
+
ignore_active_scope: false,
|
|
96
|
+
finish_on_close: true)
|
|
97
|
+
if child_of.nil? && references.nil? && !ignore_active_scope
|
|
98
|
+
child_of = active_span
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
span = start_span(
|
|
102
|
+
operation_name,
|
|
103
|
+
child_of: child_of,
|
|
104
|
+
references: references,
|
|
105
|
+
start_time: start_time,
|
|
106
|
+
tags: tags,
|
|
107
|
+
ignore_active_scope: ignore_active_scope
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
scope_manager.activate(span: span, finish_on_close: finish_on_close).tap do |scope|
|
|
111
|
+
if block_given?
|
|
112
|
+
yield scope
|
|
113
|
+
scope.close
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# Returns the span from the active scope, if any.
|
|
119
|
+
#
|
|
120
|
+
# @return [Span, nil] the active span. This is a shorthand for
|
|
121
|
+
# `scope_manager.active.span`, and nil will be returned if
|
|
122
|
+
# Scope#active is nil.
|
|
123
|
+
def active_span
|
|
124
|
+
scope = scope_manager.active
|
|
125
|
+
scope.span if scope
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# Starts a new span.
|
|
129
|
+
#
|
|
130
|
+
# @param operation_name [String] The operation name for the Span
|
|
131
|
+
# @param child_of [SpanContext] SpanContext that acts as a parent to
|
|
132
|
+
# the newly-started Span. If a Span instance is provided, its
|
|
133
|
+
# .span_context is automatically substituted.
|
|
134
|
+
# @param references [Array<SpanContext>] An array of SpanContexts that
|
|
135
|
+
# identify any parent SpanContexts of newly-started Span. If Spans
|
|
136
|
+
# are provided, their .span_context is automatically substituted.
|
|
137
|
+
# @param start_time [Time] When the Span started, if not now
|
|
138
|
+
# @param tags [Hash] Tags to assign to the Span at start time
|
|
139
|
+
# @param ignore_active_scope [Boolean] whether to create an implicit
|
|
140
|
+
# References#CHILD_OF reference to the ScopeManager#active.
|
|
141
|
+
# @return [Span]
|
|
142
|
+
def start_span(operation_name, child_of: nil, references: nil, start_time: nil, tags: nil, ignore_active_scope: false)
|
|
143
|
+
if child_of.nil? && references.nil? && !ignore_active_scope
|
|
144
|
+
child_of = active_span
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
Span.new(
|
|
148
|
+
tracer: self,
|
|
149
|
+
operation_name: operation_name,
|
|
150
|
+
child_of: child_of,
|
|
151
|
+
references: references,
|
|
152
|
+
start_micros: start_time.nil? ? SplunkTracing.micros(Time.now) : SplunkTracing.micros(start_time),
|
|
153
|
+
tags: tags,
|
|
154
|
+
max_log_records: max_log_records,
|
|
155
|
+
)
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# Inject a SpanContext into the given carrier
|
|
159
|
+
#
|
|
160
|
+
# @param spancontext [SpanContext]
|
|
161
|
+
# @param format [OpenTracing::FORMAT_TEXT_MAP, OpenTracing::FORMAT_BINARY]
|
|
162
|
+
# @param carrier [Carrier] A carrier object of the type dictated by the specified `format`
|
|
163
|
+
def inject(span_context, format, carrier)
|
|
164
|
+
case format
|
|
165
|
+
when OpenTracing::FORMAT_TEXT_MAP
|
|
166
|
+
inject_to_text_map(span_context, carrier)
|
|
167
|
+
when OpenTracing::FORMAT_BINARY
|
|
168
|
+
warn 'Binary inject format not yet implemented'
|
|
169
|
+
when OpenTracing::FORMAT_RACK
|
|
170
|
+
inject_to_rack(span_context, carrier)
|
|
171
|
+
else
|
|
172
|
+
warn 'Unknown inject format'
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
# Extract a SpanContext from a carrier
|
|
177
|
+
# @param format [OpenTracing::FORMAT_TEXT_MAP, OpenTracing::FORMAT_BINARY, OpenTracing::FORMAT_RACK]
|
|
178
|
+
# @param carrier [Carrier] A carrier object of the type dictated by the specified `format`
|
|
179
|
+
# @return [SpanContext] the extracted SpanContext or nil if none could be found
|
|
180
|
+
def extract(format, carrier)
|
|
181
|
+
case format
|
|
182
|
+
when OpenTracing::FORMAT_TEXT_MAP
|
|
183
|
+
extract_from_text_map(carrier)
|
|
184
|
+
when OpenTracing::FORMAT_BINARY
|
|
185
|
+
warn 'Binary join format not yet implemented'
|
|
186
|
+
nil
|
|
187
|
+
when OpenTracing::FORMAT_RACK
|
|
188
|
+
extract_from_rack(carrier)
|
|
189
|
+
else
|
|
190
|
+
warn 'Unknown join format'
|
|
191
|
+
nil
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
# @return true if the tracer is enabled
|
|
196
|
+
def enabled?
|
|
197
|
+
return @enabled if defined?(@enabled)
|
|
198
|
+
@enabled = true
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
# Enables the tracer
|
|
202
|
+
def enable
|
|
203
|
+
@enabled = true
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
# Disables the tracer
|
|
207
|
+
# @param discard [Boolean] whether to discard queued data
|
|
208
|
+
def disable(discard: true)
|
|
209
|
+
@enabled = false
|
|
210
|
+
@reporter.clear if discard
|
|
211
|
+
@reporter.flush
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
# Flush to the Transport
|
|
215
|
+
def flush
|
|
216
|
+
return unless enabled?
|
|
217
|
+
@reporter.flush
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
# Internal use only.
|
|
221
|
+
# @private
|
|
222
|
+
def finish_span(span)
|
|
223
|
+
return unless enabled?
|
|
224
|
+
@reporter.add_span(span)
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
protected
|
|
228
|
+
|
|
229
|
+
def configure(component_name:, access_token: nil, transport: nil, tags: {})
|
|
230
|
+
raise ConfigurationError, "component_name must be a string" unless component_name.is_a?(String)
|
|
231
|
+
raise ConfigurationError, "component_name cannot be blank" if component_name.empty?
|
|
232
|
+
|
|
233
|
+
if transport.nil? and !access_token.nil?
|
|
234
|
+
transport = Transport::HTTPJSON.new(access_token: access_token)
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
raise ConfigurationError, "you must provide an access token or a transport" if transport.nil?
|
|
238
|
+
raise ConfigurationError, "#{transport} is not a SplunkTracing transport class" if !(SplunkTracing::Transport::Base === transport)
|
|
239
|
+
|
|
240
|
+
@guid = SplunkTracing.guid
|
|
241
|
+
|
|
242
|
+
@reporter = SplunkTracing::Reporter.new(
|
|
243
|
+
max_span_records: max_span_records,
|
|
244
|
+
transport: transport,
|
|
245
|
+
guid: guid,
|
|
246
|
+
component_name: component_name,
|
|
247
|
+
tags: tags
|
|
248
|
+
)
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
private
|
|
252
|
+
|
|
253
|
+
CARRIER_TRACER_STATE_PREFIX = 'ot-tracer-'.freeze
|
|
254
|
+
CARRIER_BAGGAGE_PREFIX = 'ot-baggage-'.freeze
|
|
255
|
+
|
|
256
|
+
CARRIER_SPAN_ID = (CARRIER_TRACER_STATE_PREFIX + 'spanid').freeze
|
|
257
|
+
CARRIER_TRACE_ID = (CARRIER_TRACER_STATE_PREFIX + 'traceid').freeze
|
|
258
|
+
CARRIER_SAMPLED = (CARRIER_TRACER_STATE_PREFIX + 'sampled').freeze
|
|
259
|
+
|
|
260
|
+
DEFAULT_MAX_LOG_RECORDS = 1000
|
|
261
|
+
MIN_MAX_LOG_RECORDS = 1
|
|
262
|
+
DEFAULT_MAX_SPAN_RECORDS = 1000
|
|
263
|
+
MIN_MAX_SPAN_RECORDS = 1
|
|
264
|
+
|
|
265
|
+
def inject_to_text_map(span_context, carrier)
|
|
266
|
+
carrier[CARRIER_SPAN_ID] = span_context.id
|
|
267
|
+
carrier[CARRIER_TRACE_ID] = span_context.trace_id unless span_context.trace_id.nil?
|
|
268
|
+
carrier[CARRIER_SAMPLED] = 'true'
|
|
269
|
+
|
|
270
|
+
span_context.baggage.each do |key, value|
|
|
271
|
+
carrier[CARRIER_BAGGAGE_PREFIX + key] = value
|
|
272
|
+
end
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
def extract_from_text_map(carrier)
|
|
276
|
+
# If the carrier does not have both the span_id and trace_id key
|
|
277
|
+
# skip the processing and just return a normal span
|
|
278
|
+
if !carrier.has_key?(CARRIER_SPAN_ID) || !carrier.has_key?(CARRIER_TRACE_ID)
|
|
279
|
+
return nil
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
baggage = carrier.reduce({}) do |baggage, tuple|
|
|
283
|
+
key, value = tuple
|
|
284
|
+
if key.start_with?(CARRIER_BAGGAGE_PREFIX)
|
|
285
|
+
plain_key = key.to_s[CARRIER_BAGGAGE_PREFIX.length..key.to_s.length]
|
|
286
|
+
baggage[plain_key] = value
|
|
287
|
+
end
|
|
288
|
+
baggage
|
|
289
|
+
end
|
|
290
|
+
SpanContext.new(
|
|
291
|
+
id: carrier[CARRIER_SPAN_ID],
|
|
292
|
+
trace_id: carrier[CARRIER_TRACE_ID],
|
|
293
|
+
baggage: baggage,
|
|
294
|
+
)
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
def inject_to_rack(span_context, carrier)
|
|
298
|
+
carrier[CARRIER_SPAN_ID] = span_context.id
|
|
299
|
+
carrier[CARRIER_TRACE_ID] = span_context.trace_id unless span_context.trace_id.nil?
|
|
300
|
+
carrier[CARRIER_SAMPLED] = 'true'
|
|
301
|
+
|
|
302
|
+
span_context.baggage.each do |key, value|
|
|
303
|
+
if key =~ /[^A-Za-z0-9\-_]/
|
|
304
|
+
# TODO: log the error internally
|
|
305
|
+
next
|
|
306
|
+
end
|
|
307
|
+
carrier[CARRIER_BAGGAGE_PREFIX + key] = value
|
|
308
|
+
end
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
def extract_from_rack(env)
|
|
312
|
+
extract_from_text_map(env.reduce({}){|memo, tuple|
|
|
313
|
+
raw_header, value = tuple
|
|
314
|
+
header = raw_header.to_s.gsub(/^HTTP_/, '').tr('_', '-').downcase
|
|
315
|
+
|
|
316
|
+
memo[header] = value if header.start_with?(CARRIER_TRACER_STATE_PREFIX, CARRIER_BAGGAGE_PREFIX)
|
|
317
|
+
memo
|
|
318
|
+
})
|
|
319
|
+
end
|
|
320
|
+
end
|
|
321
|
+
end
|