aspecto-opentelemetry 0.1.1 → 0.1.5

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: dd25c9da7c25d1c4c7e7a389189e7f0369dc7b97120425eda7cf49d9fb344b42
4
- data.tar.gz: '075783f49804d1e01c16b6b80d1c37ed3b0d1de4c05b6ed4f2cd19007c28ccfc'
3
+ metadata.gz: 42bf7169cad40cd8e0fc53c8ef85c766f783330e06ed185c5bb14fcf48ce4d4d
4
+ data.tar.gz: aa44a60faa0b08052bbfb60458e53697fe6c11c22f0b64fe7285a4f4cec19635
5
5
  SHA512:
6
- metadata.gz: eb1b4410ae3dfa99e61592663cc1f4dff627908632f784fc39938b72a698a9f220e2ce88feb636402ae7a1c791b3395ad7dd5a376daa240ee0398f62961a25a2
7
- data.tar.gz: 67606263a5e1dc5ecbf8375bd511fd97e9728dd8fd6fa259d746641abb8a488b8e88d81a0b72416a3aee8184c94f30f61e2600bda267cfba6d8a16d814b1d82a
6
+ metadata.gz: 81009b5b65381793e19eee34fb04c579a910952a4ad965b6b7879f69d15ac42477ec274380895a38a49b6fd31a991090a7ac7ab6ca9b1bbc27da524ceae23971
7
+ data.tar.gz: 2a1ac520558ef20b6fa09e7cad8c310b0507336ed7f1ab715bfe42ae8ab2df5dd8670e7132810b009e7289298d4572d16370cf45b8199d9d318b8ea47917448d
data/README.md CHANGED
@@ -1,76 +1,110 @@
1
1
  # Aspecto::OpenTelemetry
2
2
 
3
- Aspecto's SDK for ruby. This gem is a distribution of OpenTelemetry configured to use all available instrumentations and export trace data to Aspecto.
3
+ Aspecto's SDK for ruby.
4
+ This gem is a distribution of OpenTelemetry pre-configured to use all available instrumentations and export trace data to Aspecto.
4
5
 
5
6
  ## Installation
6
7
 
7
- Add this line to your application's Gemfile:
8
+ Install the gem using:
8
9
 
9
- ```ruby
10
- gem 'aspecto-opentelemetry'
10
+ ```zsh
11
+ $ gem install aspecto-opentelemetry
11
12
  ```
12
13
 
13
- And then execute:
14
+ Or, if you use [bundler](https://bundler.io), include aspecto-opentelemetry in your Gemfile.
14
15
 
15
- $ bundle install
16
+ ## Usage
16
17
 
17
- Or install it yourself as:
18
+ ### Rails Applications
18
19
 
19
- $ gem install aspecto-opentelemetry
20
+ #### Configuration in Code
20
21
 
21
- ## Usage
22
+ Add this code to a new file `aspecto.rb` under `config/initializers/`:
23
+
24
+ ```rb
25
+ # config/initializers/aspecto.rb
26
+ require 'aspecto/opentelemetry'
27
+
28
+ Aspecto::OpenTelemetry::configure do |c|
29
+ c.service_name = '<YOUR_SERVICE_NAME>'
30
+ c.aspecto_auth = '<YOUR_ASPECTO_TOKEN>'
31
+ # c.sampling_ratio = 1.0 # [optional]: defaults to 1.0, use aspecto app to configure remotely
32
+ end
33
+ ```
22
34
 
23
- ### Auto Instrumentation
35
+ #### Configuration via Environment Variables
24
36
 
25
- Does not require code changes. Just modify you `Gemfile` to add `require` like this:
37
+ In your Gemfile:
26
38
 
27
39
  ```
28
40
  gem 'aspecto-opentelemetry', require: 'aspecto/auto_instrument'
29
41
  ```
30
42
 
31
- When using auto-instrumentation, you need to pass relevant configuration options via environment variables.
43
+ And set environment variables:
44
+
45
+ ```
46
+ OTEL_SERVICE_NAME=<YOUR_SERVICE_NAME>
47
+ ASPECTO_AUTH=<YOUR_ASPECTO_TOKEN>
48
+ # ASPECTO_SAMPLING_RATIO=1.0 # [optional]: defaults to 1.0, use aspecto app to configure remotely
49
+ ```
50
+
51
+ ### Ruby Applications
32
52
 
33
- ### Manual Instrumentation
53
+ #### Configuration in Code
34
54
 
35
- Allow you to configure the SDK via code:
55
+ Add this code after your require other gems:
36
56
 
37
57
  ```rb
