instana 1.217.1 → 2.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.
Files changed (228) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -1
  3. data/lib/instana/base.rb +4 -2
  4. data/lib/instana/instrumentation/action_cable.rb +8 -4
  5. data/lib/instana/instrumentation/action_controller.rb +2 -4
  6. data/lib/instana/instrumentation/action_mailer.rb +1 -1
  7. data/lib/instana/instrumentation/action_view.rb +4 -4
  8. data/lib/instana/instrumentation/active_job.rb +20 -5
  9. data/lib/instana/instrumentation/active_record.rb +1 -1
  10. data/lib/instana/instrumentation/aws_sdk_dynamodb.rb +1 -1
  11. data/lib/instana/instrumentation/aws_sdk_lambda.rb +1 -1
  12. data/lib/instana/instrumentation/aws_sdk_s3.rb +1 -1
  13. data/lib/instana/instrumentation/aws_sdk_sns.rb +1 -1
  14. data/lib/instana/instrumentation/aws_sdk_sqs.rb +1 -1
  15. data/lib/instana/instrumentation/dalli.rb +1 -1
  16. data/lib/instana/instrumentation/excon.rb +2 -2
  17. data/lib/instana/instrumentation/graphql.rb +3 -3
  18. data/lib/instana/instrumentation/grpc.rb +14 -13
  19. data/lib/instana/instrumentation/mongo.rb +3 -3
  20. data/lib/instana/instrumentation/net-http.rb +5 -4
  21. data/lib/instana/instrumentation/rack.rb +36 -4
  22. data/lib/instana/instrumentation/redis.rb +1 -1
  23. data/lib/instana/instrumentation/resque.rb +10 -8
  24. data/lib/instana/instrumentation/rest-client.rb +4 -4
  25. data/lib/instana/instrumentation/sequel.rb +3 -3
  26. data/lib/instana/instrumentation/shoryuken.rb +4 -1
  27. data/lib/instana/instrumentation/sidekiq-client.rb +21 -19
  28. data/lib/instana/instrumentation/sidekiq-worker.rb +22 -21
  29. data/lib/instana/instrumented_logger.rb +1 -1
  30. data/lib/instana/samplers/result.rb +32 -0
  31. data/lib/instana/samplers/samplers.rb +76 -0
  32. data/lib/instana/serverless.rb +4 -2
  33. data/lib/instana/setup.rb +5 -5
  34. data/lib/instana/span_filtering/condition.rb +134 -0
  35. data/lib/instana/span_filtering/configuration.rb +262 -0
  36. data/lib/instana/span_filtering/filter_rule.rb +31 -0
  37. data/lib/instana/span_filtering.rb +62 -0
  38. data/lib/instana/trace/export.rb +36 -0
  39. data/lib/instana/{tracing → trace}/processor.rb +19 -15
  40. data/lib/instana/trace/span.rb +534 -0
  41. data/lib/instana/{tracing → trace}/span_context.rb +17 -8
  42. data/lib/instana/trace/span_kind.rb +51 -0
  43. data/lib/instana/trace/span_limits.rb +63 -0
  44. data/lib/instana/{tracer.rb → trace/tracer.rb} +106 -54
  45. data/lib/instana/trace/tracer_provider.rb +198 -0
  46. data/lib/instana/trace.rb +74 -0
  47. data/lib/instana/util.rb +11 -0
  48. data/lib/instana/version.rb +1 -1
  49. metadata +89 -267
  50. data/.circleci/config.yml +0 -485
  51. data/.codeclimate.yml +0 -23
  52. data/.editorconfig +0 -10
  53. data/.fasterer.yml +0 -23
  54. data/.github/ISSUE_TEMPLATE/bug.yml +0 -39
  55. data/.github/ISSUE_TEMPLATE/config.yml +0 -8
  56. data/.github/workflows/pr_commits_signed_off.yml +0 -16
  57. data/.github/workflows/release-notification-on-slack.yml +0 -34
  58. data/.gitignore +0 -19
  59. data/.rubocop.yml +0 -34
  60. data/.rubocop_todo.yml +0 -1140
  61. data/.tekton/.currency/docs/report.md +0 -20
  62. data/.tekton/.currency/resources/requirements.txt +0 -4
  63. data/.tekton/.currency/resources/table.json +0 -100
  64. data/.tekton/.currency/scripts/generate_report.py +0 -136
  65. data/.tekton/README.md +0 -278
  66. data/.tekton/github-interceptor-secret.yaml +0 -8
  67. data/.tekton/github-pr-eventlistener.yaml +0 -104
  68. data/.tekton/github-pr-pipeline.yaml.part +0 -38
  69. data/.tekton/github-set-status-task.yaml +0 -43
  70. data/.tekton/github-webhook-ingress.yaml +0 -20
  71. data/.tekton/pipeline.yaml +0 -571
  72. data/.tekton/pipelinerun.yaml +0 -21
  73. data/.tekton/ruby-tracer-prepuller.yaml +0 -87
  74. data/.tekton/run_unittests.sh +0 -87
  75. data/.tekton/scheduled-eventlistener.yaml +0 -108
  76. data/.tekton/task.yaml +0 -449
  77. data/.tekton/tekton-triggers-eventlistener-serviceaccount.yaml +0 -29
  78. data/Appraisals +0 -124
  79. data/CONTRIBUTING.md +0 -86
  80. data/Gemfile +0 -17
  81. data/LICENSE +0 -22
  82. data/MAINTAINERS.md +0 -3
  83. data/Rakefile +0 -49
  84. data/bin/announce_release_on_slack.py +0 -103
  85. data/docker-compose.yml +0 -20
  86. data/download.sh +0 -85
  87. data/examples/opentracing.rb +0 -35
  88. data/examples/tracing.rb +0 -84
  89. data/extras/license_header.rb +0 -44
  90. data/gemfiles/.bundle/config +0 -2
  91. data/gemfiles/aws_30.gemfile +0 -21
  92. data/gemfiles/aws_60.gemfile +0 -16
  93. data/gemfiles/cuba_30.gemfile +0 -16
  94. data/gemfiles/cuba_40.gemfile +0 -13
  95. data/gemfiles/dalli_20.gemfile +0 -15
  96. data/gemfiles/dalli_30.gemfile +0 -12
  97. data/gemfiles/dalli_32.gemfile +0 -12
  98. data/gemfiles/excon_0100.gemfile +0 -14
  99. data/gemfiles/excon_021.gemfile +0 -17
  100. data/gemfiles/excon_079.gemfile +0 -17
  101. data/gemfiles/excon_100.gemfile +0 -14
  102. data/gemfiles/graphql_10.gemfile +0 -16
  103. data/gemfiles/graphql_20.gemfile +0 -15
  104. data/gemfiles/grpc_10.gemfile +0 -15
  105. data/gemfiles/mongo_216.gemfile +0 -15
  106. data/gemfiles/mongo_219.gemfile +0 -12
  107. data/gemfiles/net_http_01.gemfile +0 -17
  108. data/gemfiles/rack_16.gemfile +0 -15
  109. data/gemfiles/rack_20.gemfile +0 -15
  110. data/gemfiles/rack_30.gemfile +0 -13
  111. data/gemfiles/rails_42.gemfile +0 -18
  112. data/gemfiles/rails_50.gemfile +0 -19
  113. data/gemfiles/rails_52.gemfile +0 -19
  114. data/gemfiles/rails_60.gemfile +0 -19
  115. data/gemfiles/rails_61.gemfile +0 -20
  116. data/gemfiles/rails_70.gemfile +0 -17
  117. data/gemfiles/rails_71.gemfile +0 -17
  118. data/gemfiles/rails_80.gemfile +0 -17
  119. data/gemfiles/redis_40.gemfile +0 -15
  120. data/gemfiles/redis_50.gemfile +0 -13
  121. data/gemfiles/redis_51.gemfile +0 -13
  122. data/gemfiles/resque_122.gemfile +0 -16
  123. data/gemfiles/resque_1274_3scale.gemfile +0 -17
  124. data/gemfiles/resque_20.gemfile +0 -16
  125. data/gemfiles/rest_client_16.gemfile +0 -17
  126. data/gemfiles/rest_client_20.gemfile +0 -17
  127. data/gemfiles/roda_20.gemfile +0 -16
  128. data/gemfiles/roda_30.gemfile +0 -16
  129. data/gemfiles/rubocop_162.gemfile +0 -6
  130. data/gemfiles/sequel_56.gemfile +0 -16
  131. data/gemfiles/sequel_57.gemfile +0 -16
  132. data/gemfiles/sequel_58.gemfile +0 -16
  133. data/gemfiles/shoryuken_50.gemfile +0 -16
  134. data/gemfiles/shoryuken_60.gemfile +0 -13
  135. data/gemfiles/sidekiq_42.gemfile +0 -15
  136. data/gemfiles/sidekiq_50.gemfile +0 -15
  137. data/gemfiles/sidekiq_60.gemfile +0 -12
  138. data/gemfiles/sidekiq_65.gemfile +0 -12
  139. data/gemfiles/sidekiq_70.gemfile +0 -12
  140. data/gemfiles/sinatra_14.gemfile +0 -15
  141. data/gemfiles/sinatra_22.gemfile +0 -12
  142. data/gemfiles/sinatra_30.gemfile +0 -12
  143. data/gemfiles/sinatra_40.gemfile +0 -12
  144. data/instana.gemspec +0 -48
  145. data/lib/instana/open_tracing/carrier.rb +0 -7
  146. data/lib/instana/open_tracing/instana_tracer.rb +0 -99
  147. data/lib/instana/tracing/span.rb +0 -431
  148. data/lib/opentracing.rb +0 -32
  149. data/log/.keep +0 -0
  150. data/sonar-project.properties +0 -9
  151. data/test/activator_test.rb +0 -50
  152. data/test/backend/agent_test.rb +0 -80
  153. data/test/backend/gc_snapshot_test.rb +0 -11
  154. data/test/backend/host_agent_activation_observer_test.rb +0 -73
  155. data/test/backend/host_agent_lookup_test.rb +0 -78
  156. data/test/backend/host_agent_reporting_observer_test.rb +0 -276
  157. data/test/backend/host_agent_test.rb +0 -89
  158. data/test/backend/process_info_test.rb +0 -83
  159. data/test/backend/request_client_test.rb +0 -39
  160. data/test/backend/serverless_agent_test.rb +0 -83
  161. data/test/benchmarks/bench_id_generation.rb +0 -15
  162. data/test/benchmarks/bench_opentracing.rb +0 -16
  163. data/test/config_test.rb +0 -34
  164. data/test/frameworks/cuba_test.rb +0 -61
  165. data/test/frameworks/roda_test.rb +0 -60
  166. data/test/frameworks/sinatra_test.rb +0 -71
  167. data/test/instana_test.rb +0 -37
  168. data/test/instrumentation/aws_test.rb +0 -241
  169. data/test/instrumentation/dalli_test.rb +0 -325
  170. data/test/instrumentation/excon_test.rb +0 -204
  171. data/test/instrumentation/graphql_test.rb +0 -289
  172. data/test/instrumentation/grpc_test.rb +0 -420
  173. data/test/instrumentation/mongo_test.rb +0 -68
  174. data/test/instrumentation/net_http_test.rb +0 -220
  175. data/test/instrumentation/rack_instrumented_request_test.rb +0 -211
  176. data/test/instrumentation/rack_test.rb +0 -415
  177. data/test/instrumentation/rails_action_cable_test.rb +0 -135
  178. data/test/instrumentation/rails_action_controller_test.rb +0 -218
  179. data/test/instrumentation/rails_action_mailer_test.rb +0 -66
  180. data/test/instrumentation/rails_action_view_test.rb +0 -154
  181. data/test/instrumentation/rails_active_job_test.rb +0 -65
  182. data/test/instrumentation/rails_active_record_database_missing_test.rb +0 -45
  183. data/test/instrumentation/rails_active_record_test.rb +0 -115
  184. data/test/instrumentation/redis_test.rb +0 -152
  185. data/test/instrumentation/resque_test.rb +0 -188
  186. data/test/instrumentation/rest_client_test.rb +0 -107
  187. data/test/instrumentation/sequel_test.rb +0 -105
  188. data/test/instrumentation/shoryuken_test.rb +0 -47
  189. data/test/instrumentation/sidekiq-client_test.rb +0 -169
  190. data/test/instrumentation/sidekiq-worker_test.rb +0 -180
  191. data/test/secrets_test.rb +0 -112
  192. data/test/serverless_test.rb +0 -369
  193. data/test/snapshot/deltable_test.rb +0 -17
  194. data/test/snapshot/docker_container_test.rb +0 -82
  195. data/test/snapshot/fargate_container_test.rb +0 -82
  196. data/test/snapshot/fargate_process_test.rb +0 -35
  197. data/test/snapshot/fargate_task_test.rb +0 -49
  198. data/test/snapshot/google_cloud_run_instance_test.rb +0 -74
  199. data/test/snapshot/google_cloud_run_process_test.rb +0 -33
  200. data/test/snapshot/lambda_function_test.rb +0 -37
  201. data/test/snapshot/ruby_process_test.rb +0 -32
  202. data/test/support/apps/active_record/active_record.rb +0 -24
  203. data/test/support/apps/grpc/boot.rb +0 -23
  204. data/test/support/apps/grpc/grpc_server.rb +0 -84
  205. data/test/support/apps/http_endpoint/boot.rb +0 -28
  206. data/test/support/apps/rails/boot.rb +0 -219
  207. data/test/support/apps/rails/models/block.rb +0 -21
  208. data/test/support/apps/rails/models/block6.rb +0 -21
  209. data/test/support/apps/resque/boot.rb +0 -5
  210. data/test/support/apps/resque/jobs/resque_error_job.rb +0 -22
  211. data/test/support/apps/resque/jobs/resque_fast_job.rb +0 -23
  212. data/test/support/apps/sidekiq/boot.rb +0 -25
  213. data/test/support/apps/sidekiq/jobs/sidekiq_job_1.rb +0 -9
  214. data/test/support/apps/sidekiq/jobs/sidekiq_job_2.rb +0 -10
  215. data/test/support/apps/sidekiq/worker.rb +0 -37
  216. data/test/support/helpers.rb +0 -85
  217. data/test/support/mock_timer.rb +0 -20
  218. data/test/test_helper.rb +0 -69
  219. data/test/tracing/custom_test.rb +0 -226
  220. data/test/tracing/id_management_test.rb +0 -80
  221. data/test/tracing/instrumented_logger_test.rb +0 -39
  222. data/test/tracing/opentracing_test.rb +0 -382
  223. data/test/tracing/processor_test.rb +0 -58
  224. data/test/tracing/span_context_test.rb +0 -22
  225. data/test/tracing/span_test.rb +0 -179
  226. data/test/tracing/tracer_async_test.rb +0 -230
  227. data/test/tracing/tracer_test.rb +0 -352
  228. data/test/util_test.rb +0 -10
