datadog-lambda 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 195d3560e0bbcfc3874834d6f40e82d447b80c734445285a2f21730dfd4a7dd2
4
+ data.tar.gz: bea1f09ab1fb862507617be1c1045ace0dad31546dc3a4b8e073675e66a9cb41
5
+ SHA512:
6
+ metadata.gz: d104d3a04a0cbaa9833e479d62dadc71ddac0064c49587ba3703554bb8731776f7c25cae0fa28bfafb6bb9146e6288b1e2ce573290cda2c4b3e30915bac540a0
7
+ data.tar.gz: 7fafb2dbc147e7a7d6b2dbafb03084f459a5a179a6959fc28fcc30e510c60a3e1b663c1ead3f09c93eae03c1bd05b5f39fa30ed03ac3f30a86d142a6d2de7937
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # Unless explicitly stated otherwise all files in this repository are licensed
5
+ # under the Apache License Version 2.0.
6
+ #
7
+ # This product includes software developed at Datadog (https://www.datadoghq.com/).
8
+ # Copyright 2019 Datadog, Inc.
9
+ #
10
+
11
+ require 'datadog/lambda/trace/listener'
12
+ require 'datadog/lambda/utils/logger'
13
+ require 'datadog/lambda/trace/patch_http'
14
+ require 'json'
15
+ require 'time'
16
+
17
+ module Datadog
18
+ # Instruments AWS Lambda functions with Datadog distributed tracing and
19
+ # custom metrics
20
+ module Lambda
21
+ # Wrap the body of a lambda invocation
22
+ # @param event [Object] event sent to lambda
23
+ # @param context [Object] lambda context
24
+ # @param block [Proc] implementation of the handler function.
25
+ def self.wrap(event, _context, &block)
26
+ Datadog::Utils.update_log_level
27
+
28
+ @listener ||= Trace::Listener.new
29
+ @listener.on_start(event: event)
30
+ begin
31
+ res = block.call
32
+ ensure
33
+ @listener.on_end
34
+ end
35
+ res
36
+ end
37
+
38
+ # Gets the current tracing context
39
+ def self.trace_context
40
+ Datadog::Trace.trace_context
41
+ end
42
+
43
+ # Send a custom distribution metric
44
+ # @param name [String] name of the metric
45
+ # @param value [Numeric] value of the metric
46
+ # @param time [Time] the time of the metric, should be in the past
47
+ # @param tags [Hash] hash of tags, must be in "my.tag.name":"value" format
48
+ def self.metric(name, value, time: nil, **tags)
49
+ raise 'name must be a string' unless name.is_a?(String)
50
+ raise 'value must be a number' unless value.is_a?(Numeric)
51
+
52
+ time ||= Time.now
53
+ tag_list = ['dd_lambda_layer:datadog-ruby25']
54
+ tags.each do |tag|
55
+ tag_list.push("#{tag[0]}:#{tag[1]}")
56
+ end
57
+ time_ms = (time.to_f * 100).to_i
58
+ metric = { e: time_ms, m: name, t: tag_list, v: value }.to_json
59
+ puts metric
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # Unless explicitly stated otherwise all files in this repository are licensed
5
+ # under the Apache License Version 2.0.
6
+ #
7
+ # This product includes software developed at Datadog (https://www.datadoghq.com/).
8
+ # Copyright 2019 Datadog, Inc.
9
+ #
10
+
11
+ module Datadog
12
+ module Trace
13
+ SAMPLE_MODE_USER_REJECT = -1
14
+ SAMPLE_MODE_AUTO_REJECT = 0
15
+ SAMPLE_MODE_AUTO_KEEP = 1
16
+ SAMPLE_MODE_USER_KEEP = 2
17
+ DD_TRACE_ID_HEADER = 'x-datadog-trace-id'
18
+ DD_PARENT_ID_HEADER = 'x-datadog-parent-id'
19
+ DD_SAMPLING_PRIORITY_HEADER = 'x-datadog-sampling-priority'
20
+ DD_XRAY_SUBSEGMENT_NAME = 'datadog-metadata'
21
+ DD_XRAY_SUBSEGMENT_KEY = 'trace'
22
+ DD_XRAY_SUBSEGMENT_NAMESPACE = 'datadog'
23
+ end
24
+ end
@@ -0,0 +1,130 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # Unless explicitly stated otherwise all files in this repository are licensed
5
+ # under the Apache License Version 2.0.
6
+ #
7
+ # This product includes software developed at Datadog (https://www.datadoghq.com/).
8
+ # Copyright 2019 Datadog, Inc.
9
+ #
10
+
11
+ require 'aws-xray-sdk'
12
+ require 'datadog/lambda/trace/constants'
13
+ require 'datadog/lambda/utils/logger'
14
+
15
+ module Datadog
16
+ # Trace contains utilities related to reading/writing trace context from
17
+ # lambda events/X-Ray
18
+ module Trace
19
+ class << self
20
+ def extract_trace_context(event)
21
+ context = read_trace_context_from_event(event)
22
+ unless context.nil?
23
+ begin
24
+ add_trace_context_to_xray(context)
25
+ rescue StandardError => e
26
+ Datadog::Utils.logger.error("couldn't add metadata to xray #{e}")
27
+ end
28
+ return context
29
+ end
30
+ read_trace_context_from_xray
31
+ end
32
+
33
+ def add_trace_context_to_xray(context)
34
+ seg = XRay.recorder.begin_subsegment(
35
+ DD_XRAY_SUBSEGMENT_NAME,
36
+ namespace: DD_XRAY_SUBSEGMENT_NAMESPACE
37
+ )
38
+ data = {
39
+ "parent-id": context[:parent_id],
40
+ "sampling-priority": context[:sample_mode].to_s,
41
+ "trace-id": context[:trace_id]
42
+ }
43
+ seg.metadata(namespace: DD_XRAY_SUBSEGMENT_NAMESPACE)
44
+ .update("#{DD_XRAY_SUBSEGMENT_KEY}": data)
45
+ XRay.recorder.end_subsegment
46
+ end
47
+
48
+ def current_trace_context(trace_context)
49
+ entity = XRay.recorder.current_entity
50
+ parent_id = entity.instance_variable_get('@parent_id')
51
+ {
52
+ trace_id: trace_context[:trace_id],
53
+ parent_id: convert_to_apm_parent_id(parent_id),
54
+ sample_mode: trace_context[:sample_mode]
55
+ }
56
+ end
57
+
58
+ def read_trace_context_from_xray
59
+ segment = XRay.recorder.current_entity
60
+ parent_id = segment.instance_variable_get('@parent_id')
61
+ mode = segment.sampled ? SAMPLE_MODE_USER_KEEP : SAMPLE_MODE_USER_REJECT
62
+ {
63
+ trace_id: convert_to_apm_trace_id(segment.trace_id),
64
+ parent_id: convert_to_apm_parent_id(parent_id),
65
+ sample_mode: mode
66
+ }
67
+ rescue StandardError => e
68
+ Datadog::Utils.logger.error("couldn't read xray trace header #{e}")
69
+ nil
70
+ end
71
+
72
+ def read_trace_context_from_event(event)
73
+ return nil unless headers?(event)
74
+
75
+ headers = event['headers'].transform_keys(&:downcase)
76
+
77
+ return nil unless trace_headers_present?(headers)
78
+
79
+ {
80
+ trace_id: headers[DD_TRACE_ID_HEADER],
81
+ parent_id: headers[DD_PARENT_ID_HEADER],
82
+ sample_mode: headers[DD_SAMPLING_PRIORITY_HEADER].to_i
83
+ }
84
+ end
85
+
86
+ def headers?(event)
87
+ event.is_a?(Hash) && event.key?('headers') &&
88
+ event['headers'].is_a?(Hash)
89
+ end
90
+
91
+ def trace_headers_present?(headers)
92
+ expected = [
93
+ DD_TRACE_ID_HEADER,
94
+ DD_PARENT_ID_HEADER,
95
+ DD_SAMPLING_PRIORITY_HEADER
96
+ ]
97
+ expected.each do |key|
98
+ return false unless headers.key?(key) && headers[key].is_a?(String)
99
+ end
100
+ true
101
+ end
102
+
103
+ def convert_to_apm_trace_id(xray_trace_id)
104
+ parts = xray_trace_id.split('-')
105
+ return nil if parts.length < 3
106
+
107
+ last_part = parts[2]
108
+ return nil if last_part.length != 24
109
+ # Make sure every character is hex
110
+ return nil if last_part.upcase[/\H/]
111
+
112
+ hex = last_part.to_i(16)
113
+ last_63_bits = hex & 0x7fffffffffffffff
114
+ last_63_bits.to_s(10)
115
+ end
116
+
117
+ def convert_to_apm_parent_id(xray_parent_id)
118
+ return nil if xray_parent_id.length != 16
119
+ return nil if xray_parent_id.upcase[/\H/]
120
+
121
+ hex = xray_parent_id.to_i(16)
122
+ hex.to_s(10)
123
+ end
124
+
125
+ def convert_to_sample_mode(xray_sampled)
126
+ xray_sampled == '1' ? SAMPLE_MODE_USER_KEEP : SAMPLE_MODE_USER_REJECT
127
+ end
128
+ end
129
+ end
130
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # Unless explicitly stated otherwise all files in this repository are licensed
5
+ # under the Apache License Version 2.0.
6
+ #
7
+ # This product includes software developed at Datadog (https://www.datadoghq.com/).
8
+ # Copyright 2019 Datadog, Inc.
9
+ #
10
+
11
+ require 'datadog/lambda/trace/context'
12
+ require 'datadog/lambda/trace/xray_lambda'
13
+ require 'datadog/lambda/trace/patch_http'
14
+
15
+ require 'aws-xray-sdk'
16
+
17
+ module Datadog
18
+ module Trace
19
+ # TraceListener tracks tracing context information
20
+ class Listener
21
+ def initialize
22
+ XRay.recorder.configure(
23
+ patch: %I[aws_sdk],
24
+ context: Datadog::Trace::LambdaContext.new,
25
+ streamer: Datadog::Trace::LambdaStreamer.new,
26
+ emitter: Datadog::Trace::LambdaEmitter.new
27
+ )
28
+ Datadog::Trace.patch_http
29
+ end
30
+
31
+ def on_start(event:)
32
+ trace_context = Datadog::Trace.extract_trace_context(event)
33
+ Datadog::Trace.trace_context = trace_context
34
+ Datadog::Utils.logger.debug "extracted trace context #{trace_context}"
35
+ rescue StandardError => e
36
+ Datadog::Utils.logger.error "couldn't read tracing context #{e}"
37
+ end
38
+
39
+ def on_end; end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # Unless explicitly stated otherwise all files in this repository are licensed
5
+ # under the Apache License Version 2.0.
6
+ #
7
+ # This product includes software developed at Datadog (https://www.datadoghq.com/).
8
+ # Copyright 2019 Datadog, Inc.
9
+ #
10
+
11
+ require 'net/http'
12
+
13
+ module Datadog
14
+ # Trace contains methods to help with patching Net/HTTP
15
+ module Trace
16
+ def self.trace_context
17
+ @trace_context
18
+ end
19
+
20
+ def self.trace_context=(val)
21
+ @trace_context = val
22
+ end
23
+
24
+ @patched = false
25
+ def self.patch_http
26
+ Net::HTTP.prepend NetExtensions unless @patched
27
+ @patched = true
28
+ end
29
+
30
+ # NetExtensions contains patches which add tracing context to http calls
31
+ module NetExtensions
32
+ def request(req, body = nil, &block)
33
+ logger = Datadog::Utils.logger
34
+ begin
35
+ context = Datadog::Trace.current_trace_context(
36
+ Datadog::Trace.trace_context
37
+ )
38
+
39
+ req[Datadog::Trace::DD_SAMPLING_PRIORITY_HEADER.to_sym] =
40
+ context[:sample_mode]
41
+ req[Datadog::Trace::DD_PARENT_ID_HEADER.to_sym] = context[:parent_id]
42
+ req[Datadog::Trace::DD_TRACE_ID_HEADER.to_sym] = context[:trace_id]
43
+ logger.debug("added context #{context} to request")
44
+ rescue StandardError => e
45
+ logger.error(
46
+ "couldn't add tracing context #{context} to request #{e}"
47
+ )
48
+ end
49
+ super(req, body, &block)
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # Unless explicitly stated otherwise all files in this repository are licensed
5
+ # under the Apache License Version 2.0.
6
+ #
7
+ # This product includes software developed at Datadog (https://www.datadoghq.com/).
8
+ # Copyright 2019 Datadog, Inc.
9
+ #
10
+
11
+ require 'aws-xray-sdk'
12
+
13
+ module Datadog
14
+ module Trace
15
+ # Workaround to XRay not being supported in Lambda
16
+ # https://github.com/aws/aws-xray-sdk-ruby/issues/20
17
+ class LambdaStreamer < XRay::DefaultStreamer
18
+ def initialize
19
+ @stream_threshold = 1 # Stream every subsegment as it is available
20
+ end
21
+ end
22
+
23
+ # LambdaEmitter filters out spans generated from the lambda daemon
24
+ class LambdaEmitter < XRay::DefaultEmitter
25
+ def should_send?(entity:)
26
+ entity.name != '127.0.0.1' # Do not send localhost entities.
27
+ end
28
+
29
+ def send_entity(entity:)
30
+ return nil unless should_send?(entity: entity)
31
+
32
+ super
33
+ end
34
+ end
35
+
36
+ # LambdaContext updated the trace id based on the curren trace header,
37
+ # using a FacadeSegment
38
+ class LambdaContext < XRay::DefaultContext
39
+ def handle_context_missing
40
+ nil
41
+ end
42
+
43
+ def check_context
44
+ # Create a new FacadeSegment if the _X_AMZN_TRACE_ID changes.
45
+ return if ENV['_X_AMZN_TRACE_ID'] == @current_trace_id
46
+
47
+ # puts "XRAY: Starting new segment for #{ENV['_X_AMZN_TRACE_ID']}"
48
+ @current_trace_id = ENV['_X_AMZN_TRACE_ID']
49
+ trace_header = XRay::TraceHeader.from_header_string(
50
+ header_str: @current_trace_id
51
+ )
52
+ segment = FacadeSegment.new(trace_id: trace_header.root,
53
+ parent_id: trace_header.parent_id,
54
+ id: trace_header.parent_id,
55
+ name: 'lambda_context',
56
+ sampled: trace_header.sampled)
57
+ store_entity(entity: segment)
58
+ end
59
+
60
+ def current_entity
61
+ # ensure the FacadeSegment is current whenever the current_entity is
62
+ # retrieved
63
+ check_context
64
+ super
65
+ end
66
+ end
67
+
68
+ # FacadeSegment is used to create a mock root span, that segments/
69
+ # subsegments can be attached to. This span will never be submitted to the
70
+ # X-Ray daemon
71
+ class FacadeSegment < XRay::Segment
72
+ def initialize(trace_id: nil, name: nil, parent_id: nil, id: nil,
73
+ sampled: true)
74
+ super(trace_id: trace_id, name: name, parent_id: parent_id)
75
+ @id = id
76
+ @sampled = sampled
77
+ end
78
+
79
+ def ready_to_send?
80
+ false # never send this facade.
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # Unless explicitly stated otherwise all files in this repository are licensed
5
+ # under the Apache License Version 2.0.
6
+ #
7
+ # This product includes software developed at Datadog (https://www.datadoghq.com/).
8
+ # Copyright 2019 Datadog, Inc.
9
+ #
10
+
11
+ require 'logger'
12
+
13
+ module Datadog
14
+ # Utils contains utility functions shared between modules
15
+ module Utils
16
+ def self.logger
17
+ @logger ||= Logger.new(STDOUT)
18
+ end
19
+
20
+ def self.update_log_level
21
+ log_level = (ENV['DD_LOG_LEVEL'] || 'error').downcase
22
+ logger.level = case log_level
23
+ when 'debug'
24
+ Logger::DEBUG
25
+ when 'info'
26
+ Logger::INFO
27
+ when 'warn'
28
+ Logger::WARN
29
+ else
30
+ Logger::ERROR
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # Unless explicitly stated otherwise all files in this repository are licensed
5
+ # under the Apache License Version 2.0.
6
+ #
7
+ # This product includes software developed at Datadog (https://www.datadoghq.com/).
8
+ # Copyright 2019 Datadog, Inc.
9
+ #
10
+
11
+ module Datadog
12
+ module Lambda
13
+ module VERSION
14
+ MAJOR = 0
15
+ MINOR = 1
16
+ PATCH = 0
17
+ PRE = nil
18
+
19
+ STRING = [MAJOR, MINOR, PATCH, PRE].compact.join('.')
20
+ end
21
+ end
22
+ end
metadata ADDED
@@ -0,0 +1,126 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: datadog-lambda
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Datadog, Inc.
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2019-08-30 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: aws-xray-sdk
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '0.11'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '0.11'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '12.3'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '12.3'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.8'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.8'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec-collection_matchers
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '1.1'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '1.1'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rubocop
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '0.74'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '0.74'
83
+ description: |
84
+ datadog-lambda is Datadog’s AWS Lambda integration for ruby. It is used to perform
85
+ distributed tracing between serverful and serverless environments, and send
86
+ custom metrics to Datadog.
87
+ email:
88
+ - dev@datadoghq.com
89
+ executables: []
90
+ extensions: []
91
+ extra_rdoc_files: []
92
+ files:
93
+ - lib/datadog/lambda.rb
94
+ - lib/datadog/lambda/trace/constants.rb
95
+ - lib/datadog/lambda/trace/context.rb
96
+ - lib/datadog/lambda/trace/listener.rb
97
+ - lib/datadog/lambda/trace/patch_http.rb
98
+ - lib/datadog/lambda/trace/xray_lambda.rb
99
+ - lib/datadog/lambda/utils/logger.rb
100
+ - lib/datadog/lambda/version.rb
101
+ homepage: https://github.com/DataDog/dd-lambda-rb
102
+ licenses:
103
+ - Apache-2.0
104
+ metadata:
105
+ allowed_push_host: https://rubygems.org
106
+ post_install_message:
107
+ rdoc_options: []
108
+ require_paths:
109
+ - lib
110
+ required_ruby_version: !ruby/object:Gem::Requirement
111
+ requirements:
112
+ - - ">="
113
+ - !ruby/object:Gem::Version
114
+ version: 2.5.0
115
+ required_rubygems_version: !ruby/object:Gem::Requirement
116
+ requirements:
117
+ - - ">="
118
+ - !ruby/object:Gem::Version
119
+ version: '0'
120
+ requirements: []
121
+ rubyforge_project:
122
+ rubygems_version: 2.7.6
123
+ signing_key:
124
+ specification_version: 4
125
+ summary: Instruments your Ruby AWS Lambda functions with Datadog
126
+ test_files: []