38
58
  require 'aspecto/opentelemetry'
39
59
 
40
- Aspecto::OpenTelemetry::configure
60
+ Aspecto::OpenTelemetry::configure do |c|
61
+ c.service_name = '<YOUR_SERVICE_NAME>'
62
+ c.aspecto_auth = '<YOUR_ASPECTO_TOKEN>'
63
+ # c.env = '<CURRENT_ENVIRONMENT>' # [optional]: automatically detected for rails and sinatra
64
+ # c.sampling_ratio = 1.0 # [optional]: defaults to 1.0, use aspecto app to configure remotely
65
+ end
41
66
  ```
42
67
 
43
- Add this code at the beginning of your application.
44
- For rails - add this code to a file `aspecto.rb` under `config/initializers/`.
68
+ #### Configuration via Environment Variables
45
69
 
46
- ## Configuration
70
+ Add this require statement after your require other gems:
47
71
 
48
- You can set configuration it 2 ways: via environment variables or via code.
49
- The only required config options are `aspecto_auth` and `service_name`.
72
+ ```rb
73
+ require 'aspecto/auto_instrument'
74
+ ```
50
75
 
51
- ### Configuration in Code
76
+ And set environment variables:
52
77
 
53
- ```rb
54
- require 'aspecto/opentelemetry'
78
+ ```
79
+ OTEL_SERVICE_NAME=<YOUR_SERVICE_NAME>
80
+ ASPECTO_AUTH=<YOUR_ASPECTO_TOKEN>
81
+ # ASPECTO_ENV=<CURRENT_ENVIRONMENT> # [optional]: automatically detected for rails and sinatra
82
+ # ASPECTO_SAMPLING_RATIO=1.0 # [optional]: defaults to 1.0, use aspecto app to configure remotely
83
+ ```
55
84
 
56
- Aspecto::OpenTelemetry::configure do |c|
57
- c.service_name = 'MY_SERIVCE'
58
- c.aspecto_auth = 'YOUR_TOKEN_HERE'
59
- c.env = 'MY_COOL_ENV'
60
- c.sampling_ratio = 0.1
61
- end
85
+ ### Shutdown
86
+
87
+ Call this function when your application shuts down
88
+
89
+ ```rb
90
+ Aspecto::OpenTelemetry::shutdown
62
91
  ```
63
92
 