@@ -5,11 +5,11 @@ module Instana
5
5
  module Instrumentation
6
6
  class SidekiqWorker
7
7
  def call(_worker, msg, _queue)
8
- kv_payload = { :'sidekiq-worker' => {} }
9
- kv_payload[:'sidekiq-worker'][:job_id] = msg['jid']
10
- kv_payload[:'sidekiq-worker'][:queue] = msg['queue']
11
- kv_payload[:'sidekiq-worker'][:job] = msg['class'].to_s
12
- kv_payload[:'sidekiq-worker'][:retry] = msg['retry'].to_s
8
+ kvs = { :'sidekiq-worker' => {} }
9
+ kvs[:'sidekiq-worker'][:job_id] = msg['jid']
10
+ kvs[:'sidekiq-worker'][:queue] = msg['queue']
11
+ kvs[:'sidekiq-worker'][:job] = msg['class'].to_s
12
+ kvs[:'sidekiq-worker'][:retry] = msg['retry'].to_s
13
13
 
14
14
  # Temporary until we move connection collection to redis
15
15
  # instrumentation
@@ -24,29 +24,30 @@ module Instana
24
24
  else # Unexpected version, continue without recording any redis-url
25
25
  break
26
26
  end
27
- kv_payload[:'sidekiq-worker'][:'redis-url'] = "#{host}:#{port}"
27
+ kvs[:'sidekiq-worker'][:'redis-url'] = "#{host}:#{port}"
28
28
  end
