Module: Datadog::Lambda

Defined in:
lib/datadog/lambda.rb,
lib/datadog/lambda/version.rb

Overview

Instruments AWS Lambda functions with Datadog distributed tracing and custom metrics

Defined Under Namespace

Modules: 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



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

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?
  end
end

.dd_lambda_layer_tagObject



94
95
96
# File 'lib/datadog/lambda.rb', line 94

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



163
164
165
166
167
168
# File 'lib/datadog/lambda.rb', line 163

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



102
103
104
105
106
107
108
109
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
# File 'lib/datadog/lambda.rb', line 102

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['ddtrace'].version
  rescue StandardError
    Datadog::Utils.logger.debug 'dd-trace 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



179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
# File 'lib/datadog/lambda.rb', line 179

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 'dd-trace 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



87
88
89
90
91
92
# File 'lib/datadog/lambda.rb', line 87

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



152
153
154
155
156
157
158
# File 'lib/datadog/lambda.rb', line 152

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



78
79
80
# File 'lib/datadog/lambda.rb', line 78

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



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

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

Parameters:

  • event (Object)

    event sent to lambda

  • context (Object)

    lambda context

  • block (Proc)

    implementation of the handler function.



58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
# File 'lib/datadog/lambda.rb', line 58

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