tpt_serverless 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c1f06b4fad5f9e34e05e818977304236568122d0a604ecd88e8cf6da4b331d5a
4
- data.tar.gz: 2b54c154a4457028ed90778f699aa159acef24ba4abe6d40f01f80deb0926393
3
+ metadata.gz: eaf679b74f62f413493556247cd9111725a55dfc8346363b30700dd4f44e32a1
4
+ data.tar.gz: 7d0d3e2f07fc6c8d10cc0e7eb801506fe2c7e0f3066361679058643d07221d0a
5
5
  SHA512:
6
- metadata.gz: bec09d6328b91ac709832540773484e01773d2da113166b592aea0cf2070823d69376bf32b5bf442cf5cbb9e367fda385bf7fdb62774d8c716b6de28faf97507
7
- data.tar.gz: 91f0d1ce2681a15b8a5602cfa3736a700e79ab5016e4c9c542fb81dd250da2e98a0c351cf295515f61549bf4746471a84c4ef107ca6a4a00ce361536cdc03e23
6
+ metadata.gz: 9d52cc7aab4a205c3041575bb0f2ada2815ecfee469c3b19e4f627716f315582cf7ebccd6183ff322f5bc5a85d12fd926c7f3d85ae66fcee447ff505648344a1
7
+ data.tar.gz: 533fcc9a0611b56650c5ac97624fca8620eb3c6013b88196b18e7b4c64381daa7375ae83162c39c95dab0ced51b380c8414062c7568df00013e87497d6b0dc08
@@ -1,5 +1,5 @@
1
1
  =begin
2
- Receives logs from CloudWatch and forwards them to Datadog.
2
+ Receives logs from CloudWatch and forwards them to Datadog Logs intake.
3
3
 
4
4
  Example of how to add this to your `serverless.yml`:
5
5
 
@@ -9,17 +9,18 @@ Example of how to add this to your `serverless.yml`:
9
9
  handler: /opt/ruby/3.4.0/gems/tpt_serverless-X.X.X/lib/tpt_serverless/datadog_log_forwarder.DatadogLogForwarder.handler
10
10
  timeout: 10 # seconds
11
11
  environment:
12
- DD_API_KEY: ${self:custom.datadogApiKey}
12
+ DD_API_KEY: ${ssm:/path/to/DD_API_KEY}
13
13
  # Optional:
14
- DATADOG_SITE: datadoghq.com # default
15
- DATADOG_SOURCE: cwl-aws-lambda # default
16
- DATADOG_SERVICE: ${self:service.name} # optional
17
- DATADOG_TAGS: stage:${self:provider.stage},team:platform # optional
18
- # If set to a truthy value (1, true, yes, or on; case/whitespace-insensitive),
19
- # filters AWS Lambda platform lifecycle boilerplate logs like
14
+ DD_SITE: datadoghq.com # default
15
+ DD_SOURCE: cwl-aws-lambda # default
16
+ DD_ENV: ${self:provider.stage} # default
17
+ DD_SERVICE: ${self:service.name} # optional
18
+ DD_TAGS: stage:${self:provider.stage},team:platform # optional
19
+
20
+ # If true, filters AWS Lambda platform lifecycle boilerplate logs like
20
21
  # INIT_START / START / END / REPORT before forwarding to Datadog.
21
22
  # Defaults to false.
22
- DATADOG_FILTER_LIFECYCLE_LOGS: "true" # optional
23
+ DD_FILTER_LIFECYCLE_LOGS: "true" # optional
23
24
  events:
24
25
  - cloudwatchLog: /aws/lambda/${self:service.name}-functionOne-${self:provider.stage}
25
26
  - cloudwatchLog: /aws/lambda/${self:service.name}-functionTwo-${self:provider.stage}
@@ -42,14 +43,10 @@ class DatadogLogForwarder
42
43
  class << self
43
44
  # This handler receives CloudWatch log events, parses the events and forwards the extracted logs to