29
29
 
30
- context = {}
30
+ trace_context = nil
31
31
  if msg.key?('X-Instana-T')
32
32
  trace_id = msg.delete('X-Instana-T')
33
33
  span_id = msg.delete('X-Instana-S')
34
- context[:trace_id] = ::Instana::Util.header_to_id(trace_id)
35
- context[:span_id] = ::Instana::Util.header_to_id(span_id) if span_id
34
+ trace_context = ::Instana::SpanContext.new(
35
+ trace_id: ::Instana::Util.header_to_id(trace_id),
36
+ span_id: span_id ? ::Instana::Util.header_to_id(span_id) : nil
37
+ )
36
38
  end
37
39
 
38
- ::Instana.tracer.log_start_or_continue(
39
- :'sidekiq-worker', kv_payload, context
40
- )
41
-
42
- yield
43
- rescue => e
44
- kv_payload[:'sidekiq-worker'][:error] = true
45
- ::Instana.tracer.log_info(kv_payload)
46
- ::Instana.tracer.log_error(e)
47
- raise
48
- ensure
49
- ::Instana.tracer.log_end(:'sidekiq-worker', {}) if ::Instana.tracer.tracing?
40
+ parent_non_recording_span = OpenTelemetry::Trace.non_recording_span(trace_context) if trace_context
41
+ Trace.with_span(parent_non_recording_span) do
42
+ Instana.tracer.in_span(:'sidekiq-worker', attributes: kvs) do |span|
43
+ yield
44
+ rescue => e
45
+ kvs[:'sidekiq-worker'][:error] = true
46
+ span.set_tags(kvs)
47
+ span.record_exception(e)
48
+ raise
49
+ end
50
+ end
50
51
  end
