instana 2.7.2 → 2.8.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/backend/gc_snapshot.rb +4 -4
- data/lib/instana/backend/host_agent_reporting_observer.rb +60 -5
- data/lib/instana/config.rb +152 -12
- data/lib/instana/exporter/otlp/aws_converter.rb +148 -0
- data/lib/instana/exporter/otlp/background_job_converter.rb +78 -0
- data/lib/instana/exporter/otlp/base_converter.rb +418 -0
- data/lib/instana/exporter/otlp/converter_factory.rb +135 -0
- data/lib/instana/exporter/otlp/custom_converter.rb +42 -0
- data/lib/instana/exporter/otlp/database_converter.rb +153 -0
- data/lib/instana/exporter/otlp/graphql_converter.rb +66 -0
- data/lib/instana/exporter/otlp/http_converter.rb +129 -0
- data/lib/instana/exporter/otlp/messaging_converter.rb +81 -0
- data/lib/instana/exporter/otlp/rails_converter.rb +114 -0
- data/lib/instana/exporter/otlp/resource.rb +403 -0
- data/lib/instana/exporter/otlp/rpc_converter.rb +102 -0
- data/lib/instana/version.rb +1 -1
- metadata +42 -2
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# (c) Copyright IBM Corp. 2026
|
|
4
|
+
|
|
5
|
+
require 'socket'
|
|
6
|
+
require 'opentelemetry/semantic_conventions'
|
|
7
|
+
require_relative '../../util'
|
|
8
|
+
|
|
9
|
+
module Instana
|
|
10
|
+
module Exporter
|
|
11
|
+
module Otlp
|
|
12
|
+
# Resource represents a resource, which captures identifying information about the entities
|
|
13
|
+
# for which telemetry (metrics or traces) is reported.
|
|
14
|
+
# This follows OpenTelemetry semantic conventions for resource attributes
|
|
15
|
+
class Resource
|
|
16
|
+
PROC_SELF_CGROUP = '/proc/self/cgroup'
|
|
17
|
+
DOCKER_ENV_FILE = '/.dockerenv'
|
|
18
|
+
PODMAN_CONTAINERENV = '/run/.containerenv'
|
|
19
|
+
# Linux-only stable machine-id paths (systemd and D-Bus fallback).
|
|
20
|
+
# These files do not exist on macOS or Windows; host_id returns nil there.
|
|
21
|
+
MACHINE_ID_PATHS = %w[/etc/machine-id /var/lib/dbus/machine-id].freeze
|
|
22
|
+
# cloud.resource_id is not yet in the installed semconv gem version
|
|
23
|
+
CLOUD_RESOURCE_ID = 'cloud.resource_id'
|
|
24
|
+
|
|
25
|
+
class << self
|
|
26
|
+
private :new
|
|
27
|
+
|
|
28
|
+
# Returns a newly created {Resource} with the specified attributes
|
|
29
|
+
#
|
|
30
|
+
# @param [Hash{String => String, Numeric, Boolean}] attributes Hash of key-value pairs to be used
|
|
31
|
+
# as attributes for this resource
|
|
32
|
+
# @return [Resource]
|
|
33
|
+
def create(attributes = {})
|
|
34
|
+
frozen_attributes = attributes.each_with_object({}) do |(k, v), memo|
|
|
35
|
+
memo[k.freeze] = v.freeze
|
|
36
|
+
end.freeze
|
|
37
|
+
|
|
38
|
+
new(frozen_attributes)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Returns the default resource with standard attributes
|
|
42
|
+
#
|
|
43
|
+
# @return [Resource]
|
|
44
|
+
def default
|
|
45
|
+
@default ||= create(OpenTelemetry::SemanticConventions::Resource::SERVICE_NAME => 'ruby-service')
|
|
46
|
+
.merge(process)
|
|
47
|
+
.merge(telemetry_sdk)
|
|
48
|
+
.merge(service_name_from_env)
|
|
49
|
+
.merge(optional_attributes)
|
|
50
|
+
.merge(container_attributes)
|
|
51
|
+
.merge(faas_attributes)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Get the global resource instance (singleton pattern)
|
|
55
|
+
# This method provides backward compatibility with the previous API
|
|
56
|
+
#
|
|
57
|
+
# @return [Hash] Resource attributes as a hash
|
|
58
|
+
def instance
|
|
59
|
+
@instance ||= default.attributes
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Reset the resource instance (useful for testing)
|
|
63
|
+
# This method provides backward compatibility with the previous API
|
|
64
|
+
def reset!
|
|
65
|
+
@instance = nil
|
|
66
|
+
@default = nil
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Returns telemetry SDK resource attributes
|
|
70
|
+
#
|
|
71
|
+
# @return [Resource]
|
|
72
|
+
def telemetry_sdk
|
|
73
|
+
create(
|
|
74
|
+
OpenTelemetry::SemanticConventions::Resource::TELEMETRY_SDK_NAME => 'instana',
|
|
75
|
+
OpenTelemetry::SemanticConventions::Resource::TELEMETRY_SDK_LANGUAGE => 'ruby',
|
|
76
|
+
OpenTelemetry::SemanticConventions::Resource::TELEMETRY_SDK_VERSION => ::Instana::VERSION
|
|
77
|
+
)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Returns process resource attributes
|
|
81
|
+
#
|
|
82
|
+
# @return [Resource]
|
|
83
|
+
def process
|
|
84
|
+
create(
|
|
85
|
+
OpenTelemetry::SemanticConventions::Resource::PROCESS_PID => Process.pid,
|
|
86
|
+
OpenTelemetry::SemanticConventions::Resource::PROCESS_COMMAND => $PROGRAM_NAME,
|
|
87
|
+
OpenTelemetry::SemanticConventions::Resource::PROCESS_EXECUTABLE_NAME => File.basename($PROGRAM_NAME),
|
|
88
|
+
OpenTelemetry::SemanticConventions::Resource::PROCESS_RUNTIME_NAME => RUBY_ENGINE,
|
|
89
|
+
OpenTelemetry::SemanticConventions::Resource::PROCESS_RUNTIME_VERSION => RUBY_VERSION,
|
|
90
|
+
OpenTelemetry::SemanticConventions::Resource::PROCESS_RUNTIME_DESCRIPTION => RUBY_DESCRIPTION
|
|
91
|
+
)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
private
|
|
95
|
+
|
|
96
|
+
# Returns service name from environment variables
|
|
97
|
+
#
|
|
98
|
+
# @return [Resource]
|
|
99
|
+
def service_name_from_env
|
|
100
|
+
service_name = ENV.fetch('OTEL_SERVICE_NAME', nil) ||
|
|
101
|
+
ENV.fetch('INSTANA_SERVICE_NAME', nil) ||
|
|
102
|
+
::Instana::Util.get_app_name
|
|
103
|
+
|
|
104
|
+
return create({}) unless service_name
|
|
105
|
+
|
|
106
|
+
create(OpenTelemetry::SemanticConventions::Resource::SERVICE_NAME => service_name)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Returns optional resource attributes:
|
|
110
|
+
# os.type, host.name, host.arch, host.id, service.version, service.instance.id
|
|
111
|
+
#
|
|
112
|
+
# service.instance.id priority (v2 spec §General Resource Attributes):
|
|
113
|
+
# container.id → k8s.pod.uid → host.id → hostname:pid
|
|
114
|
+
#
|
|
115
|
+
# @return [Resource]
|
|
116
|
+
def optional_attributes
|
|
117
|
+
attrs = {}
|
|
118
|
+
|
|
119
|
+
# os.type — Required per v2 spec
|
|
120
|
+
attrs[OpenTelemetry::SemanticConventions::Resource::OS_TYPE] = detect_os_type
|
|
121
|
+
|
|
122
|
+
host = hostname
|
|
123
|
+
|
|
124
|
+
# host.name — Recommended (Conditional) per v2 spec
|
|
125
|
+
attrs[OpenTelemetry::SemanticConventions::Resource::HOST_NAME] = host if host && host != 'unknown'
|
|
126
|
+
|
|
127
|
+
# host.arch
|
|
128
|
+
arch = host_architecture
|
|
129
|
+
attrs[OpenTelemetry::SemanticConventions::Resource::HOST_ARCH] = arch if arch
|
|
130
|
+
|
|
131
|
+
# host.id — Recommended per v2 spec; stable machine identifier
|
|
132
|
+
hid = host_id
|
|
133
|
+
attrs[OpenTelemetry::SemanticConventions::Resource::HOST_ID] = hid if hid
|
|
134
|
+
|
|
135
|
+
# service.version
|
|
136
|
+
version = ENV.fetch('OTEL_SERVICE_VERSION', nil) ||
|
|
137
|
+
ENV.fetch('INSTANA_SERVICE_VERSION', nil) ||
|
|
138
|
+
detect_app_version
|
|
139
|
+
attrs[OpenTelemetry::SemanticConventions::Resource::SERVICE_VERSION] = version if version
|
|
140
|
+
|
|
141
|
+
# service.instance.id — priority: container.id > k8s.pod.uid > host.id > hostname:pid
|
|
142
|
+
instance_id = extract_container_id ||
|
|
143
|
+
ENV.fetch('MY_POD_UID', nil) ||
|
|
144
|
+
hid ||
|
|
145
|
+
"#{host}:#{Process.pid}"
|
|
146
|
+
attrs[OpenTelemetry::SemanticConventions::Resource::SERVICE_INSTANCE_ID] = instance_id
|
|
147
|
+
|
|
148
|
+
create(attrs)
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
# Returns container and cloud platform resource attributes.
|
|
152
|
+
# AWS Lambda is intentionally excluded here — it is a FaaS platform,
|
|
153
|
+
# not a container runtime. See faas_attributes for Lambda/Cloud Run.
|
|
154
|
+
#
|
|
155
|
+
# @return [Resource]
|
|
156
|
+
def container_attributes
|
|
157
|
+
attrs = {}
|
|
158
|
+
|
|
159
|
+
add_docker_or_podman_attributes(attrs)
|
|
160
|
+
add_kubernetes_attributes(attrs)
|
|
161
|
+
add_aws_ecs_attributes(attrs)
|
|
162
|
+
|
|
163
|
+
create(attrs)
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# Returns FaaS (Function-as-a-Service) platform resource attributes.
|
|
167
|
+
# Kept separate from container_attributes because Lambda and Cloud Run
|
|
168
|
+
# are serverless runtimes, not container runtimes.
|
|
169
|
+
#
|
|
170
|
+
# @return [Resource]
|
|
171
|
+
def faas_attributes
|
|
172
|
+
attrs = {}
|
|
173
|
+
|
|
174
|
+
add_aws_lambda_attributes(attrs)
|
|
175
|
+
add_cloud_run_attributes(attrs)
|
|
176
|
+
|
|
177
|
+
create(attrs)
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
# Sets container.runtime and container.id attributes when a container
|
|
181
|
+
# engine is detected. Only runs on Linux since all sentinel paths are
|
|
182
|
+
# Linux-specific.
|
|
183
|
+
def add_docker_or_podman_attributes(attrs)
|
|
184
|
+
return unless linux?
|
|
185
|
+
|
|
186
|
+
engine = extract_container_engine
|
|
187
|
+
attrs[OpenTelemetry::SemanticConventions::Resource::CONTAINER_RUNTIME] = engine if engine
|
|
188
|
+
|
|
189
|
+
container_id = extract_container_id
|
|
190
|
+
attrs[OpenTelemetry::SemanticConventions::Resource::CONTAINER_ID] = container_id if container_id
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
# Detects the container engine by inspecting Linux sentinel files.
|
|
194
|
+
# Returns 'podman', 'docker', or nil if no container environment is found.
|
|
195
|
+
#
|
|
196
|
+
# @return [String, nil]
|
|
197
|
+
def extract_container_engine
|
|
198
|
+
if File.exist?(PODMAN_CONTAINERENV)
|
|
199
|
+
'podman'
|
|
200
|
+
elsif File.exist?(DOCKER_ENV_FILE) || File.exist?(PROC_SELF_CGROUP)
|
|
201
|
+
'docker'
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def add_kubernetes_attributes(attrs)
|
|
206
|
+
return unless ENV.fetch('KUBERNETES_SERVICE_HOST', nil)
|
|
207
|
+
|
|
208
|
+
attrs[OpenTelemetry::SemanticConventions::Resource::K8S_POD_NAME] = ENV.fetch('HOSTNAME', nil)
|
|
209
|
+
|
|
210
|
+
pod_uid = ENV.fetch('MY_POD_UID', nil)
|
|
211
|
+
attrs[OpenTelemetry::SemanticConventions::Resource::K8S_POD_UID] = pod_uid if pod_uid
|
|
212
|
+
|
|
213
|
+
ns = ENV.fetch('KUBERNETES_NAMESPACE', nil)
|
|
214
|
+
attrs[OpenTelemetry::SemanticConventions::Resource::K8S_NAMESPACE_NAME] = ns if ns
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
def add_aws_ecs_attributes(attrs)
|
|
218
|
+
return unless ENV.fetch('ECS_CONTAINER_METADATA_URI', nil) || ENV.fetch('ECS_CONTAINER_METADATA_URI_V4', nil)
|
|
219
|
+
|
|
220
|
+
attrs[OpenTelemetry::SemanticConventions::Resource::CLOUD_PROVIDER] = 'aws'
|
|
221
|
+
attrs[OpenTelemetry::SemanticConventions::Resource::CLOUD_PLATFORM] = 'aws_ecs'
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
def add_aws_lambda_attributes(attrs)
|
|
225
|
+
lambda_name = ENV.fetch('AWS_LAMBDA_FUNCTION_NAME', nil)
|
|
226
|
+
return unless lambda_name
|
|
227
|
+
|
|
228
|
+
attrs[OpenTelemetry::SemanticConventions::Resource::CLOUD_PROVIDER] = 'aws'
|
|
229
|
+
attrs[OpenTelemetry::SemanticConventions::Resource::CLOUD_PLATFORM] = 'aws_lambda'
|
|
230
|
+
attrs[OpenTelemetry::SemanticConventions::Resource::FAAS_NAME] = lambda_name
|
|
231
|
+
|
|
232
|
+
version = ENV.fetch('AWS_LAMBDA_FUNCTION_VERSION', nil)
|
|
233
|
+
attrs[OpenTelemetry::SemanticConventions::Resource::FAAS_VERSION] = version if version
|
|
234
|
+
|
|
235
|
+
arn = ENV.fetch('AWS_LAMBDA_FUNCTION_ARN', nil)
|
|
236
|
+
attrs.merge!(parse_lambda_arn(arn)) if arn
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
def add_cloud_run_attributes(attrs)
|
|
240
|
+
service = ENV.fetch('K_SERVICE', nil)
|
|
241
|
+
return unless service
|
|
242
|
+
|
|
243
|
+
attrs[OpenTelemetry::SemanticConventions::Resource::CLOUD_PROVIDER] = 'gcp'
|
|
244
|
+
attrs[OpenTelemetry::SemanticConventions::Resource::CLOUD_PLATFORM] = 'gcp_cloud_run'
|
|
245
|
+
attrs[OpenTelemetry::SemanticConventions::Resource::FAAS_NAME] = service
|
|
246
|
+
|
|
247
|
+
revision = ENV.fetch('K_REVISION', nil)
|
|
248
|
+
attrs[OpenTelemetry::SemanticConventions::Resource::FAAS_VERSION] = revision if revision
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
# Returns true when the current OS is Linux.
|
|
252
|
+
#
|
|
253
|
+
# @return [Boolean]
|
|
254
|
+
def linux?
|
|
255
|
+
detect_os_type == 'linux'
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
# Detect the OS type string per OTel semconv os.type values.
|
|
259
|
+
# Returns one of: "linux", "darwin", "windows", or the raw RbConfig string.
|
|
260
|
+
#
|
|
261
|
+
# @return [String]
|
|
262
|
+
def detect_os_type
|
|
263
|
+
raw = RbConfig::CONFIG['host_os'].to_s.downcase
|
|
264
|
+
case raw
|
|
265
|
+
when /linux/ then 'linux'
|
|
266
|
+
when /darwin/ then 'darwin'
|
|
267
|
+
when /mingw|mswin|cygwin/ then 'windows'
|
|
268
|
+
else raw
|
|
269
|
+
end
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
# Returns a stable machine-level identifier.
|
|
273
|
+
# Reads /etc/machine-id (Linux systemd standard) or
|
|
274
|
+
# /var/lib/dbus/machine-id as fallback. Returns nil on macOS/Windows.
|
|
275
|
+
#
|
|
276
|
+
# @return [String, nil]
|
|
277
|
+
def host_id
|
|
278
|
+
path = MACHINE_ID_PATHS.find { |machine_id_path| File.exist?(machine_id_path) }
|
|
279
|
+
return nil unless path
|
|
280
|
+
|
|
281
|
+
id = File.read(path).strip
|
|
282
|
+
return id unless id.empty?
|
|
283
|
+
|
|
284
|
+
nil
|
|
285
|
+
rescue StandardError
|
|
286
|
+
nil
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
# Parses a Lambda ARN and returns a hash of cloud.* resource attributes.
|
|
290
|
+
#
|
|
291
|
+
# @param arn [String] e.g. "arn:aws:lambda:us-east-1:123456789012:function:my-fn"
|
|
292
|
+
# @return [Hash]
|
|
293
|
+
def parse_lambda_arn(arn)
|
|
294
|
+
return {} if arn.nil? || arn.empty?
|
|
295
|
+
|
|
296
|
+
parts = arn.split(':')
|
|
297
|
+
result = {}
|
|
298
|
+
result[OpenTelemetry::SemanticConventions::Resource::CLOUD_REGION] = parts[3] if parts[3] && !parts[3].empty?
|
|
299
|
+
result[OpenTelemetry::SemanticConventions::Resource::CLOUD_ACCOUNT_ID] = parts[4] if parts[4] && !parts[4].empty?
|
|
300
|
+
result[CLOUD_RESOURCE_ID] = arn
|
|
301
|
+
result
|
|
302
|
+
rescue StandardError
|
|
303
|
+
{}
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
# Get hostname
|
|
307
|
+
#
|
|
308
|
+
# @return [String] Hostname
|
|
309
|
+
def hostname
|
|
310
|
+
Socket.gethostname
|
|
311
|
+
rescue StandardError
|
|
312
|
+
'unknown'
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
# Get host architecture
|
|
316
|
+
#
|
|
317
|
+
# @return [String] Host architecture
|
|
318
|
+
def host_architecture
|
|
319
|
+
RbConfig::CONFIG['host_cpu']
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
# Extract container ID from cgroup file
|
|
323
|
+
#
|
|
324
|
+
# @return [String, nil] Container ID
|
|
325
|
+
def extract_container_id
|
|
326
|
+
return nil unless File.exist?(PROC_SELF_CGROUP)
|
|
327
|
+
|
|
328
|
+
line = File.readlines(PROC_SELF_CGROUP).find do |l|
|
|
329
|
+
l.match?(%r{/docker/([a-f0-9]{64})})
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
return nil unless line
|
|
333
|
+
|
|
334
|
+
match = line.match(%r{/docker/([a-f0-9]{64})})
|
|
335
|
+
match[1]
|
|
336
|
+
rescue StandardError
|
|
337
|
+
nil
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
# Detect application version from various sources
|
|
341
|
+
#
|
|
342
|
+
# @return [String, nil] Application version
|
|
343
|
+
def detect_app_version
|
|
344
|
+
# Try to get version from Rails
|
|
345
|
+
if defined?(::Rails) && ::Rails.respond_to?(:application) && ::Rails.application
|
|
346
|
+
app_class = ::Rails.application.class
|
|
347
|
+
return app_class::VERSION if app_class.const_defined?(:VERSION)
|
|
348
|
+
end
|
|
349
|
+
|
|
350
|
+
# Try to get version from Gemfile.lock
|
|
351
|
+
if File.exist?('Gemfile.lock')
|
|
352
|
+
lockfile = File.read('Gemfile.lock')
|
|
353
|
+
# Look for the main gem version (first gem in the file)
|
|
354
|
+
match = lockfile.match(/^\s{4}(\S+)\s+\(([^)]+)\)/)
|
|
355
|
+
return match[2] if match
|
|
356
|
+
end
|
|
357
|
+
|
|
358
|
+
nil
|
|
359
|
+
rescue StandardError
|
|
360
|
+
nil
|
|
361
|
+
end
|
|
362
|
+
end
|
|
363
|
+
|
|
364
|
+
# @api private
|
|
365
|
+
# The constructor is private and only for use internally by the class.
|
|
366
|
+
# Users should use the {create} factory method to obtain a {Resource}
|
|
367
|
+
# instance.
|
|
368
|
+
#
|
|
369
|
+
# @param [Hash<String, String>] frozen_attributes Frozen-hash of frozen-string
|
|
370
|
+
# key-value pairs to be used as attributes for this resource
|
|
371
|
+
# @return [Resource]
|
|
372
|
+
def initialize(frozen_attributes)
|
|
373
|
+
@attributes = frozen_attributes
|
|
374
|
+
end
|
|
375
|
+
|
|
376
|
+
# Returns an enumerator for attributes of this {Resource}
|
|
377
|
+
#
|
|
378
|
+
# @return [Enumerator]
|
|
379
|
+
def attribute_enumerator
|
|
380
|
+
@attribute_enumerator ||= attributes.to_enum
|
|
381
|
+
end
|
|
382
|
+
|
|
383
|
+
# Returns a new, merged {Resource} by merging the current {Resource} with
|
|
384
|
+
# the other {Resource}. In case of a collision, the other {Resource}
|
|
385
|
+
# takes precedence
|
|
386
|
+
#
|
|
387
|
+
# @param [Resource] other The other resource to merge
|
|
388
|
+
# @return [Resource] A new resource formed by merging the current resource
|
|
389
|
+
# with other
|
|
390
|
+
def merge(other)
|
|
391
|
+
return self unless other.is_a?(Resource)
|
|
392
|
+
|
|
393
|
+
self.class.send(:new, attributes.merge(other.send(:attributes)).freeze)
|
|
394
|
+
end
|
|
395
|
+
|
|
396
|
+
# Returns the attributes hash for this resource
|
|
397
|
+
#
|
|
398
|
+
# @return [Hash] The frozen attributes hash
|
|
399
|
+
attr_reader :attributes
|
|
400
|
+
end
|
|
401
|
+
end
|
|
402
|
+
end
|
|
403
|
+
end
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# (c) Copyright IBM Corp. 2026
|
|
4
|
+
|
|
5
|
+
require_relative 'base_converter'
|
|
6
|
+
require 'opentelemetry/semconv/incubating/rpc'
|
|
7
|
+
require 'opentelemetry/semconv/incubating/code'
|
|
8
|
+
require 'opentelemetry/semconv/server'
|
|
9
|
+
|
|
10
|
+
module Instana
|
|
11
|
+
module Exporter
|
|
12
|
+
module Otlp
|
|
13
|
+
# Converter for RPC spans (gRPC, ActionCable) to OTLP format
|
|
14
|
+
class RpcConverter < BaseConverter
|
|
15
|
+
# Build OTel-compliant span name for RPC spans
|
|
16
|
+
#
|
|
17
|
+
# Formulas per SPAN_NAME_PATTERNS.txt Section 4:
|
|
18
|
+
# gRPC → "{package.Service/Method}" (leading "/" stripped per OTel spec)
|
|
19
|
+
# ActionCable → "{ChannelClass#action}" (call string used as-is)
|
|
20
|
+
#
|
|
21
|
+
# @return [String] The span name
|
|
22
|
+
def span_name
|
|
23
|
+
rpc_data = span[:data]&.[](:rpc) || {}
|
|
24
|
+
|
|
25
|
+
if rpc_data[:flavor] == :actioncable
|
|
26
|
+
rpc_data[:call].to_s
|
|
27
|
+
else
|
|
28
|
+
# Strip the mandatory leading slash per gRPC/OTel spec
|
|
29
|
+
rpc_data[:call].to_s.delete_prefix('/')
|
|
30
|
+
end.then { |n| n.empty? ? super : n }
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def convert_attributes
|
|
34
|
+
attributes = {}
|
|
35
|
+
|
|
36
|
+
rpc_data = span[:data]&.[](:rpc)
|
|
37
|
+
return attributes unless rpc_data
|
|
38
|
+
|
|
39
|
+
# Check if this is an ActionCable span
|
|
40
|
+
if rpc_data[:flavor] == :actioncable
|
|
41
|
+
convert_action_cable_attributes(attributes, rpc_data)
|
|
42
|
+
else
|
|
43
|
+
convert_grpc_attributes(attributes, rpc_data)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
attributes
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
private
|
|
50
|
+
|
|
51
|
+
# Convert gRPC span attributes
|
|
52
|
+
def convert_grpc_attributes(attributes, rpc_data)
|
|
53
|
+
# RPC system
|
|
54
|
+
add_attribute(attributes, OpenTelemetry::SemConv::Incubating::RPC::RPC_SYSTEM, 'grpc')
|
|
55
|
+
|
|
56
|
+
# RPC service and method
|
|
57
|
+
if rpc_data[:call]
|
|
58
|
+
service, method = parse_grpc_call(rpc_data[:call])
|
|
59
|
+
add_attribute(attributes, OpenTelemetry::SemConv::Incubating::RPC::RPC_SERVICE, service)
|
|
60
|
+
add_attribute(attributes, OpenTelemetry::SemConv::Incubating::RPC::RPC_METHOD, method)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Network peer
|
|
64
|
+
add_attribute(attributes, OpenTelemetry::SemConv::SERVER::SERVER_ADDRESS, rpc_data[:host])
|
|
65
|
+
add_attribute(attributes, OpenTelemetry::SemConv::SERVER::SERVER_ADDRESS, rpc_data.dig(:peer, :address))
|
|
66
|
+
|
|
67
|
+
# gRPC-specific attributes
|
|
68
|
+
add_attribute(attributes, 'rpc.grpc.call_type', rpc_data[:call_type])
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Convert ActionCable span attributes
|
|
72
|
+
def convert_action_cable_attributes(attributes, rpc_data)
|
|
73
|
+
# RPC system
|
|
74
|
+
add_attribute(attributes, OpenTelemetry::SemConv::Incubating::RPC::RPC_SYSTEM, 'actioncable')
|
|
75
|
+
|
|
76
|
+
# ActionCable-specific attributes
|
|
77
|
+
add_attribute(attributes, 'rails.actioncable.channel', rpc_data[:call])
|
|
78
|
+
add_attribute(attributes, 'rails.actioncable.call_type', rpc_data[:call_type])
|
|
79
|
+
add_attribute(attributes, OpenTelemetry::SemConv::Incubating::RPC::RPC_SERVICE, span[:data]&.[](:service) || span[:service])
|
|
80
|
+
|
|
81
|
+
# Extract channel class and action from the call attribute
|
|
82
|
+
# Format can be either "ChannelClass" (for transmit) or "ChannelClass#action" (for action dispatch)
|
|
83
|
+
if rpc_data[:call]
|
|
84
|
+
call_parts = rpc_data[:call].to_s.split('#')
|
|
85
|
+
add_attribute(attributes, OpenTelemetry::SemConv::Incubating::CODE::CODE_NAMESPACE, call_parts[0])
|
|
86
|
+
add_attribute(attributes, OpenTelemetry::SemConv::Incubating::CODE::CODE_FUNCTION, call_parts[1]) if call_parts[1]
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Network peer
|
|
90
|
+
add_attribute(attributes, OpenTelemetry::SemConv::SERVER::SERVER_ADDRESS, rpc_data[:host])
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def parse_grpc_call(call)
|
|
94
|
+
parts = call.to_s.split('/')
|
|
95
|
+
return [nil, nil] if parts.size < 3
|
|
96
|
+
|
|
97
|
+
[parts[1], parts[2]]
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
data/lib/instana/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: instana
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.
|
|
4
|
+
version: 2.8.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Peter Giacomo Lombardo
|
|
@@ -205,6 +205,34 @@ dependencies:
|
|
|
205
205
|
- - ">="
|
|
206
206
|
- !ruby/object:Gem::Version
|
|
207
207
|
version: '0'
|
|
208
|
+
- !ruby/object:Gem::Dependency
|
|
209
|
+
name: opentelemetry-semantic_conventions
|
|
210
|
+
requirement: !ruby/object:Gem::Requirement
|
|
211
|
+
requirements:
|
|
212
|
+
- - ">="
|
|
213
|
+
- !ruby/object:Gem::Version
|
|
214
|
+
version: '0'
|
|
215
|
+
type: :runtime
|
|
216
|
+
prerelease: false
|
|
217
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
218
|
+
requirements:
|
|
219
|
+
- - ">="
|
|
220
|
+
- !ruby/object:Gem::Version
|
|
221
|
+
version: '0'
|
|
222
|
+
- !ruby/object:Gem::Dependency
|
|
223
|
+
name: opentelemetry-exporter-otlp
|
|
224
|
+
requirement: !ruby/object:Gem::Requirement
|
|
225
|
+
requirements:
|
|
226
|
+
- - ">="
|
|
227
|
+
- !ruby/object:Gem::Version
|
|
228
|
+
version: '0'
|
|
229
|
+
type: :runtime
|
|
230
|
+
prerelease: false
|
|
231
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
232
|
+
requirements:
|
|
233
|
+
- - ">="
|
|
234
|
+
- !ruby/object:Gem::Version
|
|
235
|
+
version: '0'
|
|
208
236
|
- !ruby/object:Gem::Dependency
|
|
209
237
|
name: cgi
|
|
210
238
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -291,6 +319,18 @@ files:
|
|
|
291
319
|
- lib/instana/backend/serverless_agent.rb
|
|
292
320
|
- lib/instana/base.rb
|
|
293
321
|
- lib/instana/config.rb
|
|
322
|
+
- lib/instana/exporter/otlp/aws_converter.rb
|
|
323
|
+
- lib/instana/exporter/otlp/background_job_converter.rb
|
|
324
|
+
- lib/instana/exporter/otlp/base_converter.rb
|
|
325
|
+
- lib/instana/exporter/otlp/converter_factory.rb
|
|
326
|
+
- lib/instana/exporter/otlp/custom_converter.rb
|
|
327
|
+
- lib/instana/exporter/otlp/database_converter.rb
|
|
328
|
+
- lib/instana/exporter/otlp/graphql_converter.rb
|
|
329
|
+
- lib/instana/exporter/otlp/http_converter.rb
|
|
330
|
+
- lib/instana/exporter/otlp/messaging_converter.rb
|
|
331
|
+
- lib/instana/exporter/otlp/rails_converter.rb
|
|
332
|
+
- lib/instana/exporter/otlp/resource.rb
|
|
333
|
+
- lib/instana/exporter/otlp/rpc_converter.rb
|
|
294
334
|
- lib/instana/frameworks/cuba.rb
|
|
295
335
|
- lib/instana/frameworks/rails.rb
|
|
296
336
|
- lib/instana/frameworks/roda.rb
|
|
@@ -376,7 +416,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
376
416
|
- !ruby/object:Gem::Version
|
|
377
417
|
version: '0'
|
|
378
418
|
requirements: []
|
|
379
|
-
rubygems_version: 4.0.
|
|
419
|
+
rubygems_version: 4.0.18
|
|
380
420
|
specification_version: 4
|
|
381
421
|
summary: Ruby Distributed Tracing & Metrics Sensor for Instana
|
|
382
422
|
test_files: []
|