Class: DatadogLogForwarder

Inherits:
Object
  • Object
show all
Defined in:
lib/tpt_serverless/datadog_log_forwarder.rb

Class Method Summary collapse

Class Method Details

.get_datadog_reserved_tags(fallback_env: nil, fallback_service: nil) ⇒ Object

Computes the Datadog unified service tagging reserved tags from environment variables, using the following precedence:

  • env: DD_ENV → APP_STAGE

  • service: DD_SERVICE → APP_SERVICE_NAME

  • version: DD_VERSION

  • preset: DD_TAGS



138
139
140
141
142
143
144
145
146
147
148
149
# File 'lib/tpt_serverless/datadog_log_forwarder.rb', line 138

def get_datadog_reserved_tags(fallback_env: nil, fallback_service: nil)
  env = ENV['DD_ENV'] || ENV['APP_STAGE'] || fallback_env
  service = ENV['DD_SERVICE'] || ENV['APP_SERVICE_NAME'] || fallback_service
  version = ENV['DD_VERSION']
  preset = ENV['DD_TAGS'] ? ENV['DD_TAGS'].split(',').map(&:strip).reject(&:empty?) : []

  tags = []
  tags << "env:#{env}" if env
  tags << "service:#{service}" if service
  tags << "version:#{version}" if version
  tags + preset
end

.handler(event:, context:) ⇒ Object

This handler receives CloudWatch log events, parses the events and forwards the extracted logs to Datadog.



46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
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
# File 'lib/tpt_serverless/datadog_log_forwarder.rb', line 46

def handler(event:, context:)
  unless ENV['DD_API_KEY']
    puts 'ERROR: DD_API_KEY is not set. Skipping log forwarding.'
    return nil
  end

  raw_data = event.fetch('awslogs').fetch('data')
  unzipped_data = Zlib.gunzip(Base64.decode64(raw_data))
  data = JSON.parse(unzipped_data)

  message_type = data.fetch('messageType')
  log_group = data.fetch('logGroup')
  log_stream = data.fetch('logStream')
  log_events = data.fetch('logEvents')

  if message_type == 'CONTROL_MESSAGE'
    puts 'skipping control message'
    return nil
  end

  # Normalize first so lifecycle filtering works even if the raw message didn't have a timestamp
  normalized_log_events = normalize_messages(log_events)

  # Drop AWS Lambda platform lifecycle boilerplate
  filtered_log_events =
    if should_filter_lifecycle_logs?
      normalized_log_events.reject { |e| lambda_lifecycle_line?(e[:message]) }
    else
      normalized_log_events
    end

  puts "message_count=#{log_events.length} forwarded_count=#{filtered_log_events.length}"

  region = 'unknown'
   = 'unknown'

  if context&.respond_to?(:invoked_function_arn) && !context.invoked_function_arn.to_s.empty?
    arn_parts = context.invoked_function_arn.split(':')
    region = arn_parts[3] if arn_parts[3]
     = arn_parts[4] if arn_parts[4]
  end

  function_name = log_group&.split('/')&.last
  function_arn = "arn:aws:lambda:#{region}:#{}:function:#{function_name}"

  name_parts = (function_name || '').split('-')
  # Extract the stage (everything from index 2 onwards e.g., "dev" or "dev-ivan")
  extracted_env = name_parts.length > 2 ? name_parts[2..-1].join('-') : 'unknown'

  normalized_data = filtered_log_events.map do |log_event|
    ddtags_array = get_datadog_reserved_tags(
      fallback_env: extracted_env,
      fallback_service: function_name
    )

    {
      message: log_event[:message],
      ddsource: ENV['DD_SOURCE'] || 'cwl-aws-lambda',
      service: ENV['DD_SERVICE'] || ENV['APP_SERVICE_NAME'] || function_name,
      hostname: function_arn,
      ddtags: (ddtags_array + ["region:#{region}"]).reject { |t| t.nil? || t.empty? }.join(','),
      aws: {
        awslogs: {
          logGroup: log_group,
          logStream: log_stream
        }
      },
      id: log_event[:id]
    }
  end

  # If we filtered everything out, do nothing.
  return nil if normalized_data.empty?

  begin
    send_json_to_datadog(normalized_data.to_json)
    puts "Datadog upload successful: sent #{normalized_data.length} log(s) for #{function_name}"
  rescue => e
    puts e.message
    puts "Error details: #{e.backtrace.first}"
  end

  nil
end