51
52
  end
52
53
  end
@@ -17,7 +17,7 @@ module Instana
17
17
  level: LEVEL_LABELS[severity],
18
18
  message: "#{message} #{progname}".strip
19
19
  }
20
- Instana::Tracer.trace(:log, {log: tags}) {}
20
+ Instana.tracer.in_span(:log, attributes: {log: tags}) {}
21
21
  end
22
22
 
23
23
  super(severity, message, progname)
@@ -0,0 +1,32 @@
1
+ # (c) Copyright IBM Corp. 2025
2
+
3
+ module Instana
4
+ module Trace
5
+ module Samplers
6
+ class Result
7
+ EMPTY_HASH = {}.freeze
8
+ attr_reader :tracestate, :attributes
9
+
10
+ def initialize(decision:, tracestate:, attributes: nil)
11
+ @decision = decision
12
+ @attributes = attributes.freeze || EMPTY_HASH
13
+ @tracestate = tracestate
14
+ end
15
+
16
+ # Returns true if this span should be sampled.
17
+ #
18
+ # @return FALSE always
19
+ def sampled?
20
+ false
21
+ end
22
+
23
+ # Returns true if this span should record events, attributes, status, etc.
24
+ #
25
+ # returns TRUE always
26
+ def recording?
27
+ true
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,76 @@
1
+ # (c) Copyright IBM Corp. 2025
2
+ require 'instana/samplers/result'
3
+ module Instana
4
+ module Trace
5
+ # The Samplers module contains the sampling logic for OpenTelemetry. The
6
+ # reference implementation provides a {TraceIdRatioBased}, {ALWAYS_ON},
7
+ # {ALWAYS_OFF}, and {ParentBased}.
8
+ #
9
+ # Custom samplers can be provided by SDK users. The required interface is:
10
+ #
11
+ # should_sample?(trace_id:, parent_context:, links:, name:, kind:, attributes:) -> Result
12
+ # description -> String
13
+ #
14
+ # Where:
15
+ #
16
+ # @param [String] trace_id The trace_id of the {Span} to be created.
17
+ # @param [OpenTelemetry::Context] parent_context The
18
+ # {OpenTelemetry::Context} with a parent {Span}. The {Span}'s
19
+ # {OpenTelemetry::Trace::SpanContext} may be invalid to indicate a
20
+ # root span.
21
+ # @param [Enumerable<Link>] links A collection of links to be associated
22
+ # with the {Span} to be created. Can be nil.
23
+ # @param [String] name Name of the {Span} to be created.
24
+ # @param [Symbol] kind The {OpenTelemetry::Trace::SpanKind} of the {Span}
25
+ # to be created. Can be nil.
26
+ # @param [Hash<String, Object>] attributes Attributes to be attached
27
+ # to the {Span} to be created. Can be nil.
28
+ # @return [Result] The sampling result.
29
+ module Samplers
30
+ # Returns a {Result} with {Decision::RECORD_AND_SAMPLE}.
31
+ ALWAYS_ON = false
32
+ # # Returns a {Result} with {Decision::DROP}.
33
+ ALWAYS_OFF = true
34
+
35
+ # Returns a new sampler. It delegates to samplers according to the following rules:
36
+ #
37
+ # | Parent | parent.remote? | parent.trace_flags.sampled? | Invoke sampler |
38
+ # |--|--|--|--|
39
+ # | absent | n/a | n/a | root |
40
+ # | present | true | true | remote_parent_sampled |
41
+ # | present | true | false | remote_parent_not_sampled |
42
+ # | present | false | true | local_parent_sampled |
43
+ # | present | false | false | local_parent_not_sampled |
44
+ #
45
+ # @param [Sampler] root The sampler to which the sampling
46
+ # decision is delegated for spans with no parent (root spans).
47
+ # @param [optional Sampler] remote_parent_sampled The sampler to which the sampling
48
+ # decision is delegated for remote parent sampled spans. Defaults to ALWAYS_ON.
49
+ # @param [optional Sampler] remote_parent_not_sampled The sampler to which the sampling
50
+ # decision is delegated for remote parent not sampled spans. Defaults to ALWAYS_OFF.
51
+ # @param [optional Sampler] local_parent_sampled The sampler to which the sampling
52
+ # decision is delegated for local parent sampled spans. Defaults to ALWAYS_ON.
53
+ # @param [optional Sampler] local_parent_not_sampled The sampler to which the sampling
54
+ # decision is delegated for local parent not sampld spans. Defaults to ALWAYS_OFF.
55
+ def self.parent_based(_)
56
+ self
57
+ end
58
+
59
+ # Returns a new sampler. The ratio describes the proportion of the trace ID
60
+ # space that is sampled.
61
+ #
62
+ # @param [Numeric] ratio The desired sampling ratio.
63
+ # Must be within [0.0, 1.0].
64
+ # @raise [ArgumentError] if ratio is out of range
65
+ def self.trace_id_ratio_based(_)
66
+ self
67
+ end
68
+
69
+ def self.should_sample?(trace_id:, parent_context:, links:, name:, kind:, attributes:) # rubocop:disable Metrics/ParameterLists, Lint/UnusedMethodArgument:
70
+ parent_span_context = OpenTelemetry::Trace.current_span(parent_context).context
71
+ tracestate = parent_span_context&.tracestate
72
+ Result.new(decision: :__record_only__, tracestate: tracestate)
73
+ end
74
+ end
75
+ end
76
+ end
@@ -42,8 +42,10 @@ module Instana
42
42
  else
