opentelemetry-instrumentation-aws_lambda 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8aaeb5ccdef7fdcbffb476fb16c62fb5cbed347344baa912a6d2ea196a10bad6
4
- data.tar.gz: 958208610a3e8d6afa1c4a7f6513508f8ccfb54d9e62e7ad9bd7fd709d59e0c0
3
+ metadata.gz: dc0776c77bfd96bef38c29a85b8e80ac9ab58edf35791e21c65f726637944155
4
+ data.tar.gz: 644962009b15fa624dc14ce58bad2ba61448a0a538d7d76be6b0900663ab8dac
5
5
  SHA512:
6
- metadata.gz: 6a9a8e52b61b6594849e64e1f390fa91cb7736d04fc2cfab39665307336caf1e9bce6445586ba6ec5cf7e876cbe94fd478bd9688acfc8347d52e6ff8eb3ec91f
7
- data.tar.gz: 04c2dac7e2c15bee49aaf262082dd9695b91cf69ea02e0c6d3496f01224f606ef3100e48924a8030bc22b235a4c1fa2016754a3a2d150db2317ebfa8344c0f67
6
+ metadata.gz: d3a6e698fa06ff9bb8af23f95ef1dc17d20f769c2826e2a134a280ebcb9e4958b264e9c4e0bc90b2bb8763df8305f40cb61a102fe1e563e9d85a77b0c29ca710
7
+ data.tar.gz: fab7d0e891d5d660af842cf76cff130093de8b90cd87c354954306ed6081ba6c77cca32b1e83d23fb655379bc69316c873908c48c3327547000a39171e75e7a4
data/CHANGELOG.md CHANGED
@@ -1,5 +1,10 @@
1
1
  # Release History: opentelemetry-instrumentation-aws_lambda
2
2
 
3
+ ### v0.3.0 / 2025-02-04
4
+
5
+ * ADDED: AWS Lambda programmatic wrap
6
+ * FIXED: AWS Lambda test fix
7
+
3
8
  ### v0.2.0 / 2025-01-16
4
9
 
5
10
  * BREAKING CHANGE: Set minimum supported version to Ruby 3.1
data/README.md CHANGED
@@ -28,6 +28,32 @@ def otel_wrapper(event:, context:)
28
28
  end
