@frontmcp/observability 0.0.1 → 1.0.0-rc.1
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/esm/index.mjs +2986 -0
- package/esm/package.json +86 -0
- package/index.d.ts +11 -0
- package/logging/index.d.ts +9 -0
- package/logging/log-sink.interface.d.ts +81 -0
- package/logging/redaction.d.ts +17 -0
- package/logging/sink.factory.d.ts +15 -0
- package/logging/sinks/callback.sink.d.ts +12 -0
- package/logging/sinks/console.sink.d.ts +16 -0
- package/logging/sinks/index.d.ts +7 -0
- package/logging/sinks/otlp.sink.d.ts +67 -0
- package/logging/sinks/pino.sink.d.ts +13 -0
- package/logging/sinks/stdout.sink.d.ts +17 -0
- package/logging/sinks/winston.sink.d.ts +13 -0
- package/logging/structured-log-transport.d.ts +52 -0
- package/logging/structured-log.types.d.ts +71 -0
- package/otel/context-propagator.d.ts +17 -0
- package/otel/index.d.ts +8 -0
- package/otel/otel.setup.d.ts +32 -0
- package/otel/otel.tokens.d.ts +7 -0
- package/otel/otel.types.d.ts +151 -0
- package/otel/spans/auth.span.d.ts +27 -0
- package/otel/spans/fetch.span.d.ts +28 -0
- package/otel/spans/hook.span.d.ts +33 -0
- package/otel/spans/http-server.span.d.ts +29 -0
- package/otel/spans/index.d.ts +23 -0
- package/otel/spans/pretty-span-exporter.d.ts +19 -0
- package/otel/spans/prompt.span.d.ts +19 -0
- package/otel/spans/resource.span.d.ts +21 -0
- package/otel/spans/rpc.span.d.ts +33 -0
- package/otel/spans/span.utils.d.ts +38 -0
- package/otel/spans/startup.span.d.ts +28 -0
- package/otel/spans/tool.span.d.ts +25 -0
- package/otel/spans/transport.span.d.ts +30 -0
- package/otel/trace-context-bridge.d.ts +41 -0
- package/package.json +1 -1
- package/plugin/index.d.ts +3 -0
- package/plugin/observability.hooks.d.ts +92 -0
- package/plugin/observability.plugin.d.ts +141 -0
- package/plugin/observability.plugin.types.d.ts +38 -0
- package/request-log/index.d.ts +3 -0
- package/request-log/request-log.collector.d.ts +97 -0
- package/request-log/request-log.tokens.d.ts +7 -0
- package/request-log/request-log.types.d.ts +82 -0
- package/telemetry/index.d.ts +3 -0
- package/telemetry/telemetry.accessor.d.ts +151 -0
- package/telemetry/telemetry.context-extension.d.ts +35 -0
- package/telemetry/telemetry.tokens.d.ts +7 -0
- package/testing/index.d.ts +76 -0
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { DynamicPlugin, ProviderType } from '@frontmcp/sdk';
|
|
2
|
+
import type { ObservabilityPluginOptions, ObservabilityPluginOptionsInput } from './observability.plugin.types';
|
|
3
|
+
import type { StartupTelemetryData } from '../otel/spans/startup.span';
|
|
4
|
+
/**
|
|
5
|
+
* ObservabilityPlugin — comprehensive, zero-config OpenTelemetry instrumentation
|
|
6
|
+
* for the entire FrontMCP system.
|
|
7
|
+
*
|
|
8
|
+
* When installed, spans are created automatically for:
|
|
9
|
+
* - HTTP requests (every stage: trace → auth → route → transport → finalize)
|
|
10
|
+
* - Tool calls (parseInput → findTool → auth → validate → execute → UI → finalize)
|
|
11
|
+
* - Resource reads (parseInput → find → execute → validate → finalize)
|
|
12
|
+
* - Prompt invocations (parseInput → find → execute → validate → finalize)
|
|
13
|
+
* - Agent calls (parseInput → find → auth → validate → execute → finalize)
|
|
14
|
+
* - Skills (search, load)
|
|
15
|
+
* - List operations (tools, resources, prompts, resource templates)
|
|
16
|
+
* - Completions
|
|
17
|
+
* - Session lifecycle (creation, auth verification)
|
|
18
|
+
*
|
|
19
|
+
* Single trace ID per request. Privacy-safe session tracing ID.
|
|
20
|
+
* Every flow stage recorded as a timed span event.
|
|
21
|
+
*/
|
|
22
|
+
export default class ObservabilityPlugin extends DynamicPlugin<ObservabilityPluginOptions, ObservabilityPluginOptionsInput> {
|
|
23
|
+
static defaultOptions: ObservabilityPluginOptions;
|
|
24
|
+
options: ObservabilityPluginOptions;
|
|
25
|
+
private tracingOpts;
|
|
26
|
+
constructor(options?: ObservabilityPluginOptionsInput);
|
|
27
|
+
private get tracingEnabled();
|
|
28
|
+
_httpWillTrace(ctx: unknown): void;
|
|
29
|
+
_httpWillQuota(ctx: unknown): void;
|
|
30
|
+
_httpDidSemaphore(ctx: unknown): void;
|
|
31
|
+
_httpWillAuth(ctx: unknown): void;
|
|
32
|
+
_httpDidAuth(ctx: unknown): void;
|
|
33
|
+
_httpWillRoute(ctx: unknown): void;
|
|
34
|
+
_httpDidRoute(ctx: unknown): void;
|
|
35
|
+
_httpDidFinalize(ctx: unknown): void;
|
|
36
|
+
_toolWillParse(ctx: unknown): void;
|
|
37
|
+
_toolWillFind(ctx: unknown): void;
|
|
38
|
+
_toolWillCheckAuth(ctx: unknown): void;
|
|
39
|
+
_toolWillCreateCtx(ctx: unknown): void;
|
|
40
|
+
_toolWillValidate(ctx: unknown): void;
|
|
41
|
+
_toolWillExecute(ctx: unknown): void;
|
|
42
|
+
_toolDidExecute(ctx: unknown): void;
|
|
43
|
+
_toolWillValidateOut(ctx: unknown): void;
|
|
44
|
+
_toolWillApplyUI(ctx: unknown): void;
|
|
45
|
+
_toolDidFinalize(ctx: unknown): void;
|
|
46
|
+
_resourceWillParse(ctx: unknown): void;
|
|
47
|
+
_resourceWillFind(ctx: unknown): void;
|
|
48
|
+
_resourceWillExecute(ctx: unknown): void;
|
|
49
|
+
_resourceDidExecute(ctx: unknown): void;
|
|
50
|
+
_resourceDidFinalize(ctx: unknown): void;
|
|
51
|
+
_promptWillParse(ctx: unknown): void;
|
|
52
|
+
_promptWillFind(ctx: unknown): void;
|
|
53
|
+
_promptWillExecute(ctx: unknown): void;
|
|
54
|
+
_promptDidExecute(ctx: unknown): void;
|
|
55
|
+
_promptDidFinalize(ctx: unknown): void;
|
|
56
|
+
_agentWillParse(ctx: unknown): void;
|
|
57
|
+
_agentWillFind(ctx: unknown): void;
|
|
58
|
+
_agentWillExecute(ctx: unknown): void;
|
|
59
|
+
_agentDidExecute(ctx: unknown): void;
|
|
60
|
+
_agentDidFinalize(ctx: unknown): void;
|
|
61
|
+
_listToolsWill(ctx: unknown): void;
|
|
62
|
+
_listToolsDidFind(ctx: unknown): void;
|
|
63
|
+
_listToolsDone(ctx: unknown): void;
|
|
64
|
+
_listResourcesWill(ctx: unknown): void;
|
|
65
|
+
_listResourcesDone(ctx: unknown): void;
|
|
66
|
+
_listTemplatesWill(ctx: unknown): void;
|
|
67
|
+
_listTemplatesDone(ctx: unknown): void;
|
|
68
|
+
_listPromptsWill(ctx: unknown): void;
|
|
69
|
+
_listPromptsDone(ctx: unknown): void;
|
|
70
|
+
_skillSearchWill(ctx: unknown): void;
|
|
71
|
+
_skillSearchDone(ctx: unknown): void;
|
|
72
|
+
_skillLoadWill(ctx: unknown): void;
|
|
73
|
+
_skillLoadStage(ctx: unknown): void;
|
|
74
|
+
_skillLoadDone(ctx: unknown): void;
|
|
75
|
+
_completionWill(ctx: unknown): void;
|
|
76
|
+
_completionDone(ctx: unknown): void;
|
|
77
|
+
_sseWillParse(ctx: unknown): void;
|
|
78
|
+
_sseDidRoute(ctx: unknown): void;
|
|
79
|
+
_sseDidInit(ctx: unknown): void;
|
|
80
|
+
_sseDidMsg(ctx: unknown): void;
|
|
81
|
+
_sseDidElicit(ctx: unknown): void;
|
|
82
|
+
_sseDidCleanup(ctx: unknown): void;
|
|
83
|
+
_shttpWillParse(ctx: unknown): void;
|
|
84
|
+
_shttpDidRoute(ctx: unknown): void;
|
|
85
|
+
_shttpDidInit(ctx: unknown): void;
|
|
86
|
+
_shttpDidMsg(ctx: unknown): void;
|
|
87
|
+
_shttpDidElicit(ctx: unknown): void;
|
|
88
|
+
_shttpDidSse(ctx: unknown): void;
|
|
89
|
+
_shttpDidExtApps(ctx: unknown): void;
|
|
90
|
+
_shttpDidCleanup(ctx: unknown): void;
|
|
91
|
+
_slhttpWillParse(ctx: unknown): void;
|
|
92
|
+
_slhttpDidHandle(ctx: unknown): void;
|
|
93
|
+
_slhttpDidCleanup(ctx: unknown): void;
|
|
94
|
+
_authVerifyWill(ctx: unknown): void;
|
|
95
|
+
_authVerifyDidMode(ctx: unknown): void;
|
|
96
|
+
_authVerifyDidToken(ctx: unknown): void;
|
|
97
|
+
_authVerifyDidBuild(ctx: unknown): void;
|
|
98
|
+
_sessionVerifyWill(ctx: unknown): void;
|
|
99
|
+
_sessionVerifyDidJwt(ctx: unknown): void;
|
|
100
|
+
_sessionVerifyDidBuild(ctx: unknown): void;
|
|
101
|
+
_oauthTokenWill(ctx: unknown): void;
|
|
102
|
+
_oauthTokenDone(ctx: unknown): void;
|
|
103
|
+
_oauthAuthzWill(ctx: unknown): void;
|
|
104
|
+
_oauthAuthzDone(ctx: unknown): void;
|
|
105
|
+
_oauthCallbackWill(ctx: unknown): void;
|
|
106
|
+
_oauthCallbackDone(ctx: unknown): void;
|
|
107
|
+
_oauthProviderWill(ctx: unknown): void;
|
|
108
|
+
_oauthProviderDone(ctx: unknown): void;
|
|
109
|
+
_oauthRegisterWill(ctx: unknown): void;
|
|
110
|
+
_oauthRegisterDone(ctx: unknown): void;
|
|
111
|
+
_elicitReqWill(ctx: unknown): void;
|
|
112
|
+
_elicitReqDone(ctx: unknown): void;
|
|
113
|
+
_elicitResWill(ctx: unknown): void;
|
|
114
|
+
_elicitResDone(ctx: unknown): void;
|
|
115
|
+
_subscribeWill(ctx: unknown): void;
|
|
116
|
+
_subscribeDone(ctx: unknown): void;
|
|
117
|
+
_unsubscribeWill(ctx: unknown): void;
|
|
118
|
+
_unsubscribeDone(ctx: unknown): void;
|
|
119
|
+
_skillsLlmTxtWill(ctx: unknown): void;
|
|
120
|
+
_skillsLlmTxtDone(ctx: unknown): void;
|
|
121
|
+
_skillsLlmFullTxtWill(ctx: unknown): void;
|
|
122
|
+
_skillsLlmFullTxtDone(ctx: unknown): void;
|
|
123
|
+
_skillsApiWill(ctx: unknown): void;
|
|
124
|
+
_skillsApiDone(ctx: unknown): void;
|
|
125
|
+
_jwksWill(ctx: unknown): void;
|
|
126
|
+
_jwksDone(ctx: unknown): void;
|
|
127
|
+
_oauthServerWill(ctx: unknown): void;
|
|
128
|
+
_oauthServerDone(ctx: unknown): void;
|
|
129
|
+
_prmWill(ctx: unknown): void;
|
|
130
|
+
_prmDone(ctx: unknown): void;
|
|
131
|
+
_setLevelWill(ctx: unknown): void;
|
|
132
|
+
_setLevelDone(ctx: unknown): void;
|
|
133
|
+
_toolDidCreateCtxFetch(ctx: unknown): void;
|
|
134
|
+
_agentDidExecuteMeta(ctx: unknown): void;
|
|
135
|
+
/**
|
|
136
|
+
* Emit startup telemetry report.
|
|
137
|
+
* Call after scope.initialize() completes with component counts.
|
|
138
|
+
*/
|
|
139
|
+
reportStartupTelemetry(data: StartupTelemetryData): void;
|
|
140
|
+
static dynamicProviders: (input: ObservabilityPluginOptionsInput) => ProviderType[];
|
|
141
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { TracingOptions } from '../otel/otel.types';
|
|
2
|
+
import type { SinkConfig } from '../logging/log-sink.interface';
|
|
3
|
+
import type { RequestLogCollectorOptions } from '../request-log/request-log.types';
|
|
4
|
+
/**
|
|
5
|
+
* Full resolved options for ObservabilityPlugin.
|
|
6
|
+
*/
|
|
7
|
+
export interface ObservabilityPluginOptions {
|
|
8
|
+
/** OTel tracing configuration */
|
|
9
|
+
tracing: TracingOptions | false;
|
|
10
|
+
/** Structured logging configuration */
|
|
11
|
+
logging: ObservabilityLoggingOptions | false;
|
|
12
|
+
/** Request log collection configuration */
|
|
13
|
+
requestLogs: RequestLogCollectorOptions | false;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Logging options within the plugin.
|
|
17
|
+
*/
|
|
18
|
+
export interface ObservabilityLoggingOptions {
|
|
19
|
+
/** Sink configurations (default: StdoutSink in Node.js, ConsoleSink in browser) */
|
|
20
|
+
sinks?: SinkConfig[];
|
|
21
|
+
/** Field names to redact from log attributes */
|
|
22
|
+
redactFields?: string[];
|
|
23
|
+
/** Include stack traces in error entries (default: true) */
|
|
24
|
+
includeStacks?: boolean;
|
|
25
|
+
/** Static fields to include in every log entry */
|
|
26
|
+
staticFields?: Record<string, unknown>;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* User-facing input options (everything is optional).
|
|
30
|
+
*/
|
|
31
|
+
export interface ObservabilityPluginOptionsInput {
|
|
32
|
+
/** Enable/configure OTel tracing (default: true with all spans enabled) */
|
|
33
|
+
tracing?: boolean | TracingOptions;
|
|
34
|
+
/** Enable/configure structured logging */
|
|
35
|
+
logging?: boolean | ObservabilityLoggingOptions;
|
|
36
|
+
/** Enable/configure request log collection */
|
|
37
|
+
requestLogs?: boolean | RequestLogCollectorOptions;
|
|
38
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import type { StructuredLogEntry } from '../logging/structured-log.types';
|
|
2
|
+
import type { RequestLog, RequestLogCollectorOptions } from './request-log.types';
|
|
3
|
+
/**
|
|
4
|
+
* RequestLogCollector — per-request accumulator for structured log entries.
|
|
5
|
+
*
|
|
6
|
+
* Created once per request (CONTEXT-scoped). Accumulates log entries
|
|
7
|
+
* and metadata, then produces a complete RequestLog on finalize.
|
|
8
|
+
*/
|
|
9
|
+
export declare class RequestLogCollector {
|
|
10
|
+
private readonly entries;
|
|
11
|
+
private readonly hooksTriggered;
|
|
12
|
+
private readonly maxEntries;
|
|
13
|
+
private readonly onComplete?;
|
|
14
|
+
private readonly requestId;
|
|
15
|
+
private readonly traceId;
|
|
16
|
+
private readonly sessionIdHash;
|
|
17
|
+
private readonly scopeId;
|
|
18
|
+
private readonly startTime;
|
|
19
|
+
private httpMethod?;
|
|
20
|
+
private httpPath?;
|
|
21
|
+
private rpcMethod?;
|
|
22
|
+
private toolName?;
|
|
23
|
+
private resourceUri?;
|
|
24
|
+
private promptName?;
|
|
25
|
+
private authType?;
|
|
26
|
+
private authenticated;
|
|
27
|
+
private status;
|
|
28
|
+
private statusCode?;
|
|
29
|
+
private error?;
|
|
30
|
+
private finalized;
|
|
31
|
+
private finalizedLog?;
|
|
32
|
+
constructor(context: {
|
|
33
|
+
requestId: string;
|
|
34
|
+
traceId: string;
|
|
35
|
+
sessionIdHash: string;
|
|
36
|
+
scopeId: string;
|
|
37
|
+
}, options?: RequestLogCollectorOptions);
|
|
38
|
+
/**
|
|
39
|
+
* Add a structured log entry to this request's log.
|
|
40
|
+
* Called by StructuredLogTransport for each log record that
|
|
41
|
+
* matches this request's requestId.
|
|
42
|
+
*/
|
|
43
|
+
addEntry(entry: StructuredLogEntry): void;
|
|
44
|
+
/**
|
|
45
|
+
* Record a hook stage that was triggered during this request.
|
|
46
|
+
*/
|
|
47
|
+
addHook(stage: string): void;
|
|
48
|
+
/**
|
|
49
|
+
* Set HTTP request metadata.
|
|
50
|
+
*/
|
|
51
|
+
setHttpInfo(method: string, path: string): void;
|
|
52
|
+
/**
|
|
53
|
+
* Set the MCP JSON-RPC method being invoked.
|
|
54
|
+
*/
|
|
55
|
+
setRpcMethod(method: string): void;
|
|
56
|
+
/**
|
|
57
|
+
* Set the tool name being invoked.
|
|
58
|
+
*/
|
|
59
|
+
setToolName(name: string): void;
|
|
60
|
+
/**
|
|
61
|
+
* Set the resource URI being read.
|
|
62
|
+
*/
|
|
63
|
+
setResourceUri(uri: string): void;
|
|
64
|
+
/**
|
|
65
|
+
* Set the prompt name being invoked.
|
|
66
|
+
*/
|
|
67
|
+
setPromptName(name: string): void;
|
|
68
|
+
/**
|
|
69
|
+
* Set authentication info.
|
|
70
|
+
*/
|
|
71
|
+
setAuthInfo(type: string, authenticated: boolean): void;
|
|
72
|
+
/**
|
|
73
|
+
* Set request status.
|
|
74
|
+
*/
|
|
75
|
+
setStatus(status: RequestLog['status'], statusCode?: number): void;
|
|
76
|
+
/**
|
|
77
|
+
* Set error details.
|
|
78
|
+
*/
|
|
79
|
+
setError(error: RequestLog['error']): void;
|
|
80
|
+
/**
|
|
81
|
+
* Finalize and produce the complete RequestLog.
|
|
82
|
+
* Fires the onRequestComplete callback if configured.
|
|
83
|
+
*/
|
|
84
|
+
finalize(): Promise<RequestLog>;
|
|
85
|
+
/**
|
|
86
|
+
* Build the RequestLog object from accumulated state.
|
|
87
|
+
*/
|
|
88
|
+
toRequestLog(): RequestLog;
|
|
89
|
+
/**
|
|
90
|
+
* Check if the collector has been finalized.
|
|
91
|
+
*/
|
|
92
|
+
isFinalized(): boolean;
|
|
93
|
+
/**
|
|
94
|
+
* Get the number of accumulated entries.
|
|
95
|
+
*/
|
|
96
|
+
getEntryCount(): number;
|
|
97
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Request Log — per-request aggregated view of all events
|
|
3
|
+
* during a request lifecycle.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Single entry within a request log.
|
|
7
|
+
*/
|
|
8
|
+
export interface RequestLogEntry {
|
|
9
|
+
/** ISO 8601 timestamp */
|
|
10
|
+
timestamp: string;
|
|
11
|
+
/** Log level */
|
|
12
|
+
level: string;
|
|
13
|
+
/** Log message */
|
|
14
|
+
message: string;
|
|
15
|
+
/** Flow stage that produced the entry */
|
|
16
|
+
stage?: string;
|
|
17
|
+
/** Elapsed time since request start (ms) */
|
|
18
|
+
elapsed_ms?: number;
|
|
19
|
+
/** Additional structured attributes */
|
|
20
|
+
attributes?: Record<string, unknown>;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Complete request log — aggregated view of a single request lifecycle.
|
|
24
|
+
*/
|
|
25
|
+
export interface RequestLog {
|
|
26
|
+
/** Unique request identifier */
|
|
27
|
+
request_id: string;
|
|
28
|
+
/** W3C trace ID */
|
|
29
|
+
trace_id: string;
|
|
30
|
+
/** SHA-256 truncated session ID */
|
|
31
|
+
session_id_hash: string;
|
|
32
|
+
/** Scope identifier */
|
|
33
|
+
scope_id: string;
|
|
34
|
+
/** ISO 8601 start time */
|
|
35
|
+
start_time: string;
|
|
36
|
+
/** ISO 8601 end time */
|
|
37
|
+
end_time: string;
|
|
38
|
+
/** Total duration in milliseconds */
|
|
39
|
+
duration_ms: number;
|
|
40
|
+
/** HTTP method (GET, POST, etc.) */
|
|
41
|
+
http_method?: string;
|
|
42
|
+
/** URL path */
|
|
43
|
+
http_path?: string;
|
|
44
|
+
/** MCP JSON-RPC method (e.g., "tools/call", "resources/read") */
|
|
45
|
+
rpc_method?: string;
|
|
46
|
+
/** Tool name (for tools/call requests) */
|
|
47
|
+
tool_name?: string;
|
|
48
|
+
/** Resource URI (for resources/read requests) */
|
|
49
|
+
resource_uri?: string;
|
|
50
|
+
/** Prompt name (for prompts/get requests) */
|
|
51
|
+
prompt_name?: string;
|
|
52
|
+
/** Authentication type used */
|
|
53
|
+
auth_type?: string;
|
|
54
|
+
/** Whether the request was authenticated */
|
|
55
|
+
authenticated: boolean;
|
|
56
|
+
/** Overall request status */
|
|
57
|
+
status: 'ok' | 'error' | 'aborted' | 'rate_limited';
|
|
58
|
+
/** HTTP response status code */
|
|
59
|
+
status_code?: number;
|
|
60
|
+
/** Error details (if status = 'error') */
|
|
61
|
+
error?: {
|
|
62
|
+
type: string;
|
|
63
|
+
message: string;
|
|
64
|
+
code?: string;
|
|
65
|
+
error_id?: string;
|
|
66
|
+
};
|
|
67
|
+
/** Hook stages that were triggered during this request */
|
|
68
|
+
hooks_triggered: string[];
|
|
69
|
+
/** All structured log entries for this request */
|
|
70
|
+
entries: RequestLogEntry[];
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Options for RequestLogCollector.
|
|
74
|
+
*/
|
|
75
|
+
export interface RequestLogCollectorOptions {
|
|
76
|
+
/** Maximum entries to collect per request (default: 500) */
|
|
77
|
+
maxEntries?: number;
|
|
78
|
+
/** Include input/output summaries (default: true) */
|
|
79
|
+
includeSummaries?: boolean;
|
|
80
|
+
/** Callback when request log is complete */
|
|
81
|
+
onRequestComplete?: (log: RequestLog) => void | Promise<void>;
|
|
82
|
+
}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TelemetryAccessor — developer-facing telemetry API.
|
|
3
|
+
*
|
|
4
|
+
* Exposed as `this.telemetry` on all execution contexts (ToolContext,
|
|
5
|
+
* ResourceContext, PromptContext, AgentContext) when ObservabilityPlugin
|
|
6
|
+
* is installed.
|
|
7
|
+
*
|
|
8
|
+
* Key design: `addEvent()` and `setAttributes()` target the **active
|
|
9
|
+
* flow execution span** (e.g., the tool span during tool.execute()).
|
|
10
|
+
* This means events appear in the correct parent span timeline,
|
|
11
|
+
* not as detached spans.
|
|
12
|
+
*
|
|
13
|
+
* `startSpan()` and `withSpan()` create child spans under the active
|
|
14
|
+
* execution span, so they nest correctly in the trace.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```typescript
|
|
18
|
+
* class MyTool extends ToolContext {
|
|
19
|
+
* async execute(input) {
|
|
20
|
+
* // Events go directly on the tool's execution span
|
|
21
|
+
* this.telemetry.addEvent('validation-complete', { valid: true });
|
|
22
|
+
* this.telemetry.setAttributes({ 'user.tier': 'premium' });
|
|
23
|
+
*
|
|
24
|
+
* // Child span — nested under the tool span
|
|
25
|
+
* const result = await this.telemetry.withSpan('fetch-api', async (span) => {
|
|
26
|
+
* const res = await this.fetch('https://api.example.com');
|
|
27
|
+
* span.setAttribute('response.status', res.status);
|
|
28
|
+
* return res.json();
|
|
29
|
+
* });
|
|
30
|
+
* }
|
|
31
|
+
* }
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
import { type Span } from '@opentelemetry/api';
|
|
35
|
+
import { type TraceContextLike } from '../otel/trace-context-bridge';
|
|
36
|
+
/**
|
|
37
|
+
* Minimal context shape — avoids tight coupling to FrontMcpContext.
|
|
38
|
+
*/
|
|
39
|
+
interface TelemetryContextData {
|
|
40
|
+
requestId: string;
|
|
41
|
+
sessionId: string;
|
|
42
|
+
scopeId: string;
|
|
43
|
+
traceContext: TraceContextLike;
|
|
44
|
+
/** FrontMcpContext.get() for reading the active span */
|
|
45
|
+
get?<T>(key: string | symbol): T | undefined;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* A lightweight span wrapper for developer use.
|
|
49
|
+
* Hides OTel complexity while preserving full functionality.
|
|
50
|
+
*/
|
|
51
|
+
export declare class TelemetrySpan {
|
|
52
|
+
private readonly span;
|
|
53
|
+
private hasError;
|
|
54
|
+
constructor(span: Span);
|
|
55
|
+
/** Set a string/number/boolean attribute */
|
|
56
|
+
setAttribute(key: string, value: string | number | boolean): this;
|
|
57
|
+
/** Set multiple attributes at once */
|
|
58
|
+
setAttributes(attrs: Record<string, string | number | boolean>): this;
|
|
59
|
+
/** Add a named event (with optional attributes) */
|
|
60
|
+
addEvent(name: string, attributes?: Record<string, string | number | boolean>): this;
|
|
61
|
+
/** Record an error on this span */
|
|
62
|
+
recordError(error: Error): this;
|
|
63
|
+
/** End the span (preserves ERROR status if already set) */
|
|
64
|
+
end(): void;
|
|
65
|
+
/** End the span with error */
|
|
66
|
+
endWithError(error: Error | string): void;
|
|
67
|
+
/** Get the underlying OTel span (escape hatch) */
|
|
68
|
+
get raw(): Span;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* TelemetryAccessor — the developer-facing API exposed as `this.telemetry`.
|
|
72
|
+
*
|
|
73
|
+
* One instance per request (CONTEXT-scoped). Automatically inherits
|
|
74
|
+
* the request's trace context for seamless span correlation.
|
|
75
|
+
*
|
|
76
|
+
* `addEvent` and `setAttributes` target the active flow span (set by
|
|
77
|
+
* the auto-instrumentation hooks during willExecute). `startSpan` and
|
|
78
|
+
* `withSpan` create child spans under the active flow span.
|
|
79
|
+
*/
|
|
80
|
+
export declare class TelemetryAccessor {
|
|
81
|
+
private readonly tracer;
|
|
82
|
+
private readonly fallbackContext;
|
|
83
|
+
private readonly sessionHash;
|
|
84
|
+
private readonly baseAttributes;
|
|
85
|
+
private readonly ctx;
|
|
86
|
+
constructor(ctx: TelemetryContextData);
|
|
87
|
+
/**
|
|
88
|
+
* Get the active OTel context — prefers the execution span's context
|
|
89
|
+
* (set by hooks during willExecute), falls back to the request-level trace.
|
|
90
|
+
*/
|
|
91
|
+
private getActiveContext;
|
|
92
|
+
/**
|
|
93
|
+
* Get the active execution span (set by auto-instrumentation hooks).
|
|
94
|
+
* Returns undefined if not inside an instrumented execution stage.
|
|
95
|
+
*/
|
|
96
|
+
private getActiveSpan;
|
|
97
|
+
/**
|
|
98
|
+
* Start a new child span under the current execution span.
|
|
99
|
+
*
|
|
100
|
+
* If called inside a tool's execute(), the span is a child of
|
|
101
|
+
* the tool execution span. Otherwise falls back to the request trace.
|
|
102
|
+
*
|
|
103
|
+
* You MUST call `span.end()` or `span.endWithError()` when done.
|
|
104
|
+
*/
|
|
105
|
+
startSpan(name: string, attributes?: Record<string, string | number | boolean>): TelemetrySpan;
|
|
106
|
+
/**
|
|
107
|
+
* Run a function within a child span. Automatically ended on
|
|
108
|
+
* success (OK) or error (ERROR + exception recorded).
|
|
109
|
+
*
|
|
110
|
+
* @example
|
|
111
|
+
* ```typescript
|
|
112
|
+
* const data = await this.telemetry.withSpan('fetch-api', async (span) => {
|
|
113
|
+
* span.addEvent('request-sent');
|
|
114
|
+
* const res = await this.fetch(url);
|
|
115
|
+
* span.setAttribute('status', res.status);
|
|
116
|
+
* return res.json();
|
|
117
|
+
* });
|
|
118
|
+
* ```
|
|
119
|
+
*/
|
|
120
|
+
withSpan<T>(name: string, fn: (span: TelemetrySpan) => Promise<T>, attributes?: Record<string, string | number | boolean>): Promise<T>;
|
|
121
|
+
/**
|
|
122
|
+
* Add an event to the **active flow execution span**.
|
|
123
|
+
*
|
|
124
|
+
* If called inside a tool's execute(), the event appears on the
|
|
125
|
+
* tool execution span. If no active span exists, creates a
|
|
126
|
+
* lightweight child span carrying the event.
|
|
127
|
+
*
|
|
128
|
+
* @param name — event name (e.g., 'validation-complete', 'cache-hit')
|
|
129
|
+
* @param attributes — optional event attributes
|
|
130
|
+
*/
|
|
131
|
+
addEvent(name: string, attributes?: Record<string, string | number | boolean>): void;
|
|
132
|
+
/**
|
|
133
|
+
* Set attributes on the **active flow execution span**.
|
|
134
|
+
*
|
|
135
|
+
* If called inside a tool's execute(), attributes are set on the
|
|
136
|
+
* tool execution span. If no active span exists, this is a no-op.
|
|
137
|
+
*
|
|
138
|
+
* @param attrs — key-value attributes
|
|
139
|
+
*/
|
|
140
|
+
setAttributes(attrs: Record<string, string | number | boolean>): void;
|
|
141
|
+
/**
|
|
142
|
+
* Get the trace ID of the current request.
|
|
143
|
+
* Useful for including in external API calls or logs.
|
|
144
|
+
*/
|
|
145
|
+
get traceId(): string;
|
|
146
|
+
/**
|
|
147
|
+
* Get the session tracing ID (privacy-safe hash).
|
|
148
|
+
*/
|
|
149
|
+
get sessionId(): string;
|
|
150
|
+
}
|
|
151
|
+
export {};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Module augmentation for TypeScript — adds `this.telemetry` to all
|
|
3
|
+
* execution contexts (ToolContext, ResourceContext, PromptContext, AgentContext).
|
|
4
|
+
*
|
|
5
|
+
* This file provides type information only. The runtime getter is
|
|
6
|
+
* installed by the SDK's context extension mechanism when the
|
|
7
|
+
* ObservabilityPlugin registers its `contextExtensions`.
|
|
8
|
+
*/
|
|
9
|
+
import type { TelemetryAccessor } from './telemetry.accessor';
|
|
10
|
+
declare module '@frontmcp/sdk' {
|
|
11
|
+
interface ExecutionContextBase {
|
|
12
|
+
/**
|
|
13
|
+
* Telemetry API — create spans, add events, set attributes.
|
|
14
|
+
*
|
|
15
|
+
* Available when ObservabilityPlugin is installed.
|
|
16
|
+
* Automatically inherits the current request's trace context.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```typescript
|
|
20
|
+
* // Create a child span
|
|
21
|
+
* const span = this.telemetry.startSpan('my-operation');
|
|
22
|
+
* span.setAttribute('key', 'value');
|
|
23
|
+
* span.end();
|
|
24
|
+
*
|
|
25
|
+
* // Auto-managed span
|
|
26
|
+
* await this.telemetry.withSpan('fetch-data', async (span) => {
|
|
27
|
+
* const data = await this.fetch(url);
|
|
28
|
+
* span.addEvent('data-received', { count: data.length });
|
|
29
|
+
* return data;
|
|
30
|
+
* });
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
readonly telemetry: TelemetryAccessor;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Testing utilities for @frontmcp/observability.
|
|
3
|
+
*
|
|
4
|
+
* Provides helpers to set up OTel tracing in tests and assert
|
|
5
|
+
* on exported spans without boilerplate.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```typescript
|
|
9
|
+
* import { createTestTracer, getFinishedSpans, assertSpanExists } from '@frontmcp/observability/testing';
|
|
10
|
+
*
|
|
11
|
+
* describe('my tool', () => {
|
|
12
|
+
* const { tracer, exporter, cleanup } = createTestTracer();
|
|
13
|
+
*
|
|
14
|
+
* afterEach(() => {
|
|
15
|
+
* exporter.reset();
|
|
16
|
+
* });
|
|
17
|
+
*
|
|
18
|
+
* afterAll(async () => {
|
|
19
|
+
* await cleanup();
|
|
20
|
+
* });
|
|
21
|
+
*
|
|
22
|
+
* it('should create a span', async () => {
|
|
23
|
+
* // ... invoke tool ...
|
|
24
|
+
* const spans = getFinishedSpans(exporter);
|
|
25
|
+
* assertSpanExists(spans, 'tool get_weather');
|
|
26
|
+
* });
|
|
27
|
+
* });
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
import { type Tracer } from '@opentelemetry/api';
|
|
31
|
+
import { BasicTracerProvider, InMemorySpanExporter, type ReadableSpan } from '@opentelemetry/sdk-trace-base';
|
|
32
|
+
/**
|
|
33
|
+
* Result from createTestTracer().
|
|
34
|
+
*/
|
|
35
|
+
export interface TestTracer {
|
|
36
|
+
/** OTel Tracer instance (from the test provider, not global) */
|
|
37
|
+
tracer: Tracer;
|
|
38
|
+
/** In-memory span exporter — call .getFinishedSpans() to inspect */
|
|
39
|
+
exporter: InMemorySpanExporter;
|
|
40
|
+
/** The test TracerProvider */
|
|
41
|
+
provider: BasicTracerProvider;
|
|
42
|
+
/** Cleanup function — call in afterAll/afterEach to shutdown the provider */
|
|
43
|
+
cleanup: () => Promise<void>;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Create a test tracer with an in-memory span exporter.
|
|
47
|
+
*
|
|
48
|
+
* Does NOT register globally — uses provider.getTracer() directly
|
|
49
|
+
* to avoid polluting the global tracer provider across tests.
|
|
50
|
+
*
|
|
51
|
+
* @param name - Tracer name (default: 'test')
|
|
52
|
+
*/
|
|
53
|
+
export declare function createTestTracer(name?: string): TestTracer;
|
|
54
|
+
/**
|
|
55
|
+
* Get finished spans from an exporter.
|
|
56
|
+
*/
|
|
57
|
+
export declare function getFinishedSpans(exporter: InMemorySpanExporter): ReadableSpan[];
|
|
58
|
+
/**
|
|
59
|
+
* Assert that a span with the given name exists.
|
|
60
|
+
*
|
|
61
|
+
* @returns The matching span
|
|
62
|
+
* @throws If no span with the name is found
|
|
63
|
+
*/
|
|
64
|
+
export declare function assertSpanExists(spans: ReadableSpan[], name: string): ReadableSpan;
|
|
65
|
+
/**
|
|
66
|
+
* Assert that a span has a specific attribute value.
|
|
67
|
+
*/
|
|
68
|
+
export declare function assertSpanAttribute(span: ReadableSpan, key: string, value: string | number | boolean): void;
|
|
69
|
+
/**
|
|
70
|
+
* Find a span by name.
|
|
71
|
+
*/
|
|
72
|
+
export declare function findSpan(spans: ReadableSpan[], name: string): ReadableSpan | undefined;
|
|
73
|
+
/**
|
|
74
|
+
* Find all spans with a specific attribute value.
|
|
75
|
+
*/
|
|
76
|
+
export declare function findSpansByAttribute(spans: ReadableSpan[], key: string, value: string | number | boolean): ReadableSpan[];
|