opentelemetry-api 0.5.1

Sign up to get free protection for your applications and to get access to all the features.
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