@mongodb-js/agent-engine-runner-shared 0.11.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +55 -0
- package/LICENSE.md +201 -0
- package/README.md +29 -0
- package/dist/agent_config.d.ts +167 -0
- package/dist/agent_config.d.ts.map +1 -0
- package/dist/agent_config.js +544 -0
- package/dist/call_interrupted.d.ts +12 -0
- package/dist/call_interrupted.d.ts.map +1 -0
- package/dist/call_interrupted.js +11 -0
- package/dist/checkpoint_workspace.d.ts +25 -0
- package/dist/checkpoint_workspace.d.ts.map +1 -0
- package/dist/checkpoint_workspace.js +44 -0
- package/dist/context.d.ts +235 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +322 -0
- package/dist/db_config.d.ts +28 -0
- package/dist/db_config.d.ts.map +1 -0
- package/dist/db_config.js +66 -0
- package/dist/db_naming.d.ts +54 -0
- package/dist/db_naming.d.ts.map +1 -0
- package/dist/db_naming.js +94 -0
- package/dist/error_reporting.d.ts +67 -0
- package/dist/error_reporting.d.ts.map +1 -0
- package/dist/error_reporting.js +311 -0
- package/dist/generated/workflow/v1/activity_pb.d.ts +342 -0
- package/dist/generated/workflow/v1/activity_pb.d.ts.map +1 -0
- package/dist/generated/workflow/v1/activity_pb.js +115 -0
- package/dist/generated/workflow/v1/common_pb.d.ts +184 -0
- package/dist/generated/workflow/v1/common_pb.d.ts.map +1 -0
- package/dist/generated/workflow/v1/common_pb.js +86 -0
- package/dist/generated/workflow/v1/runtime_pb.d.ts +200 -0
- package/dist/generated/workflow/v1/runtime_pb.d.ts.map +1 -0
- package/dist/generated/workflow/v1/runtime_pb.js +40 -0
- package/dist/generated/workflow/v1/state_pb.d.ts +254 -0
- package/dist/generated/workflow/v1/state_pb.d.ts.map +1 -0
- package/dist/generated/workflow/v1/state_pb.js +68 -0
- package/dist/guardrails_evaluator/core.d.ts +23 -0
- package/dist/guardrails_evaluator/core.d.ts.map +1 -0
- package/dist/guardrails_evaluator/core.js +122 -0
- package/dist/guardrails_evaluator/index.d.ts +10 -0
- package/dist/guardrails_evaluator/index.d.ts.map +1 -0
- package/dist/guardrails_evaluator/index.js +11 -0
- package/dist/guardrails_evaluator/regex.d.ts +20 -0
- package/dist/guardrails_evaluator/regex.d.ts.map +1 -0
- package/dist/guardrails_evaluator/regex.js +233 -0
- package/dist/hooks.d.ts +109 -0
- package/dist/hooks.d.ts.map +1 -0
- package/dist/hooks.js +216 -0
- package/dist/http_path.d.ts +18 -0
- package/dist/http_path.d.ts.map +1 -0
- package/dist/http_path.js +53 -0
- package/dist/index.d.ts +35 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +41 -0
- package/dist/launcher.d.ts +130 -0
- package/dist/launcher.d.ts.map +1 -0
- package/dist/launcher.js +325 -0
- package/dist/logger.d.ts +96 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +204 -0
- package/dist/mcp_oauth.d.ts +51 -0
- package/dist/mcp_oauth.d.ts.map +1 -0
- package/dist/mcp_oauth.js +389 -0
- package/dist/mcp_oauth_secret.d.ts +21 -0
- package/dist/mcp_oauth_secret.d.ts.map +1 -0
- package/dist/mcp_oauth_secret.js +122 -0
- package/dist/mcp_tools.d.ts +71 -0
- package/dist/mcp_tools.d.ts.map +1 -0
- package/dist/mcp_tools.js +301 -0
- package/dist/memory_appbound.d.ts +42 -0
- package/dist/memory_appbound.d.ts.map +1 -0
- package/dist/memory_appbound.js +159 -0
- package/dist/memory_writer.d.ts +49 -0
- package/dist/memory_writer.d.ts.map +1 -0
- package/dist/memory_writer.js +171 -0
- package/dist/metrics.d.ts +84 -0
- package/dist/metrics.d.ts.map +1 -0
- package/dist/metrics.js +205 -0
- package/dist/models.d.ts +1458 -0
- package/dist/models.d.ts.map +1 -0
- package/dist/models.js +1726 -0
- package/dist/node_logger.d.ts +43 -0
- package/dist/node_logger.d.ts.map +1 -0
- package/dist/node_logger.js +158 -0
- package/dist/owner_callback.d.ts +16 -0
- package/dist/owner_callback.d.ts.map +1 -0
- package/dist/owner_callback.js +40 -0
- package/dist/progress.d.ts +57 -0
- package/dist/progress.d.ts.map +1 -0
- package/dist/progress.js +140 -0
- package/dist/runtime.d.ts +131 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +351 -0
- package/dist/secure_llm_proxy.d.ts +115 -0
- package/dist/secure_llm_proxy.d.ts.map +1 -0
- package/dist/secure_llm_proxy.js +922 -0
- package/dist/secure_wrapper.d.ts +332 -0
- package/dist/secure_wrapper.d.ts.map +1 -0
- package/dist/secure_wrapper.js +1249 -0
- package/dist/server/aer.d.ts +61 -0
- package/dist/server/aer.d.ts.map +1 -0
- package/dist/server/aer.js +1124 -0
- package/dist/server/auth.d.ts +56 -0
- package/dist/server/auth.d.ts.map +1 -0
- package/dist/server/auth.js +132 -0
- package/dist/server/base.d.ts +104 -0
- package/dist/server/base.d.ts.map +1 -0
- package/dist/server/base.js +150 -0
- package/dist/server/callInterrupt.d.ts +49 -0
- package/dist/server/callInterrupt.d.ts.map +1 -0
- package/dist/server/callInterrupt.js +68 -0
- package/dist/server/callback_delivery.d.ts +14 -0
- package/dist/server/callback_delivery.d.ts.map +1 -0
- package/dist/server/callback_delivery.js +141 -0
- package/dist/server/chunk_types.d.ts +50 -0
- package/dist/server/chunk_types.d.ts.map +1 -0
- package/dist/server/chunk_types.js +62 -0
- package/dist/server/cors.d.ts +52 -0
- package/dist/server/cors.d.ts.map +1 -0
- package/dist/server/cors.js +107 -0
- package/dist/server/drain.d.ts +169 -0
- package/dist/server/drain.d.ts.map +1 -0
- package/dist/server/drain.js +455 -0
- package/dist/server/function.d.ts +77 -0
- package/dist/server/function.d.ts.map +1 -0
- package/dist/server/function.js +337 -0
- package/dist/server/http_retry.d.ts +37 -0
- package/dist/server/http_retry.d.ts.map +1 -0
- package/dist/server/http_retry.js +157 -0
- package/dist/server/index.d.ts +7 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +5 -0
- package/dist/server/metadata.d.ts +50 -0
- package/dist/server/metadata.d.ts.map +1 -0
- package/dist/server/metadata.js +193 -0
- package/dist/server/oe_url.d.ts +36 -0
- package/dist/server/oe_url.d.ts.map +1 -0
- package/dist/server/oe_url.js +50 -0
- package/dist/server/owner_url.d.ts +35 -0
- package/dist/server/owner_url.d.ts.map +1 -0
- package/dist/server/owner_url.js +146 -0
- package/dist/server/query.d.ts +42 -0
- package/dist/server/query.d.ts.map +1 -0
- package/dist/server/query.js +28 -0
- package/dist/server/tool.d.ts +138 -0
- package/dist/server/tool.d.ts.map +1 -0
- package/dist/server/tool.js +1017 -0
- package/dist/span_names.d.ts +21 -0
- package/dist/span_names.d.ts.map +1 -0
- package/dist/span_names.js +31 -0
- package/dist/structured_logging/constants.d.ts +17 -0
- package/dist/structured_logging/constants.d.ts.map +1 -0
- package/dist/structured_logging/constants.js +71 -0
- package/dist/structured_logging/env.d.ts +18 -0
- package/dist/structured_logging/env.d.ts.map +1 -0
- package/dist/structured_logging/env.js +39 -0
- package/dist/structured_logging/install.d.ts +56 -0
- package/dist/structured_logging/install.d.ts.map +1 -0
- package/dist/structured_logging/install.js +107 -0
- package/dist/structured_logging/layout.d.ts +9 -0
- package/dist/structured_logging/layout.d.ts.map +1 -0
- package/dist/structured_logging/layout.js +144 -0
- package/dist/structured_logging/serialize.d.ts +27 -0
- package/dist/structured_logging/serialize.d.ts.map +1 -0
- package/dist/structured_logging/serialize.js +61 -0
- package/dist/structured_logging/stdio_capture.d.ts +59 -0
- package/dist/structured_logging/stdio_capture.d.ts.map +1 -0
- package/dist/structured_logging/stdio_capture.js +164 -0
- package/dist/structured_logging/uncaught.d.ts +14 -0
- package/dist/structured_logging/uncaught.d.ts.map +1 -0
- package/dist/structured_logging/uncaught.js +58 -0
- package/dist/structured_logging.d.ts +48 -0
- package/dist/structured_logging.d.ts.map +1 -0
- package/dist/structured_logging.js +47 -0
- package/dist/tls_client.d.ts +61 -0
- package/dist/tls_client.d.ts.map +1 -0
- package/dist/tls_client.js +298 -0
- package/dist/tool_api_error.d.ts +62 -0
- package/dist/tool_api_error.d.ts.map +1 -0
- package/dist/tool_api_error.js +399 -0
- package/dist/tool_memory_ownership.d.ts +10 -0
- package/dist/tool_memory_ownership.d.ts.map +1 -0
- package/dist/tool_memory_ownership.js +36 -0
- package/dist/toolpod_handlers.d.ts +126 -0
- package/dist/toolpod_handlers.d.ts.map +1 -0
- package/dist/toolpod_handlers.js +1016 -0
- package/dist/tracing/exporters.d.ts +51 -0
- package/dist/tracing/exporters.d.ts.map +1 -0
- package/dist/tracing/exporters.js +327 -0
- package/dist/tracing/index.d.ts +3 -0
- package/dist/tracing/index.d.ts.map +1 -0
- package/dist/tracing/index.js +2 -0
- package/dist/tracing/setup.d.ts +76 -0
- package/dist/tracing/setup.d.ts.map +1 -0
- package/dist/tracing/setup.js +436 -0
- package/dist/utils.d.ts +204 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +867 -0
- package/dist/workflow/activity.d.ts +71 -0
- package/dist/workflow/activity.d.ts.map +1 -0
- package/dist/workflow/activity.js +357 -0
- package/dist/workflow/attempt.d.ts +12 -0
- package/dist/workflow/attempt.d.ts.map +1 -0
- package/dist/workflow/attempt.js +96 -0
- package/dist/workflow/client.d.ts +46 -0
- package/dist/workflow/client.d.ts.map +1 -0
- package/dist/workflow/client.js +299 -0
- package/dist/workflow/context.d.ts +37 -0
- package/dist/workflow/context.d.ts.map +1 -0
- package/dist/workflow/context.js +350 -0
- package/dist/workflow/heartbeat.d.ts +15 -0
- package/dist/workflow/heartbeat.d.ts.map +1 -0
- package/dist/workflow/heartbeat.js +78 -0
- package/dist/workflow/index.d.ts +14 -0
- package/dist/workflow/index.d.ts.map +1 -0
- package/dist/workflow/index.js +10 -0
- package/dist/workflow/memory.d.ts +17 -0
- package/dist/workflow/memory.d.ts.map +1 -0
- package/dist/workflow/memory.js +184 -0
- package/package.json +73 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stable span names for the first-invoke lifecycle. There is no manual
|
|
3
|
+
* `llm.call` span; LangChain's auto-instrumentation already covers it.
|
|
4
|
+
*/
|
|
5
|
+
export declare const AER_BUILD_AGENT = "aer.build_agent";
|
|
6
|
+
export declare const GRAPH_BUILD = "graph.build";
|
|
7
|
+
export declare const MODEL_REQUEST_PREPARE = "request.prepare";
|
|
8
|
+
export declare const MODEL_RESPONSE_PROCESS = "response.process";
|
|
9
|
+
export declare const SKILLS_MIDDLEWARE_BEFORE_AGENT = "skills.middleware";
|
|
10
|
+
export declare const ATTR_COLD_START = "cold_start";
|
|
11
|
+
export declare const ATTR_CACHE_HIT = "cache_hit";
|
|
12
|
+
export declare const ATTR_SKILLS_SOURCE_COUNT = "skills.source_count";
|
|
13
|
+
export declare const ATTR_SKILLS_LOADED_COUNT = "skills.loaded_count";
|
|
14
|
+
export declare const OPENINFERENCE_SPAN_KIND = "openinference.span.kind";
|
|
15
|
+
/** OpenInference span kinds, matching the Python SDK's `OpenInferenceSpanKind`. */
|
|
16
|
+
export declare const OpenInferenceSpanKind: {
|
|
17
|
+
readonly AGENT: "AGENT";
|
|
18
|
+
readonly CHAIN: "CHAIN";
|
|
19
|
+
readonly TOOL: "TOOL";
|
|
20
|
+
};
|
|
21
|
+
//# sourceMappingURL=span_names.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"span_names.d.ts","sourceRoot":"","sources":["../src/span_names.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,eAAO,MAAM,eAAe,oBAAoB,CAAC;AACjD,eAAO,MAAM,WAAW,gBAAgB,CAAC;AAGzC,eAAO,MAAM,qBAAqB,oBAAoB,CAAC;AACvD,eAAO,MAAM,sBAAsB,qBAAqB,CAAC;AAMzD,eAAO,MAAM,8BAA8B,sBAAsB,CAAC;AAGlE,eAAO,MAAM,eAAe,eAAe,CAAC;AAC5C,eAAO,MAAM,cAAc,cAAc,CAAC;AAC1C,eAAO,MAAM,wBAAwB,wBAAwB,CAAC;AAC9D,eAAO,MAAM,wBAAwB,wBAAwB,CAAC;AAO9D,eAAO,MAAM,uBAAuB,4BAA4B,CAAC;AAEjE,mFAAmF;AACnF,eAAO,MAAM,qBAAqB;;;;CAIxB,CAAC"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stable span names for the first-invoke lifecycle. There is no manual
|
|
3
|
+
* `llm.call` span; LangChain's auto-instrumentation already covers it.
|
|
4
|
+
*/
|
|
5
|
+
export const AER_BUILD_AGENT = "aer.build_agent";
|
|
6
|
+
export const GRAPH_BUILD = "graph.build";
|
|
7
|
+
// Children of the existing LangChain auto-instrumented span.
|
|
8
|
+
export const MODEL_REQUEST_PREPARE = "request.prepare";
|
|
9
|
+
export const MODEL_RESPONSE_PROCESS = "response.process";
|
|
10
|
+
// Wraps deepagents' createSkillsMiddleware beforeAgent hook as a single
|
|
11
|
+
// span. Its list/download calls happen inside a third-party package, so
|
|
12
|
+
// this attributes total time to the hook without inventing internal
|
|
13
|
+
// phases that don't exist in code we own.
|
|
14
|
+
export const SKILLS_MIDDLEWARE_BEFORE_AGENT = "skills.middleware";
|
|
15
|
+
// Low-cardinality only; never prompts/completions/secrets.
|
|
16
|
+
export const ATTR_COLD_START = "cold_start";
|
|
17
|
+
export const ATTR_CACHE_HIT = "cache_hit";
|
|
18
|
+
export const ATTR_SKILLS_SOURCE_COUNT = "skills.source_count";
|
|
19
|
+
export const ATTR_SKILLS_LOADED_COUNT = "skills.loaded_count";
|
|
20
|
+
// Not written as `export const X = "...";` directly: this repo's
|
|
21
|
+
// no-LangChain-imports lint scans for quoted strings on `export`/`import`
|
|
22
|
+
// lines as a proxy for import specifiers, and would misread this literal
|
|
23
|
+
// as an OpenInference import.
|
|
24
|
+
const openinferenceSpanKindAttr = "openinference.span.kind";
|
|
25
|
+
export const OPENINFERENCE_SPAN_KIND = openinferenceSpanKindAttr;
|
|
26
|
+
/** OpenInference span kinds, matching the Python SDK's `OpenInferenceSpanKind`. */
|
|
27
|
+
export const OpenInferenceSpanKind = {
|
|
28
|
+
AGENT: "AGENT",
|
|
29
|
+
CHAIN: "CHAIN",
|
|
30
|
+
TOOL: "TOOL",
|
|
31
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared constants for the structured-logging module.
|
|
3
|
+
*/
|
|
4
|
+
export declare const SERVICE_BY_MODE: Record<string, string>;
|
|
5
|
+
/** @internal — exported only for tests; not part of the public API. */
|
|
6
|
+
export declare const MAX_TRACEBACK_BYTES = 8192;
|
|
7
|
+
/** @internal — exported only for tests; not part of the public API. */
|
|
8
|
+
export declare const MAX_BUFFER_BYTES = 65536;
|
|
9
|
+
export declare const DEFAULT_RECORD_SOURCE = "node-logging";
|
|
10
|
+
export declare const STDIO_CAPTURE_CATEGORY = "agent_engine_runner_shared.stdio_capture";
|
|
11
|
+
export declare const NOISY_LOGGER_NAMES: ReadonlyArray<string>;
|
|
12
|
+
export declare const ORIGINAL_WRITE_SLOT: unique symbol;
|
|
13
|
+
export declare const EXIT_FLUSH_REGISTERED: unique symbol;
|
|
14
|
+
export declare const ACTIVE_CAPTURES: unique symbol;
|
|
15
|
+
export declare const UNCAUGHT_HANDLERS_REGISTERED: unique symbol;
|
|
16
|
+
export declare const ACTIVE_ROOT_LOGGER: unique symbol;
|
|
17
|
+
//# sourceMappingURL=constants.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../src/structured_logging/constants.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,eAAO,MAAM,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAMlD,CAAC;AAKF,uEAAuE;AACvE,eAAO,MAAM,mBAAmB,OAAO,CAAC;AAOxC,uEAAuE;AACvE,eAAO,MAAM,gBAAgB,QAAQ,CAAC;AAOtC,eAAO,MAAM,qBAAqB,iBAAiB,CAAC;AAYpD,eAAO,MAAM,sBAAsB,6CACS,CAAC;AAI7C,eAAO,MAAM,kBAAkB,EAAE,aAAa,CAAC,MAAM,CAOpD,CAAC;AAOF,eAAO,MAAM,mBAAmB,eAE/B,CAAC;AAIF,eAAO,MAAM,qBAAqB,eAEjC,CAAC;AAOF,eAAO,MAAM,eAAe,eAE3B,CAAC;AAIF,eAAO,MAAM,4BAA4B,eAExC,CAAC;AAIF,eAAO,MAAM,kBAAkB,eAE9B,CAAC"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared constants for the structured-logging module.
|
|
3
|
+
*/
|
|
4
|
+
// Map RUNNER_MODE values to the canonical service identifier on the log line.
|
|
5
|
+
export const SERVICE_BY_MODE = {
|
|
6
|
+
orchestrator: "orchestration-engine",
|
|
7
|
+
aer: "agent-execution-runtime",
|
|
8
|
+
tool: "tool-executor",
|
|
9
|
+
tool_function: "tool-executor",
|
|
10
|
+
"memory-server": "memory-server",
|
|
11
|
+
};
|
|
12
|
+
// Truncation for the formatted stack embedded in fields.exc_traceback.
|
|
13
|
+
// Long traces explode the JSON line size and choke Fluent Bit's batch
|
|
14
|
+
// pipeline; 8 KiB is enough to retain the call site and outer frames.
|
|
15
|
+
/** @internal — exported only for tests; not part of the public API. */
|
|
16
|
+
export const MAX_TRACEBACK_BYTES = 8192;
|
|
17
|
+
// Cap on LoggingStream's per-stream pending buffer. A library that writes a
|
|
18
|
+
// huge payload without `\n` (binary blob accidentally hitting stdout, a
|
|
19
|
+
// multi-MB stack from a native module, a misbehaving progress-bar lib)
|
|
20
|
+
// would otherwise grow the buffer until process OOM. 64 KiB is well above
|
|
21
|
+
// any reasonable log line and far below a runaway producer.
|
|
22
|
+
/** @internal — exported only for tests; not part of the public API. */
|
|
23
|
+
export const MAX_BUFFER_BYTES = 65536;
|
|
24
|
+
// Source value attached to log records by LoggingStream. For records that
|
|
25
|
+
// come straight from `logger.*` (i.e. not stdout/stderr capture) we default
|
|
26
|
+
// to `node-logging`. Mirrors the Python `_RECORD_SOURCE_ATTR` /
|
|
27
|
+
// `_DEFAULT_RECORD_SOURCE` constants; the value travels on the `source`
|
|
28
|
+
// field of the extras object passed to `logger.info({source}, msg)`.
|
|
29
|
+
export const DEFAULT_RECORD_SOURCE = "node-logging";
|
|
30
|
+
// Dedicated log4js category for stdout/stderr capture. Pinned to `trace` in
|
|
31
|
+
// the configuration (see install.ts) so captured records emerge regardless of
|
|
32
|
+
// the operator-set `LOG_LEVEL`. Python achieves the same effect by calling
|
|
33
|
+
// `Logger.handle(record)` from its `LoggingStream._emit` — which bypasses
|
|
34
|
+
// `isEnabledFor` (structured_logging.py:269-280 + 347-350 comment). log4js
|
|
35
|
+
// has no per-call bypass, but it does have per-category levels, so we route
|
|
36
|
+
// captures through a separate category whose level is held below the lowest
|
|
37
|
+
// LoggingStream emit level (info). Result: a stray `console.log` still ships
|
|
38
|
+
// as a JSON record even when an operator flips `LOG_LEVEL=ERROR` to chase an
|
|
39
|
+
// issue — same property Python guarantees.
|
|
40
|
+
export const STDIO_CAPTURE_CATEGORY = "agent_engine_runner_shared.stdio_capture";
|
|
41
|
+
// Mirrors Python's `for noisy in ("httpx", "httpcore", "urllib3")` silencing
|
|
42
|
+
// block. Node-native names kept too.
|
|
43
|
+
export const NOISY_LOGGER_NAMES = [
|
|
44
|
+
"undici",
|
|
45
|
+
"fastify.access",
|
|
46
|
+
"httpx",
|
|
47
|
+
"httpcore",
|
|
48
|
+
"urllib3",
|
|
49
|
+
"uvicorn.access",
|
|
50
|
+
];
|
|
51
|
+
// Symbol slot used to stash the original `write` function on
|
|
52
|
+
// `process.stdout` / `process.stderr` before we patch them. `Symbol.for`
|
|
53
|
+
// shares the slot across module instances, so a double-install (or a tester
|
|
54
|
+
// reloading the module) finds the prior snapshot instead of wrapping the
|
|
55
|
+
// already-patched write — which would recurse to RangeError.
|
|
56
|
+
export const ORIGINAL_WRITE_SLOT = Symbol.for("agent_engine_runner_shared.original_write");
|
|
57
|
+
// Guard so the process-exit buffer-flush handler is registered only once per
|
|
58
|
+
// process, even across re-installs / a module reload (see patchStdio).
|
|
59
|
+
export const EXIT_FLUSH_REGISTERED = Symbol.for("agent_engine_runner_shared.structured_logging_exit_flush");
|
|
60
|
+
// Slot holding the currently-installed capture streams. The exit handler is
|
|
61
|
+
// registered once and reads this slot at exit time (rather than closing over
|
|
62
|
+
// the first install's streams), so a re-install's trailing buffer is the one
|
|
63
|
+
// that gets flushed. Shared across module reloads via `Symbol.for`, matching
|
|
64
|
+
// the one-shot exit handler it feeds.
|
|
65
|
+
export const ACTIVE_CAPTURES = Symbol.for("agent_engine_runner_shared.structured_logging_active_captures");
|
|
66
|
+
// Registers the crash listeners once — `process.on` is additive, so a repeat
|
|
67
|
+
// install would log the same crash twice.
|
|
68
|
+
export const UNCAUGHT_HANDLERS_REGISTERED = Symbol.for("agent_engine_runner_shared.structured_logging_uncaught_handlers_registered");
|
|
69
|
+
// Read by those listeners at fire time, so a re-install logs through the
|
|
70
|
+
// current logger rather than the first install's.
|
|
71
|
+
export const ACTIVE_ROOT_LOGGER = Symbol.for("agent_engine_runner_shared.structured_logging_active_root_logger");
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Install-time snapshot of the pod/runner environment that decorates every
|
|
3
|
+
* structured-logging record (service, component, tenant/project, pod name).
|
|
4
|
+
*/
|
|
5
|
+
export declare function envOrNull(name: string): string | null;
|
|
6
|
+
export declare function serviceFor(mode: string | null): string;
|
|
7
|
+
export declare function resolveLevel(level?: string | null): string;
|
|
8
|
+
export interface FormatterEnv {
|
|
9
|
+
service: string;
|
|
10
|
+
component: string;
|
|
11
|
+
tenantId: string | null;
|
|
12
|
+
projectId: string | null;
|
|
13
|
+
workspaceIdEnv: string | null;
|
|
14
|
+
bootId: string | null;
|
|
15
|
+
podName: string | null;
|
|
16
|
+
}
|
|
17
|
+
export declare function snapshotEnv(mode: string | null): FormatterEnv;
|
|
18
|
+
//# sourceMappingURL=env.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"env.d.ts","sourceRoot":"","sources":["../../src/structured_logging/env.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAGrD;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,CAGtD;AAED,wBAAgB,YAAY,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,CAE1D;AAED,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CACxB;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,YAAY,CAqB7D"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Install-time snapshot of the pod/runner environment that decorates every
|
|
3
|
+
* structured-logging record (service, component, tenant/project, pod name).
|
|
4
|
+
*/
|
|
5
|
+
import { SERVICE_BY_MODE } from "./constants.js";
|
|
6
|
+
export function envOrNull(name) {
|
|
7
|
+
const v = process.env[name];
|
|
8
|
+
return v && v.length > 0 ? v : null;
|
|
9
|
+
}
|
|
10
|
+
export function serviceFor(mode) {
|
|
11
|
+
if (!mode)
|
|
12
|
+
return "agent-execution-runtime";
|
|
13
|
+
return SERVICE_BY_MODE[mode.toLowerCase()] ?? mode.toLowerCase();
|
|
14
|
+
}
|
|
15
|
+
export function resolveLevel(level) {
|
|
16
|
+
return (level ?? envOrNull("LOG_LEVEL") ?? "info").toLowerCase();
|
|
17
|
+
}
|
|
18
|
+
export function snapshotEnv(mode) {
|
|
19
|
+
// Env vars (RUNNER_MODE, ORG_ID, PROJECT_ID, WORKSPACE_ID, AGENTIC_BOOT_ID,
|
|
20
|
+
// POD_NAME) are snapshotted here at install time, not re-read per record —
|
|
21
|
+
// every log event goes through the layout and re-resolving env on each call
|
|
22
|
+
// would noticeably load the hot path. Callers must ensure the pod env is
|
|
23
|
+
// set before `installStructuredLogging()` runs; if the install runs before
|
|
24
|
+
// the entrypoint stamps env, the layout caches stale (or missing) values for
|
|
25
|
+
// the process lifetime. The `mode` arg lets callers override
|
|
26
|
+
// RUNNER_MODE explicitly — useful when the pod env happens to be unset
|
|
27
|
+
// but the caller knows its mode (e.g. `setupLogging` passes its `mode=`
|
|
28
|
+
// arg through).
|
|
29
|
+
const resolvedMode = mode ?? envOrNull("RUNNER_MODE");
|
|
30
|
+
return {
|
|
31
|
+
service: serviceFor(resolvedMode),
|
|
32
|
+
component: (resolvedMode ?? "agent").toLowerCase(),
|
|
33
|
+
tenantId: envOrNull("ORG_ID"),
|
|
34
|
+
projectId: envOrNull("PROJECT_ID"),
|
|
35
|
+
workspaceIdEnv: envOrNull("WORKSPACE_ID"),
|
|
36
|
+
bootId: envOrNull("AGENTIC_BOOT_ID"),
|
|
37
|
+
podName: envOrNull("POD_NAME") ?? envOrNull("HOSTNAME"),
|
|
38
|
+
};
|
|
39
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public entry point: `installStructuredLogging()` wires the structured-logging layout
|
|
3
|
+
* into log4js as a custom appender, silences noisy HTTP loggers, and (unless
|
|
4
|
+
* disabled) patches stdout/stderr capture.
|
|
5
|
+
*/
|
|
6
|
+
import { type Logger } from "log4js";
|
|
7
|
+
export interface InstallStructuredLoggingArgs {
|
|
8
|
+
/**
|
|
9
|
+
* Log level — string name (`"DEBUG"`, `"info"`). Defaults to `LOG_LEVEL`
|
|
10
|
+
* env var, then INFO. Must be honored on rollout because operators
|
|
11
|
+
* continue to flip `LOG_LEVEL=DEBUG` to chase issues.
|
|
12
|
+
*/
|
|
13
|
+
level?: string | null;
|
|
14
|
+
/**
|
|
15
|
+
* Runner mode (`"orchestrator"`, `"aer"`, `"tool"`, `"memory-server"`)
|
|
16
|
+
* used to derive `service` and `fields.component` on the wire. Takes
|
|
17
|
+
* precedence over `RUNNER_MODE` env var when set; falls back to env
|
|
18
|
+
* when `null`. Lets `setupLogging(mode=...)` callers stay authoritative
|
|
19
|
+
* even if pod env hasn't been stamped.
|
|
20
|
+
*/
|
|
21
|
+
mode?: string | null;
|
|
22
|
+
/**
|
|
23
|
+
* Set to `false` to skip `process.stdout` / `process.stderr` patching.
|
|
24
|
+
* Useful in tests that share a Node process across cases, or in callers
|
|
25
|
+
* that own their own stdio routing. Defaults to `true` (matches Python).
|
|
26
|
+
*/
|
|
27
|
+
captureStdio?: boolean;
|
|
28
|
+
/**
|
|
29
|
+
* Absolute path for a dev-mode `dateFile` appender alongside the
|
|
30
|
+
* structured stdout appender. When set, a second log4js appender writes
|
|
31
|
+
* human-readable records to this path, rotating daily and keeping the
|
|
32
|
+
* last 5 files (Python `TimedRotatingFileHandler` parity). Only used by
|
|
33
|
+
* `setupLogging` when `AGENTIC_DEV_MODES` is set; left unset in
|
|
34
|
+
* production, where Fluent Bit is the only sink.
|
|
35
|
+
*/
|
|
36
|
+
fileLogPath?: string | null;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Install structured logging on the log4js root.
|
|
40
|
+
*
|
|
41
|
+
* Behaves like the Python SDK's `install_structured_logging`:
|
|
42
|
+
*
|
|
43
|
+
* - Idempotent: re-installing routes the appender through the snapshotted
|
|
44
|
+
* original `stdout.write` (stashed under `ORIGINAL_WRITE_SLOT`), so it
|
|
45
|
+
* keeps writing to the real stdout instead of recursing through a
|
|
46
|
+
* patched write.
|
|
47
|
+
* - Silences noisy HTTP libraries (see `NOISY_LOGGER_NAMES`) — equivalent
|
|
48
|
+
* to Python's `logging.getLogger("httpx").setLevel(WARNING)` block.
|
|
49
|
+
* - Patches `process.stdout.write` / `process.stderr.write` (unless
|
|
50
|
+
* `captureStdio: false`) so stray writes emerge as structured-logging records
|
|
51
|
+
* tagged with `source: "stdout"` (INFO) or `source: "stderr"` (WARNING).
|
|
52
|
+
* stderr was deliberately *not* mapped to ERROR — Python had to back that
|
|
53
|
+
* out because every deprecation warning would have paged on-call.
|
|
54
|
+
*/
|
|
55
|
+
export declare function installStructuredLogging(args?: InstallStructuredLoggingArgs): Logger;
|
|
56
|
+
//# sourceMappingURL=install.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"install.d.ts","sourceRoot":"","sources":["../../src/structured_logging/install.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAe,EAEb,KAAK,MAAM,EAEZ,MAAM,QAAQ,CAAC;AAwBhB,MAAM,WAAW,4BAA4B;IAC3C;;;;OAIG;IACH,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB;;;;;;OAMG;IACH,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB;;;;OAIG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB;;;;;;;OAOG;IACH,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,wBAAwB,CACtC,IAAI,GAAE,4BAAiC,GACtC,MAAM,CA0ER"}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public entry point: `installStructuredLogging()` wires the structured-logging layout
|
|
3
|
+
* into log4js as a custom appender, silences noisy HTTP loggers, and (unless
|
|
4
|
+
* disabled) patches stdout/stderr capture.
|
|
5
|
+
*/
|
|
6
|
+
import log4js from "log4js";
|
|
7
|
+
import { markConfigured } from "../logger.js";
|
|
8
|
+
import { NOISY_LOGGER_NAMES, STDIO_CAPTURE_CATEGORY } from "./constants.js";
|
|
9
|
+
import { resolveLevel, snapshotEnv } from "./env.js";
|
|
10
|
+
import { buildStructuredLayout } from "./layout.js";
|
|
11
|
+
import { patchStdio, snapshotOriginalWrite } from "./stdio_capture.js";
|
|
12
|
+
import { installUncaughtExceptionHandlers } from "./uncaught.js";
|
|
13
|
+
// Custom log4js appender — writes via the snapshotted original stdout write so
|
|
14
|
+
// it doesn't recurse through our patched `process.stdout.write`.
|
|
15
|
+
function buildAppenderModule(layout) {
|
|
16
|
+
return {
|
|
17
|
+
configure: () => {
|
|
18
|
+
const originalWrite = snapshotOriginalWrite(process.stdout);
|
|
19
|
+
return (event) => {
|
|
20
|
+
originalWrite(layout(event) + "\n");
|
|
21
|
+
};
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Install structured logging on the log4js root.
|
|
27
|
+
*
|
|
28
|
+
* Behaves like the Python SDK's `install_structured_logging`:
|
|
29
|
+
*
|
|
30
|
+
* - Idempotent: re-installing routes the appender through the snapshotted
|
|
31
|
+
* original `stdout.write` (stashed under `ORIGINAL_WRITE_SLOT`), so it
|
|
32
|
+
* keeps writing to the real stdout instead of recursing through a
|
|
33
|
+
* patched write.
|
|
34
|
+
* - Silences noisy HTTP libraries (see `NOISY_LOGGER_NAMES`) — equivalent
|
|
35
|
+
* to Python's `logging.getLogger("httpx").setLevel(WARNING)` block.
|
|
36
|
+
* - Patches `process.stdout.write` / `process.stderr.write` (unless
|
|
37
|
+
* `captureStdio: false`) so stray writes emerge as structured-logging records
|
|
38
|
+
* tagged with `source: "stdout"` (INFO) or `source: "stderr"` (WARNING).
|
|
39
|
+
* stderr was deliberately *not* mapped to ERROR — Python had to back that
|
|
40
|
+
* out because every deprecation warning would have paged on-call.
|
|
41
|
+
*/
|
|
42
|
+
export function installStructuredLogging(args = {}) {
|
|
43
|
+
const env = snapshotEnv(args.mode ?? null);
|
|
44
|
+
const level = resolveLevel(args.level);
|
|
45
|
+
const layout = buildStructuredLayout(env);
|
|
46
|
+
const appenderModule = buildAppenderModule(layout);
|
|
47
|
+
// `enableCallStack: true` makes log4js attach `fileName`/`lineNumber`/
|
|
48
|
+
// `functionName` to each LoggingEvent — required for the layout to emit
|
|
49
|
+
// `fields.filename` / `fields.lineno` / `fields.funcName` for logger-
|
|
50
|
+
// sourced records (Python parity, see layout above). It's set per-
|
|
51
|
+
// category in log4js (no global toggle), so enable on every category we
|
|
52
|
+
// configure including the noisy ones — a noisy library that does fire a
|
|
53
|
+
// WARN is exactly the case where the call site is useful to keep.
|
|
54
|
+
const noisyCategories = Object.fromEntries(NOISY_LOGGER_NAMES.map((name) => [
|
|
55
|
+
name,
|
|
56
|
+
{ appenders: ["structured"], level: "warn", enableCallStack: true },
|
|
57
|
+
]));
|
|
58
|
+
const appenders = {
|
|
59
|
+
// log4js accepts an inline module object for `type` — its `configure`
|
|
60
|
+
// function is invoked once at `log4js.configure(...)` time and
|
|
61
|
+
// returns the appender function used for every record.
|
|
62
|
+
structured: { type: appenderModule },
|
|
63
|
+
};
|
|
64
|
+
if (args.fileLogPath) {
|
|
65
|
+
appenders.devFile = {
|
|
66
|
+
type: "dateFile",
|
|
67
|
+
filename: args.fileLogPath,
|
|
68
|
+
layout: {
|
|
69
|
+
type: "pattern",
|
|
70
|
+
pattern: "%d{yyyy-MM-dd hh:mm:ss} | %p | %c | %m",
|
|
71
|
+
},
|
|
72
|
+
// Daily rotation, 5 kept files — Python TimedRotatingFileHandler parity.
|
|
73
|
+
pattern: "yyyy-MM-dd",
|
|
74
|
+
numToKeep: 5,
|
|
75
|
+
keepFileExt: true,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
const defaultAppenders = args.fileLogPath
|
|
79
|
+
? ["structured", "devFile"]
|
|
80
|
+
: ["structured"];
|
|
81
|
+
const config = {
|
|
82
|
+
appenders,
|
|
83
|
+
categories: {
|
|
84
|
+
default: { appenders: defaultAppenders, level, enableCallStack: true },
|
|
85
|
+
// Stdio capture category pinned to `trace` so emits never get
|
|
86
|
+
// filtered by the operator-set `LOG_LEVEL`. Mirrors Python's
|
|
87
|
+
// `Logger.handle()` bypass; see STDIO_CAPTURE_CATEGORY comment.
|
|
88
|
+
// `enableCallStack` deliberately off — the call site for a captured
|
|
89
|
+
// line is always `LoggingStream.emit`, which is noise, and the
|
|
90
|
+
// layout already suppresses fields.filename/lineno/funcName when
|
|
91
|
+
// source !== DEFAULT_RECORD_SOURCE.
|
|
92
|
+
[STDIO_CAPTURE_CATEGORY]: { appenders: defaultAppenders, level: "trace" },
|
|
93
|
+
...noisyCategories,
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
log4js.configure(config);
|
|
97
|
+
// Keep `logger.ts` state in sync: a later `getLogger()` must not re-run
|
|
98
|
+
// `ensureDefaultConfig()` and replace this structured-logging config with the human
|
|
99
|
+
// stdout appender (which would also re-`configure` over patched stdio).
|
|
100
|
+
markConfigured();
|
|
101
|
+
const rootLogger = log4js.getLogger();
|
|
102
|
+
if (args.captureStdio !== false)
|
|
103
|
+
patchStdio();
|
|
104
|
+
installUncaughtExceptionHandlers(rootLogger);
|
|
105
|
+
rootLogger.debug({ source: "node-logging" }, "structured logging installed");
|
|
106
|
+
return rootLogger;
|
|
107
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The structured-logging layout: turns a `LoggingEvent` into the single-line JSON
|
|
3
|
+
* envelope (timestamp, level, logger, message, service, tenant/project/
|
|
4
|
+
* workspace/execution/session ids, pod, source, fields).
|
|
5
|
+
*/
|
|
6
|
+
import type { LoggingEvent } from "log4js";
|
|
7
|
+
import type { FormatterEnv } from "./env.js";
|
|
8
|
+
export declare function buildStructuredLayout(env: FormatterEnv): (event: LoggingEvent) => string;
|
|
9
|
+
//# sourceMappingURL=layout.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"layout.d.ts","sourceRoot":"","sources":["../../src/structured_logging/layout.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAIH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AAU3C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAuE7C,wBAAgB,qBAAqB,CACnC,GAAG,EAAE,YAAY,GAChB,CAAC,KAAK,EAAE,YAAY,KAAK,MAAM,CA+EjC"}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The structured-logging layout: turns a `LoggingEvent` into the single-line JSON
|
|
3
|
+
* envelope (timestamp, level, logger, message, service, tenant/project/
|
|
4
|
+
* workspace/execution/session ids, pod, source, fields).
|
|
5
|
+
*/
|
|
6
|
+
import { basename } from "node:path";
|
|
7
|
+
import { getCurrentExecutionId, getCurrentLogOrigin, getCurrentTraceId, getCurrentSessionId, getCurrentWorkspaceId, } from "../context.js";
|
|
8
|
+
import { DEFAULT_RECORD_SOURCE, MAX_TRACEBACK_BYTES } from "./constants.js";
|
|
9
|
+
import { jsonSafeStringify, truncateUtf8 } from "./serialize.js";
|
|
10
|
+
// log4js spells some levels differently from Python's stdlib `logging`
|
|
11
|
+
// (WARN vs WARNING, FATAL vs CRITICAL, TRACE has no stdlib peer). The Splunk
|
|
12
|
+
// dashboards and the playground UI logs tab were built against the Python
|
|
13
|
+
// SDK's level strings, so normalize log4js names to the stdlib names here —
|
|
14
|
+
// otherwise a TypeScript agent's warnings would ship as `"WARN"` and miss a
|
|
15
|
+
// `level=WARNING` filter / render inconsistently next to Python agents.
|
|
16
|
+
const LEVEL_NAME_BY_LOG4JS = {
|
|
17
|
+
WARN: "WARNING",
|
|
18
|
+
FATAL: "CRITICAL",
|
|
19
|
+
TRACE: "DEBUG",
|
|
20
|
+
};
|
|
21
|
+
function normalizeLevelName(levelStr) {
|
|
22
|
+
return LEVEL_NAME_BY_LOG4JS[levelStr] ?? levelStr;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Split log4js's `event.data` into a message string, an `extra` field bag,
|
|
26
|
+
* and an optional `Error`. Matches the common call shape used in this
|
|
27
|
+
* codebase: `logger.info({...extra}, "message")`. If no leading object is
|
|
28
|
+
* present, all args are stringified and joined.
|
|
29
|
+
*/
|
|
30
|
+
function parseEventData(data) {
|
|
31
|
+
let extra = {};
|
|
32
|
+
let parts = data;
|
|
33
|
+
let err;
|
|
34
|
+
const first = data[0];
|
|
35
|
+
if (first &&
|
|
36
|
+
typeof first === "object" &&
|
|
37
|
+
!(first instanceof Error) &&
|
|
38
|
+
!Array.isArray(first)) {
|
|
39
|
+
extra = { ...first };
|
|
40
|
+
parts = data.slice(1);
|
|
41
|
+
}
|
|
42
|
+
else if (first instanceof Error) {
|
|
43
|
+
err = first;
|
|
44
|
+
parts = data.slice(1);
|
|
45
|
+
}
|
|
46
|
+
const extraErr = extra["err"];
|
|
47
|
+
if (extraErr instanceof Error) {
|
|
48
|
+
err = extraErr;
|
|
49
|
+
delete extra["err"];
|
|
50
|
+
}
|
|
51
|
+
const message = parts
|
|
52
|
+
.map((p) => {
|
|
53
|
+
if (typeof p === "string")
|
|
54
|
+
return p;
|
|
55
|
+
// Use the cycle/BigInt-safe serializer — a BigInt or circular value in a
|
|
56
|
+
// non-leading message arg must not throw and take down the log line.
|
|
57
|
+
try {
|
|
58
|
+
return jsonSafeStringify(p) ?? String(p);
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
return String(p);
|
|
62
|
+
}
|
|
63
|
+
})
|
|
64
|
+
.join(" ");
|
|
65
|
+
return { message, extra, err };
|
|
66
|
+
}
|
|
67
|
+
export function buildStructuredLayout(env) {
|
|
68
|
+
// Tenant context is read from the AsyncLocalStorage contextvars in
|
|
69
|
+
// `context.ts` at layout time so records emitted from inside an
|
|
70
|
+
// execution carry executionId/sessionId automatically; records emitted
|
|
71
|
+
// outside an execution (startup, idle) leave those fields `null`.
|
|
72
|
+
return (event) => {
|
|
73
|
+
const { message, extra, err } = parseEventData(event.data);
|
|
74
|
+
// Intentional divergence from the Python formatter: structured_logging.py
|
|
75
|
+
// ignores `extra=` kwargs on `logger.info(...)`, so Python callers have
|
|
76
|
+
// no way to attach structured key/value pairs to `fields`. log4js's
|
|
77
|
+
// idiomatic call shape *is* `logger.info({...extra}, "msg")`, so we
|
|
78
|
+
// spread the first-arg object into `fields` and let TS callers do what
|
|
79
|
+
// Python callers cannot. The `source` key is special-cased below and
|
|
80
|
+
// stripped from `fields` so it never appears twice on the wire.
|
|
81
|
+
const fields = {
|
|
82
|
+
component: env.component,
|
|
83
|
+
...extra,
|
|
84
|
+
};
|
|
85
|
+
if (err) {
|
|
86
|
+
// Capture the formatted stack so on-call can debug from the
|
|
87
|
+
// archived JSON without reproducing. Truncated to keep S3 batch
|
|
88
|
+
// sizes manageable; truncation is marked so consumers know the
|
|
89
|
+
// tail was cut.
|
|
90
|
+
fields["exc_type"] = err.name;
|
|
91
|
+
fields["exc_message"] = err.message;
|
|
92
|
+
if (err.stack)
|
|
93
|
+
fields["exc_traceback"] = truncateUtf8(err.stack, MAX_TRACEBACK_BYTES);
|
|
94
|
+
}
|
|
95
|
+
const source = typeof extra["source"] === "string"
|
|
96
|
+
? extra["source"]
|
|
97
|
+
: DEFAULT_RECORD_SOURCE;
|
|
98
|
+
delete fields["source"];
|
|
99
|
+
// Match Python's `record.filename` / `record.lineno` / `record.funcName`
|
|
100
|
+
// (structured_logging.py:125-128): include caller-site info only for
|
|
101
|
+
// logger-emitted records, not for stdout/stderr capture lines whose
|
|
102
|
+
// call site is the producer that wrote to the stream — meaningless for
|
|
103
|
+
// debugging. log4js populates these on LoggingEvent only when
|
|
104
|
+
// `enableCallStack: true` is set on the category (done in the
|
|
105
|
+
// configuration below); generating a stack trace per record is not free
|
|
106
|
+
// but the volume is bounded by LOG_LEVEL filtering on the default
|
|
107
|
+
// category, which keeps the cost acceptable in the steady state.
|
|
108
|
+
if (source === DEFAULT_RECORD_SOURCE) {
|
|
109
|
+
if (event.fileName)
|
|
110
|
+
// Python's `LogRecord.filename` is the basename, not the full path.
|
|
111
|
+
// log4js returns the absolute path (or a `file://` URL under ESM);
|
|
112
|
+
// `basename` handles both string forms.
|
|
113
|
+
fields["filename"] = basename(event.fileName);
|
|
114
|
+
if (typeof event.lineNumber === "number")
|
|
115
|
+
fields["lineno"] = event.lineNumber;
|
|
116
|
+
if (event.functionName)
|
|
117
|
+
fields["funcName"] = event.functionName;
|
|
118
|
+
}
|
|
119
|
+
const origin = getCurrentLogOrigin();
|
|
120
|
+
const traceId = getCurrentTraceId();
|
|
121
|
+
return jsonSafeStringify({
|
|
122
|
+
timestamp: event.startTime.toISOString(),
|
|
123
|
+
level: normalizeLevelName(event.level.levelStr),
|
|
124
|
+
logger: event.categoryName,
|
|
125
|
+
message,
|
|
126
|
+
service: env.service,
|
|
127
|
+
tenantId: env.tenantId,
|
|
128
|
+
projectId: env.projectId,
|
|
129
|
+
workspaceId: getCurrentWorkspaceId() ?? env.workspaceIdEnv,
|
|
130
|
+
executionId: getCurrentExecutionId(),
|
|
131
|
+
sessionId: getCurrentSessionId(),
|
|
132
|
+
bootId: env.bootId,
|
|
133
|
+
podName: env.podName,
|
|
134
|
+
source,
|
|
135
|
+
fields,
|
|
136
|
+
...(origin !== null ? { origin } : {}),
|
|
137
|
+
...(traceId &&
|
|
138
|
+
/^[0-9a-f]{32}$/.test(traceId) &&
|
|
139
|
+
traceId !== "0".repeat(32)
|
|
140
|
+
? { traceId }
|
|
141
|
+
: {}),
|
|
142
|
+
});
|
|
143
|
+
};
|
|
144
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Serialization helpers shared by the layout and the stdout-capture stream:
|
|
3
|
+
* a crash-proof JSON stringifier and a UTF-8-aware truncator.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Stringify the structured-logging envelope with the same robustness Python gets from
|
|
7
|
+
* `json.dumps(entry, default=str)`. Two failure modes the bare
|
|
8
|
+
* `JSON.stringify` would otherwise expose:
|
|
9
|
+
*
|
|
10
|
+
* - `TypeError: Do not know how to serialize a BigInt` — any extras
|
|
11
|
+
* value that happens to be a BigInt (e.g. nanosecond timings,
|
|
12
|
+
* 64-bit IDs) crashes the appender for the whole record.
|
|
13
|
+
* - `TypeError: Converting circular structure to JSON` — a caller
|
|
14
|
+
* accidentally passes a node from a graph (Error.cause chains in
|
|
15
|
+
* newer Node, langgraph state) and takes down logging for that line.
|
|
16
|
+
*
|
|
17
|
+
* The replacer coerces BigInt to a decimal string and replaces objects that
|
|
18
|
+
* are their own ancestor (a true reference cycle) with the sentinel
|
|
19
|
+
* `"[Circular]"`. Tracking the ancestor chain rather than a flat seen-set
|
|
20
|
+
* means a DAG — the same object referenced from multiple siblings, e.g. an
|
|
21
|
+
* array of identical items — serializes in full instead of being falsely
|
|
22
|
+
* truncated to `"[Circular]"`. A fresh `ancestors` stack per call means
|
|
23
|
+
* independent records can't poison each other.
|
|
24
|
+
*/
|
|
25
|
+
export declare function jsonSafeStringify(value: unknown): string;
|
|
26
|
+
export declare function truncateUtf8(text: string, maxBytes: number): string;
|
|
27
|
+
//# sourceMappingURL=serialize.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"serialize.d.ts","sourceRoot":"","sources":["../../src/structured_logging/serialize.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAcxD;AAED,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAgBnE"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Serialization helpers shared by the layout and the stdout-capture stream:
|
|
3
|
+
* a crash-proof JSON stringifier and a UTF-8-aware truncator.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Stringify the structured-logging envelope with the same robustness Python gets from
|
|
7
|
+
* `json.dumps(entry, default=str)`. Two failure modes the bare
|
|
8
|
+
* `JSON.stringify` would otherwise expose:
|
|
9
|
+
*
|
|
10
|
+
* - `TypeError: Do not know how to serialize a BigInt` — any extras
|
|
11
|
+
* value that happens to be a BigInt (e.g. nanosecond timings,
|
|
12
|
+
* 64-bit IDs) crashes the appender for the whole record.
|
|
13
|
+
* - `TypeError: Converting circular structure to JSON` — a caller
|
|
14
|
+
* accidentally passes a node from a graph (Error.cause chains in
|
|
15
|
+
* newer Node, langgraph state) and takes down logging for that line.
|
|
16
|
+
*
|
|
17
|
+
* The replacer coerces BigInt to a decimal string and replaces objects that
|
|
18
|
+
* are their own ancestor (a true reference cycle) with the sentinel
|
|
19
|
+
* `"[Circular]"`. Tracking the ancestor chain rather than a flat seen-set
|
|
20
|
+
* means a DAG — the same object referenced from multiple siblings, e.g. an
|
|
21
|
+
* array of identical items — serializes in full instead of being falsely
|
|
22
|
+
* truncated to `"[Circular]"`. A fresh `ancestors` stack per call means
|
|
23
|
+
* independent records can't poison each other.
|
|
24
|
+
*/
|
|
25
|
+
export function jsonSafeStringify(value) {
|
|
26
|
+
const ancestors = [];
|
|
27
|
+
return JSON.stringify(value, function (_key, v) {
|
|
28
|
+
if (typeof v === "bigint")
|
|
29
|
+
return v.toString();
|
|
30
|
+
if (typeof v !== "object" || v === null)
|
|
31
|
+
return v;
|
|
32
|
+
// `this` is the object `v` was reached through. Unwind ancestors that are
|
|
33
|
+
// no longer on the path to `v` so only genuine back-references remain.
|
|
34
|
+
while (ancestors.length > 0 && ancestors[ancestors.length - 1] !== this) {
|
|
35
|
+
ancestors.pop();
|
|
36
|
+
}
|
|
37
|
+
if (ancestors.includes(v))
|
|
38
|
+
return "[Circular]";
|
|
39
|
+
ancestors.push(v);
|
|
40
|
+
return v;
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
export function truncateUtf8(text, maxBytes) {
|
|
44
|
+
// Encode to bytes for the size check so the budget reflects what Fluent
|
|
45
|
+
// Bit actually ships — slicing the string by char count would let a
|
|
46
|
+
// non-ASCII traceback blow past the limit. Walk back from `maxBytes` to
|
|
47
|
+
// the last UTF-8 sequence boundary before decoding: Node's
|
|
48
|
+
// `Buffer.toString("utf-8")` REPLACES a partial sequence at the cut
|
|
49
|
+
// point with U+FFFD (3 bytes each), which would expand the result past
|
|
50
|
+
// the budget. Walking back over continuation bytes (`10xxxxxx`,
|
|
51
|
+
// i.e. `byte & 0xc0 === 0x80`) drops the partial sequence so the
|
|
52
|
+
// decoded body is strictly `<= maxBytes`. Mirrors Python's
|
|
53
|
+
// `errors="ignore"` decode at structured_logging.py:146.
|
|
54
|
+
const buf = Buffer.from(text, "utf-8");
|
|
55
|
+
if (buf.length <= maxBytes)
|
|
56
|
+
return text;
|
|
57
|
+
let cut = maxBytes;
|
|
58
|
+
while (cut > 0 && ((buf[cut] ?? 0) & 0xc0) === 0x80)
|
|
59
|
+
cut--;
|
|
60
|
+
return buf.subarray(0, cut).toString("utf-8") + "...(truncated)";
|
|
61
|
+
}
|