datadog-lambda 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: []