Class: LambdaLoadout::Metrics

Inherits:
Object
  • Object
show all
Defined in:
lib/lambda_loadout/metrics.rb

Overview

CloudWatch Metrics using Embedded Metric Format (EMF)

Creates custom metrics asynchronously by outputting metrics to stdout following Amazon CloudWatch Embedded Metric Format (EMF).

Examples:

Basic usage

metrics = LambdaLoadout::Metrics.new(namespace: "MyApp", service: "payment")
metrics.add_metric(name: "PaymentProcessed", unit: "Count", value: 1)
metrics.add_dimension(name: "region", value: "us-east-1")
metrics.flush

With middleware

def lambda_handler(event:, context:)
  metrics = LambdaLoadout::Metrics.new(namespace: "MyApp")

  begin
    metrics.add_metric(name: "RequestReceived", unit: "Count", value: 1)
    # Your logic here
  ensure
    metrics.flush
  end
end

Constant Summary collapse

UNITS =

CloudWatch metric units

%w[
  Seconds Microseconds Milliseconds
  Bytes Kilobytes Megabytes Gigabytes Terabytes
  Bits Kilobits Megabits Gigabits Terabits
  Percent Count
  Bytes/Second Kilobytes/Second Megabytes/Second Gigabytes/Second Terabytes/Second
  Bits/Second Kilobits/Second Megabits/Second Gigabits/Second Terabits/Second
  Count/Second None
].freeze
RESOLUTIONS =

Metric resolutions in seconds

[1, 60].freeze
MAX_DIMENSIONS =
29
MAX_METRICS =
100

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(namespace: nil, service: nil, output: $stdout) ⇒ Metrics

Initialize metrics

Parameters:

  • namespace (String) (defaults to: nil)

    CloudWatch metric namespace (required)

  • service (String) (defaults to: nil)

    Service name (added as default dimension)

  • output (IO) (defaults to: $stdout)

    Output stream for EMF (defaults to $stdout)

Raises:

  • (ArgumentError)


54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
# File 'lib/lambda_loadout/metrics.rb', line 54

def initialize(namespace: nil, service: nil, output: $stdout)
  @namespace = namespace || ENV.fetch('POWERTOOLS_METRICS_NAMESPACE', nil)
  @service = service || ENV.fetch('POWERTOOLS_SERVICE_NAME', nil)
  @output = output

  raise ArgumentError, 'Namespace is required' if @namespace.nil? || @namespace.empty?

  @metrics = {}
  @dimensions = {}
  @metadata = {}
  @default_dimensions = {}
  @timestamp = nil
  @cold_start_captured = false

  # Add service as default dimension if provided
  add_dimension(name: 'service', value: @service) if @service
end

Instance Attribute Details

#namespaceObject (readonly)

Returns the value of attribute namespace.



47
48
49
# File 'lib/lambda_loadout/metrics.rb', line 47

def namespace
  @namespace
end

#outputObject (readonly)

Returns the value of attribute output.



47
48
49
# File 'lib/lambda_loadout/metrics.rb', line 47

def output
  @output
end

#serviceObject (readonly)

Returns the value of attribute service.



47
48
49
# File 'lib/lambda_loadout/metrics.rb', line 47

def service
  @service
end

Instance Method Details

#add_cold_start_metric(context) ⇒ void

This method returns an undefined value.

Add cold start metric

Parameters:

  • context (Object)

    Lambda context



152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
# File 'lib/lambda_loadout/metrics.rb', line 152

def add_cold_start_metric(context)
  return if @cold_start_captured
  return unless LambdaLoadout.cold_start?

  @cold_start_captured = true

  # Create a separate EMF blob for cold start metric
  cold_start_metrics = self.class.new(namespace: @namespace, output: @output)
  cold_start_metrics.add_metric(name: 'ColdStart', unit: 'Count', value: 1)
  cold_start_metrics.add_dimension(name: 'function_name', value: context.function_name)
  cold_start_metrics.add_dimension(name: 'service', value: @service) if @service
  cold_start_metrics.flush
rescue StandardError => e
  warn "[LambdaLoadout::Metrics] Failed to add cold start metric: #{e.message}"
end

#add_dimension(name:, value:) ⇒ void

This method returns an undefined value.

Add a dimension to all metrics

Parameters:

  • name (String)

    Dimension name

  • value (String)

    Dimension value

Raises:

  • (ArgumentError)

    if max dimensions exceeded



