instana 1.197.0.pre1 → 1.199.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/lib/instana/backend/agent.rb +9 -1
  3. data/lib/instana/backend/host_agent.rb +27 -10
  4. data/lib/instana/backend/host_agent_activation_observer.rb +17 -7
  5. data/lib/instana/backend/request_client.rb +6 -16
  6. data/lib/instana/backend/serverless_agent.rb +13 -18
  7. data/lib/instana/base.rb +2 -0
  8. data/lib/instana/config.rb +1 -1
  9. data/lib/instana/instrumentation/excon.rb +7 -5
  10. data/lib/instana/instrumentation/instrumented_request.rb +62 -7
  11. data/lib/instana/instrumentation/net-http.rb +7 -5
  12. data/lib/instana/instrumentation/rack.rb +12 -7
  13. data/lib/instana/serverless.rb +139 -0
  14. data/lib/instana/setup.rb +5 -0
  15. data/lib/instana/snapshot/google_cloud_run_instance.rb +69 -0
  16. data/lib/instana/snapshot/google_cloud_run_process.rb +58 -0
  17. data/lib/instana/snapshot/lambda_function.rb +39 -0
  18. data/lib/instana/tracing/processor.rb +11 -1
  19. data/lib/instana/tracing/span.rb +10 -4
  20. data/lib/instana/tracing/span_context.rb +14 -9
  21. data/lib/instana/util.rb +4 -2
  22. data/lib/instana/version.rb +1 -1
  23. data/test/backend/agent_test.rb +26 -0
  24. data/test/backend/host_agent_activation_observer_test.rb +16 -9
  25. data/test/backend/host_agent_test.rb +17 -2
  26. data/test/backend/request_client_test.rb +0 -22
  27. data/test/instrumentation/rack_instrumented_request_test.rb +2 -0
  28. data/test/serverless_test.rb +323 -0
  29. data/test/snapshot/google_cloud_run_instance_test.rb +74 -0
  30. data/test/snapshot/google_cloud_run_process_test.rb +33 -0
  31. data/test/snapshot/lambda_function_test.rb +37 -0
  32. data/test/test_helper.rb +1 -1
  33. data/test/tracing/id_management_test.rb +4 -0
  34. data/test/tracing/span_context_test.rb +3 -3
  35. data/test/tracing/span_test.rb +9 -0
  36. metadata +16 -4
@@ -12,7 +12,6 @@ module Instana
12
12
 
13
13
  def call(env)
14
14
  req = InstrumentedRequest.new(env)
