Skip to content

Commit 6b6d93a

Browse files
jchrostek-ddclaude
andauthored
chore(integration-tests): improve error logging for better debugging (#1109)
## Summary - Integration tests can sometimes be a little flaky. It isn't clear what the errors are, but seems like some sort of request throttling. Rerunning the tests succeeds. Adding some better logging for this. - No changes to actual extension code. ## Changes - **invoke.ts**: Log `TooManyRequestsException` reason and `retryAfterSeconds` - **lambda.ts**: Add detailed error logging to `publishVersion()`, `setTimestampEnvVar()`, `waitForSnapStartReady()` - **datadog.ts**: Log HTTP status, rate limit headers, and query context for API errors - **misc**: Moved lambda invoke code from invoke.ts to lambda.ts. ## Example Output ``` Lambda invocation failed for 'my-function': TooManyRequestsException - Rate exceeded - (Reason: ConcurrentSnapshotCreateLimitExceeded) - (retryAfterSeconds: 5) Error searching traces: Error (query: 'service:my-service @request_id:abc123'): HTTP 429 Too Many Requests (retry-after: 30s) ``` ## Test plan - [x] TypeScript build passes - [x] Manual verification during next integration test run 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 7225b58 commit 6b6d93a

4 files changed

Lines changed: 119 additions & 55 deletions

File tree

integration-tests/tests/utils/datadog.ts

Lines changed: 48 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import axios, { AxiosInstance } from 'axios';
1+
import axios, { AxiosInstance, AxiosError } from 'axios';
22

33
const DD_API_KEY = process.env.DD_API_KEY;
44
const DD_APP_KEY = process.env.DD_APP_KEY;
@@ -17,6 +17,43 @@ const datadogClient: AxiosInstance = axios.create({
1717
timeout: 30000,
1818
});
1919

20+
function formatDatadogError(error: unknown, query: string): string {
21+
if (error instanceof AxiosError && error.response) {
22+
const status = error.response.status;
23+
const statusText = error.response.statusText || '';
24+
let message = `HTTP ${status} ${statusText}`.trim();
25+
26+
// Include rate limit info for 429 errors
27+
if (status === 429) {
28+
const retryAfter = error.response.headers['x-ratelimit-reset'] || error.response.headers['retry-after'];
29+
if (retryAfter) {
30+
message += ` (retry-after: ${retryAfter}s)`;
31+
}
32+
const remaining = error.response.headers['x-ratelimit-remaining'];
33+
if (remaining !== undefined) {
34+
message += ` (remaining: ${remaining})`;
35+
}
36+
}
37+
38+
// Include response body if available
39+
const responseData = error.response.data;
40+
if (responseData) {
41+
const bodyStr = typeof responseData === 'string' ? responseData : JSON.stringify(responseData);
42+
if (bodyStr && bodyStr !== '{}') {
43+
message += ` - ${bodyStr}`;
44+
}
45+
}
46+
47+
return `Error (query: '${query}'): ${message}`;
48+
}
49+
50+
if (error instanceof Error) {
51+
return `Error (query: '${query}'): ${error.message}`;
52+
}
53+
54+
return `Error (query: '${query}'): ${String(error)}`;
55+
}
56+
2057
export interface DatadogTelemetry {
2158
requestId: string;
2259
statusCode?: number;
@@ -72,12 +109,11 @@ export async function getTraces(
72109
const now = Date.now();
73110
const fromTime = now - (1 * 60 * 60 * 1000); // 1 hour ago
74111
const toTime = now;
75-
try {
76-
// Convert service name to lowercase as Datadog stores it that way
77-
const serviceNameLower = serviceName.toLowerCase();
78-
79-
const query = `service:${serviceNameLower} @request_id:${requestId}`;
112+
// Convert service name to lowercase as Datadog stores it that way
113+
const serviceNameLower = serviceName.toLowerCase();
114+
const query = `service:${serviceNameLower} @request_id:${requestId}`;
80115

116+
try {
81117
console.log(`Searching for traces: ${query}`);
82118

83119
// First, find spans matching the request_id to get trace IDs
@@ -164,8 +200,8 @@ export async function getTraces(
164200
}
165201

166202
return traces;
167-
} catch (error: any) {
168-
console.error('Error searching traces:', error.response?.data || error.message);
203+
} catch (error: unknown) {
204+
console.error(`Error searching traces: ${formatDatadogError(error, query)}`);
169205
throw error;
170206
}
171207
}
@@ -182,9 +218,9 @@ export async function getLogs(
182218
const now = Date.now();
183219
const fromTime = now - (2 * 60 * 60 * 1000); // 2 hours ago
184220
const toTime = now;
185-
try {
186-
const query = `service:${serviceName} @lambda.request_id:${requestId}`;
221+
const query = `service:${serviceName} @lambda.request_id:${requestId}`;
187222

223+
try {
188224
console.log(`Searching for logs: ${query}`);
189225

190226
const response = await datadogClient.post('/api/v2/logs/events/search', {
@@ -214,8 +250,8 @@ export async function getLogs(
214250
});
215251

216252
return logs;
217-
} catch (error: any) {
218-
console.error('Error searching logs:', error.response?.data || error.message);
253+
} catch (error: unknown) {
254+
console.error(`Error searching logs: ${formatDatadogError(error, query)}`);
219255
throw error;
220256
}
221257
}

integration-tests/tests/utils/default.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { invokeLambda, InvocationResult } from './invoke';
1+
import { invokeLambda, InvocationResult } from './lambda';
22
import { getDatadogTelemetryByRequestId, DatadogTelemetry } from './datadog';
33
import { DEFAULT_DATADOG_INDEXING_WAIT_MS } from '../../config';
44

integration-tests/tests/utils/invoke.ts

Lines changed: 0 additions & 35 deletions
This file was deleted.

integration-tests/tests/utils/lambda.ts

Lines changed: 70 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,70 @@
1-
import { LambdaClient, UpdateFunctionConfigurationCommand, GetFunctionConfigurationCommand, PublishVersionCommand } from '@aws-sdk/client-lambda';
1+
import {
2+
LambdaClient,
3+
InvokeCommand,
4+
UpdateFunctionConfigurationCommand,
5+
GetFunctionConfigurationCommand,
6+
PublishVersionCommand,
7+
TooManyRequestsException,
8+
ServiceException,
9+
} from '@aws-sdk/client-lambda';
210

311
const lambdaClient = new LambdaClient({ region: 'us-east-1' });
412

13+
function formatLambdaError(error: unknown): string {
14+
if (error instanceof TooManyRequestsException) {
15+
const parts = [error.name, error.message];
16+
if (error.Reason) {
17+
parts.push(`(Reason: ${error.Reason})`);
18+
}
19+
if (error.retryAfterSeconds !== undefined) {
20+
parts.push(`(retryAfterSeconds: ${error.retryAfterSeconds})`);
21+
}
22+
return parts.join(' - ');
23+
}
24+
25+
if (error instanceof ServiceException) {
26+
return `${error.name} - ${error.message}`;
27+
}
28+
29+
if (error instanceof Error) {
30+
return error.message;
31+
}
32+
33+
return String(error);
34+
}
35+
36+
export interface InvocationResult {
37+
functionName: string;
38+
requestId: string;
39+
statusCode?: number;
40+
}
41+
42+
export async function invokeLambda(
43+
functionName: string,
44+
payload: any = {},
45+
): Promise<InvocationResult> {
46+
const command = new InvokeCommand({
47+
FunctionName: functionName,
48+
Payload: JSON.stringify(payload),
49+
});
50+
51+
let response;
52+
try {
53+
response = await lambdaClient.send(command);
54+
} catch (error: unknown) {
55+
console.error(`Lambda invocation failed for '${functionName}': ${formatLambdaError(error)}`);
56+
throw error;
57+
}
58+
59+
const requestId: string = response.$metadata.requestId || '';
60+
61+
return {
62+
functionName,
63+
requestId,
64+
statusCode: response.StatusCode || 200,
65+
};
66+
}
67+
568
export async function forceColdStart(functionName: string): Promise<void> {
669
await setTimestampEnvVar(functionName);
770
await new Promise(resolve => setTimeout(resolve, 10000));
@@ -19,8 +82,8 @@ export async function publishVersion(functionName: string): Promise<string> {
1982
const version = response.Version || '$LATEST';
2083
console.debug(`Published version: ${version} for ${functionName}`);
2184
return version;
22-
} catch (error: any) {
23-
console.error('Failed to publish Lambda version:', error.message);
85+
} catch (error: unknown) {
86+
console.error(`Failed to publish Lambda version for '${functionName}': ${formatLambdaError(error)}`);
2487
throw error;
2588
}
2689
}
@@ -45,8 +108,8 @@ export async function setTimestampEnvVar(functionName: string): Promise<void> {
45108
Environment: updatedEnvironment,
46109
});
47110
await lambdaClient.send(updateConfigCommand);
48-
} catch (error: any) {
49-
console.error('Failed to set timestamp environment variable:', error.message);
111+
} catch (error: unknown) {
112+
console.error(`Failed to set timestamp environment variable for '${functionName}': ${formatLambdaError(error)}`);
50113
throw error;
51114
}
52115
}
@@ -71,8 +134,8 @@ export async function waitForSnapStartReady(functionName: string, version: strin
71134
}
72135

73136
await new Promise(resolve => setTimeout(resolve, 10000));
74-
} catch (error: any) {
75-
console.error(`Error checking SnapStart status: ${error.message}`);
137+
} catch (error: unknown) {
138+
console.error(`Error checking SnapStart status for '${functionName}:${version}': ${formatLambdaError(error)}`);
76139
await new Promise(resolve => setTimeout(resolve, 10_000));
77140
}
78141
}

0 commit comments

Comments
 (0)