100
101
102
103
104
# File 'lib/lambda_loadout/metrics.rb', line 100

def add_dimension(name:, value:)
  raise ArgumentError, "Maximum number of dimensions (#{MAX_DIMENSIONS}) exceeded" if @dimensions.size >= MAX_DIMENSIONS

  @dimensions[name.to_s] = value.to_s
end

#add_metadata(key:, value:) ⇒ void

This method returns an undefined value.

Add metadata (searchable in CloudWatch Logs but not as a metric dimension)

Parameters:

  • key (String)

    Metadata key

  • value (Object)

    Metadata value



111
112
113
# File 'lib/lambda_loadout/metrics.rb', line 111

def (key:, value:)
  @metadata[key.to_s] = value
end

#add_metric(name:, unit:, value:, resolution: 60) ⇒ void

This method returns an undefined value.

Add a metric

Parameters:

  • name (String)

    Metric name

  • unit (String)

    Metric unit (must be valid CloudWatch unit)

  • value (Numeric)

    Metric value

  • resolution (Integer) (defaults to: 60)

    Storage resolution in seconds (1 or 60)

Raises:

  • (ArgumentError)

    if parameters are invalid



80
81
82
83
84
85
86
87
88
89
90
91
92
# File 'lib/lambda_loadout/metrics.rb', line 80

def add_metric(name:, unit:, value:, resolution: 60)
  validate_metric_name!(name)
  validate_metric_unit!(unit)
  validate_metric_value!(value)
  validate_metric_resolution!(resolution)

  # Auto-flush if we've hit the max metrics limit
  flush_metrics if @metrics.size >= MAX_METRICS

  metric_key = name.to_s
  @metrics[metric_key] ||= { 'Unit' => unit.to_s, 'StorageResolution' => resolution, 'Values' => [] }
  @metrics[metric_key]['Values'] << value.to_f
end

#clearvoid

This method returns an undefined value.

Clear all metrics, dimensions, and metadata



189
190
191
192
193
194
195
# File 'lib/lambda_loadout/metrics.rb', line 189

def clear
  @metrics.clear
  @dimensions.clear
  @metadata.clear
  @dimensions.merge!(@default_dimensions)
  @timestamp = nil
end

#flush(raise_on_empty: false) ⇒ void

This method returns an undefined value.

Serialize and flush all metrics

Outputs metrics in CloudWatch Embedded Metric Format (EMF) to stdout. Metrics are automatically ingested by CloudWatch when running in Lambda.

Parameters:

  • raise_on_empty (Boolean) (defaults to: false)

    Raise error if no metrics to flush



175
176
177
178
179
180
181
182
183
184
# File 'lib/lambda_loadout/metrics.rb', line 175

def flush(raise_on_empty: false)
  if @metrics.empty?
    warn '[LambdaLoadout::Metrics] No metrics to flush' unless raise_on_empty
    raise 'No metrics to flush' if raise_on_empty

    return
  end

  flush_metrics
end

#set_default_dimensions(**dimensions) ⇒ void

This method returns an undefined value.

Set default dimensions that persist across flushes

Parameters:

  • dimensions (Hash)

    Default dimensions



119
120
121
122
# File 'lib/lambda_loadout/metrics.rb', line 119

def set_default_dimensions(**dimensions)
  @default_dimensions.merge!(dimensions.transform_keys(&:to_s))
  @dimensions.merge!(@default_dimensions)
end

#set_timestamp(timestamp) ⇒ void

This method returns an undefined value.

Set custom timestamp for metrics

Parameters:

  • timestamp (Time, Integer)

    Timestamp (Time object or epoch in milliseconds)



128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
# File 'lib/lambda_loadout/metrics.rb', line 128

def set_timestamp(timestamp)
  @timestamp = case timestamp
               when Time
                 (timestamp.to_f * 1000).to_i
               when Integer
                 timestamp
               else
                 raise ArgumentError, 'Timestamp must be Time object or Integer (epoch ms)'
               end

  # Validate timestamp is within CloudWatch limits (14 days past, 2 hours future)
  now = (Time.now.to_f * 1000).to_i
  min_time = now - (14 * 24 * 60 * 60 * 1000)
  max_time = now + (2 * 60 * 60 * 1000)

  return unless @timestamp < min_time || @timestamp > max_time

  warn '[LambdaLoadout::Metrics] Timestamp outside CloudWatch limits (14 days past, 2 hours future)'
end