opentelemetry-api 0.5.1

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 (53) hide show
  1. checksums.yaml +7 -0
  2. data/.yardopts +9 -0
  3. data/CHANGELOG.md +1 -0
  4. data/LICENSE +201 -0
  5. data/OVERVIEW.md +66 -0
  6. data/lib/opentelemetry-api.rb +7 -0
  7. data/lib/opentelemetry.rb +61 -0
  8. data/lib/opentelemetry/context.rb +154 -0
  9. data/lib/opentelemetry/context/key.rb +29 -0
  10. data/lib/opentelemetry/context/propagation.rb +22 -0
  11. data/lib/opentelemetry/context/propagation/composite_propagator.rb +73 -0
  12. data/lib/opentelemetry/context/propagation/default_getter.rb +26 -0
  13. data/lib/opentelemetry/context/propagation/default_setter.rb +26 -0
  14. data/lib/opentelemetry/context/propagation/noop_extractor.rb +26 -0
  15. data/lib/opentelemetry/context/propagation/noop_injector.rb +26 -0
  16. data/lib/opentelemetry/context/propagation/propagation.rb +27 -0
  17. data/lib/opentelemetry/context/propagation/propagator.rb +64 -0
  18. data/lib/opentelemetry/correlation_context.rb +16 -0
  19. data/lib/opentelemetry/correlation_context/builder.rb +18 -0
  20. data/lib/opentelemetry/correlation_context/manager.rb +36 -0
  21. data/lib/opentelemetry/correlation_context/propagation.rb +57 -0
  22. data/lib/opentelemetry/correlation_context/propagation/context_keys.rb +27 -0
  23. data/lib/opentelemetry/correlation_context/propagation/text_extractor.rb +60 -0
  24. data/lib/opentelemetry/correlation_context/propagation/text_injector.rb +55 -0
  25. data/lib/opentelemetry/error.rb +9 -0
  26. data/lib/opentelemetry/instrumentation.rb +15 -0
  27. data/lib/opentelemetry/instrumentation/base.rb +245 -0
  28. data/lib/opentelemetry/instrumentation/registry.rb +87 -0
  29. data/lib/opentelemetry/internal.rb +22 -0
  30. data/lib/opentelemetry/metrics.rb +16 -0
  31. data/lib/opentelemetry/metrics/handles.rb +44 -0
  32. data/lib/opentelemetry/metrics/instruments.rb +105 -0
  33. data/lib/opentelemetry/metrics/meter.rb +72 -0
  34. data/lib/opentelemetry/metrics/meter_provider.rb +22 -0
  35. data/lib/opentelemetry/trace.rb +51 -0
  36. data/lib/opentelemetry/trace/event.rb +46 -0
  37. data/lib/opentelemetry/trace/link.rb +46 -0
  38. data/lib/opentelemetry/trace/propagation.rb +17 -0
  39. data/lib/opentelemetry/trace/propagation/context_keys.rb +35 -0
  40. data/lib/opentelemetry/trace/propagation/trace_context.rb +59 -0
  41. data/lib/opentelemetry/trace/propagation/trace_context/text_extractor.rb +58 -0
  42. data/lib/opentelemetry/trace/propagation/trace_context/text_injector.rb +55 -0
  43. data/lib/opentelemetry/trace/propagation/trace_context/trace_parent.rb +130 -0
  44. data/lib/opentelemetry/trace/span.rb +145 -0
  45. data/lib/opentelemetry/trace/span_context.rb +56 -0
  46. data/lib/opentelemetry/trace/span_kind.rb +35 -0
  47. data/lib/opentelemetry/trace/status.rb +114 -0
  48. data/lib/opentelemetry/trace/trace_flags.rb +50 -0
  49. data/lib/opentelemetry/trace/tracer.rb +103 -0
  50. data/lib/opentelemetry/trace/tracer_provider.rb +22 -0
  51. data/lib/opentelemetry/trace/util/http_to_status.rb +47 -0
  52. data/lib/opentelemetry/version.rb +10 -0
  53. metadata +220 -0
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright 2019 OpenTelemetry Authors
4
+ #
5
+ # SPDX-License-Identifier: Apache-2.0
6
+
7
+ module OpenTelemetry
8
+ module CorrelationContext
9
+ module Propagation
10
+ # The ContextKeys module contains the keys used to index correlations
11
+ # in a {Context} instance
12
+ module ContextKeys
13
+ extend self
14
+
15
+ CORRELATION_CONTEXT_KEY = Context.create_key('correlation-context')
16
+ private_constant :CORRELATION_CONTEXT_KEY
17
+
18
+ # Returns the context key that correlations are indexed by
19
+ #
20
+ # @return [Context::Key]
21
+ def correlation_context_key
22
+ CORRELATION_CONTEXT_KEY
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright 2019 OpenTelemetry Authors
4
+ #
5
+ # SPDX-License-Identifier: Apache-2.0
6
+
7
+ require 'cgi'
8
+
9
+ module OpenTelemetry
10
+ module CorrelationContext
11
+ module Propagation
12
+ # Extracts correlations from carriers in the W3C Correlation Context format
13
+ class TextExtractor
14
+ include Context::Propagation::DefaultGetter
15
+
16
+ # Returns a new TextExtractor that extracts context using the specified
17
+ # header key
18
+ #
19
+ # @param [String] correlation_context_key The correlation context header
20
+ # key used in the carrier
21
+ # @return [TextExtractor]
22
+ def initialize(correlation_context_key: 'otcorrelations')
23
+ @correlation_context_key = correlation_context_key
24
+ end
25
+
26
+ # Extract remote correlations from the supplied carrier.
27
+ # If extraction fails, the original context will be returned
28
+ #
29
+ # @param [Carrier] carrier The carrier to get the header from
30
+ # @param [Context] context The context to be updated with extracted correlations
31
+ # @param [optional Callable] getter An optional callable that takes a carrier and a key and
32
+ # returns the value associated with the key. If omitted the default getter will be used
33
+ # which expects the carrier to respond to [] and []=.
34
+ # @yield [Carrier, String] if an optional getter is provided, extract will yield the carrier
35
+ # and the header key to the getter.
36
+ # @return [Context] context updated with extracted correlations, or the original context
37
+ # if extraction fails
38
+ def extract(carrier, context, &getter)
39
+ getter ||= default_getter
40
+ header = getter.call(carrier, @correlation_context_key)
41
+
42
+ entries = header.gsub(/\s/, '').split(',')
43
+
44
+ correlations = entries.each_with_object({}) do |entry, memo|
45
+ # The ignored variable below holds properties as per the W3C spec.
46
+ # OTel is not using them currently, but they might be used for
47
+ # metadata in the future
48
+ kv, = entry.split(';', 2)
49
+ k, v = kv.split('=').map!(&CGI.method(:unescape))
50
+ memo[k] = v
51
+ end
52
+
53
+ context.set_value(ContextKeys.correlation_context_key, correlations)
54
+ rescue StandardError
55
+ context
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright 2019 OpenTelemetry Authors
4
+ #
5
+ # SPDX-License-Identifier: Apache-2.0
6
+
7
+ require 'cgi'
8
+
9
+ module OpenTelemetry
10
+ module CorrelationContext
11
+ module Propagation
12
+ # Injects correlation context using the W3C Correlation Context format
13
+ class TextInjector
14
+ include Context::Propagation::DefaultSetter
15
+
16
+ # Returns a new TextInjector that injects context using the specified
17
+ # header key
18
+ #
19
+ # @param [String] correlation_context_header_key The correlation context header
20
+ # key used in the carrier
21
+ # @return [TextInjector]
22
+ def initialize(correlation_context_key: 'otcorrelations')
23
+ @correlation_context_key = correlation_context_key
24
+ end
25
+
26
+ # Inject in-process correlations into the supplied carrier.
27
+ #
28
+ # @param [Carrier] carrier The carrier to inject correlations into
29
+ # @param [Context] context The context to read correlations from
30
+ # @param [optional Callable] getter An optional callable that takes a carrier and a key and
31
+ # returns the value associated with the key. If omitted the default getter will be used
32
+ # which expects the carrier to respond to [] and []=.
33
+ # @yield [Carrier, String] if an optional getter is provided, inject will yield the carrier
34
+ # and the header key to the getter.
35
+ # @return [Object] carrier with injected correlations
36
+ def inject(carrier, context, &setter)
37
+ return carrier unless (correlations = context[ContextKeys.correlation_context_key]) && !correlations.empty?
38
+
39
+ setter ||= default_setter
40
+ setter.call(carrier, @correlation_context_key, encode(correlations))
41
+
42
+ carrier
43
+ end
44
+
45
+ private
46
+
47
+ def encode(correlations)
48
+ correlations.inject(+'') do |memo, (k, v)|
49
+ memo << CGI.escape(k.to_s) << '=' << CGI.escape(v.to_s) << ','
50
+ end.chop!
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright 2019 OpenTelemetry Authors
4
+ #
5
+ # SPDX-License-Identifier: Apache-2.0
6
+ module OpenTelemetry
7
+ class Error < StandardError
8
+ end
9
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright 2020 OpenTelemetry Authors
4
+ #
5
+ # SPDX-License-Identifier: Apache-2.0
6
+
7
+ require 'opentelemetry/instrumentation/registry'
8
+ require 'opentelemetry/instrumentation/base'
9
+
10
+ module OpenTelemetry
11
+ # The instrumentation module contains functionality to register and install
12
+ # instrumentation
13
+ module Instrumentation
14
+ end
15
+ end
@@ -0,0 +1,245 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright 2020 OpenTelemetry Authors
4
+ #
5
+ # SPDX-License-Identifier: Apache-2.0
6
+
7
+ module OpenTelemetry
8
+ module Instrumentation
9
+ # The Base class holds all metadata and configuration for an
10
+ # instrumentation. All instrumentation packages should
11
+ # include a subclass of +Instrumentation::Base+ that will register
12
+ # it with +OpenTelemetry.instrumentation_registry+ and make it available for
13
+ # discovery and installation by an SDK.
14
+ #
15
+ # A typical subclass of Base will provide an install block, a present
16
+ # block, and possibly a compatible block. Below is an
17
+ # example:
18
+ #
19
+ # module OpenTelemetry
20
+ # module Instrumentation
21
+ # module Sinatra
22
+ # class Instrumentation < OpenTelemetry::Instrumentation::Base
23
+ # install do |config|
24
+ # # install instrumentation, either by library hook or applying
25
+ # # a monkey patch
26
+ # end
27
+ #
28
+ # # determine if the target library is present
29
+ # present do
30
+ # defined?(::Sinatra)
31
+ # end
32
+ #
33
+ # # if the target library is present, is it compatible?
34
+ # compatible do
35
+ # Gem.loaded_specs['sinatra'].version > MIN_VERSION
36
+ # end
37
+ # end
38
+ # end
39
+ # end
40
+ # end
41
+ #
42
+ # The instrumentation name and version will be inferred from the namespace of the
43
+ # class. In this example, they'd be 'OpenTelemetry::Instrumentation::Sinatra' and
44
+ # OpenTelemetry::Instrumentation::Sinatra::VERSION, but can be explicitly set using
45
+ # the +instrumentation_name+ and +instrumetation_version+ methods if necessary.
46
+ #
47
+ # All subclasses of OpenTelemetry::Instrumentation::Base are automatically
48
+ # registered with OpenTelemetry.instrumentation_registry which is used by
49
+ # SDKs for instrumentation discovery and installation.
50
+ #
51
+ # Instrumentation libraries can use the instrumentation subclass to easily gain
52
+ # a reference to its named tracer. For example:
53
+ #
54
+ # OpenTelemetry::Instrumentation::Sinatra.instance.tracer
55
+ #
56
+ # The instrumention class establishes a convention for disabling an instrumentation
57
+ # by environment variable and local configuration. An instrumentation disabled
58
+ # by environment variable will take precedence over local config. The
59
+ # convention for environment variable name is the library name, upcased with
60
+ # '::' replaced by underscores, OPENTELEMETRY shortened to OTEL_{LANG}, and '_ENABLED' appended.
61
+ # For example: OTEL_RUBY_INSTRUMENTATION_SINATRA_ENABLED = false.
62
+ class Base
63
+ class << self
64
+ NAME_REGEX = /^(?:(?<namespace>[a-zA-Z0-9_:]+):{2})?(?<classname>[a-zA-Z0-9_]+)$/.freeze
65
+ private_constant :NAME_REGEX
66
+
67
+ private :new # rubocop:disable Style/AccessModifierDeclarations
68
+
69
+ def inherited(subclass)
70
+ OpenTelemetry.instrumentation_registry.register(subclass)
71
+ end
72
+
73
+ # Optionally set the name of this instrumentation. If not
74
+ # explicitly set, the name will default to the namespace of the class,
75
+ # or the class name if it does not have a namespace. If there is not
76
+ # a namespace, or a class name, it will default to 'unknown'.
77
+ #
78
+ # @param [String] instrumentation_name The full name of the instrumentation package
79
+ def instrumentation_name(instrumentation_name = nil)
80
+ if instrumentation_name
81
+ @instrumentation_name = instrumentation_name
82
+ else
83
+ @instrumentation_name ||= infer_name || 'unknown'
84
+ end
85
+ end
86
+
87
+ # Optionally set the version of this instrumentation. If not explicitly set,
88
+ # the version will default to the VERSION constant under namespace of
89
+ # the class, or the VERSION constant under the class name if it does not
90
+ # have a namespace. If a VERSION constant cannot be found, it defaults
91
+ # to '0.0.0'.
92
+ #
93
+ # @param [String] instrumentation_version The version of the instrumentation package
94
+ def instrumentation_version(instrumentation_version = nil)
95
+ if instrumentation_version
96
+ @instrumentation_version = instrumentation_version
97
+ else
98
+ @instrumentation_version ||= infer_version || '0.0.0'
99
+ end
100
+ end
101
+
102
+ # The install block for this instrumentation. This will be where you install
103
+ # instrumentation, either by framework hook or applying a monkey patch.
104
+ #
105
+ # @param [Callable] blk The install block for this instrumentation
106
+ # @yieldparam [Hash] config The instrumentation config will be yielded to the
107
+ # install block
108
+ def install(&blk)
109
+ @install_blk = blk
110
+ end
111
+
112
+ # The present block for this instrumentation. This block is used to detect if
113
+ # target library is present on the system. Typically this will involve
114
+ # checking to see if the target gem spec was loaded or if expected
115
+ # constants from the target library are present.
116
+ #
117
+ # @param [Callable] blk The present block for this instrumentation
118
+ def present(&blk)
119
+ @present_blk = blk
120
+ end
121
+
122
+ # The compatible block for this instrumentation. This check will be run if the
123
+ # target library is present to determine if it's compatible. It's not
124
+ # required, but a common use case will be to check to target library
125
+ # version for compatibility.
126
+ #
127
+ # @param [Callable] blk The compatibility block for this instrumentation
128
+ def compatible(&blk)
129
+ @compatible_blk = blk
130
+ end
131
+
132
+ def instance
133
+ @instance ||= new(instrumentation_name, instrumentation_version, install_blk,
134
+ present_blk, compatible_blk)
135
+ end
136
+
137
+ private
138
+
139
+ attr_reader :install_blk, :present_blk, :compatible_blk
140
+
141
+ def infer_name
142
+ @inferred_name ||= if (md = name.match(NAME_REGEX)) # rubocop:disable Naming/MemoizedInstanceVariableName
143
+ md['namespace'] || md['classname']
144
+ end
145
+ end
146
+
147
+ def infer_version
148
+ return unless (inferred_name = infer_name)
149
+
150
+ mod = inferred_name.split('::').map(&:to_sym).inject(Object) do |object, const|
151
+ object.const_get(const)
152
+ end
153
+ mod.const_get(:VERSION)
154
+ rescue NameError
155
+ nil
156
+ end
157
+ end
158
+
159
+ attr_reader :name, :version, :config, :installed, :tracer
160
+
161
+ alias installed? installed
162
+
163
+ def initialize(name, version, install_blk, present_blk,
164
+ compatible_blk)
165
+ @name = name
166
+ @version = version
167
+ @install_blk = install_blk
168
+ @present_blk = present_blk
169
+ @compatible_blk = compatible_blk
170
+ @config = {}
171
+ @installed = false
172
+ end
173
+
174
+ # Install instrumentation with the given config. The present? and compatible?
175
+ # will be run first, and install will return false if either fail. Will
176
+ # return true if install was completed successfully.
177
+ #
178
+ # @param [Hash] config The config for this instrumentation
179
+ def install(config = {})
180
+ return true if installed?
181
+ return false unless installable?(config)
182
+
183
+ @config = config unless config.nil?
184
+ instance_exec(@config, &@install_blk)
185
+ @tracer ||= OpenTelemetry.tracer_provider.tracer(name, version)
186
+ @installed = true
187
+ end
188
+
189
+ # Whether or not this instrumentation is installable in the current process. Will
190
+ # be true when the instrumentation defines an install block, is not disabled
191
+ # by environment or config, and the target library present and compatible.
192
+ #
193
+ # @param [Hash] config The config for this instrumentation
194
+ def installable?(config = {})
195
+ @install_blk && enabled?(config) && present? && compatible?
196
+ end
197
+
198
+ # Calls the present block of the Instrumentation subclasses, if no block is provided
199
+ # it's assumed the instrumentation is not present
200
+ def present?
201
+ return false unless @present_blk
202
+
203
+ instance_exec(&@present_blk)
204
+ end
205
+
206
+ # Calls the compatible block of the Instrumentation subclasses, if no block is provided
207
+ # it's assumed to be compatible
208
+ def compatible?
209
+ return true unless @compatible_blk
210
+
211
+ instance_exec(&@compatible_blk)
212
+ end
213
+
214
+ # Whether this instrumentation is enabled. It first checks to see if it's enabled
215
+ # by an environment variable and will proceed to check if it's enabled
216
+ # by local config, if given.
217
+ #
218
+ # @param [optional Hash] config The local config
219
+ def enabled?(config = nil)
220
+ return false unless enabled_by_env_var?
221
+ return config[:enabled] if config&.key?(:enabled)
222
+
223
+ true
224
+ end
225
+
226
+ private
227
+
228
+ # Checks to see if this instrumentation is enabled by env var. By convention, the
229
+ # environment variable will be the instrumentation name upper cased, with '::'
230
+ # replaced by underscores, OPENTELEMETRY shortened to OTEL_{LANG} and _ENABLED appended.
231
+ # For example, the, environment variable name for OpenTelemetry::Instrumentation::Sinatra
232
+ # will be OTEL_RUBY_INSTRUMENTATION_SINATRA_ENABLED. A value of 'false' will disable
233
+ # the instrumentation, all other values will enable it.
234
+ def enabled_by_env_var?
235
+ var_name = name.dup.tap do |n|
236
+ n.upcase!
237
+ n.gsub!('::', '_')
238
+ n.gsub!('OPENTELEMETRY_', 'OTEL_RUBY_')
239
+ n << '_ENABLED'
240
+ end
241
+ ENV[var_name] != 'false'
242
+ end
243
+ end
244
+ end
245
+ end
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright 2020 OpenTelemetry Authors
4
+ #
5
+ # SPDX-License-Identifier: Apache-2.0
6
+
7
+ module OpenTelemetry
8
+ module Instrumentation
9
+ # The instrumentation Registry contains information about instrumentation
10
+ # available and facilitates discovery, installation and
11
+ # configuration. This functionality is primarily useful for SDK
12
+ # implementors.
13
+ class Registry
14
+ def initialize
15
+ @lock = Mutex.new
16
+ @instrumentation = []
17
+ end
18
+
19
+ # @api private
20
+ def register(instrumentation)
21
+ @lock.synchronize do
22
+ @instrumentation << instrumentation
23
+ end
24
+ end
25
+
26
+ # Lookup an instrumentation definition by name. Returns nil if +instrumentation_name+
27
+ # is not found.
28
+ #
29
+ # @param [String] instrumentation_name A stringified class name for an instrumentation
30
+ # @return [Instrumentation]
31
+ def lookup(instrumentation_name)
32
+ @lock.synchronize do
33
+ find_instrumentation(instrumentation_name)
34
+ end
35
+ end
36
+
37
+ # Install the specified instrumentation with optionally specified configuration.
38
+ #
39
+ # @param [Array<String>] instrumentation_names An array of instrumentation names to
40
+ # install
41
+ # @param [optional Hash<String, Hash>] instrumentation_config_map A map of
42
+ # instrumentation_name to config. This argument is optional and config can be
43
+ # passed for as many or as few instrumentations as desired.
44
+ def install(instrumentation_names, instrumentation_config_map = {})
45
+ @lock.synchronize do
46
+ instrumentation_names.each do |instrumentation_name|
47
+ instrumentation = find_instrumentation(instrumentation_name)
48
+ OpenTelemetry.logger.warn "Could not install #{instrumentation_name} because it was not found" unless instrumentation
49
+
50
+ install_instrumentation(instrumentation, instrumentation_config_map[instrumentation.name])
51
+ end
52
+ end
53
+ end
54
+
55
+ # Install all instrumentation available and installable in this process.
56
+ #
57
+ # @param [optional Hash<String, Hash>] instrumentation_config_map A map of
58
+ # instrumentation_name to config. This argument is optional and config can be
59
+ # passed for as many or as few instrumentations as desired.
60
+ def install_all(instrumentation_config_map = {})
61
+ @lock.synchronize do
62
+ @instrumentation.map(&:instance).each do |instrumentation|
63
+ install_instrumentation(instrumentation, instrumentation_config_map[instrumentation.name])
64
+ end
65
+ end
66
+ end
67
+
68
+ private
69
+
70
+ def find_instrumentation(instrumentation_name)
71
+ @instrumentation.detect { |a| a.instance.name == instrumentation_name }
72
+ &.instance
73
+ end
74
+
75
+ def install_instrumentation(instrumentation, config)
76
+ if instrumentation.install(config)
77
+ OpenTelemetry.logger.info "Instrumentation: #{instrumentation.name} was successfully installed"
78
+ else
79
+ OpenTelemetry.logger.warn "Instrumentation: #{instrumentation.name} failed to install"
80
+ end
81
+ rescue => e # rubocop:disable Style/RescueStandardError
82
+ OpenTelemetry.logger.warn "Instrumentation: #{instrumentation.name} unhandled exception" \
83
+ "during install #{e}: #{e.backtrace}"
84
+ end
85
+ end
86
+ end
87
+ end