Repository navigation
Forward each invocation's logs before Lambda freezes, with an E2E of the AWS Lambda docs - #5
Merged
Merged
Conversation
Follows https://betterstack.com/docs/logs/aws-lambda/ as written: the article's code blocks are read from the page, the layer is built with the article's pnpm commands (latest pnpm, pnpm 9 when that fails) and published with its publish-layer-version command, and one function per runtime and architecture it lists runs its logging snippet with its environment variables. Python functions are packaged from its pip lines and run its setup and logging snippets. Each line must be stored once, as logged, with dt inside the invocation, in order, and received within 60 s. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The article's first block may now either build this repository or download the latest release ZIP. The schedule downloads what the article links; pull requests and pushes to main build the checkout the way releases.yml does. The layer job prints the ZIP's file modes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A job that failed before its credentials step had its cleanup fail on every AWS listing. Credentials now come first; without them the job created nothing in AWS and the cleanup says so. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
One invocation never got a line stored in 15 minutes: Lambda freezes the execution environment as soon as the invocation returns, so the layer only hands over its lines when the environment thaws, and Python's background flush never runs. The first read-back now waits 90 s for the first invocation's lines, a second invocation follows, and the second read-back waits up to 15 minutes for both invocations' lines. Rows are assigned to the invocation their dt falls into. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
To tell a delay of the environment's initialization from one that every invocation has, the function is now invoked once (a cold start, whose init duration the summary shows), read back for 90 s, then invoked four more times 60 s apart. The final read-back says for every line during which invocation, or after which one, Better Stack received it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The article's Python step is about to show the logging calls inside `def lambda_handler(event, context):`, ending with a flush of the Better Stack handler. The docs job now takes either shape: bare logging calls still become the body of lambda_handler, and a handler function is used as written, with its name as the Lambda handler. A `<name>.flush()` call is allowed among the logging calls. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The extension now subscribes to platform events, waits for each invocation's platform.runtimeDone (or until a second before its deadline), forwards that invocation's logs, and only then asks for the next event. Tests first in b467311 (red), fix in 97ca7c8 and a378acb. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A request Better Stack never answers held the extension, and so the invocation, until the function's timeout. The forwarder must give up after its timeout and keep the logs queued. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The forward had no timeout, so an endpoint that never answered held the invocation until the function's timeout. The request now aborts after REQUEST_TIMEOUT_MS (1000 ms) and the logs stay queued for the next forward. The fallback before an invocation's deadline uses the same margin, so a forward always ends before the deadline. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The article's Python handler is about to wrap its logging calls in try and flush the Better Stack handler in finally. The docs job unwraps a plain try/finally (no except, no else) and reads the calls in both parts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The extension waits for Lambda's telemetry hand-off at the end of every invocation, and Lambda hands the last batch over only when TIMEOUT_MS expires, so the window is added to every invocation. 25 ms keeps that wait short; Better Stack still gets one request per invocation. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Logs sent through this layer reached Better Stack one or two invocations late, and the last invocation's logs before the execution environment shut down never arrived. This forwards each invocation's logs before Lambda freezes the environment, and adds an E2E workflow that follows the AWS Lambda docs article exactly as written to prove it.
Why logs were late:
TIMEOUT_MSbatching window closes, but Lambda freezes the environment as soon as the runtime and the extension ask for the next event. An invocation's logs reached the extension only when the next invocation thawed it.INVOKE, before those logs arrived, so they waited one invocation more. On a quiet function that is minutes.SHUTDOWNthe extension forwarded before Lambda handed over the last logs, so those were lost.The fix:
platformevents, and the HTTP listener tellsplatform.runtimeDoneapart from the function's logs; other platform events are dropped. A batch with a malformed function log is still rejected.INVOKE, the extension waits for that invocation'splatform.runtimeDone(events arrive in order, so its logs are queued by then), forwards them in one request, and only then asks for the next event. If the report never comes, it forwards anyway one second before the invocation's deadline, so the extension never makes an invocation time out.TIMEOUT_MSafter the handler finished, plus the request to Better Stack. That time is billed, and a synchronous caller waits for it, soTIMEOUT_MSnow defaults to 25 ms, Lambda's minimum, instead of 1000 ms. It is how long Lambda buffers telemetry before the hand-off inside the sandbox, not how often the extension sends to Better Stack, which stays one request per invocation. In the E2E, a handler logging two lines took 344 to 692 ms with the old extension, 1366 to 1428 ms with a 1000 ms window and 435 to 778 ms with 25 ms.The E2E (
.github/workflows/e2e.yml):mainbuild the checkout the wayreleases.ymldoes, so a PR tests its own changes. The workflow also understands the older build-from-source shape of the article.dtinside that invocation, in order, and received within 60 s of the invocation returning. The summary says during which invocation Better Stack received each line and lists each invocation's duration.e2e-lambda-docs-*. Every job deletes what it created and stale leftovers of its own runtime and architecture.The tests for the fix came first in their own commits (red in CI), then the fixes (green), in #7 and #8, which this PR now contains.
🤖 Generated with Claude Code