64
- ### Configuration Options
93
+ ## Configuration
94
+
95
+ You can set configuration via environment variables or via code. Values set in code takes precedence.
96
+ The only required config options are [`aspecto_auth`](https://app.aspecto.io/app/integration/api-key) and `service_name`.
65
97
 
66
- | Option Name | Environment Variable | Type | Default | Description |
67
- | -------------- | ---------------------- | ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- |
68
- | `aspecto_auth` | `ASPECTO_AUTH` | UUID string | - | Aspecto's [API key for authentication](https://app.aspecto.io/app/integration/api-key) |
69
- | `service_name` | `OTEL_SERVICE_NAME` | string | - | name of the service which is sending telemetry |
70
- | `env` | `ASPECTO_ENV` | string | extracted from rails or sinatra if used | deployment environment: `production` / `staging` / `development`, etc. |
71
- | `log_level` | `OTEL_LOG_LEVEL` | string | `ERROR` | `ERROR` / `WARN` / `INFO`, etc. |
72
- | `sampling_ratio` | `ASPECTO_SAMPLING_RATIO` | float | 1.0 | How many of the traces starting in this service should be sampled. set to number in range [0.0, 1.0] where 0.0 is no sampling, and 1.0 is sample all |
98
+ ### Configuration Options
73
99
 
100
+ | Option Name | Environment Variable | Type | Default | Description |
101
+ | ------------------------------------ | ------------------------------------ | ----------- | --------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
102
+ | `aspecto_auth` | `ASPECTO_AUTH` | UUID string | - | Aspecto's [API key for authentication](https://app.aspecto.io/app/integration/api-key) |
103
+ | `service_name` | `OTEL_SERVICE_NAME` | string | - | name of the service which is sending telemetry |
104
+ | `env` | `ASPECTO_ENV` | string | extracted from rails or sinatra if used | deployment environment: `production` / `staging` / `development`, etc. |
105
+ | `log_level` | `OTEL_LOG_LEVEL` | string | `ERROR` | `ERROR` / `WARN` / `INFO`, etc. |
106
+ | `sampling_ratio` | `ASPECTO_SAMPLING_RATIO` | float | 1.0 | How many of the traces starting in this service should be sampled. set to number in range [0.0, 1.0] where 0.0 is no sampling, and 1.0 is sample all |
107
+ | `otel_exporter_otlp_traces_endpoint` | `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT` | URL | "https://otelcol.aspecto.io/v1/trace" | Url |
74
108
 
75
109
  ## Contributing
76
110
 
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "faraday"
4
+ require "faraday_middleware"
5
+ require "rufus/scheduler"
6
+
7
+ require_relative "../sampler/rules_sampler"
8
+ require_relative "../sampler/message_process_sampler"
9
+
10
+ module Aspecto
11
+ # Aspecto's OpenTelemetry distribution
12
+ module OpenTelemetry
13
+ module Config
14
+ # Handle fetching of remote configuration from aspecto
15
+ class RemoteConfig
16
+ def initialize(aspecto_auth, service_name, env, fallback_sampler)
17
+ @service_name = service_name
18
+ @env = env
19
+ @fallback_sampler = fallback_sampler
20
+
21
+ aspecto_config_url = ENV.fetch("ASPECTO_CONFIG_HOST", "https://config.aspecto.io")
22
+ @http_client = Faraday.new "#{aspecto_config_url}/config/#{aspecto_auth}" do |f|
23
+ f.response :json # decode response bodies as JSON
24
+ end
25
+ @http_client.options.timeout = 10
26
+
27
+ @scheduler = Rufus::Scheduler.new
28
+ @remote_config_poll_frequency = ENV.fetch("ASPECTO_REMOTE_CONFIG_POLL_FREQUENCY", "30s")
29
+ @scheduler.interval @remote_config_poll_frequency, first: :now do
30
+ update_config
31
+ end
32
+
33
+ ::OpenTelemetry.logger.info "[Aspecto] SDK client initialized"
34
+ end
35
+
36
+ def shutdown
37
+ @scheduler.shutdown
38
+ end
39
+
40
+ private
41
+
42
+ def update_config # rubocop:disable Metrics/AbcSize
43
+ ::OpenTelemetry::Common::Utilities.untraced do
44
+ response = @http_client.get do |req|
45
+ req.headers["If-None-Match"] = @latest_config_etag unless @latest_config_etag.nil?
46
+ end
47
+ @latest_config_etag = response.headers["etag"]
48
+ return if response.status == 304
49
+
50
+ if response.status >= 400
51
+ ::OpenTelemetry.logger.error("[Aspecto] error when trying to get remote config. will try again in #{@remote_config_poll_frequency}")
52
+ return
53
+ end
54
+ handle_new_config response.body if response.status < 300
55
+ end
56
+ rescue StandardError => e
57
+ ::OpenTelemetry.logger.error "[Aspecto] updating remote config failed. using previous remote config"
58
+ ::OpenTelemetry.logger.debug e
59
+ end
60
+
61
+ def handle_new_config(config)
62
+ ::OpenTelemetry.logger.info("[Aspecto] successfully received remote configuration")
63
+ update_sampler config["samplingRules"]
64
+ end
65
+
66
+ def update_sampler(sampler_config)
67
+ rules_sampler = Sampler::RulesSampler.new sampler_config, @fallback_sampler, @service_name, @env
68
+ service_sampler = ::OpenTelemetry::SDK::Trace::Samplers.parent_based(root: rules_sampler)
69
+ message_process_sampler = Sampler::MessageProcessSampler.new rules_sampler, service_sampler
70
+ # updating the sampler should be thread safe as it's an atomic setter on a global value
71
+ ::OpenTelemetry.tracer_provider.sampler = message_process_sampler
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
@@ -13,6 +13,7 @@ module Aspecto
13
13
  self.log_level = ENV.fetch("OTEL_LOG_LEVEL", Logger::ERROR)
14
14
  self.env = ENV["ASPECTO_ENV"] if ENV["ASPECTO_ENV"]
15
15
  self.sampling_ratio = Float(ENV.fetch("ASPECTO_SAMPLING_RATIO", 1.0))
16
+ self.otel_exporter_otlp_traces_endpoint = ENV.fetch("OTEL_EXPORTER_OTLP_TRACES_ENDPOINT", "https://otelcol.aspecto.io/v1/trace")
16
17
  end
17
18
 
18
19
  def service_name=(service_name)
@@ -23,7 +24,7 @@ module Aspecto
23
24
  @override_resource_attributes[::OpenTelemetry::SemanticConventions::Resource::DEPLOYMENT_ENVIRONMENT] = env
24
25
  end
25
26
 
26
- attr_accessor :sampling_ratio, :log_level
27
+ attr_accessor :sampling_ratio, :log_level, :otel_exporter_otlp_traces_endpoint
27
28
  attr_reader :aspecto_auth
28
29
 
29
30
  def aspecto_auth=(aspecto_auth)
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "./utils"
3
+ require_relative "./operator"
4
4
 
5
5
  module Aspecto
6
6
  # Aspecto OpenTelemetry Distro
@@ -9,18 +9,17 @@ module Aspecto
9
9
  # Aspecto's sampling rule condition
10
10
  class Condition
11
11
  def initialize(condition_config)
12
- @comparison = condition_config["comparison"]&.to_sym
13
- @value = condition_config["value"]
12
+ @operator = Operator.new condition_config["comparison"], condition_config["value"]
14
13
  @from = condition_config["from"]&.to_sym
15
14
  @key = condition_config["key"]
16
15
  end
17
16
 
18
- def satisfied?(attributes, span_name)
17
+ def satisfies?(attributes, span_name)
19
18
  case @from
20
19
  when :attribute
21
- Sampler.meets_operator?(@comparison, @value, attributes[@key]&.to_s)
20
+ @operator.satisfies? attributes&.[](@key)&.to_s
22
21
  when :operation
23
- Sampler.meets_operator?(@comparison, @value, span_name)
22
+ @operator.satisfies? span_name
24
23
  else
25
24
  # Other "from" are not implemented for now
26
25
  false
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "opentelemetry/semantic_conventions"
4
+ require "opentelemetry-api"
5
+
6
+ module Aspecto
7
+ # Aspecto OpenTelemetry Distro
8
+ module OpenTelemetry
9
+ module Sampler
10
+ # OpenTelemetry sampler which implements the remote rules logic with fallback to service sampler
11
+ class MessageProcessSampler
12
+ def initialize(rules_sampler, service_sampler)
13
+ @rules_sampler = rules_sampler
14
+ @service_sampler = service_sampler
15
+ end
16
+
17
+ def should_sample?(trace_id:, parent_context:, links:, name:, kind:, attributes:)
18
+ if attributes && attributes[::OpenTelemetry::SemanticConventions::Trace::MESSAGING_OPERATION] == "process"
19
+ sampling_decision = should_sample_processing(parent_context, links, name, attributes)
20
+ return sampling_decision if sampling_decision
21
+ end
22
+ @service_sampler.should_sample?(trace_id: trace_id, parent_context: parent_context, links: links, name: name, kind: kind, attributes: attributes)
23
+ end
24
+
25
+ def description
26
+ "MessageProcessSampler"
27
+ end
28
+
29
+ private
30
+
31
+ def should_sample_processing(parent_context, links, name, attributes)
32
+ return if links.length.zero?
33
+
34
+ decision = link_decision(links[0])
35
+
36
+ # sidekiq receive side is a single "process" span which is the entry for the trace
37
+ is_root = ::OpenTelemetry::Trace.current_span(parent_context) == ::OpenTelemetry::Trace::Span::INVALID
38
+ if is_root # rubocop:disable Style/GuardClause
39
+ matched_rule = @rules_sampler.matching_rule(name: name, attributes: attributes)
40
+ rule_context = matched_rule&.context
41
+ return ::OpenTelemetry::SDK::Trace::Samplers::Result.new(decision: decision, tracestate: ::OpenTelemetry::Trace.current_span(parent_context).context.tracestate) if rule_context && rule_context["messaging.sampling.inherit_from_publisher"]
42
+ end
43
+
44
+ # if not root, then we need to check if the entry span set "messaging.sampling.inherit_from_publisher" on the context.
45
+ # since we currently only support sidekiq, it is not needed.
46
+ # but in the future, it can be implemented with context.attach when the rule is applied, and context.get here
47
+ end
48
+
49
+ def link_decision(link)
50
+ publisher_sampled = link.span_context.trace_flags.sampled?
51
+ publisher_sampled ? ::OpenTelemetry::SDK::Trace::Samplers::Decision::RECORD_AND_SAMPLE : ::OpenTelemetry::SDK::Trace::Samplers::Decision::DROP
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aspecto
4
+ # Aspecto OpenTelemetry Distro
5
+ module OpenTelemetry
6
+ # Sampling logic for aspecto otel distribution
7
+ module Sampler
8
+ # A single operator used to evaluate sampling rules
9
+ class Operator
10
+ def initialize(operator, expected) # rubocop:disable Metrics/AbcSize
11
+ @expected = expected&.downcase
12
+ @expected = Regexp.new(@expected) if operator == "matches"
13
+
14
+ operator_to_proc = {
15
+ "eq" => proc { |actual| @expected == actual },
16
+ "ne" => proc { |actual| @expected != actual },
17
+ "starts_with" => proc { |actual| actual&.start_with?(expected) },
18
+ "ends_with" => proc { |actual| actual&.end_with?(expected) },
19
+ "contains" => proc { |actual| actual&.include?(expected) },
20
+ "not_contains" => proc { |actual| !actual&.include?(expected) },
21
+ "matches" => proc { |actual| @expected.match(actual) },
22
+ "defined" => proc { |actual| !actual.nil? },
23
+ "undefined" => proc { |actual| actual.nil? },
24
+ "any" => proc { true }
25
+ }
26
+
27
+ @evaluate = operator_to_proc.fetch(operator, proc { false })
28
+ end
29
+
30
+ def satisfies?(actual)
31
+ @evaluate.call actual&.downcase
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "./sampling_rule"
4
- require_relative "./utils"
4
+ require_relative "./operator"
5
5
 
6
6
  module Aspecto
7
7
  # Aspecto OpenTelemetry Distro
@@ -11,21 +11,25 @@ module Aspecto
11
11
  class RulesSampler
12
12
  def initialize(config, fallback_sampler, service_name, env)
13
13
  @rules = config
14
- .select { |rule| Sampler.meets_operator?(rule["packageName"]["comparison"].to_sym, rule["packageName"]["value"], service_name) }
15
- .select { |rule| Sampler.meets_operator?(rule["env"]["comparison"].to_sym, rule["env"]["value"], env) }
14
+ .select { |rule| Operator.new(rule["packageName"]["comparison"], rule["packageName"]["value"]).satisfies?(service_name) }
15
+ .select { |rule| Operator.new(rule["env"]["comparison"], rule["env"]["value"]).satisfies?(env) }
16
16
  .map { |rule_config| SamplingRule.new rule_config }
17
17
  @fallback_sampler = fallback_sampler
18
18
  end
19
19
 
20
20
  def should_sample?(trace_id:, parent_context:, links:, name:, kind:, attributes:)
21
- matching_rule = @rules.find { |rule| rule.satisfied?(attributes, name) }
22
- delegate_sampler = matching_rule ? matching_rule.sampler : @fallback_sampler
21
+ rule = matching_rule(name: name, attributes: attributes)
22
+ delegate_sampler = rule ? rule.sampler : @fallback_sampler
23
23
  delegate_sampler.should_sample?(trace_id: trace_id, parent_context: parent_context, links: links, name: name, kind: kind, attributes: attributes)
24
24
  end
25
25
 
26
26
  def description
27
27
  "RulesSampler {#{rules.length} rules}"
28
28
  end
29
+
30
+ def matching_rule(name:, attributes:)
31
+ @rules.find { |rule| rule.satisfies?(attributes, name) }
32
+ end
29
33
  end
30
34
  end
31
35
  end
@@ -13,12 +13,13 @@ module Aspecto
13
13
  @id = rule_config["_id"]
14
14
  @sampler = ::OpenTelemetry::SDK::Trace::Samplers.trace_id_ratio_based(rule_config["samplingRate"])
15
15
  @conditions = rule_config["conditions"].map { |condition_config| Condition.new condition_config }
16
+ @context = rule_config["context"]
16
17
  end
17
18
 
18
- attr_reader :sampler
19
+ attr_reader :sampler, :context
19
20
 
20
- def satisfied?(attributes, span_name)
21
- @conditions.all? { |condition| condition.satisfied?(attributes, span_name) }
21
+ def satisfies?(attributes, span_name)
22
+ @conditions.all? { |condition| condition.satisfies?(attributes, span_name) }
22
23
  end
23
24
  end
24
25
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Aspecto
4
4
  module OpenTelemetry
5
- VERSION = "0.1.1"
5
+ VERSION = "0.1.5"
6
6
  end
7
7
  end
@@ -4,8 +4,7 @@ require_relative "opentelemetry/version"
4
4
  require_relative "opentelemetry/configurator"
5
5
  require_relative "opentelemetry/resource/detectors/aspecto"
6
6
  require_relative "opentelemetry/resource/detectors/deployment"
7
- require_relative "opentelemetry/remote_config"
8
- require_relative "opentelemetry/sampler/rules_sampler"
7
+ require_relative "opentelemetry/config/remote_config"
9
8
 
10
9
  require "opentelemetry/sdk"
11
10
  require "opentelemetry/exporter/otlp"
@@ -31,36 +30,45 @@ module Aspecto
31
30
  c.resource = Aspecto::OpenTelemetry::Resource::Detectors::Deployment.detect
32
31
  c.resource = configurator.config_override_resource # must be last
33
32
  c.use_all "OpenTelemetry::Instrumentation::ActionPack" => { enable_recognize_route: true },
34
- "OpenTelemetry::Instrumentation::AwsSdk" => { suppress_internal_instrumentation: true }
33
+ "OpenTelemetry::Instrumentation::AwsSdk" => {
34
+ suppress_internal_instrumentation: true,
35
+ inject_messaging_context: true,
36
+ extract_messaging_context: true
37
+ }
35
38
 
36
- c.add_span_processor(
37
- ::OpenTelemetry::SDK::Trace::Export::BatchSpanProcessor.new(
38
- ::OpenTelemetry::Exporter::OTLP::Exporter.new(endpoint: "https://otelcol.aspecto.io/v1/trace", headers: {
39
- "Authorization" => configurator.aspecto_auth
40
- })
41
- )
42
- )
39
+ otlp_exporter = ::OpenTelemetry::Exporter::OTLP::Exporter.new(endpoint: configurator.otel_exporter_otlp_traces_endpoint, headers: {
40
+ "Authorization" => configurator.aspecto_auth
41
+ })
42
+ span_processor = ::OpenTelemetry::SDK::Trace::Export::BatchSpanProcessor.new otlp_exporter
43
+ c.add_span_processor span_processor
43
44
 
44
45
  at_exit do
45
- ::OpenTelemetry.tracer_provider.shutdown
46
+ # at_exit might be call when service terminates
47
+ # but can also be the initiator or the application like with sinatra:
48
+ # https://github.com/sinatra/sinatra/blob/cd503e6c590cd48c2c9bb7869522494bfc62cb14/lib/sinatra/main.rb#L25
49
+ span_processor.force_flush timeout: 2
46
50
  end
47
51
  end
48
52
 
49
53
  fallback_sampler = ::OpenTelemetry::SDK::Trace::Samplers.trace_id_ratio_based(configurator.sampling_ratio)
50
- remote_config = fetch_config configurator.aspecto_auth
51
54
  # TODO: how to properly extract the data from resource?
52
55
  _, service_name = ::OpenTelemetry.tracer_provider.resource.attribute_enumerator.detect { |elem| elem[0] == ::OpenTelemetry::SemanticConventions::Resource::SERVICE_NAME }
53
56
  _, env = ::OpenTelemetry.tracer_provider.resource.attribute_enumerator.detect { |elem| elem[0] == ::OpenTelemetry::SemanticConventions::Resource::DEPLOYMENT_ENVIRONMENT }
54
- rules_sampler = Sampler::RulesSampler.new remote_config["samplingRules"], fallback_sampler, service_name, env
55
- ::OpenTelemetry.tracer_provider.sampler = ::OpenTelemetry::SDK::Trace::Samplers.parent_based(root: rules_sampler)
57
+ @remote_config_service = Config::RemoteConfig.new configurator.aspecto_auth, service_name, env, fallback_sampler
56
58
  rescue StandardError => e
57
59
  warn "Failed to initialize Aspecto tracing."
58
60
  warn e
59
61
  end
60
62
 
63
+ def shutdown
64
+ ::OpenTelemetry.logger.info("Aspecto is shuting down. tracing is now stopped")
65
+ ::OpenTelemetry.tracer_provider.shutdown
66
+ @remote_config_service&.shutdown
67
+ end
68
+
61
69
  def validate_configurator_options(configurator)
62
70
  # aspecto_auth
63
- unless configurator.aspecto_auth.instance_of?(String)
71
+ unless configurator.aspecto_auth.instance_of?(String) && !configurator.aspecto_auth.empty?
64
72
  raise "
65
73
  Unable to retrieve Aspecto token.
66
74
  In order for the Aspecto service to work, it requires an auth token.
metadata CHANGED
@@ -1,15 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: aspecto-opentelemetry
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.1.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Aspecto
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-11-23 00:00:00.000000000 Z
11
+ date: 2021-12-21 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: aspecto-opentelemetry-instrumentation-aws_sdk
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '='
18
+ - !ruby/object:Gem::Version
19
+ version: 0.1.8
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '='
25
+ - !ruby/object:Gem::Version
26
+ version: 0.1.8
13
27
  - !ruby/object:Gem::Dependency
14
28
  name: faraday
15
29
  requirement: !ruby/object:Gem::Requirement
@@ -39,61 +53,61 @@ dependencies:
39
53
  - !ruby/object:Gem::Version
40
54
  version: '1.2'
41
55
  - !ruby/object:Gem::Dependency
42
- name: aspecto-opentelemetry-instrumentation-aws_sdk
56
+ name: opentelemetry-exporter-otlp
43
57
  requirement: !ruby/object:Gem::Requirement
44
58
  requirements:
45
59
  - - '='
46
60
  - !ruby/object:Gem::Version
47
- version: 0.1.4
61
+ version: 0.21.0
48
62
  type: :runtime
49
63
  prerelease: false
50
64
  version_requirements: !ruby/object:Gem::Requirement
51
65
  requirements:
52
66
  - - '='
53
67
  - !ruby/object:Gem::Version
54
- version: 0.1.4
68
+ version: 0.21.0
55
69
  - !ruby/object:Gem::Dependency
56
- name: opentelemetry-exporter-otlp
70
+ name: opentelemetry-instrumentation-all
57
71
  requirement: !ruby/object:Gem::Requirement
58
72
  requirements:
59
73
  - - '='
60
74
  - !ruby/object:Gem::Version
61
- version: 0.20.6
75
+ version: 0.22.0
62
76
  type: :runtime
63
77
  prerelease: false
64
78
  version_requirements: !ruby/object:Gem::Requirement
65
79
  requirements:
66
80
  - - '='
67
81
  - !ruby/object:Gem::Version
68
- version: 0.20.6
82
+ version: 0.22.0
69
83
  - !ruby/object:Gem::Dependency
70
- name: opentelemetry-instrumentation-all
84
+ name: opentelemetry-sdk
71
85
  requirement: !ruby/object:Gem::Requirement
72
86
  requirements:
73
87
  - - '='
74
88
  - !ruby/object:Gem::Version
75
- version: 0.21.3
89
+ version: 1.0.2
76
90
  type: :runtime
77
91
  prerelease: false
78
92
  version_requirements: !ruby/object:Gem::Requirement
79
93
  requirements:
80
94
  - - '='
81
95
  - !ruby/object:Gem::Version
82
- version: 0.21.3
96
+ version: 1.0.2
83
97
  - !ruby/object:Gem::Dependency
84
- name: opentelemetry-sdk
98
+ name: rufus-scheduler
85
99
  requirement: !ruby/object:Gem::Requirement
86
100
  requirements:
87
- - - '='
101
+ - - "~>"
88
102
  - !ruby/object:Gem::Version
89
- version: '1.0'
103
+ version: '3.8'
90
104
  type: :runtime
91
105
  prerelease: false
92
106
  version_requirements: !ruby/object:Gem::Requirement
93
107
  requirements:
94
- - - '='
108
+ - - "~>"
95
109
  - !ruby/object:Gem::Version
96
- version: '1.0'
110
+ version: '3.8'
97
111
  - !ruby/object:Gem::Dependency
98
112
  name: minitest
99
113
  requirement: !ruby/object:Gem::Requirement
@@ -108,6 +122,20 @@ dependencies:
108
122
  - - "~>"
109
123
  - !ruby/object:Gem::Version
110
124
  version: '5.0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: opentelemetry-common
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - '='
130
+ - !ruby/object:Gem::Version
131
+ version: 0.19.3
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - '='
137
+ - !ruby/object:Gem::Version
138
+ version: 0.19.3
111
139
  - !ruby/object:Gem::Dependency
112
140
  name: rake
113
141
  requirement: !ruby/object:Gem::Requirement
@@ -122,6 +150,20 @@ dependencies:
122
150
  - - "~>"
123
151
  - !ruby/object:Gem::Version
124
152
  version: '13.0'
153
+ - !ruby/object:Gem::Dependency
154
+ name: rspec-mocks
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - ">="
158
+ - !ruby/object:Gem::Version
159
+ version: '0'
160
+ type: :development
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - ">="
165
+ - !ruby/object:Gem::Version
166
+ version: '0'
125
167
  - !ruby/object:Gem::Dependency
126
168
  name: rubocop
127
169
  requirement: !ruby/object:Gem::Requirement
@@ -151,14 +193,15 @@ files:
151
193
  - Rakefile
152
194
  - lib/aspecto/auto_instrument.rb
153
195
  - lib/aspecto/opentelemetry.rb
196
+ - lib/aspecto/opentelemetry/config/remote_config.rb
154
197
  - lib/aspecto/opentelemetry/configurator.rb
155
- - lib/aspecto/opentelemetry/remote_config.rb
156
198
  - lib/aspecto/opentelemetry/resource/detectors/aspecto.rb
157
199
  - lib/aspecto/opentelemetry/resource/detectors/deployment.rb
158
200
  - lib/aspecto/opentelemetry/sampler/condition.rb
201
+ - lib/aspecto/opentelemetry/sampler/message_process_sampler.rb
202
+ - lib/aspecto/opentelemetry/sampler/operator.rb
159
203
  - lib/aspecto/opentelemetry/sampler/rules_sampler.rb
160
204
  - lib/aspecto/opentelemetry/sampler/sampling_rule.rb
161
- - lib/aspecto/opentelemetry/sampler/utils.rb
162
205
  - lib/aspecto/opentelemetry/version.rb
163
206
  homepage: https://docs.aspecto.io/
164
207
  licenses:
@@ -1,22 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "faraday"
4
- require "faraday_middleware"
5
-
6
- module Aspecto
7
- # Aspecto's OpenTelemetry distribution
8
- module OpenTelemetry
9
- def self.fetch_config(aspecto_auth)
10
- aspecto_config_url = ENV["ASPECTO_CONFIG_HOST"] || "https://config.aspecto.io"
11
-
12
- conn = Faraday.new "#{aspecto_config_url}/config/#{aspecto_auth}" do |f|
13
- f.response :json # decode response bodies as JSON
14
- end
15
-
16
- ::OpenTelemetry::Common::Utilities.untraced do
17
- response = conn.get
18
- response.body
19
- end
20
- end
21
- end
22
- end
@@ -1,40 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Aspecto
4
- # Aspecto OpenTelemetry Distro
5
- module OpenTelemetry
6
- # Sampling logic for aspecto otel distribution
7
- module Sampler
8
- extend self
9
-
10
- def meets_operator?(operator, expected, actual) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength, Metrics/CyclomaticComplexity
11
- expected_lower = expected&.downcase
12
- actual_lower = actual&.downcase
13
- case operator
14
- when :eq
15
- expected_lower == actual_lower
16
- when :ne
17
- expected_lower != actual_lower
18
- when :starts_with
19
- actual_lower.start_with?(expected_lower)
20
- when :ends_with
21
- actual_lower.end_with?(expected_lower)
22
- when :contains
23
- actual_lower.include?(expected_lower)
24
- when :not_contains
25
- !actual_lower.include?(expected_lower)
26
- when :matches
27
- Regexp.new(expected_lower).match(actual_lower)
28
- when :defined
29
- !actual.nil?
30
- when :undefined
31
- actual.nil?
32
- when :any
33
- true
34
- else
35
- false
36
- end
37
- end
38
- end
39
- end
40
- end