43
43
  tags[:lambda] = tags[:lambda].merge(event_tags)
44
44
  end
45
-
46
- @tracer.start_or_continue_trace(:'aws.lambda.entry', tags, span_context, &block)
45
+ Trace.with_span(OpenTelemetry::Trace.non_recording_span(::Instana::SpanContext.new(trace_id: span_context[:trace_id], span_id: span_context[:span_id],
46
+ level: span_context[:level]))) do
47
+ @tracer.in_span(:'aws.lambda.entry', attributes: tags, &block)
48
+ end
47
49
  ensure
48
50
  begin
49
51
  @agent.send_bundle
data/lib/instana/setup.rb CHANGED
@@ -11,8 +11,8 @@ require 'instana/instrumented_logger'
11
11
  require "instana/base"
12
12
  require "instana/config"
13
13
  require "instana/secrets"
14
- require "instana/tracer"
15
- require "instana/tracing/processor"
14
+ require "instana/trace/tracer"
15
+ require "instana/trace/processor"
16
16
 
17
17
  require 'instana/serverless'
18
18
 
@@ -39,13 +39,13 @@ require 'instana/backend/host_agent_reporting_observer'
39
39
  require 'instana/backend/host_agent'
40
40
  require 'instana/backend/serverless_agent'
41
41
  require 'instana/backend/agent'
42
+ require 'instana/trace'
43
+ require 'instana/trace/tracer_provider'
44
+ require 'instana/span_filtering'
42
45
 
43
46
  ::Instana.setup
44
47
  ::Instana.agent.setup
45
48
 
46
- # Require supported OpenTracing interfaces
47
- require "opentracing"
48
-
49
49
  # The Instana agent is now setup. The only remaining
50
50
  # task for a complete boot is to call
51
51
  # `Instana.agent.start` in the thread of your choice.
