Module: Datadog::Lambda

Defined in:
lib/datadog/lambda.rb,
lib/datadog/lambda/appsec.rb,
lib/datadog/lambda/version.rb,
lib/datadog/lambda/appsec/request.rb,
lib/datadog/lambda/appsec/event_normalizer.rb,
lib/datadog/lambda/appsec/response_normalizer.rb

Overview

Instruments AWS Lambda functions with Datadog distributed tracing and custom metrics

Defined Under Namespace

Modules: AppSec, VERSION

Class Method Summary collapse

Class Method Details

.configure_apmObject

Configures Datadog’s APM tracer with lambda specific defaults. Same options can be given as Datadog.configure in tracer See github.com/DataDog/dd-trace-rb/blob/master/docs/GettingStarted.md#quickstart-for-ruby-applications

rubocop:disable Metrics/AbcSize



34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
# File 'lib/datadog/lambda.rb', line 34

def self.configure_apm
  require 'datadog/tracing'
  require 'datadog/tracing/transport/io'

  @patch_http = false
  # Needed to keep trace flushes on a single line
  $stdout.sync = true

  Datadog.configure do |c|
    unless Datadog::Utils.extension_running?
      c.tracing.writer = Datadog::Tracing::SyncWriter.new(
        transport: Datadog::Tracing::Transport::IO.default
      )
    end
    c.tags = { "_dd.origin": 'lambda' }
    # Enable AWS SDK instrumentation
    c.tracing.instrument :aws if trace_managed_services?

    yield(c) if block_given?

    c.appsec.instrument(:aws_lambda)
  end
end

.dd_lambda_layer_tagObject



102
103
104
# File 'lib/datadog/lambda.rb', line 102

def self.dd_lambda_layer_tag
  RUBY_VERSION[0, 3].tr('.', '')
end

.do_enhanced_metrics?boolean

Check the DD_ENHANCED_METRICS environment variable enhanced metrics

Returns:

  • (boolean)

    true if this lambda should have



171
172
173
174
175
176
# File 'lib/datadog/lambda.rb', line 171

def self.do_enhanced_metrics?
  dd_enhanced_metrics = ENV['DD_ENHANCED_METRICS']
  return true if dd_enhanced_metrics.nil?

  dd_enhanced_metrics.downcase == 'true'
end

.gen_enhanced_tags(context) ⇒ hash

Generate tags for enhanced metrics rubocop:disable Metrics/AbcSize, Metrics/MethodLength

Parameters:

Returns:

  • (hash)

    a hash of the enhanced metrics tags



110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
# File 'lib/datadog/lambda.rb', line 110

def self.gen_enhanced_tags(context)
  arn_parts = context.invoked_function_arn.to_s.split(':')
  # Check if we have an alias or version
  function_alias = arn_parts[7].nil? ? nil : arn_parts[7]

  tags = {
    functionname: context.function_name,
    region: arn_parts[3],
    account_id: arn_parts[4],
    memorysize: context.memory_limit_in_mb,
    cold_start: @is_cold_start,
    runtime: "Ruby #{RUBY_VERSION}",
    resource: context.function_name,
    datadog_lambda: Datadog::Lambda::VERSION::STRING.to_sym
  }
  begin
    tags[:dd_trace] = Gem.loaded_specs['datadog'].version
  rescue StandardError
    Datadog::Utils.logger.debug 'datadog unavailable'
  end
  # If we have an alias...
  unless function_alias.nil?
    # If the alis version is $Latest, drop the $ for ddog tag convention.
    if function_alias.start_with?('$')
      function_alias[0] = ''
      # If the alias is not a version number add the executed version tag
    elsif !/\A\d+\z/.match(function_alias)
      tags[:executedversion] = context.function_version
    end
    # Append the alias to the resource tag
    tags[:resource] = "#{context.function_name}:#{function_alias}"
  end

  tags
rescue StandardError => e
  Datadog::Utils.logger.error 'Unable to parse Lambda context' \
  "#{context}: #{e}"
  {}
end

.initialize_listenerObject



