instana 2.0.0 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/instana/instrumentation/instrumented_request.rb +2 -0
- data/lib/instana/instrumentation/net-http.rb +2 -1
- data/lib/instana/instrumentation/rack.rb +86 -66
- data/lib/instana/setup.rb +1 -0
- data/lib/instana/span_filtering/condition.rb +134 -0
- data/lib/instana/span_filtering/configuration.rb +262 -0
- data/lib/instana/span_filtering/filter_rule.rb +31 -0
- data/lib/instana/span_filtering.rb +62 -0
- data/lib/instana/trace/span.rb +5 -3
- data/lib/instana/trace/span_context.rb +10 -2
- data/lib/instana/trace/tracer.rb +0 -21
- data/lib/instana/util.rb +13 -0
- data/lib/instana/version.rb +1 -1
- metadata +7 -258
- data/.circleci/config.yml +0 -361
- data/.codeclimate.yml +0 -23
- data/.editorconfig +0 -10
- data/.fasterer.yml +0 -23
- data/.github/ISSUE_TEMPLATE/bug.yml +0 -39
- data/.github/ISSUE_TEMPLATE/config.yml +0 -8
- data/.github/workflows/pr_commits_signed_off.yml +0 -16
- data/.github/workflows/release-notification-on-slack.yml +0 -34
- data/.gitignore +0 -19
- data/.rubocop.yml +0 -34
- data/.rubocop_todo.yml +0 -1140
- data/.tekton/.currency/docs/report.md +0 -20
- data/.tekton/.currency/resources/requirements.txt +0 -4
- data/.tekton/.currency/resources/table.json +0 -100
- data/.tekton/.currency/scripts/generate_report.py +0 -308
- data/.tekton/README.md +0 -278
- data/.tekton/github-interceptor-secret.yaml +0 -8
- data/.tekton/github-pr-eventlistener.yaml +0 -104
- data/.tekton/github-pr-pipeline.yaml.part +0 -38
- data/.tekton/github-set-status-task.yaml +0 -43
- data/.tekton/github-webhook-ingress.yaml +0 -20
- data/.tekton/pipeline.yaml +0 -484
- data/.tekton/pipelinerun.yaml +0 -21
- data/.tekton/prepuller-restart-service-account.yaml +0 -31
- data/.tekton/ruby-tracer-prepuller-cronjob.yaml +0 -20
- data/.tekton/ruby-tracer-prepuller.yaml +0 -88
- data/.tekton/run_unittests.sh +0 -87
- data/.tekton/scheduled-eventlistener.yaml +0 -108
- data/.tekton/task.yaml +0 -453
- data/.tekton/tekton-triggers-eventlistener-serviceaccount.yaml +0 -29
- data/Appraisals +0 -124
- data/CONTRIBUTING.md +0 -86
- data/Gemfile +0 -22
- data/LICENSE +0 -22
- data/MAINTAINERS.md +0 -3
- data/Rakefile +0 -49
- data/bin/announce_release_on_slack.py +0 -103
- data/docker-compose.yml +0 -20
- data/download.sh +0 -85
- data/examples/otel.rb +0 -98
- data/examples/tracing.rb +0 -85
- data/extras/license_header.rb +0 -44
- data/gemfiles/.bundle/config +0 -2
- data/gemfiles/aws_30.gemfile +0 -21
- data/gemfiles/aws_60.gemfile +0 -16
- data/gemfiles/cuba_30.gemfile +0 -16
- data/gemfiles/cuba_40.gemfile +0 -13
- data/gemfiles/dalli_20.gemfile +0 -15
- data/gemfiles/dalli_30.gemfile +0 -12
- data/gemfiles/dalli_32.gemfile +0 -12
- data/gemfiles/excon_0100.gemfile +0 -14
- data/gemfiles/excon_021.gemfile +0 -17
- data/gemfiles/excon_079.gemfile +0 -17
- data/gemfiles/excon_100.gemfile +0 -14
- data/gemfiles/graphql_10.gemfile +0 -16
- data/gemfiles/graphql_20.gemfile +0 -15
- data/gemfiles/grpc_10.gemfile +0 -15
- data/gemfiles/mongo_216.gemfile +0 -15
- data/gemfiles/mongo_219.gemfile +0 -12
- data/gemfiles/net_http_01.gemfile +0 -17
- data/gemfiles/rack_16.gemfile +0 -15
- data/gemfiles/rack_20.gemfile +0 -15
- data/gemfiles/rack_30.gemfile +0 -13
- data/gemfiles/rails_42.gemfile +0 -18
- data/gemfiles/rails_50.gemfile +0 -19
- data/gemfiles/rails_52.gemfile +0 -19
- data/gemfiles/rails_60.gemfile +0 -19
- data/gemfiles/rails_61.gemfile +0 -21
- data/gemfiles/rails_70.gemfile +0 -18
- data/gemfiles/rails_71.gemfile +0 -17
- data/gemfiles/rails_80.gemfile +0 -17
- data/gemfiles/redis_40.gemfile +0 -15
- data/gemfiles/redis_50.gemfile +0 -13
- data/gemfiles/redis_51.gemfile +0 -13
- data/gemfiles/resque_122.gemfile +0 -16
- data/gemfiles/resque_1274_3scale.gemfile +0 -17
- data/gemfiles/resque_20.gemfile +0 -16
- data/gemfiles/rest_client_16.gemfile +0 -17
- data/gemfiles/rest_client_20.gemfile +0 -17
- data/gemfiles/roda_20.gemfile +0 -16
- data/gemfiles/roda_30.gemfile +0 -16
- data/gemfiles/rubocop_162.gemfile +0 -6
- data/gemfiles/sequel_56.gemfile +0 -16
- data/gemfiles/sequel_57.gemfile +0 -16
- data/gemfiles/sequel_58.gemfile +0 -16
- data/gemfiles/shoryuken_50.gemfile +0 -16
- data/gemfiles/shoryuken_60.gemfile +0 -13
- data/gemfiles/sidekiq_42.gemfile +0 -15
- data/gemfiles/sidekiq_50.gemfile +0 -15
- data/gemfiles/sidekiq_60.gemfile +0 -12
- data/gemfiles/sidekiq_65.gemfile +0 -12
- data/gemfiles/sidekiq_70.gemfile +0 -12
- data/gemfiles/sinatra_14.gemfile +0 -15
- data/gemfiles/sinatra_22.gemfile +0 -12
- data/gemfiles/sinatra_30.gemfile +0 -12
- data/gemfiles/sinatra_40.gemfile +0 -12
- data/instana.gemspec +0 -53
- data/log/.keep +0 -0
- data/sonar-project.properties +0 -9
- data/test/activator_test.rb +0 -50
- data/test/backend/agent_test.rb +0 -80
- data/test/backend/gc_snapshot_test.rb +0 -11
- data/test/backend/host_agent_activation_observer_test.rb +0 -73
- data/test/backend/host_agent_lookup_test.rb +0 -78
- data/test/backend/host_agent_reporting_observer_test.rb +0 -276
- data/test/backend/host_agent_test.rb +0 -89
- data/test/backend/process_info_test.rb +0 -83
- data/test/backend/request_client_test.rb +0 -39
- data/test/backend/serverless_agent_test.rb +0 -83
- data/test/benchmarks/bench_id_generation.rb +0 -15
- data/test/benchmarks/bench_opentracing.rb +0 -16
- data/test/config_test.rb +0 -34
- data/test/frameworks/cuba_test.rb +0 -61
- data/test/frameworks/roda_test.rb +0 -60
- data/test/frameworks/sinatra_test.rb +0 -71
- data/test/instana_test.rb +0 -37
- data/test/instrumentation/aws_test.rb +0 -241
- data/test/instrumentation/dalli_test.rb +0 -325
- data/test/instrumentation/excon_test.rb +0 -204
- data/test/instrumentation/graphql_test.rb +0 -289
- data/test/instrumentation/grpc_test.rb +0 -420
- data/test/instrumentation/mongo_test.rb +0 -68
- data/test/instrumentation/net_http_test.rb +0 -220
- data/test/instrumentation/rack_instrumented_request_test.rb +0 -211
- data/test/instrumentation/rack_test.rb +0 -415
- data/test/instrumentation/rails_action_cable_test.rb +0 -135
- data/test/instrumentation/rails_action_controller_test.rb +0 -218
- data/test/instrumentation/rails_action_mailer_test.rb +0 -66
- data/test/instrumentation/rails_action_view_test.rb +0 -154
- data/test/instrumentation/rails_active_job_test.rb +0 -65
- data/test/instrumentation/rails_active_record_database_missing_test.rb +0 -44
- data/test/instrumentation/rails_active_record_test.rb +0 -116
- data/test/instrumentation/redis_test.rb +0 -152
- data/test/instrumentation/resque_test.rb +0 -188
- data/test/instrumentation/rest_client_test.rb +0 -106
- data/test/instrumentation/sequel_test.rb +0 -111
- data/test/instrumentation/shoryuken_test.rb +0 -47
- data/test/instrumentation/sidekiq-client_test.rb +0 -169
- data/test/instrumentation/sidekiq-worker_test.rb +0 -180
- data/test/secrets_test.rb +0 -112
- data/test/serverless_test.rb +0 -369
- data/test/snapshot/deltable_test.rb +0 -17
- data/test/snapshot/docker_container_test.rb +0 -82
- data/test/snapshot/fargate_container_test.rb +0 -82
- data/test/snapshot/fargate_process_test.rb +0 -35
- data/test/snapshot/fargate_task_test.rb +0 -49
- data/test/snapshot/google_cloud_run_instance_test.rb +0 -74
- data/test/snapshot/google_cloud_run_process_test.rb +0 -33
- data/test/snapshot/lambda_function_test.rb +0 -37
- data/test/snapshot/ruby_process_test.rb +0 -32
- data/test/support/apps/active_record/active_record.rb +0 -24
- data/test/support/apps/grpc/boot.rb +0 -23
- data/test/support/apps/grpc/grpc_server.rb +0 -84
- data/test/support/apps/http_endpoint/boot.rb +0 -28
- data/test/support/apps/rails/boot.rb +0 -219
- data/test/support/apps/rails/models/block.rb +0 -21
- data/test/support/apps/rails/models/block6.rb +0 -21
- data/test/support/apps/resque/boot.rb +0 -5
- data/test/support/apps/resque/jobs/resque_error_job.rb +0 -22
- data/test/support/apps/resque/jobs/resque_fast_job.rb +0 -23
- data/test/support/apps/sidekiq/boot.rb +0 -25
- data/test/support/apps/sidekiq/jobs/sidekiq_job_1.rb +0 -9
- data/test/support/apps/sidekiq/jobs/sidekiq_job_2.rb +0 -10
- data/test/support/apps/sidekiq/worker.rb +0 -37
- data/test/support/helpers.rb +0 -85
- data/test/support/mock_timer.rb +0 -20
- data/test/test_helper.rb +0 -69
- data/test/trace/custom_test.rb +0 -233
- data/test/trace/id_management_test.rb +0 -78
- data/test/trace/instrumented_logger_test.rb +0 -39
- data/test/trace/processor_test.rb +0 -58
- data/test/trace/span_context_test.rb +0 -22
- data/test/trace/span_test.rb +0 -179
- data/test/trace/tracer_async_test.rb +0 -243
- data/test/trace/tracer_provider_test.rb +0 -148
- data/test/trace/tracer_test.rb +0 -363
- data/test/util_test.rb +0 -10
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4a940d6791ca18a45f73ca949de0268cb4195ca269991d78683d1d89c3801919
|
|
4
|
+
data.tar.gz: c4a79bc94556b873ca0d2cb3cbe2b988914e2579d6fd124bbf4925142316dd85
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f0c11ead2a6eb5e7cf4811a44b0edb6af0edbed588e712a080b5de07a704fbd28b91474e593b6342537459fe69cc0fcc60dfb83c18618e8e4ca5babea5096d6b
|
|
7
|
+
data.tar.gz: 5a22a83d6a6f930df189a914116398b372976d95f76ce86ae63b18c2f014eb4aa170a5415f5b6feb2f69b63bc9aff511d16e2d5e5bfb2682c369a0299cb5d2d3
|
|
@@ -122,6 +122,7 @@ module Instana
|
|
|
122
122
|
long_instana_id: long_instana_id? ? sanitized_t : nil,
|
|
123
123
|
external_trace_id: external_trace_id,
|
|
124
124
|
external_state: @env['HTTP_TRACESTATE'],
|
|
125
|
+
external_trace_flags: context_from_trace_parent[:external_trace_flags],
|
|
125
126
|
from_w3c: false
|
|
126
127
|
}.reject { |_, v| v.nil? }
|
|
127
128
|
end
|
|
@@ -140,6 +141,7 @@ module Instana
|
|
|
140
141
|
external_state: @env['HTTP_TRACESTATE'],
|
|
141
142
|
trace_id: trace_id,
|
|
142
143
|
span_id: span_id,
|
|
144
|
+
external_trace_flags: matches['flags'],
|
|
143
145
|
from_w3c: true
|
|
144
146
|
}
|
|
145
147
|
end
|
|
@@ -56,7 +56,8 @@ module Instana
|
|
|
56
56
|
# without a backtrace (no exception)
|
|
57
57
|
current_span.record_exception(nil)
|
|
58
58
|
end
|
|
59
|
-
|
|
59
|
+
extra_headers = ::Instana::Util.extra_header_tags(response)&.merge(::Instana::Util.extra_header_tags(request))
|
|
60
|
+
kv_payload[:http][:header] = extra_headers unless extra_headers&.empty?
|
|
60
61
|
response
|
|
61
62
|
rescue => e
|
|
62
63
|
current_span&.record_exception(e)
|
|
@@ -10,7 +10,7 @@ module Instana
|
|
|
10
10
|
@app = app
|
|
11
11
|
end
|
|
12
12
|
|
|
13
|
-
def call(env)
|
|
13
|
+
def call(env)
|
|
14
14
|
req = InstrumentedRequest.new(env)
|
|
15
15
|
kvs = {
|
|
16
16
|
http: req.request_tags
|
|
@@ -26,75 +26,14 @@ module Instana
|
|
|
26
26
|
@trace_token = OpenTelemetry::Context.attach(trace_ctx)
|
|
27
27
|
status, headers, response = @app.call(env)
|
|
28
28
|
|
|
29
|
-
if ::Instana.tracer.tracing?
|
|
30
|
-
|
|
31
|
-
current_span[:crid] = req.correlation_data[:id]
|
|
32
|
-
current_span[:crtp] = req.correlation_data[:type]
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
if !req.instana_ancestor.empty? && req.continuing_from_trace_parent?
|
|
36
|
-
current_span[:ia] = req.instana_ancestor
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
if req.continuing_from_trace_parent?
|
|
40
|
-
current_span[:tp] = true
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
if req.external_trace_id?
|
|
44
|
-
current_span[:lt] = req.external_trace_id
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
if req.synthetic?
|
|
48
|
-
current_span[:sy] = true
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
# In case some previous middleware returned a string status, make sure that we're dealing with
|
|
52
|
-
# an integer. In Ruby nil.to_i, "asdfasdf".to_i will always return 0 from Ruby versions 1.8.7 and newer.
|
|
53
|
-
# So if an 0 status is reported here, it indicates some other issue (e.g. no status from previous middleware)
|
|
54
|
-
# See Rack Spec: https://www.rubydoc.info/github/rack/rack/file/SPEC#label-The+Status
|
|
55
|
-
kvs[:http][:status] = status.to_i
|
|
56
|
-
|
|
57
|
-
if status.to_i >= 500
|
|
58
|
-
# Because of the 5xx response, we flag this span as errored but
|
|
59
|
-
# without a backtrace (no exception)
|
|
60
|
-
::Instana.tracer.log_error(nil)
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
# If the framework instrumentation provides a path template,
|
|
64
|
-
# pass it into the span here.
|
|
65
|
-
# See: https://www.instana.com/docs/tracing/custom-best-practices/#path-templates-visual-grouping-of-http-endpoints
|
|
66
|
-
kvs[:http][:path_tpl] = env['INSTANA_HTTP_PATH_TEMPLATE'] if env['INSTANA_HTTP_PATH_TEMPLATE']
|
|
67
|
-
|
|
68
|
-
# Save the span context before the trace ends so we can place
|
|
69
|
-
# them in the response headers in the ensure block
|
|
70
|
-
trace_context = ::Instana.tracer.current_span.context
|
|
71
|
-
end
|
|
72
|
-
|
|
29
|
+
trace_context = process_span_tags(req, current_span, kvs, status, env) if ::Instana.tracer.tracing?
|
|
30
|
+
merge_response_headers(kvs, headers)
|
|
73
31
|
[status, headers, response]
|
|
74
32
|
rescue Exception => e
|
|
75
33
|
current_span.record_exception(e) if ::Instana.tracer.tracing?
|
|
76
34
|
raise
|
|
77
35
|
ensure
|
|
78
|
-
if ::Instana.tracer.tracing?
|
|
79
|
-
if headers
|
|
80
|
-
# Set response headers; encode as hex string
|
|
81
|
-
if trace_context.active?
|
|
82
|
-
headers['X-Instana-T'] = trace_context.trace_id_header
|
|
83
|
-
headers['X-Instana-S'] = trace_context.span_id_header
|
|
84
|
-
headers['X-Instana-L'] = '1'
|
|
85
|
-
|
|
86
|
-
headers['Tracestate'] = trace_context.trace_state_header
|
|
87
|
-
else
|
|
88
|
-
headers['X-Instana-L'] = '0'
|
|
89
|
-
end
|
|
90
|
-
|
|
91
|
-
headers['Traceparent'] = trace_context.trace_parent_header
|
|
92
|
-
headers['Server-Timing'] = "intid;desc=#{trace_context.trace_id_header}"
|
|
93
|
-
end
|
|
94
|
-
current_span.set_tags(kvs)
|
|
95
|
-
OpenTelemetry::Context.detach(@trace_token) if @trace_token
|
|
96
|
-
current_span.finish
|
|
97
|
-
end
|
|
36
|
+
finalize_trace(current_span, kvs, headers, trace_context) if ::Instana.tracer.tracing?
|
|
98
37
|
end
|
|
99
38
|
|
|
100
39
|
private
|
|
@@ -112,7 +51,8 @@ module Instana
|
|
|
112
51
|
level: incoming_context[:level],
|
|
113
52
|
baggage: {
|
|
114
53
|
external_trace_id: incoming_context[:external_trace_id],
|
|
115
|
-
external_state: incoming_context[:external_state]
|
|
54
|
+
external_state: incoming_context[:external_state],
|
|
55
|
+
external_trace_flags: incoming_context[:external_trace_flags]
|
|
116
56
|
}
|
|
117
57
|
)
|
|
118
58
|
end
|
|
@@ -121,5 +61,85 @@ module Instana
|
|
|
121
61
|
end
|
|
122
62
|
parent_context
|
|
123
63
|
end
|
|
64
|
+
|
|
65
|
+
def process_span_tags(req, current_span, kvs, status, env)
|
|
66
|
+
add_correlation_data(req, current_span)
|
|
67
|
+
add_trace_parent_data(req, current_span)
|
|
68
|
+
add_status_and_error(kvs, status)
|
|
69
|
+
add_path_template(kvs, env)
|
|
70
|
+
|
|
71
|
+
# Save the span context before the trace ends so we can place
|
|
72
|
+
# them in the response headers in the ensure block
|
|
73
|
+
::Instana.tracer.current_span.context
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def add_correlation_data(req, current_span)
|
|
77
|
+
return if req.correlation_data.empty?
|
|
78
|
+
|
|
79
|
+
current_span[:crid] = req.correlation_data[:id]
|
|
80
|
+
current_span[:crtp] = req.correlation_data[:type]
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def add_trace_parent_data(req, current_span)
|
|
84
|
+
if !req.instana_ancestor.empty? && req.continuing_from_trace_parent?
|
|
85
|
+
current_span[:ia] = req.instana_ancestor
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
current_span[:tp] = true if req.continuing_from_trace_parent?
|
|
89
|
+
current_span[:lt] = req.external_trace_id if req.external_trace_id?
|
|
90
|
+
current_span[:sy] = true if req.synthetic?
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def add_status_and_error(kvs, status)
|
|
94
|
+
# In case some previous middleware returned a string status, make sure that we're dealing with
|
|
95
|
+
# an integer. In Ruby nil.to_i, "asdfasdf".to_i will always return 0 from Ruby versions 1.8.7 and newer.
|
|
96
|
+
# So if an 0 status is reported here, it indicates some other issue (e.g. no status from previous middleware)
|
|
97
|
+
# See Rack Spec: https://www.rubydoc.info/github/rack/rack/file/SPEC#label-The+Status
|
|
98
|
+
kvs[:http][:status] = status.to_i
|
|
99
|
+
|
|
100
|
+
return unless status.to_i >= 500
|
|
101
|
+
|
|
102
|
+
# Because of the 5xx response, we flag this span as errored but
|
|
103
|
+
# without a backtrace (no exception)
|
|
104
|
+
::Instana.tracer.log_error(nil)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def add_path_template(kvs, env)
|
|
108
|
+
# If the framework instrumentation provides a path template,
|
|
109
|
+
# pass it into the span here.
|
|
110
|
+
# See: https://www.instana.com/docs/tracing/custom-best-practices/#path-templates-visual-grouping-of-http-endpoints
|
|
111
|
+
kvs[:http][:path_tpl] = env['INSTANA_HTTP_PATH_TEMPLATE'] if env['INSTANA_HTTP_PATH_TEMPLATE']
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def merge_response_headers(kvs, headers)
|
|
115
|
+
extra_response_headers = ::Instana::Util.extra_header_tags(headers)
|
|
116
|
+
if kvs[:http][:header].nil?
|
|
117
|
+
kvs[:http][:header] = extra_response_headers
|
|
118
|
+
else
|
|
119
|
+
kvs[:http][:header].merge!(extra_response_headers)
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def finalize_trace(current_span, kvs, headers, trace_context)
|
|
124
|
+
set_response_headers(headers, trace_context) if headers
|
|
125
|
+
current_span.set_tags(kvs)
|
|
126
|
+
OpenTelemetry::Context.detach(@trace_token) if @trace_token
|
|
127
|
+
current_span.finish
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def set_response_headers(headers, trace_context)
|
|
131
|
+
# Set response headers; encode as hex string
|
|
132
|
+
if trace_context.active?
|
|
133
|
+
headers['X-Instana-T'] = trace_context.trace_id_header
|
|
134
|
+
headers['X-Instana-S'] = trace_context.span_id_header
|
|
135
|
+
headers['X-Instana-L'] = '1'
|
|
136
|
+
headers['Tracestate'] = trace_context.trace_state_header
|
|
137
|
+
else
|
|
138
|
+
headers['X-Instana-L'] = '0'
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
headers['Traceparent'] = trace_context.trace_parent_header
|
|
142
|
+
headers['Server-Timing'] = "intid;desc=#{trace_context.trace_id_header}"
|
|
143
|
+
end
|
|
124
144
|
end
|
|
125
145
|
end
|
data/lib/instana/setup.rb
CHANGED
|
@@ -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
|