Skip to content

Commit c78b051

Browse files
authored
feat: Add ignore-timeouts option (#41)
* first attempt * document ignore-timeouts
1 parent 446b421 commit c78b051

File tree

5 files changed

+184
-51
lines changed

5 files changed

+184
-51
lines changed

README.md

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ This Action lets you send events and metrics to Datadog from a GitHub workflow.
66

77
## Usage
88

9-
> [!IMPORTANT]
9+
> [!IMPORTANT]
1010
> The action can send metrics and events to any Datadog site by setting the `api-url` param. When
1111
> omitted, it defaults to the US endpoint: `https://api.datadoghq.com`.
1212
@@ -100,6 +100,53 @@ steps:
100100
service: "payment"
101101
```
102102

103+
## Inputs
104+
105+
```yaml
106+
- name: Datadog
107+
uses: masci/datadog@v1
108+
with:
109+
# The api key to use.
110+
# Type: string
111+
# Required
112+
api-key: ${{ secrets.DATADOG_API_KEY }}
113+
114+
# The ingestion endpoint to use, US by default.
115+
# Type: string
116+
# Default: 'https://api.datadoghq.com'
117+
api-url: ''
118+
119+
# The URL of the Log API endpoint.
120+
# Type: string
121+
# Default: 'https://http-intake.logs.datadoghq.com'
122+
log-api-url: ''
123+
124+
# If true, timeout errors will be ignored and step will always succeed
125+
# Type: boolean
126+
# Default: false
127+
ignore-timeouts:
128+
129+
# A list of metric objects to send, defined in YAML format
130+
# Type: string
131+
# Default: '[]'
132+
metrics: ''
133+
134+
# A list of event objects to send, defined in YAML format.
135+
# Type: string
136+
# Default: '[]'
137+
events: ''
138+
139+
# A list of service check objects to send, defined in YAML format.
140+
# Type: string
141+
# Default: '[]'
142+
service-checks: ''
143+
144+
# A list of log objects to send, defined in YAML format.
145+
# Type: string
146+
# Default: '[]'
147+
logs: ''
148+
```
149+
103150
## Development
104151

105152
Install the dependencies

__tests__/main.test.ts

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@ describe('unit-tests', () => {
2424
expect(dd.sendMetrics).toHaveBeenCalledWith(
2525
'https://api.datadoghq.com',
2626
'fooBarBaz',
27-
[]
27+
[],
28+
false
2829
)
2930
})
3031

@@ -34,7 +35,8 @@ describe('unit-tests', () => {
3435
expect(dd.sendMetrics).toHaveBeenCalledWith(
3536
'http://example.com',
3637
'fooBarBaz',
37-
[]
38+
[],
39+
false
3840
)
3941
process.env['INPUT_API-URL'] = ''
4042
})
@@ -44,7 +46,8 @@ describe('unit-tests', () => {
4446
expect(dd.sendLogs).toHaveBeenCalledWith(
4547
'https://http-intake.logs.datadoghq.com',
4648
'fooBarBaz',
47-
[]
49+
[],
50+
false
4851
)
4952
})
5053

@@ -54,7 +57,8 @@ describe('unit-tests', () => {
5457
expect(dd.sendLogs).toHaveBeenCalledWith(
5558
'http://example.com',
5659
'fooBarBaz',
57-
[]
60+
[],
61+
false
5862
)
5963
process.env['INPUT_LOG-API-URL'] = ''
6064
})
@@ -64,22 +68,26 @@ describe('unit-tests', () => {
6468
expect(dd.sendMetrics).toHaveBeenCalledWith(
6569
'https://api.datadoghq.com',
6670
'fooBarBaz',
67-
[]
71+
[],
72+
false
6873
)
6974
expect(dd.sendEvents).toHaveBeenCalledWith(
7075
'https://api.datadoghq.com',
7176
'fooBarBaz',
72-
[]
77+
[],
78+
false
7379
)
7480
expect(dd.sendServiceChecks).toHaveBeenCalledWith(
7581
'https://api.datadoghq.com',
7682
'fooBarBaz',
77-
[]
83+
[],
84+
false
7885
)
7986
expect(dd.sendLogs).toHaveBeenCalledWith(
8087
'https://http-intake.logs.datadoghq.com',
8188
'fooBarBaz',
82-
[]
89+
[],
90+
false
8391
)
8492
})
8593
})

action.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ inputs:
2323
logs:
2424
description: 'A list of log objects to send, see docs for details'
2525
default: '[]'
26+
ignore-timeouts:
27+
description: 'Ignore timeout errors and continue execution'
28+
required: false
29+
default: 'false'
2630
runs:
2731
using: 'node20'
2832
main: 'dist/index.js'

src/datadog.ts

Lines changed: 111 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -42,32 +42,51 @@ export function getClient(apiKey: string): httpm.HttpClient {
4242
})
4343
}
4444

45+
function isTimeoutError(error: Error): boolean {
46+
return error.message.includes('timeout') || error.message.includes('Timeout')
47+
}
48+
4549
async function postMetricsIfAny(
4650
http: httpm.HttpClient,
4751
apiURL: string,
4852
metrics: {series: Array<Record<string, unknown>>},
49-
endpoint: string
53+
endpoint: string,
54+
ignoreTimeouts: boolean
5055
): Promise<void> {
5156
// POST data
5257
if (metrics.series.length) {
53-
core.debug(`About to send ${metrics.series.length} metrics`)
54-
const res: httpm.HttpClientResponse = await http.post(
55-
`${apiURL}/api/${endpoint}`,
56-
JSON.stringify(metrics)
57-
)
58-
59-
if (res.message.statusCode === undefined || res.message.statusCode >= 400) {
60-
throw new Error(
61-
`HTTP request failed: ${res.message.statusMessage} ${res.message.statusCode}`
58+
try {
59+
core.debug(`About to send ${metrics.series.length} metrics`)
60+
const res: httpm.HttpClientResponse = await http.post(
61+
`${apiURL}/api/${endpoint}`,
62+
JSON.stringify(metrics)
6263
)
64+
65+
if (
66+
res.message.statusCode === undefined ||
67+
res.message.statusCode >= 400
68+
) {
69+
throw new Error(
70+
`HTTP request failed: ${res.message.statusMessage} ${res.message.statusCode}`
71+
)
72+
}
73+
} catch (error) {
74+
if (ignoreTimeouts && isTimeoutError(error)) {
75+
core.warning(
76+
`Timeout occurred while sending metrics, but continuing due to ignore-timeout setting`
77+
)
78+
return
79+
}
80+
throw error
6381
}
6482
}
6583
}
6684

6785
export async function sendMetrics(
6886
apiURL: string,
6987
apiKey: string,
70-
metrics: Metric[]
88+
metrics: Metric[],
89+
ignoreTimeouts: boolean
7190
): Promise<void> {
7291
const http: httpm.HttpClient = getClient(apiKey)
7392
// distributions use a different procotol.
@@ -89,27 +108,53 @@ export async function sendMetrics(
89108
})
90109
}
91110

92-
await postMetricsIfAny(http, apiURL, otherMetrics, 'v1/series')
93-
await postMetricsIfAny(http, apiURL, distributions, 'v1/distribution_points')
111+
await postMetricsIfAny(
112+
http,
113+
apiURL,
114+
otherMetrics,
115+
'v1/series',
116+
ignoreTimeouts
117+
)
118+
await postMetricsIfAny(
119+
http,
120+
apiURL,
121+
distributions,
122+
'v1/distribution_points',
123+
ignoreTimeouts
124+
)
94125
}
95126

96127
export async function sendEvents(
97128
apiURL: string,
98129
apiKey: string,
99-
events: Event[]
130+
events: Event[],
131+
ignoreTimeouts: boolean
100132
): Promise<void> {
101133
const http: httpm.HttpClient = getClient(apiKey)
102134
let errors = 0
103135

104136
core.debug(`About to send ${events.length} events`)
105137
for (const ev of events) {
106-
const res: httpm.HttpClientResponse = await http.post(
107-
`${apiURL}/api/v1/events`,
108-
JSON.stringify(ev)
109-
)
110-
if (res.message.statusCode === undefined || res.message.statusCode >= 400) {
111-
errors++
112-
core.error(`HTTP request failed: ${res.message.statusMessage}`)
138+
try {
139+
const res: httpm.HttpClientResponse = await http.post(
140+
`${apiURL}/api/v1/events`,
141+
JSON.stringify(ev)
142+
)
143+
if (
144+
res.message.statusCode === undefined ||
145+
res.message.statusCode >= 400
146+
) {
147+
errors++
148+
core.error(`HTTP request failed: ${res.message.statusMessage}`)
149+
}
150+
} catch (error) {
151+
if (ignoreTimeouts && isTimeoutError(error)) {
152+
core.warning(
153+
`Timeout occurred while sending event, but continuing due to ignore-timeout setting`
154+
)
155+
continue
156+
}
157+
throw error
113158
}
114159
}
115160

@@ -121,20 +166,34 @@ export async function sendEvents(
121166
export async function sendServiceChecks(
122167
apiURL: string,
123168
apiKey: string,
124-
serviceChecks: ServiceCheck[]
169+
serviceChecks: ServiceCheck[],
170+
ignoreTimeouts: boolean
125171
): Promise<void> {
126172
const http: httpm.HttpClient = getClient(apiKey)
127173
let errors = 0
128174

129175
core.debug(`About to send ${serviceChecks.length} service checks`)
130176
for (const sc of serviceChecks) {
131-
const res: httpm.HttpClientResponse = await http.post(
132-
`${apiURL}/api/v1/check_run`,
133-
JSON.stringify(sc)
134-
)
135-
if (res.message.statusCode === undefined || res.message.statusCode >= 400) {
136-
errors++
137-
core.error(`HTTP request failed: ${res.message.statusMessage}`)
177+
try {
178+
const res: httpm.HttpClientResponse = await http.post(
179+
`${apiURL}/api/v1/check_run`,
180+
JSON.stringify(sc)
181+
)
182+
if (
183+
res.message.statusCode === undefined ||
184+
res.message.statusCode >= 400
185+
) {
186+
errors++
187+
core.error(`HTTP request failed: ${res.message.statusMessage}`)
188+
}
189+
} catch (error) {
190+
if (ignoreTimeouts && isTimeoutError(error)) {
191+
core.warning(
192+
`Timeout occurred while sending service check, but continuing due to ignore-timeout setting`
193+
)
194+
continue
195+
}
196+
throw error
138197
}
139198
}
140199

@@ -148,21 +207,35 @@ export async function sendServiceChecks(
148207
export async function sendLogs(
149208
logApiURL: string,
150209
apiKey: string,
151-
logs: Log[]
210+
logs: Log[],
211+
ignoreTimeouts: boolean
152212
): Promise<void> {
153213
const http: httpm.HttpClient = getClient(apiKey)
154214
let errors = 0
155215

156216
core.debug(`About to send ${logs.length} logs`)
157217
for (const log of logs) {
158-
const res: httpm.HttpClientResponse = await http.post(
159-
`${logApiURL}/v1/input`,
160-
JSON.stringify(log)
161-
)
162-
if (res.message.statusCode === undefined || res.message.statusCode >= 400) {
163-
errors++
164-
core.error(`HTTP request failed: ${res.message.statusMessage}`)
165-
throw new Error(`Failed sending ${errors} out of ${logs.length} events`)
218+
try {
219+
const res: httpm.HttpClientResponse = await http.post(
220+
`${logApiURL}/v1/input`,
221+
JSON.stringify(log)
222+
)
223+
if (
224+
res.message.statusCode === undefined ||
225+
res.message.statusCode >= 400
226+
) {
227+
errors++
228+
core.error(`HTTP request failed: ${res.message.statusMessage}`)
229+
throw new Error(`Failed sending ${errors} out of ${logs.length} events`)
230+
}
231+
} catch (error) {
232+
if (ignoreTimeouts && isTimeoutError(error)) {
233+
core.warning(
234+
`Timeout occurred while sending logs, but continuing due to ignore-timeout setting`
235+
)
236+
continue
237+
}
238+
throw error
166239
}
167240
}
168241

src/run.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,22 +5,23 @@ import * as dd from './datadog'
55
export async function run(): Promise<void> {
66
const apiKey: string = core.getInput('api-key', {required: true})
77
const apiURL: string = core.getInput('api-url') || 'https://api.datadoghq.com'
8+
const ignoreTimeouts: boolean = core.getInput('ignore-timeouts') === 'false'
89

910
const metrics: dd.Metric[] =
1011
(yaml.safeLoad(core.getInput('metrics')) as dd.Metric[]) || []
11-
await dd.sendMetrics(apiURL, apiKey, metrics)
12+
await dd.sendMetrics(apiURL, apiKey, metrics, ignoreTimeouts)
1213

1314
const events: dd.Event[] =
1415
(yaml.safeLoad(core.getInput('events')) as dd.Event[]) || []
15-
await dd.sendEvents(apiURL, apiKey, events)
16+
await dd.sendEvents(apiURL, apiKey, events, ignoreTimeouts)
1617

1718
const serviceChecks: dd.ServiceCheck[] =
1819
(yaml.safeLoad(core.getInput('service-checks')) as dd.ServiceCheck[]) || []
19-
await dd.sendServiceChecks(apiURL, apiKey, serviceChecks)
20+
await dd.sendServiceChecks(apiURL, apiKey, serviceChecks, ignoreTimeouts)
2021

2122
const logApiURL: string =
2223
core.getInput('log-api-url') || 'https://http-intake.logs.datadoghq.com'
2324
const logs: dd.Log[] =
2425
(yaml.safeLoad(core.getInput('logs')) as dd.Log[]) || []
25-
await dd.sendLogs(logApiURL, apiKey, logs)
26+
await dd.sendLogs(logApiURL, apiKey, logs, ignoreTimeouts)
2627
}

0 commit comments

Comments
 (0)