| 149 | } |
| 150 | |
| 151 | async function postWithRetries(input: PostInput): Promise<Response> { |
| 152 | const { body, headers } = input.prepared |
| 153 | let lastError: unknown |
| 154 | for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) { |
| 155 | if (input.signal.aborted) throw input.signal.reason ?? new Error('Aborted') |
| 156 | const perAttempt = AbortSignal.any([input.signal, AbortSignal.timeout(PER_ATTEMPT_TIMEOUT_MS)]) |
| 157 | let retryAfterMs: number | null = null |
| 158 | let response: Response | undefined |
| 159 | try { |
| 160 | response = await fetch(input.url, { |
| 161 | method: 'POST', |
| 162 | // double-cast-allowed: Uint8Array is a valid runtime BodyInit but the DOM lib types only enumerate Blob/FormData/string/etc. |
| 163 | body: body as unknown as BodyInit, |
| 164 | headers, |
| 165 | signal: perAttempt, |
| 166 | }) |
| 167 | } catch (error) { |
| 168 | lastError = error |
| 169 | logger.debug('Datadog request failed', { attempt, error: toError(error).message }) |
| 170 | } |
| 171 | if (response) { |
| 172 | if (response.ok) { |
| 173 | /** Drain the success body so undici can return the socket to the keep-alive pool. Headers remain readable after consumption. */ |
| 174 | await response.text().catch(() => '') |
| 175 | return response |
| 176 | } |
| 177 | if (!isRetryableStatus(response.status)) { |
| 178 | const text = await response.text().catch(() => '') |
| 179 | throw new Error(`Datadog responded with HTTP ${response.status}: ${text}`) |
| 180 | } |
| 181 | lastError = new Error(`Datadog responded with HTTP ${response.status}`) |
| 182 | retryAfterMs = parseRetryAfter(response.headers.get('retry-after')) |
| 183 | /** Drain the retryable response body so undici can return the socket to the keep-alive pool. */ |
| 184 | await response.text().catch(() => '') |
| 185 | } |
| 186 | if (attempt < MAX_ATTEMPTS) { |
| 187 | await sleepUntilAborted(backoffWithJitter(attempt, retryAfterMs), input.signal) |
| 188 | } |
| 189 | } |
| 190 | throw lastError instanceof Error ? lastError : new Error('Datadog delivery failed after retries') |
| 191 | } |
| 192 | |
| 193 | export const datadogDestination: DrainDestination< |
| 194 | DatadogDestinationConfig, |