44
45
  # Datadog.
45
- #
46
- # It performs a few helpful cleanup/prep functions as well.
47
46
  def handler(event:, context:)
48
- dd_api_key = ENV['DD_API_KEY']
49
-
50
- if dd_api_key.nil? || dd_api_key.strip.empty?
47
+ unless ENV['DD_API_KEY']
51
48
  puts 'ERROR: DD_API_KEY is not set. Skipping log forwarding.'
52
- return
49
+ return nil
53
50
  end
54
51
 
55
52
  raw_data = event.fetch('awslogs').fetch('data')
@@ -63,11 +60,13 @@ class DatadogLogForwarder
63
60
 
64
61
  if message_type == 'CONTROL_MESSAGE'
65
62
  puts 'skipping control message'
66
- return
63
+ return nil
67
64
  end
68
65
 
66
+ # Normalize first so lifecycle filtering works even if the raw message didn't have a timestamp
69
67
  normalized_log_events = normalize_messages(log_events)
70
68
 
69
+ # Drop AWS Lambda platform lifecycle boilerplate
71
70
  filtered_log_events =
72
71
  if should_filter_lifecycle_logs?
73
72
  normalized_log_events.reject { |e| lambda_lifecycle_line?(e[:message]) }
@@ -80,7 +79,7 @@ class DatadogLogForwarder
80
79
  region = 'unknown'
81
80
  account_id = 'unknown'
82
81
 
83
- if context&.respond_to?(:invoked_function_arn) && !context.invoked_function_arn.empty?
82
+ if context&.respond_to?(:invoked_function_arn) && !context.invoked_function_arn.to_s.empty?
84
83
  arn_parts = context.invoked_function_arn.split(':')
85
84
  region = arn_parts[3] if arn_parts[3]
86
85
  account_id = arn_parts[4] if arn_parts[4]
@@ -89,13 +88,22 @@ class DatadogLogForwarder
89
88
  function_name = log_group&.split('/')&.last
90
89
  function_arn = "arn:aws:lambda:#{region}:#{account_id}:function:#{function_name}"
91
90
 
91
+ name_parts = (function_name || '').split('-')
92
+ # Extract the stage (everything from index 2 onwards e.g., "dev" or "dev-ivan")
93
+ extracted_env = name_parts.length > 2 ? name_parts[2..-1].join('-') : 'unknown'
94
+
92
95
  normalized_data = filtered_log_events.map do |log_event|