15
- return @app.call(env) if req.skip_trace?
16
15
  kvs = {
17
16
  http: req.request_tags,
18
17
  service: ENV['INSTANA_SERVICE_NAME']
@@ -34,12 +33,16 @@ module Instana
34
33
 
35
34
  if req.continuing_from_trace_parent?
36
35
  current_span[:tp] = true
37
- current_span[:lt] = req.incoming_context[:external_trace_id]
36
+ end
37
+
38
+ if req.external_trace_id?
39
+ current_span[:lt] = req.external_trace_id
38
40
  end
39
41
 
40
42
  if req.synthetic?
41
43
  current_span[:sy] = true
42
44
  end
45
+
43
46
  # In case some previous middleware returned a string status, make sure that we're dealing with
44
47
  # an integer. In Ruby nil.to_i, "asdfasdf".to_i will always return 0 from Ruby versions 1.8.7 and newer.
45
48
  # So if an 0 status is reported here, it indicates some other issue (e.g. no status from previous middleware)
@@ -70,15 +73,17 @@ module Instana
70
73
  if ::Instana.tracer.tracing?
71
74
  if headers
72
75
  # Set response headers; encode as hex string
73
- headers['X-Instana-T'] = trace_context.trace_id_header
74
- headers['X-Instana-S'] = trace_context.span_id_header
75
- headers['X-Instana-L'] = '1'
76
+ if trace_context.active?
77
+ headers['X-Instana-T'] = trace_context.trace_id_header
78
+ headers['X-Instana-S'] = trace_context.span_id_header
79
+ headers['X-Instana-L'] = '1'
76
80
 
77
- if ::Instana.config[:w3_trace_correlation]
78
- headers['Traceparent'] = trace_context.trace_parent_header
79
81
  headers['Tracestate'] = trace_context.trace_state_header
82
+ else
83
+ headers['X-Instana-L'] = '0'
80
84
  end
81
85
 
86
+ headers['Traceparent'] = trace_context.trace_parent_header
82
87
  headers['Server-Timing'] = "intid;desc=#{trace_context.trace_id_header}"
83
88
  end
84
89
 
@@ -0,0 +1,139 @@
1
+ # (c) Copyright IBM Corp. 2021
2
+ # (c) Copyright Instana Inc. 2021
3
+
4
+ require 'base64'
5
+ require 'zlib'
6
+
7
+ # :nocov:
8
+ begin
9
+ require 'instana/instrumentation/instrumented_request'
10
+ rescue LoadError => _e
11
+ Instana.logger.warn("Unable to Instana::InstrumentedRequest. HTTP based triggers won't generate spans.")
12
+ end
13
+ # :nocov:
14
+
15
+ module Instana
16
+ # @since 1.198.0
17
+ class Serverless
18
+ def initialize(agent: ::Instana.agent, tracer: ::Instana.tracer, logger: ::Instana.logger)
19
+ @agent = agent
20
+ @tracer = tracer
21
+ @logger = logger
22
+ end
23
+
24
+ def wrap_aws(event, context, &block)
25
+ Thread.current[:instana_function_arn] = [context.invoked_function_arn, context.function_version].join(':')
26
+ trigger, event_tags, span_context = trigger_from_event(event)
27
+
28
+ tags = {
29
+ lambda: {
30
+ arn: context.invoked_function_arn,
31
+ functionName: context.function_name,
32
+ functionVersion: context.function_version,
33
+ runtime: 'ruby',
34
+ trigger: trigger
35
+ }
36
+ }
37
+
38
+ if event_tags.key?(:http)
39
+ tags = tags.merge(event_tags)
40
+ else
41
+ tags[:lambda] = tags[:lambda].merge(event_tags)
42
+ end
43
+
44
+ @tracer.start_or_continue_trace(:'aws.lambda.entry', tags, span_context, &block)
45
+ ensure
46
+ begin
47
+ @agent.send_bundle
48
+ rescue StandardError => e
49
+ @logger.error(e.message)
50
+ end
51
+ Thread.current[:instana_function_arn] = nil
52
+ end
53
+
54
+ private
55
+
56
+ def trigger_from_event(event) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
57
+ case event
58
+ when ->(e) { defined?(::Instana::InstrumentedRequest) && e.is_a?(Hash) && e.key?('requestContext') && e['requestContext'].key?('elb') }
59
+ request = InstrumentedRequest.new(event_to_rack(event))
60
+ ['aws:application.load.balancer', {http: request.request_tags}, request.incoming_context]
61
+ when ->(e) { defined?(::Instana::InstrumentedRequest) && e.is_a?(Hash) && e.key?('httpMethod') && e.key?('path') && e.key?('headers') }
62
+ request = InstrumentedRequest.new(event_to_rack(event))
63
+ ['aws:api.gateway', {http: request.request_tags}, request.incoming_context]
64
+ when ->(e) { e.is_a?(Hash) && e['source'] == 'aws.events' && e['detail-type'] == 'Scheduled Event' }
65
+ tags = decode_cloudwatch_events(event)
66
+ ['aws:cloudwatch.events', {cw: tags}, {}]
67
+ when ->(e) { e.is_a?(Hash) && e.key?('awslogs') }
68
+ tags = decode_cloudwatch_logs(event)
69
+ ['aws:cloudwatch.logs', {cw: tags}, {}]
70
+ when ->(e) { e.is_a?(Hash) && e.key?('Records') && e['Records'].is_a?(Array) && e['Records'].first && e['Records'].first['source'] == 'aws:s3' }
71
+ tags = decode_s3(event)
72
+ ['aws:s3', {s3: tags}, {}]
73
+ when ->(e) { e.is_a?(Hash) && e.key?('Records') && e['Records'].is_a?(Array) && e['Records'].first && e['Records'].first['source'] == 'aws:sqs' }
74
+ tags = decode_sqs(event)
75
+ ['aws:sqs', {sqs: tags}, {}]
76
+ else
77
+ ['aws:api.gateway.noproxy', {}, {}]
78
+ end
79
+ end
80
+
81
+ def event_to_rack(event)
82
+ event['headers']
83
+ .transform_keys { |k| "HTTP_#{k.gsub('-', '_').upcase}" }
84
+ .merge(
85
+ 'QUERY_STRING' => URI.encode_www_form(event['queryStringParameters'] || {}),
86
+ 'PATH_INFO' => event['path'],
87
+ 'REQUEST_METHOD' => event['httpMethod']
88
+ )
89
+ end
90
+
91
+ def decode_cloudwatch_events(event)
92
+ {
93
+ events: {
94
+ id: event['id'],
95
+ resources: event['resources']
96
+ }
97
+ }
98
+ end
99
+
100
+ def decode_cloudwatch_logs(event)
101
+ logs = begin
102
+ payload = JSON.parse(Zlib::Inflate.inflate(Base64.decode64(event['awslogs']['data'])))
103
+
104
+ {
105
+ group: payload['logGroup'],
106
+ stream: payload['logStream']
107
+ }
108
+ rescue StandardError => e
109
+ {
110
+ decodingError: e.message
111
+ }
112
+ end
113
+
114
+ {logs: logs}
115
+ end
116
+
117
+ def decode_s3(event)
118
+ span_events = event['Records'].map do |record|
119
+ {
120
+ name: record['eventName'],
121
+ bucket: record['s3'] && record['s3']['bucket'] ? record['s3']['bucket']['name'] : nil,
122
+ object: record['s3'] && record['s3']['object'] ? record['s3']['object']['key'] : nil
123
+ }
124
+ end
125
+
126
+ {events: span_events}
127
+ end
128
+
129
+ def decode_sqs(event)
130
+ span_events = event['Records'].map do |record|
131
+ {
132
+ queue: record['eventSourceARN']
133
+ }
134
+ end
135
+
136
+ {messages: span_events}
137
+ end
138
+ end
139
+ end
data/lib/instana/setup.rb CHANGED
@@ -9,6 +9,8 @@ require "instana/secrets"
9
9
  require "instana/tracer"
10
10
  require "instana/tracing/processor"
11
11
 
12
+ require 'instana/serverless'
13
+
12
14
  require 'instana/activator'
13
15
 
14
16
  require 'instana/backend/request_client'
@@ -21,6 +23,9 @@ require 'instana/snapshot/fargate_process'
21
23
  require 'instana/snapshot/fargate_task'
22
24
  require 'instana/snapshot/fargate_container'
23
25
  require 'instana/snapshot/docker_container'
26
+ require 'instana/snapshot/lambda_function'
27
+ require 'instana/snapshot/google_cloud_run_instance'
28
+ require 'instana/snapshot/google_cloud_run_process'
24
29
 
25
30
  require 'instana/backend/host_agent_lookup'
26
31
  require 'instana/backend/host_agent_activation_observer'
@@ -0,0 +1,69 @@
1
+ # (c) Copyright IBM Corp. 2021
2
+ # (c) Copyright Instana Inc. 2021
3
+
4
+ module Instana
5
+ module Snapshot
6
+ # @since 1.199
7
+ class GoogleCloudRunInstance
8
+ ID = 'com.instana.plugin.gcp.run.revision.instance'.freeze
9
+
10
+ def initialize(metadata_uri: 'http://metadata.google.internal')
11
+ @metadata_uri = URI(metadata_uri)
12
+ @client = Backend::RequestClient.new(@metadata_uri.host, @metadata_uri.port, use_ssl: @metadata_uri.scheme == "https")
13
+ end
14
+
15
+ def entity_id
16
+ lookup('/computeMetadata/v1/instance/id')
17
+ end
18
+
19
+ def data
20
+ {
21
+ runtime: 'ruby',
22
+ region: gcp_region,
23
+ service: ENV['K_SERVICE'],
24
+ configuration: ENV['K_CONFIGURATION'],
25
+ revision: ENV['K_REVISION'],
26
+ instanceId: entity_id,
27
+ port: ENV['PORT'],
28
+ numericProjectId: lookup('/computeMetadata/v1/project/numeric-project-id'),
29
+ projectId: lookup('/computeMetadata/v1/project/project-id')
30
+ }.compact
31
+ end
32
+
33
+ def snapshot
34
+ {
35
+ name: ID,
36
+ entityId: entity_id,
37
+ data: data
38
+ }
39
+ end
40
+
41
+ def source
42
+ {
43
+ hl: true,
44
+ cp: 'gcp',
45
+ e: entity_id
46
+ }
47
+ end
48
+
49
+ def host_name
50
+ "gcp:cloud-run:revision:#{ENV['K_REVISION']}"
51
+ end
52
+
53
+ private
54
+
55
+ def gcp_region
56
+ lookup('/computeMetadata/v1/instance/zone').split('/').last
57
+ end
58
+
59
+ def lookup(resource)
60
+ path = @metadata_uri.path + resource
61
+ response = @client.send_request('GET', path, nil, {'Metadata-Flavor' => 'Google'})
62
+
63
+ raise "Unable to get `#{path}`. Got `#{response.code}` `#{response['location']}`." unless response.ok?
64
+
65
+ response.body
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,58 @@
1
+ # (c) Copyright IBM Corp. 2021
2
+ # (c) Copyright Instana Inc. 2021
3
+
4
+ module Instana
5
+ module Snapshot
6
+ # @since 1.199.0
7
+ class GoogleCloudRunProcess
8
+ ID = 'com.instana.plugin.process'.freeze
9
+
10
+ def initialize(metadata_uri: 'http://metadata.google.internal')
11
+ @metadata_uri = URI(metadata_uri)
12
+ @client = Backend::RequestClient.new(@metadata_uri.host, @metadata_uri.port, use_ssl: @metadata_uri.scheme == "https")
13
+ @start_time = Time.now
14
+ end
15
+
16
+ def entity_id
17
+ Process.pid.to_s
18
+ end
19
+
20
+ def data
21
+ proc_table = Sys::ProcTable.ps(pid: Process.pid)
22
+ process = Backend::ProcessInfo.new(proc_table)
23
+
24
+ {
25
+ pid: process.pid.to_i,
26
+ env: ENV.to_h,
27
+ exec: process.name,
28
+ args: process.arguments,
29
+ user: process.uid,
30
+ group: process.gid,
31
+ start: @start_time.to_i * 1000,
32
+ containerType: 'gcpCloudRunInstance',
33
+ container: lookup('/computeMetadata/v1/instance/id'),
34
+ "com.instana.plugin.host.name": "gcp:cloud-run:revision:#{ENV['K_REVISION']}"
35
+ }
36
+ end
37
+
38
+ def snapshot
39
+ {
40
+ name: ID,
41
+ entityId: entity_id,
42
+ data: data
43
+ }
44
+ end
45
+
46
+ private
47
+
48
+ def lookup(resource)
49
+ path = @metadata_uri.path + resource
50
+ response = @client.send_request('GET', path, nil, {'Metadata-Flavor' => 'Google'})
51
+
52
+ raise "Unable to get `#{path}`. Got `#{response.code}` `#{response['location']}`." unless response.ok?
53
+
54
+ response.body
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,39 @@
1
+ # (c) Copyright IBM Corp. 2021
2
+ # (c) Copyright Instana Inc. 2021
3
+
4
+ module Instana
5
+ module Snapshot
6
+ # @since 1.198.0
7
+ class LambdaFunction
8
+ ID = "com.instana.plugin.aws.lambda".freeze
9
+
10
+ def entity_id
11
+ Thread.current[:instana_function_arn]
12
+ end
13
+
14
+ def data
15
+ {}
16
+ end
17
+
18
+ def snapshot
19
+ {
20
+ name: ID,
21
+ entityId: entity_id,
22
+ data: data
23
+ }
24
+ end
25
+
26
+ def source
27
+ {
28
+ hl: true,
29
+ cp: "aws",
30
+ e: entity_id
31
+ }
32
+ end
33
+
34
+ def host_name
35
+ entity_id
36
+ end
37
+ end
38
+ end
39
+ end
@@ -5,7 +5,7 @@ require 'thread'
5
5
 
6
6
  module Instana
7
7
  class Processor
8
- def initialize
8
+ def initialize(logger: ::Instana.logger)
9
9
  # The main queue before being reported to the
10
10
  # host agent. Spans in this queue are complete
11
11
  # and ready to be sent.
@@ -14,12 +14,22 @@ module Instana
14
14
  # This is the maximum number of spans we send to the host
15
15
  # agent at once.
16
16
  @batch_size = 3000
17
+ @logger = logger
18
+ @pid = $PROCESS_ID
17
19
  end
18
20
 
19
21
  # Adds a span to the span queue
20
22
  #
21
23
  # @param [Trace] - the trace to be added to the queue
22
24
  def add_span(span)
25
+ # :nocov:
26
+ if @pid != $PROCESS_ID
27
+ @logger.info("Proces `#{@pid}` has forked into #{$PROCESS_ID}. Resetting discovery.")
28
+ ::Instana.agent.spawn_background_thread
29
+ @pid = $PROCESS_ID
30
+ end
31
+ # :nocov:
32
+
23
33
  @queue.push(span)
24
34
  end
25
35
 
@@ -6,8 +6,8 @@ module Instana
6
6
  REGISTERED_SPANS = [ :actioncontroller, :actionview, :activerecord, :excon,
7
7
  :memcache, :'net-http', :rack, :render, :'rpc-client',
8
8
  :'rpc-server', :'sidekiq-client', :'sidekiq-worker',
9
- :redis, :'resque-client', :'resque-worker', :'graphql.server', :dynamodb, :s3, :sns, :sqs ].freeze
10
- ENTRY_SPANS = [ :rack, :'resque-worker', :'rpc-server', :'sidekiq-worker', :'graphql.server', :sqs ].freeze
9
+ :redis, :'resque-client', :'resque-worker', :'graphql.server', :dynamodb, :s3, :sns, :sqs, :'aws.lambda.entry' ].freeze
10
+ ENTRY_SPANS = [ :rack, :'resque-worker', :'rpc-server', :'sidekiq-worker', :'graphql.server', :sqs, :'aws.lambda.entry' ].freeze
11
11
  EXIT_SPANS = [ :activerecord, :excon, :'net-http', :'resque-client',
12
12
  :'rpc-client', :'sidekiq-client', :redis, :dynamodb, :s3, :sns, :sqs ].freeze