187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
# File 'lib/datadog/lambda.rb', line 187

def self.initialize_listener
  handler = ENV['_HANDLER'].nil? ? 'handler' : ENV['_HANDLER']
  function = ENV['AWS_LAMBDA_FUNCTION_NAME']
  merge_xray_traces = false
  merge_xray_traces_env = ENV['DD_MERGE_DATADOG_XRAY_TRACES']
  unless merge_xray_traces_env.nil?
    merge_xray_traces = merge_xray_traces_env.downcase == 'true'
    Datadog::Utils.logger.debug("Setting merge traces #{merge_xray_traces}")
  end

  # Only initialize listener if Tracing enabled.
  unless Datadog::Tracing.enabled?
    Datadog::Utils.logger.debug 'datadog unavailable'
    return nil
  end

  Trace::Listener.new(
    handler_name: handler,
    function_name: function,
    patch_http: @patch_http,
    merge_xray_traces:
  )
end

.metric(name, value, time: nil, **tags) ⇒ Object

Send a custom distribution metric

Parameters:

  • name (String)

    name of the metric

  • value (Numeric)

    value of the metric

  • time (Time) (defaults to: nil)

    the time of the metric, should be in the past

  • tags (Hash)

    hash of tags, must be in “my.tag.name”:“value” format



95
96
97
98
99
100
# File 'lib/datadog/lambda.rb', line 95

def self.metric(name, value, time: nil, **tags)
  raise 'name must be a string' unless name.is_a?(String)
  raise 'value must be a number' unless value.is_a?(Numeric)

  @metrics_client.distribution(name, value, time:, **tags)
end

.record_enhanced(metric_name, context) ⇒ boolean

rubocop:enable Metrics/AbcSize, Metrics/MethodLength Format and add tags to enhanced metrics This method wraps the metric method, checking the DD_ENHANCED_METRICS environment variable, adding ‘aws.lambda.enhanced’ to the metric name, and adding the enhanced metric tags to the enhanced metrics.

Parameters:

  • metric_name (String)

    basic name of the metric

  • context (Object)

    AWS Ruby Lambda Context

Returns:

  • (boolean)

    false if the metric was not added for some reason, true otherwise (for ease of testing



160
161
162
163
164
165
166
# File 'lib/datadog/lambda.rb', line 160

def self.record_enhanced(metric_name, context)
  return false unless do_enhanced_metrics?

  etags = gen_enhanced_tags(context)
  metric("aws.lambda.enhanced.#{metric_name}", 1, **etags)
  true
end

.trace_contextObject

Gets the current tracing context



86
87
88
# File 'lib/datadog/lambda.rb', line 86

def self.trace_context
  Hash[Datadog::Trace.trace_context]
end

.trace_managed_services?boolean

Read DD_TRACE_MANAGED_SERVICES environment variable

Returns:

  • (boolean)

    true if we should trace AWS services



180
181
182
183
184
185
# File 'lib/datadog/lambda.rb', line 180

def self.trace_managed_services?
  dd_trace_managed_services = ENV[Trace::DD_TRACE_MANAGED_SERVICES]
  return true if dd_trace_managed_services.nil?

  dd_trace_managed_services.downcase == 'true'
end

.wrap(event, context, &block) ⇒ Object

Wrap the body of a lambda invocation rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity

Parameters:

  • event (Object)

    event sent to lambda

  • context (Object)

    lambda context

  • block (Proc)

    implementation of the handler function.



64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
# File 'lib/datadog/lambda.rb', line 64

def self.wrap(event, context, &block)
  @response = nil
  @listener ||= initialize_listener
  record_enhanced('invocations', context)
  begin
    cold = @is_cold_start
    @listener&.on_start(event:, request_context: context, cold_start: cold)
    @response = @listener&.response_override || block.call
  rescue StandardError => e
    record_enhanced('errors', context)
    raise e
  ensure
    @listener&.on_end(response: @response, request_context: context)
    @response = @listener&.response_override || @response
    @is_cold_start = false
    @metrics_client.close
  end
  @response
end