96
+ ddtags_array = get_datadog_reserved_tags(
97
+ fallback_env: extracted_env,
98
+ fallback_service: function_name
99
+ )
100
+
93
101
  {
94
102
  message: log_event[:message],
95
- ddsource: ENV['DATADOG_SOURCE'] || 'cwl-aws-lambda',
96
- service: ENV['DATADOG_SERVICE'] || function_name,
103
+ ddsource: ENV['DD_SOURCE'] || 'cwl-aws-lambda',
104
+ service: ENV['DD_SERVICE'] || ENV['APP_SERVICE_NAME'] || function_name,
97
105
  hostname: function_arn,
98
- ddtags: [ENV['DATADOG_TAGS'], "region:#{region}"].compact.reject(&:empty?).join(','),
106
+ ddtags: (ddtags_array + ["region:#{region}"]).reject { |t| t.nil? || t.empty? }.join(','),
99
107
  aws: {
100
108
  awslogs: {
101
109
  logGroup: log_group,
@@ -107,7 +115,7 @@ class DatadogLogForwarder
107
115
  end
108
116
 
109
117
  # If we filtered everything out, do nothing.
110
- return if normalized_data.empty?
118
+ return nil if normalized_data.empty?
111
119
 
112
120
  begin
113
121
  send_json_to_datadog(normalized_data.to_json)
@@ -120,6 +128,26 @@ class DatadogLogForwarder
120
128
  nil
121
129
  end
122
130
 
131
+ # Computes the Datadog unified service tagging reserved tags from environment
132
+ # variables, using the following precedence:
133
+ #
134
+ # - env: DD_ENV → APP_STAGE
135
+ # - service: DD_SERVICE → APP_SERVICE_NAME
136
+ # - version: DD_VERSION
137
+ # - preset: DD_TAGS
138
+ def get_datadog_reserved_tags(fallback_env: nil, fallback_service: nil)
139
+ env = ENV['DD_ENV'] || ENV['APP_STAGE'] || fallback_env
140
+ service = ENV['DD_SERVICE'] || ENV['APP_SERVICE_NAME'] || fallback_service
141
+ version = ENV['DD_VERSION']
142
+ preset = ENV['DD_TAGS'] ? ENV['DD_TAGS'].split(',').map(&:strip).reject(&:empty?) : []
143
+
144
+ tags = []
145
+ tags << "env:#{env}" if env
146
+ tags << "service:#{service}" if service
147
+ tags << "version:#{version}" if version
148
+ tags + preset
149
+ end
150
+
123
151
  private
124
152
 
125
153
  def normalize_messages(log_events)
@@ -128,7 +156,7 @@ class DatadogLogForwarder
128
156
  message = log_event.fetch('message')
129
157
  timestamp_ms = Integer(log_event.fetch('timestamp'))
130
158
 
131
- # Ensure the message starts with an ISO timestamp (same intent as Node)
159
+ # Ensure the message starts with an ISO timestamp
132
160
  if TIME_REGEX !~ message
133
161
  time_string = Time.at(timestamp_ms / 1000.0).utc.strftime('%Y-%m-%dT%H:%M:%S.%LZ')
134
162
  message = "#{time_string} #{message}"
@@ -142,7 +170,7 @@ class DatadogLogForwarder
142
170
  end
143
171
 
144
172
  def should_filter_lifecycle_logs?
145
- truthy_env?(ENV['DATADOG_FILTER_LIFECYCLE_LOGS'])
173
+ truthy_env?(ENV['DD_FILTER_LIFECYCLE_LOGS'])
146
174
  end
147
175
 
148
176
  def truthy_env?(value)
@@ -157,8 +185,8 @@ class DatadogLogForwarder
157
185
  end
158
186
 
159
187
  def send_json_to_datadog(data)
160
- datadog_site = ENV['DATADOG_SITE'] || 'datadoghq.com'
161
- datadog_url = URI("https://http-intake.logs.#{datadog_site}/api/v2/logs")
188
+ dd_site = ENV['DD_SITE'] || 'datadoghq.com'
189
+ datadog_url = URI("https://http-intake.logs.#{dd_site}/api/v2/logs")
162
190
 
163
191
  response = Net::HTTP.start(datadog_url.host, datadog_url.port, use_ssl: datadog_url.scheme == 'https') do |http|
164
192
  http.open_timeout = 3
@@ -168,7 +196,7 @@ class DatadogLogForwarder
168
196
  datadog_url,
169
197
  {
170
198
  'Content-Type' => 'application/json',
171
- 'DD-API-KEY' => ENV['DD_API_KEY'],
199
+ 'DD-API-KEY' => ENV['DD_API_KEY']
172
200
  }
173
201
  )
174
202
 
@@ -176,8 +204,8 @@ class DatadogLogForwarder
176
204
  http.request(req)
177
205
  end
178
206
 
179
- unless ['200', '202'].include?(response.code)
180
- raise "Datadog upload failed: #{response.code} - #{response.body}"
207
+ unless %w[200 202].include?(response.code)
208
+ raise "Datadog upload failed with status #{response.code}: #{response.body}"
181
209
  end
182
210
 
183
211
  response
@@ -1,3 +1,3 @@
1
1
  module TptServerless
2
- VERSION = "0.5.0"
2
+ VERSION = "0.6.0"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tpt_serverless
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - TpT