29
29
  ```
30
30
 
31
+ ### Alternative Usage
32
+
33
+ If using a Lambda Layer is not an option for your given setup, you can programmatically instrument a handler by using the `OpenTelemetry::Instrumentation::AwsLambda::Wrap` module.
34
+
35
+ ```ruby
36
+ require 'opentelemetry/sdk'
37
+ require 'opentelemetry/instrumentation/aws_lambda'
38
+
39
+ OpenTelemetry::SDK.configure do |c|
40
+ c.service_name = '<YOUR_SERVICE_NAME>'
41
+ c.use 'OpenTelemetry::Instrumentation::AwsLambda'
42
+ end
43
+
44
+ # Lambda Handler
45
+ module Example
46
+ class Handler
47
+ extend OpenTelemetry::Instrumentation::AwsLambda::Wrap
48
+
49
+ def self.process(event:, context:)
50
+ puts event.inspect
51
+ end
52
+ instrument_handler :process
53
+ end
54
+ end
55
+ ```
56
+
31
57
  ## Example
32
58
 
33
59
  To run the example:
@@ -7,10 +7,10 @@
7
7
  module OpenTelemetry
8
8
  module Instrumentation
9
9
  module AwsLambda
10
- AWS_TRIGGERS = ['aws:sqs', 'aws:s3', 'aws:sns', 'aws:dynamodb'].freeze
11
-
12
10
  # Handler class that creates a span around the _HANDLER
13
11
  class Handler
12
+ extend OpenTelemetry::Instrumentation::AwsLambda::Wrap
13
+
14
14
  attr_reader :handler_method, :handler_class
15
15
 
16
16
  # anytime when the code in a Lambda function is updated or the functional configuration is changed,
@@ -28,47 +28,9 @@ module OpenTelemetry
28
28
  # Try to record and re-raise any exception from the wrapped function handler
29
29
  # Instrumentation should never raise its own exception
30
30
  def call_wrapped(event:, context:)
31
- parent_context = extract_parent_context(event)
32
-
33
- span_kind = if event['Records'] && AWS_TRIGGERS.include?(event['Records'].dig(0, 'eventSource'))
34
- :consumer
35
- else
36
- :server
37
- end
38
-
39
- original_handler_error = nil
40
- original_response = nil
41
- OpenTelemetry::Context.with_current(parent_context) do
42
- tracer.in_span(@original_handler, attributes: otel_attributes(event, context), kind: span_kind) do |span|
43
- begin
44
- response = call_original_handler(event: event, context: context)
45
- status_code = response['statusCode'] || response[:statusCode] if response.is_a?(Hash)
46
- span.set_attribute(OpenTelemetry::SemanticConventions::Trace::HTTP_STATUS_CODE, status_code) if status_code
47
- rescue StandardError => e
48
- original_handler_error = e
49
- ensure
50
- original_response = response
51
- end
52
- if original_handler_error
53
- span.record_exception(original_handler_error)
54
- span.status = OpenTelemetry::Trace::Status.error(original_handler_error.message)
55
- end
56
- end
31
+ self.class.wrap_lambda(event: event, context: context, handler: @original_handler, flush_timeout: @flush_timeout) do
32
+ call_original_handler(event: event, context: context)
57
33
  end
58
-
59
- OpenTelemetry.tracer_provider.force_flush(timeout: @flush_timeout)
60
-
61
- raise original_handler_error if original_handler_error
62
-
63
- original_response
64
- end
65
-
66
- def instrumentation_config
67
- AwsLambda::Instrumentation.instance.config
68
- end
69
-
70
- def tracer
71
- AwsLambda::Instrumentation.instance.tracer
72
34
  end
73
35
 
74
36
  private
@@ -94,95 +56,6 @@ module OpenTelemetry
94
56
  __send__(@handler_method, event: event, context: context)
95
57
  end
96
58
  end
97
-
98
- # Extract parent context from request headers
99
- # Downcase Traceparent and Tracestate because TraceContext::TextMapPropagator's TRACEPARENT_KEY and TRACESTATE_KEY are all lowercase
100
- # If any error occur, rescue and give empty context
101
- def extract_parent_context(event)
102
- headers = event['headers'] || {}
103
- headers.transform_keys! do |key|
104
- %w[Traceparent Tracestate].include?(key) ? key.downcase : key
105
- end
106
-
107
- OpenTelemetry.propagation.extract(
108
- headers,
109
- getter: OpenTelemetry::Context::Propagation.text_map_getter
110
- )
111
- rescue StandardError => e
112
- OpenTelemetry.logger.error("aws-lambda instrumentation exception occurred while extracting the parent context: #{e.message}")
113
- OpenTelemetry::Context.empty
114
- end
115
-
116
- # lambda event version 1.0 and version 2.0
117
- # https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html
118
- def v1_proxy_attributes(event)
119
- attributes = {
120
- OpenTelemetry::SemanticConventions::Trace::HTTP_METHOD => event['httpMethod'],
121
- OpenTelemetry::SemanticConventions::Trace::HTTP_ROUTE => event['resource'],
122
- OpenTelemetry::SemanticConventions::Trace::HTTP_TARGET => event['resource']
123
- }
124
- attributes[OpenTelemetry::SemanticConventions::Trace::HTTP_TARGET] += "?#{event['queryStringParameters']}" if event['queryStringParameters']
125
-
126
- headers = event['headers']
127
- if headers
128
- attributes[OpenTelemetry::SemanticConventions::Trace::HTTP_USER_AGENT] = headers['User-Agent']
129
- attributes[OpenTelemetry::SemanticConventions::Trace::HTTP_SCHEME] = headers['X-Forwarded-Proto']
130
- attributes[OpenTelemetry::SemanticConventions::Trace::NET_HOST_NAME] = headers['Host']
131
- end
132
- attributes
133
- end
134
-
135
- def v2_proxy_attributes(event)
136
- request_context = event['requestContext']
137
- attributes = {
138
- OpenTelemetry::SemanticConventions::Trace::NET_HOST_NAME => request_context['domainName'],
139
- OpenTelemetry::SemanticConventions::Trace::HTTP_METHOD => request_context['http']['method'],
140
- OpenTelemetry::SemanticConventions::Trace::HTTP_USER_AGENT => request_context['http']['userAgent'],
141
- OpenTelemetry::SemanticConventions::Trace::HTTP_ROUTE => request_context['http']['path'],
142
- OpenTelemetry::SemanticConventions::Trace::HTTP_TARGET => request_context['http']['path']
143
- }
144
- attributes[OpenTelemetry::SemanticConventions::Trace::HTTP_TARGET] += "?#{event['rawQueryString']}" if event['rawQueryString']
145
- attributes
146
- end
147
-
148
- # fass.trigger set to http: https://github.com/open-telemetry/semantic-conventions/blob/main/docs/faas/aws-lambda.md#api-gateway
149
- # TODO: need to update Semantic Conventions for invocation_id, trigger and resource_id
150
- def otel_attributes(event, context)
151
- span_attributes = {}
152
- span_attributes['faas.invocation_id'] = context.aws_request_id
153
- span_attributes['cloud.resource_id'] = context.invoked_function_arn
154
- span_attributes[OpenTelemetry::SemanticConventions::Trace::AWS_LAMBDA_INVOKED_ARN] = context.invoked_function_arn
155
- span_attributes[OpenTelemetry::SemanticConventions::Resource::CLOUD_ACCOUNT_ID] = context.invoked_function_arn.split(':')[4]
156
-
157
- if event['requestContext']
158
- request_attributes = event['version'] == '2.0' ? v2_proxy_attributes(event) : v1_proxy_attributes(event)
159
- request_attributes[OpenTelemetry::SemanticConventions::Trace::FAAS_TRIGGER] = 'http'
160
- span_attributes.merge!(request_attributes)
161
- end
162
-
163
- if event['Records']
164
- trigger_attributes = trigger_type_attributes(event)
165
- span_attributes.merge!(trigger_attributes)
166
- end
167
-
168
- span_attributes
169
- rescue StandardError => e
170
- OpenTelemetry.logger.error("aws-lambda instrumentation exception occurred while preparing span attributes: #{e.message}")
171
- {}
172
- end
173
-
174
- # sqs spec for lambda: https://github.com/open-telemetry/semantic-conventions/blob/main/docs/faas/aws-lambda.md#sqs
175
- # current there is no spec for 'aws:sns', 'aws:s3' and 'aws:dynamodb'
176
- def trigger_type_attributes(event)
177
- attributes = {}
178
- case event['Records'].dig(0, 'eventSource')
179
- when 'aws:sqs'
180
- attributes[OpenTelemetry::SemanticConventions::Trace::FAAS_TRIGGER] = 'pubsub'
181
- attributes[OpenTelemetry::SemanticConventions::Trace::MESSAGING_OPERATION] = 'process'
182
- attributes[OpenTelemetry::SemanticConventions::Trace::MESSAGING_SYSTEM] = 'AmazonSQS'
183
- end
184
- attributes
185
- end
186
59
  end
187
60
  end
188
61
  end
@@ -21,6 +21,7 @@ module OpenTelemetry
21
21
  private
22
22
 
23
23
  def require_dependencies
24
+ require_relative 'wrap'
24
25
  require_relative 'handler'
25
26
  end
26
27
  end
@@ -7,7 +7,7 @@
7
7
  module OpenTelemetry
8
8
  module Instrumentation
9
9
  module AwsLambda
10
- VERSION = '0.2.0'
10
+ VERSION = '0.3.0'
11
11
  end
12
12
  end
13
13
  end
@@ -0,0 +1,171 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright The OpenTelemetry Authors
4
+ #
5
+ # SPDX-License-Identifier: Apache-2.0
6
+
7
+ module OpenTelemetry
8
+ module Instrumentation
9
+ module AwsLambda
10
+ # Helper module that can be used to wrap a lambda handler method
11
+ module Wrap # rubocop:disable Metrics/ModuleLength
12
+ AWS_TRIGGERS = ['aws:sqs', 'aws:s3', 'aws:sns', 'aws:dynamodb'].freeze
13
+ DEFAULT_FLUSH_TIMEOUT = ENV.fetch('OTEL_INSTRUMENTATION_AWS_LAMBDA_FLUSH_TIMEOUT', '30000').to_i
14
+
15
+ def instrument_handler(method, flush_timeout: DEFAULT_FLUSH_TIMEOUT)
16
+ raise ArgumentError, "#{method} is not a method of #{name}" unless respond_to?(method)
17
+
18
+ uninstrumented_method = "#{method}_without_instrumentation"
19
+ singleton_class.alias_method uninstrumented_method, method
20
+
21
+ handler = "#{name}.#{method}"
22
+
23
+ define_singleton_method(method) do |event:, context:|
24
+ wrap_lambda(event: event, context: context, handler: handler, flush_timeout: flush_timeout) { public_send(uninstrumented_method, event: event, context: context) }
25
+ end
26
+ end
27
+
28
+ # Try to record and re-raise any exception from the wrapped function handler
29
+ # Instrumentation should never raise its own exception
30
+ def wrap_lambda(event:, context:, handler:, flush_timeout: DEFAULT_FLUSH_TIMEOUT)
31
+ parent_context = extract_parent_context(event)
32
+
33
+ span_kind = if event['Records'] && AWS_TRIGGERS.include?(event['Records'].dig(0, 'eventSource'))
34
+ :consumer
35
+ else
36
+ :server
37
+ end
38
+
39
+ original_handler_error = nil
40
+ original_response = nil
41
+ OpenTelemetry::Context.with_current(parent_context) do
42
+ tracer.in_span(handler, attributes: otel_attributes(event, context), kind: span_kind) do |span|
43
+ begin
44
+ response = yield
45
+
46
+ unless span.attributes.key?(OpenTelemetry::SemanticConventions::Trace::HTTP_STATUS_CODE)
47
+ status_code = response['statusCode'] || response[:statusCode] if response.is_a?(Hash)
48
+ span.set_attribute(OpenTelemetry::SemanticConventions::Trace::HTTP_STATUS_CODE, status_code) if status_code
49
+ end
50
+ rescue StandardError => e
51
+ original_handler_error = e
52
+ ensure
53
+ original_response = response
54
+ end
55
+ if original_handler_error
56
+ span.record_exception(original_handler_error)
57
+ span.status = OpenTelemetry::Trace::Status.error(original_handler_error.message)
58
+ end
59
+ end
60
+ end
61
+
62
+ OpenTelemetry.tracer_provider.force_flush(timeout: flush_timeout)
63
+ OpenTelemetry.meter_provider.force_flush(timeout: flush_timeout) if OpenTelemetry.respond_to?(:meter_provider)
64
+
65
+ raise original_handler_error if original_handler_error
66
+
67
+ original_response
68
+ end
69
+
70
+ def instrumentation_config
71
+ AwsLambda::Instrumentation.instance.config
72
+ end
73
+
74
+ def tracer
75
+ AwsLambda::Instrumentation.instance.tracer
76
+ end
77
+
78
+ private
79
+
80
+ # Extract parent context from request headers
81
+ # Downcase Traceparent and Tracestate because TraceContext::TextMapPropagator's TRACEPARENT_KEY and TRACESTATE_KEY are all lowercase
82
+ # If any error occur, rescue and give empty context
83
+ def extract_parent_context(event)
84
+ headers = event['headers'] || {}
85
+ headers.transform_keys! do |key|
86
+ %w[Traceparent Tracestate].include?(key) ? key.downcase : key
87
+ end
88
+
89
+ OpenTelemetry.propagation.extract(
90
+ headers,
91
+ getter: OpenTelemetry::Context::Propagation.text_map_getter
92
+ )
93
+ rescue StandardError => e
94
+ OpenTelemetry.logger.error("aws-lambda instrumentation exception occurred while extracting the parent context: #{e.message}")
95
+ OpenTelemetry::Context.empty
96
+ end
97
+
98
+ # lambda event version 1.0 and version 2.0
99
+ # https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html
100
+ def v1_proxy_attributes(event)
101
+ attributes = {
102
+ OpenTelemetry::SemanticConventions::Trace::HTTP_METHOD => event['httpMethod'],
103
+ OpenTelemetry::SemanticConventions::Trace::HTTP_ROUTE => event['resource'],
104
+ OpenTelemetry::SemanticConventions::Trace::HTTP_TARGET => event['resource']
105
+ }
106
+ attributes[OpenTelemetry::SemanticConventions::Trace::HTTP_TARGET] += "?#{event['queryStringParameters']}" if event['queryStringParameters']
107
+
108
+ headers = event['headers']
109
+ if headers
110
+ attributes[OpenTelemetry::SemanticConventions::Trace::HTTP_USER_AGENT] = headers['User-Agent']
111
+ attributes[OpenTelemetry::SemanticConventions::Trace::HTTP_SCHEME] = headers['X-Forwarded-Proto']
112
+ attributes[OpenTelemetry::SemanticConventions::Trace::NET_HOST_NAME] = headers['Host']
113
+ end
114
+ attributes
115
+ end
116
+
117
+ def v2_proxy_attributes(event)
118
+ request_context = event['requestContext']
119
+ attributes = {
120
+ OpenTelemetry::SemanticConventions::Trace::NET_HOST_NAME => request_context['domainName'],
121
+ OpenTelemetry::SemanticConventions::Trace::HTTP_METHOD => request_context['http']['method'],
122
+ OpenTelemetry::SemanticConventions::Trace::HTTP_USER_AGENT => request_context['http']['userAgent'],
123
+ OpenTelemetry::SemanticConventions::Trace::HTTP_ROUTE => request_context['http']['path'],
124
+ OpenTelemetry::SemanticConventions::Trace::HTTP_TARGET => request_context['http']['path']
125
+ }
126
+ attributes[OpenTelemetry::SemanticConventions::Trace::HTTP_TARGET] += "?#{event['rawQueryString']}" if event['rawQueryString']
127
+ attributes
128
+ end
129
+
130
+ # fass.trigger set to http: https://github.com/open-telemetry/semantic-conventions/blob/main/docs/faas/aws-lambda.md#api-gateway
131
+ # TODO: need to update Semantic Conventions for invocation_id, trigger and resource_id
132
+ def otel_attributes(event, context)
133
+ span_attributes = {}
134
+ span_attributes['faas.invocation_id'] = context.aws_request_id
135
+ span_attributes['cloud.resource_id'] = context.invoked_function_arn
136
+ span_attributes[OpenTelemetry::SemanticConventions::Trace::AWS_LAMBDA_INVOKED_ARN] = context.invoked_function_arn
137
+ span_attributes[OpenTelemetry::SemanticConventions::Resource::CLOUD_ACCOUNT_ID] = context.invoked_function_arn.split(':')[4]
138
+
139
+ if event['requestContext']
140
+ request_attributes = event['version'] == '2.0' ? v2_proxy_attributes(event) : v1_proxy_attributes(event)
141
+ request_attributes[OpenTelemetry::SemanticConventions::Trace::FAAS_TRIGGER] = 'http'
142
+ span_attributes.merge!(request_attributes)
143
+ end
144
+
145
+ if event['Records']
146
+ trigger_attributes = trigger_type_attributes(event)
147
+ span_attributes.merge!(trigger_attributes)
148
+ end
149
+
150
+ span_attributes
151
+ rescue StandardError => e
152
+ OpenTelemetry.logger.error("aws-lambda instrumentation exception occurred while preparing span attributes: #{e.message}")
153
+ {}
154
+ end
155
+
156
+ # sqs spec for lambda: https://github.com/open-telemetry/semantic-conventions/blob/main/docs/faas/aws-lambda.md#sqs
157
+ # current there is no spec for 'aws:sns', 'aws:s3' and 'aws:dynamodb'
158
+ def trigger_type_attributes(event)
159
+ attributes = {}
160
+ case event['Records'].dig(0, 'eventSource')
161
+ when 'aws:sqs'
162
+ attributes[OpenTelemetry::SemanticConventions::Trace::FAAS_TRIGGER] = 'pubsub'
163
+ attributes[OpenTelemetry::SemanticConventions::Trace::MESSAGING_OPERATION] = 'process'
164
+ attributes[OpenTelemetry::SemanticConventions::Trace::MESSAGING_SYSTEM] = 'AmazonSQS'
165
+ end
166
+ attributes
167
+ end
168
+ end
169
+ end
170
+ end
171
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: opentelemetry-instrumentation-aws_lambda
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - OpenTelemetry Authors
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-01-16 00:00:00.000000000 Z
11
+ date: 2025-02-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: opentelemetry-api
@@ -156,14 +156,14 @@ dependencies:
156
156
  requirements:
157
157
  - - "~>"
158
158
  - !ruby/object:Gem::Version
159
- version: 1.69.1
159
+ version: 1.71.0
160
160
  type: :development
161
161
  prerelease: false
162
162
  version_requirements: !ruby/object:Gem::Requirement
163
163
  requirements:
164
164
  - - "~>"
165
165
  - !ruby/object:Gem::Version
166
- version: 1.69.1
166
+ version: 1.71.0
167
167
  - !ruby/object:Gem::Dependency
168
168
  name: rubocop-performance
169
169
  requirement: !ruby/object:Gem::Requirement
@@ -184,14 +184,14 @@ dependencies:
184
184
  requirements:
185
185
  - - "~>"
186
186
  - !ruby/object:Gem::Version
187
- version: 0.17.1
187
+ version: 0.22.0
188
188
  type: :development
189
189
  prerelease: false
190
190
  version_requirements: !ruby/object:Gem::Requirement
191
191
  requirements:
192
192
  - - "~>"
193
193
  - !ruby/object:Gem::Version
194
- version: 0.17.1
194
+ version: 0.22.0
195
195
  - !ruby/object:Gem::Dependency
196
196
  name: webmock
197
197
  requirement: !ruby/object:Gem::Requirement
@@ -237,14 +237,15 @@ files:
237
237
  - lib/opentelemetry/instrumentation/aws_lambda/handler.rb
238
238
  - lib/opentelemetry/instrumentation/aws_lambda/instrumentation.rb
239
239
  - lib/opentelemetry/instrumentation/aws_lambda/version.rb
240
+ - lib/opentelemetry/instrumentation/aws_lambda/wrap.rb
240
241
  homepage: https://github.com/open-telemetry/opentelemetry-ruby-contrib
241
242
  licenses:
242
243
  - Apache-2.0
243
244
  metadata:
244
- changelog_uri: https://rubydoc.info/gems/opentelemetry-instrumentation-aws_lambda/0.2.0/file/CHANGELOG.md
245
+ changelog_uri: https://rubydoc.info/gems/opentelemetry-instrumentation-aws_lambda/0.3.0/file/CHANGELOG.md
245
246
  source_code_uri: https://github.com/open-telemetry/opentelemetry-ruby-contrib/tree/main/instrumentation/aws_lambda
246
247
  bug_tracker_uri: https://github.com/open-telemetry/opentelemetry-ruby-contrib/issues
247
- documentation_uri: https://rubydoc.info/gems/opentelemetry-instrumentation-aws_lambda/0.2.0
248
+ documentation_uri: https://rubydoc.info/gems/opentelemetry-instrumentation-aws_lambda/0.3.0
248
249
  post_install_message:
249
250
  rdoc_options: []
250
251
  require_paths: