Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -504,7 +504,18 @@ export class AwsLambdaInstrumentation extends InstrumentationBase<AwsLambdaInstr
);
}

Promise.all(flushers).then(callback, callback);
const FORCE_FLUSH_TIMEOUT_MS = 2000;
let timeoutId: ReturnType<typeof setTimeout>;
const timeoutPromise = new Promise<void>(resolve => {
timeoutId = setTimeout(resolve, FORCE_FLUSH_TIMEOUT_MS);
timeoutId.unref();
});
Promise.race([Promise.all(flushers), timeoutPromise])
.catch(() => {})
.finally(() => {
clearTimeout(timeoutId);
callback();
});
}

/**
Expand Down
99 changes: 99 additions & 0 deletions packages/aws-serverless/test/instrumentation.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import { SpanStatusCode } from '@opentelemetry/api';
import { afterEach, describe, expect, test, vi } from 'vitest';
import { AwsLambdaInstrumentation } from '../src/integration/instrumentation-aws-lambda/instrumentation';

function createMockTracerProvider(forceFlushImpl: () => Promise<void>) {
return {
getTracer: () => ({
startSpan: vi.fn(),
startActiveSpan: vi.fn(),
}),
forceFlush: forceFlushImpl,
};
}

describe('AwsLambdaInstrumentation', () => {
describe('_endSpan', () => {
afterEach(() => {
vi.useRealTimers();
});

test('callback fires even when tracerProvider.forceFlush() never resolves', async () => {
vi.useFakeTimers();

const instrumentation = new AwsLambdaInstrumentation();

const hangingProvider = createMockTracerProvider(() => new Promise<void>(() => {}));
instrumentation.setTracerProvider(hangingProvider as any);

const mockSpan = {
end: vi.fn(),
recordException: vi.fn(),
setStatus: vi.fn(),
};

const callback = vi.fn();

(instrumentation as any)._endSpan(mockSpan, undefined, callback);

// Advance past any reasonable timeout (e.g. 5s) — the callback should fire
// within a bounded time even if forceFlush() hangs forever.
await vi.advanceTimersByTimeAsync(5_000);

expect(mockSpan.end).toHaveBeenCalled();
expect(callback).toHaveBeenCalledTimes(1);

vi.useRealTimers();
});

test('callback fires promptly when tracerProvider.forceFlush() resolves', async () => {
const instrumentation = new AwsLambdaInstrumentation();

const normalProvider = createMockTracerProvider(() => Promise.resolve());
instrumentation.setTracerProvider(normalProvider as any);

const mockSpan = {
end: vi.fn(),
recordException: vi.fn(),
setStatus: vi.fn(),
};

const callback = vi.fn();

(instrumentation as any)._endSpan(mockSpan, undefined, callback);

await new Promise(resolve => setTimeout(resolve, 10));

expect(callback).toHaveBeenCalledTimes(1);
expect(mockSpan.end).toHaveBeenCalled();
});

test('error information is set on span before flush attempt', async () => {
const instrumentation = new AwsLambdaInstrumentation();

const normalProvider = createMockTracerProvider(() => Promise.resolve());
instrumentation.setTracerProvider(normalProvider as any);

const mockSpan = {
end: vi.fn(),
recordException: vi.fn(),
setStatus: vi.fn(),
};

const error = new Error('lambda failure');
const callback = vi.fn();

(instrumentation as any)._endSpan(mockSpan, error, callback);

await new Promise(resolve => setTimeout(resolve, 10));

expect(mockSpan.recordException).toHaveBeenCalledWith(error);
expect(mockSpan.setStatus).toHaveBeenCalledWith({
code: SpanStatusCode.ERROR,
message: 'lambda failure',
});
expect(mockSpan.end).toHaveBeenCalled();
expect(callback).toHaveBeenCalledTimes(1);
});
});
});
Loading