@@ -0,0 +1,134 @@
1
+ # (c) Copyright IBM Corp. 2025
2
+
3
+ module Instana
4
+ module SpanFiltering
5
+ # Represents a condition for filtering spans
6
+ #
7
+ # A condition consists of:
8
+ # - key: The attribute to match against (category, kind, type, or span attribute)
9
+ # - values: List of values to match against (OR logic between values)
10
+ # - match_type: String matching strategy (strict, startswith, endswith, contains)
11
+ class Condition
12
+ attr_reader :key, :values, :match_type
13
+
14
+ def initialize(key, values, match_type = 'strict')
15
+ @key = key
16
+ @values = Array(values)
17
+ @match_type = match_type
18
+ end
19
+
20
+ # Check if a span matches this condition
21
+ # @param span [Hash] The span to check
22
+ # @return [Boolean] True if the span matches any of the values
23
+ def matches?(span)
24
+ attribute_value = extract_attribute(span, @key)
25
+ return false if attribute_value.nil?
26
+
27
+ @values.any? { |value| matches_value?(attribute_value, value) }
28
+ end
29
+
30
+ private
31
+
32
+ # Extract an attribute from a span
33
+ # @param span [Hash] The span to extract from
34
+ # @param key [String] The key to extract
35
+ # @return [Object, nil] The attribute value or nil if not found
36
+ def extract_attribute(span, key)
37
+ case key
38
+ when 'category'
39
+ # Map to appropriate span attribute for category
40
+ determine_category(span)
41
+ when 'kind'
42
+ # Map to appropriate span attribute for kind
43
+ span[:k] || span['k']
44
+ when 'type'
45
+ # Map to appropriate span attribute for type
46
+ span[:n] || span['n']
47
+ else
48
+ # Handle nested attributes with dot notation
49
+ extract_nested_attribute(span[:data] || span['data'], key)
50
+ end
51
+ end
52
+
53
+ # Determine the category of a span based on its properties
54
+ # @param span [Hash] The span to categorize
55
+ # @return [String, nil] The category or nil if not determinable
56
+ def determine_category(span)
57
+ data = span[:data] || span['data']
58
+ return nil unless data
59
+
60
+ if data[:http] || data['http']
61
+ 'protocols'
62
+ elsif data[:redis] || data[:mysql] || data[:pg] || data[:db]
63
+ 'databases'
64
+ elsif data[:sqs] || data[:sns] || data[:mq]
65
+ 'messaging'
66
+ elsif (span[:n] || span['n'])&.start_with?('log.')
67
+ 'logging'
68
+ end
69
+ end
70
+
71
+ # Extract a nested attribute using dot notation
72
+ # @param data [Hash] The data hash to extract from
73
+ # @param key [String] The key in dot notation
74
+ # @return [Object, nil] The attribute value or nil if not found
75
+ def extract_nested_attribute(data, key)
76
+ return nil unless data
77
+
78
+ parts = key.split('.')
79
+ current = data
80
+
81
+ parts.each do |part|
82
+ # Try symbol key first, then string key
83
+ if current.key?(part.to_sym)
84
+ current = current[part.to_sym]
85
+ elsif current.key?(part)
86
+ current = current[part]
87
+ else
88
+ return nil # Key not found
89
+ end
90
+
91
+ # Only return nil if the value is actually nil, not for false values
92
+ end
93
+
94
+ current
95
+ end
96
+
97
+ # Check if an attribute value matches a condition value
98
+ # @param attribute_value [Object] The attribute value
99
+ # @param condition_value [String] The condition value
100
+ # @return [Boolean] True if the attribute value matches the condition value
101
+ def matches_value?(attribute_value, condition_value)
102
+ # Handle wildcard
103
+ return true if condition_value == '*'
104
+
105
+ # Direct comparison first - this should handle boolean values correctly
106
+ return true if attribute_value == condition_value
107
+
108
+ # For strict matching with type conversion
109
+ if @match_type == 'strict'
110
+ # Convert to strings and compare
111
+ attribute_str = attribute_value.to_s
112
+ condition_str = condition_value.to_s
113
+ return attribute_str == condition_str
114
+ end
115
+
116
+ # For other match types, convert both to strings
117
+ attribute_str = attribute_value.to_s
118
+ condition_str = condition_value.to_s
119
+
120
+ case @match_type
121
+ when 'startswith'
122
+ attribute_str.start_with?(condition_str)
123
+ when 'endswith'
124
+ attribute_str.end_with?(condition_str)
125
+ when 'contains'
126
+ attribute_str.include?(condition_str)
127
+ else
128
+ # Default to strict matching
129
+ attribute_str == condition_str
130
+ end
131
+ end
132
+ end
133
+ end
134
+ end
@@ -0,0 +1,262 @@
1
+ # (c) Copyright IBM Corp. 2025
2
+
3
+ require 'yaml'
4
+
5
+ module Instana
6
+ module SpanFiltering
7
+ # Configuration class for span filtering
8
+ #
9
+ # This class handles loading and managing span filtering rules from various sources:
10
+ # - YAML configuration file (via INSTANA_CONFIG_PATH)
11
+ # - Environment variables
12
+ # - Agent discovery response
13
+ # It supports both include and exclude rules with various matching strategies
14
+ class Configuration
15
+ attr_reader :include_rules, :exclude_rules, :deactivated
16
+
17
+ TRACING_CONFIG_WARNING = 'Please use "tracing" instead of "com.instana.tracing" for local configuration file.'.freeze
18
+
19
+ def initialize
20
+ @include_rules = []
21
+ @exclude_rules = []
22
+ @deactivated = false
23
+ load_configuration
24
+ end
25
+
26
+ # Load configuration from all available sources
27
+ def load_configuration
28
+ load_from_yaml
29
+ load_from_env_vars unless rules_loaded?
30
+ load_from_agent unless rules_loaded?
31
+ end
32
+
33
+ private
34
+
35
+ # Load configuration from agent discovery response
36
+ def load_from_agent
37
+ # Try to get discovery value immediately first
38
+ discovery = ::Instana.agent&.delegate&.send(:discovery_value)
39
+ if discovery && discovery.is_a?(Hash) && !discovery.empty?
40
+ process_discovery_config(discovery)
41
+ return
42
+ end
43
+
44
+ # If not available, set up a timer task to periodically check for discovery
45
+ setup_discovery_timer
46
+ rescue => e
47
+ Instana.logger.warn("Failed to load span filtering configuration from agent: #{e.message}")
48
+ end
49
+
50
+ # Set up a timer task to periodically check for discovery
51
+ def setup_discovery_timer
52
+ # Don't create a timer task if we're in a test environment
53
+ return if ENV.key?('INSTANA_TEST')
54
+
55
+ # Create a timer task that checks for discovery every second
56
+ @discovery_timer = Concurrent::TimerTask.new(execution_interval: 1) do
57
+ check_discovery
58
+ end
59
+
60
+ # Start the timer task
61
+ @discovery_timer.execute
62
+ end
63
+
64
+ # Check if discovery is available and process it
65
+ def check_discovery
66
+ discovery = ::Instana.agent&.delegate.send(:discovery_value)
67
+ if discovery && discovery.is_a?(Hash) && !discovery.empty?
68
+ process_discovery_config(discovery)
69
+
70
+ # Shutdown the timer task after successful processing
71
+ @discovery_timer.shutdown if @discovery_timer
72
+
73
+ return true
74
+ end
75
+
76
+ false
77
+ rescue => e
78
+ Instana.logger.warn("Error checking discovery in timer task: #{e.message}")
79
+ false
80
+ end
81
+
82
+ # Process the discovery configuration
83
+ def process_discovery_config(discovery)
84
+ # Check if tracing configuration exists in the discovery response
85
+ tracing_config = discovery['tracing']
86
+ return unless tracing_config
87
+
88
+ # Process filter configuration
89
+ if tracing_config['filter']
90
+ filter_config = tracing_config['filter']
91
+ @deactivated = filter_config['deactivate'] == true
92
+
93
+ # Process include rules
94
+ process_rules(filter_config['include'], true) if filter_config['include']
95
+
96
+ # Process exclude rules
97
+ process_rules(filter_config['exclude'], false) if filter_config['exclude']
98
+ end
99
+
100
+ # Process disable configuration
101
+ if tracing_config['disable']
102
+ process_disable_config(tracing_config['disable'])
103
+ end
104
+
105
+ # Return true to indicate successful processing
106
+ true
107
+ rescue => e
108
+ Instana.logger.warn("Failed to process discovery configuration: #{e.message}")
109
+ false
110
+ end
111
+
112
+ # Check if the rules are already loaded
113
+ def rules_loaded?
114
+ @include_rules.any? || @exclude_rules.any?
115
+ end
116
+
117
+ # Load configuration from YAML file specified by INSTANA_CONFIG_PATH
118
+ def load_from_yaml
119
+ config_path = ENV['INSTANA_CONFIG_PATH']
120
+ return unless config_path && File.exist?(config_path)
121
+
122
+ begin
123
+ yaml_content = YAML.safe_load(File.read(config_path))
124
+
125
+ # Support both "tracing" and "com.instana.tracing" as top-level keys
126
+ tracing_config = yaml_content['tracing'] || yaml_content['com.instana.tracing']
127
+ ::Instana.logger.warn(TRACING_CONFIG_WARNING) if yaml_content.key?('com.instana.tracing')
128
+ return unless tracing_config
129
+
130
+ # Process filter configuration
131
+ if tracing_config['filter']
132
+ filter_config = tracing_config['filter']
133
+ @deactivated = filter_config['deactivate'] == true
134
+
135
+ # Process include rules
136
+ process_rules(filter_config['include'], true) if filter_config['include']
137
+
138
+ # Process exclude rules
139
+ process_rules(filter_config['exclude'], false) if filter_config['exclude']
140
+ end
141
+
142
+ # Process disable configuration
143
+ if tracing_config['disable']
144
+ process_disable_config(tracing_config['disable'])
145
+ end
146
+ rescue => e
147
+ Instana.logger.warn("Failed to load span filtering configuration from YAML: #{e.message}")
148
+ end
149
+ end
150
+
151
+ # Load configuration from environment variables
152
+ def load_from_env_vars
153
+ ENV.each do |key, value|
154
+ next unless key.start_with?('INSTANA_TRACING_FILTER_')
155
+
156
+ parts = key.split('_')
157
+ next unless parts.size >= 5
158
+
159
+ policy = parts[3].downcase
160
+ next unless ['include', 'exclude'].include?(policy)
161
+
162
+ if parts[4] == 'ATTRIBUTES'
163
+ process_env_attributes(policy, parts[4..].join('_'), value)
164
+ elsif policy == 'exclude' && parts[4] == 'SUPPRESSION'
165
+ process_env_suppression(parts[3..].join('_'), value)
166
+ end
167
+ end
168
+
169
+ return unless !ENV["INSTANA_TRACING_DISABLE"].nil? && !%w[True true 1].include?(ENV["INSTANA_TRACING_DISABLE"])
170
+
171
+ process_disable_config(ENV["INSTANA_TRACING_DISABLE"].split(','))
172
+ end
173
+
174
+ # Process rules from YAML configuration
175
+ def process_rules(rules_config, is_include)
176
+ rules_config.each do |rule_config|
177
+ name = rule_config['name']
178
+ suppression = is_include ? false : (rule_config['suppression'] != false) # Default true for exclude
179
+
180
+ conditions = []
181
+ rule_config['attributes'].each do |attr_config|
182
+ key = attr_config['key']
183
+ values = attr_config['values']
184
+ match_type = attr_config['match_type'] || 'strict'
185
+
186
+ conditions << Condition.new(key, values, match_type)
187
+ end
188
+
189
+ rule = FilterRule.new(name, suppression, conditions)
190
+ is_include ? @include_rules << rule : @exclude_rules << rule
191
+ end
192
+ end
193
+
194
+ # Process attributes from environment variables
195
+ def process_env_attributes(policy, name, value)
196
+ # Parse rules from environment variable format
197
+ # Format: key;values;match_type|key;values;match_type
198
+ rules = value.split('|')
199
+ conditions = []
200
+
201
+ rules.each do |rule|
202
+ parts = rule.split(';')
203
+ next unless parts.size >= 2
204
+
205
+ key = parts[0]
206
+ values = parts[1].split(',')
207
+ match_type = parts[2] || 'strict'
208
+
209
+ conditions << Condition.new(key, values, match_type)
210
+ end
211
+
212
+ rule_name = "EnvRule_#{name}"
213
+ suppression = policy == 'exclude' # Default true for exclude
214
+
215
+ rule = FilterRule.new(rule_name, suppression, conditions)
216
+ policy == 'include' ? @include_rules << rule : @exclude_rules << rule
217
+ end
218
+
219
+ # Process suppression setting from environment variables
220
+ def process_env_suppression(policy_name, value)
221
+ # Find the corresponding rule and update its suppression value
222
+ rule_index = policy_name.split('_')[1].to_i
223
+ return if rule_index >= @exclude_rules.size
224
+
225
+ suppression = %w[1 true True].include?(value)
226
+ @exclude_rules[rule_index].suppression = suppression
227
+ end
228
+
229
+ # Process disable configuration from YAML or agent discovery
230
+ # @param disable_config [Array] The disable configuration array
231
+ def process_disable_config(disable_config)
232
+ return unless disable_config.is_a?(Array)
233
+
234
+ disable_config.each do |item|
235
+ if item.is_a?(Hash)
236
+ item.each do |key, value|
237
+ if value == true
238
+ update_instana_config_for_disabled_technology(key)
239
+ end
240
+ end
241
+ elsif item.is_a?(String)
242
+ update_instana_config_for_disabled_technology(item)
243
+ end
244
+ end
245
+ end
246
+
247
+ # Update Instana::Config for a disabled technology
248
+ # @param technology [String] The technology to disable
249
+ def update_instana_config_for_disabled_technology(technology)
250
+ tech_sym = technology.to_sym
251
+
252
+ case tech_sym
253
+ when :redis
254
+ ::Instana.config[:redis][:enabled] = false
255
+ when :databases
256
+ # If databases category is disabled, also disable redis
257
+ ::Instana.config[:redis][:enabled] = false
258
+ end
259
+ end
260
+ end
261
+ end
262
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ # (c) Copyright IBM Corp. 2025
4
+
5
+ module Instana
6
+ module SpanFiltering
7
+ # Represents a filtering rule for spans
8
+ #
9
+ # A rule consists of:
10
+ # - name: A human-readable identifier for the rule
11
+ # - suppression: Whether child spans should be suppressed (only for exclude rules)
12
+ # - conditions: A list of conditions that must all be satisfied (AND logic)
13
+ class FilterRule
14
+ attr_reader :name
15
+ attr_accessor :suppression, :conditions
16
+
17
+ def initialize(name, suppression, conditions)
18
+ @name = name
19
+ @suppression = suppression
20
+ @conditions = conditions
21
+ end
22
+
23
+ # Check if a span matches this rule
24
+ # @param span [Hash] The span to check
25
+ # @return [Boolean] True if the span matches all conditions
26
+ def matches?(span)
27
+ @conditions.all? { |condition| condition.matches?(span) }
28
+ end
29
+ end
30
+ end
31
+ end