diff --git a/packages/restate-sdk/src/endpoint/handlers/generic.ts b/packages/restate-sdk/src/endpoint/handlers/generic.ts index 1a45036c..72edc6f5 100644 --- a/packages/restate-sdk/src/endpoint/handlers/generic.ts +++ b/packages/restate-sdk/src/endpoint/handlers/generic.ts @@ -17,8 +17,8 @@ import { TerminalError, } from "../../types/errors.js"; import type { - ProtocolMode, Endpoint as EndpointManifest, + ProtocolMode, } from "../discovery.js"; import type { Component, ComponentHandler } from "../components.js"; import { parseUrlComponents } from "../components.js"; @@ -37,6 +37,7 @@ import { } from "../../logging/console_logger_transport.js"; import { LoggerContext, + type LoggerTransport, LogSource, RestateLogLevel, } from "../../logging/logger_transport.js"; @@ -83,6 +84,47 @@ const ENDPOINT_MANIFEST_V2 = "application/vnd.restate.endpointmanifest.v2+json"; const ENDPOINT_MANIFEST_V3 = "application/vnd.restate.endpointmanifest.v3+json"; const ENDPOINT_MANIFEST_V4 = "application/vnd.restate.endpointmanifest.v4+json"; +export function tryCreateContextualLogger( + loggerTransport: LoggerTransport, + url: string, + headers: Headers, + additionalContext?: { [name: string]: string } +): Logger | undefined { + try { + const path = new URL(url, "https://example.com").pathname; + const parsed = parseUrlComponents(path); + if (parsed.type !== "invoke") { + return undefined; + } + const invocationId = invocationIdFromHeaders(headers); + return createLogger( + loggerTransport, + LogSource.SYSTEM, + new LoggerContext( + invocationId, + parsed.componentName, + parsed.handlerName, + undefined, + undefined, + additionalContext + ) + ); + } catch (e) { + return undefined; + } +} + +function invocationIdFromHeaders(headers: Headers) { + const invocationIdHeader = headers["x-restate-invocation-id"]; + const invocationId = + typeof invocationIdHeader === "string" + ? invocationIdHeader + : Array.isArray(invocationIdHeader) + ? invocationIdHeader[0] ?? "unknown id" + : "unknown id"; + return invocationId; +} + /** * This is an internal API to support 'fetch' like handlers. * It supports both request-reply mode and bidirectional streaming mode. @@ -130,8 +172,14 @@ export class GenericHandler implements RestateHandler { return await this._handle(request, context); } catch (e) { const error = ensureError(e); - this.endpoint.rlog.error( - "Error while handling invocation: " + (error.stack ?? error.message) + ( + tryCreateContextualLogger( + this.endpoint.loggerTransport, + request.url, + request.headers + ) ?? this.endpoint.rlog + ).error( + "Error while handling request: " + (error.stack ?? error.message) ); return this.toErrorResponse( error instanceof RestateError ? error.code : 500, @@ -285,7 +333,14 @@ export class GenericHandler implements RestateHandler { createLogger( this.endpoint.loggerTransport, LogSource.JOURNAL, - undefined + new LoggerContext( + invocationIdFromHeaders(headers), + service.name(), + handler.name(), + undefined, + undefined, + additionalContext + ) ) ); diff --git a/packages/restate-sdk/src/endpoint/handlers/lambda.ts b/packages/restate-sdk/src/endpoint/handlers/lambda.ts index 5a8790f0..ac68127b 100644 --- a/packages/restate-sdk/src/endpoint/handlers/lambda.ts +++ b/packages/restate-sdk/src/endpoint/handlers/lambda.ts @@ -22,6 +22,7 @@ import type { RestateRequest, RestateResponse, } from "./generic.js"; +import { tryCreateContextualLogger } from "./generic.js"; import { WritableStream, type ReadableStream } from "node:stream/web"; import { OnceStream } from "../../utils/streams.js"; import { X_RESTATE_SERVER } from "../../user_agent.js"; @@ -131,7 +132,13 @@ export class LambdaHandler { } catch (e) { // unlike in the streaming case, we can actually catch errors in the response body and form a nicer error const error = ensureError(e); - this.handler.endpoint.rlog.error( + ( + tryCreateContextualLogger( + this.handler.endpoint.loggerTransport, + request.url, + request.headers + ) ?? this.handler.endpoint.rlog + ).error( "Error while collecting invocation response: " + (error.stack ?? error.message) ); diff --git a/packages/restate-sdk/src/endpoint/node_endpoint.ts b/packages/restate-sdk/src/endpoint/node_endpoint.ts index b01d5033..1b45e5b4 100644 --- a/packages/restate-sdk/src/endpoint/node_endpoint.ts +++ b/packages/restate-sdk/src/endpoint/node_endpoint.ts @@ -24,7 +24,10 @@ import type { Http2ServerRequest, Http2ServerResponse } from "http2"; import * as http2 from "http2"; import type { Endpoint } from "./endpoint.js"; import { EndpointBuilder } from "./endpoint.js"; -import { GenericHandler } from "./handlers/generic.js"; +import { + GenericHandler, + tryCreateContextualLogger, +} from "./handlers/generic.js"; import { Readable, Writable } from "node:stream"; import type { WritableStream } from "node:stream/web"; import { ensureError } from "../types/errors.js"; @@ -155,9 +158,30 @@ function nodeHttp2Handler( await resp.body.pipeTo(responseWeb); } catch (e) { const error = ensureError(e); - endpoint.rlog.error( - "Error while handling connection: " + (error.stack ?? error.message) - ); + + const logger = + tryCreateContextualLogger( + endpoint.loggerTransport, + request.url, + request.headers + ) ?? endpoint.rlog; + if (error.name === "AbortError") { + logger.error( + "Got abort error from connection: " + + error.message + + "\n" + + "This might indicate that:\n" + + "* The restate-server aborted the connection after hitting the 'abort-timeout'\n" + + "* The connection with the restate-server was lost\n" + + "\n" + + "Please check the invocation in the Restate UI for more details." + ); + } else { + logger.error( + "Error while handling request: " + (error.stack ?? error.message) + ); + } + response.destroy(error); abortController.abort(); }