13
13
  HTTP_SPANS = [ :rack, :excon, :'net-http' ].freeze
@@ -28,9 +28,15 @@ module Instana
28
28
  if parent_ctx.is_a?(::Instana::SpanContext)
29
29
  @is_root = false
30
30
 
31
- @data[:t] = parent_ctx.trace_id # Trace ID
31
+ # If we have a parent trace, link to it
32
+ if parent_ctx.trace_id
33
+ @data[:t] = parent_ctx.trace_id # Trace ID
34
+ @data[:p] = parent_ctx.span_id # Parent ID
35
+ else
36
+ @data[:t] = ::Instana::Util.generate_id
37
+ end
38
+
32
39
  @data[:s] = ::Instana::Util.generate_id # Span ID
33
- @data[:p] = parent_ctx.span_id # Parent ID
34
40
 
35
41
  @baggage = parent_ctx.baggage.dup
36
42
  @level = parent_ctx.level
@@ -31,19 +31,22 @@ module Instana
31
31
  end
32
32
 
33
33
  def trace_parent_header
34
- return '' unless valid?
35
-
36
- trace = (@baggage[:external_trace_id] || @trace_id).rjust(32, '0')
37
- parent = @span_id.rjust(16, '0')
34
+ trace = (@baggage[:external_trace_id] || trace_id_header).rjust(32, '0')
35
+ parent = span_id_header.rjust(16, '0')
38
36
  flags = @level == 1 ? "01" : "00"
39
37
 
40
38
  "00-#{trace}-#{parent}-#{flags}"
41
39
  end
42
40
 
43
41
  def trace_state_header
44
- return '' unless valid?
42
+ external_state = @baggage[:external_state] || ''
43
+ state = external_state.split(/,/)
44
+
45
+ if @level == 1
46
+ state = state.reject { |s| s.start_with?('in=') }
47
+ state.unshift("in=#{trace_id_header};#{span_id_header}")
48
+ end
45
49
 
46
- state = ["in=#{trace_id_header};#{span_id_header}", @baggage[:external_state]]
47
50
  state.compact.join(',')
48
51
  end
49
52
 
@@ -51,10 +54,12 @@ module Instana
51
54
  { :trace_id => @trace_id, :span_id => @span_id }
52
55
  end
53
56
 
54
- private
55
-
56
57
  def valid?
57
- @baggage && @trace_id && @span_id
58
+ @baggage && @trace_id && !@trace_id.emtpy?
59
+ end
60
+
61
+ def active?
62
+ @level == 1
58
63
  end
59
64
  end
60
65
  end