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



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

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



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

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



101
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
# File 'lib/datadog/lambda.rb', line 101

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



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

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



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

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



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

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



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

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



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

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
# File 'lib/datadog/lambda.rb', line 58

def self.wrap(event, context, &block)
  @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