Skip to content

Commit d707fde

Browse files
Maximo-Gukdeloreyj
authored andcommitted
Use extractRelevantLogInfo() to extract relevant log information for workers observability
1 parent 78921cb commit d707fde

File tree

6 files changed

+128
-13
lines changed

6 files changed

+128
-13
lines changed

apps/workers-observability/src/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ export type Props = {
2424

2525
export type State = { activeAccountId: string | null }
2626

27-
export class MyMCP extends McpAgent<Env, State, Props> {
27+
export class ObservabilityMCP extends McpAgent<Env, State, Props> {
2828
server = new McpServer({
2929
name: 'Remote MCP Server with Workers Observability',
3030
version: '1.0.0',
@@ -78,7 +78,7 @@ const ObservabilityScopes = {
7878
export default new OAuthProvider({
7979
apiRoute: '/sse',
8080
// @ts-ignore
81-
apiHandler: MyMCP.mount('/sse'),
81+
apiHandler: ObservabilityMCP.mount('/sse'),
8282
// @ts-ignore
8383
defaultHandler: createAuthHandlers({ scopes: ObservabilityScopes }),
8484
authorizeEndpoint: '/oauth/authorize',

apps/workers-observability/src/tools/logs.ts

Lines changed: 87 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,90 @@ import { z } from 'zod'
22

33
import { handleWorkerLogs, handleWorkerLogsKeys } from '@repo/mcp-common/src/api/workers-logs'
44

5-
import type { MyMCP } from '../index'
5+
import type { zReturnedQueryRunEvents } from '@repo/mcp-common/src/types/workers-logs-schemas'
6+
import type { ObservabilityMCP } from '../index'
7+
8+
type RelevantLogInfo = z.infer<typeof RelevantLogInfoSchema>
9+
const RelevantLogInfoSchema = z.object({
10+
timestamp: z.string(),
11+
path: z.string().nullable(),
12+
method: z.string().nullable(),
13+
status: z.number().nullable(),
14+
outcome: z.string(),
15+
eventType: z.string(),
16+
duration: z.number().nullable(),
17+
error: z.string().nullable(),
18+
message: z.string().nullable(),
19+
requestId: z.string(),
20+
rayId: z.string().nullable(),
21+
exceptionStack: z.string().nullable(),
22+
})
23+
24+
/**
25+
* Extracts only the most relevant information from a worker log event ( this is to avoid crashing Claude when returning too much data )
26+
* @param event z.array(zReturnedTelemetryEvent).optional()
27+
* @returns Relevant information extracted from the log
28+
*/
29+
function extractRelevantLogInfo(events: zReturnedQueryRunEvents['events'] = []): RelevantLogInfo[] {
30+
return events.map((event) => {
31+
const workers = event.$workers
32+
const metadata = event.$metadata
33+
const source = event.source
34+
35+
let path = null
36+
let method = null
37+
let status = null
38+
if (workers?.event?.request) {
39+
path = workers.event.request.path ?? null
40+
method = workers.event.request.method ?? null
41+
}
42+
43+
if (workers?.event?.response) {
44+
status = workers.event.response.status ?? null
45+
}
46+
47+
let error = null
48+
if (metadata.error) {
49+
error = metadata.error
50+
}
51+
52+
let message = metadata?.message ?? null
53+
if (!message) {
54+
if (workers?.event?.rpcMethod) {
55+
message = `RPC: ${workers.event.rpcMethod}`
56+
} else if (path && method) {
57+
message = `${method} ${path}`
58+
}
59+
}
60+
61+
// Calculate duration
62+
const duration = (workers?.wallTimeMs || 0) + (workers?.cpuTimeMs || 0)
63+
64+
// Extract rayId if available
65+
const rayId = workers?.event?.rayId ?? null
66+
67+
let exceptionStack = null
68+
// Extract exception stack if available
69+
if (typeof source !== 'string') {
70+
exceptionStack = source?.exception?.stack ?? null
71+
}
72+
73+
return {
74+
timestamp: new Date(event.timestamp).toISOString(),
75+
path,
76+
method,
77+
status,
78+
outcome: workers?.outcome || 'unknown',
79+
eventType: workers?.eventType || 'unknown',
80+
duration: duration || null,
81+
error,
82+
message,
83+
requestId: workers?.requestId || metadata?.id || 'unknown',
84+
rayId,
85+
exceptionStack,
86+
}
87+
})
88+
}
689

790
// Worker logs parameter schema
891
const workerNameParam = z.string().describe('The name of the worker to analyze logs for')
@@ -27,7 +110,7 @@ const rayIdParam = z.string().optional().describe('Filter logs by specific Cloud
27110
* @param accountId Cloudflare account ID
28111
* @param apiToken Cloudflare API token
29112
*/
30-
export function registerLogsTools(agent: MyMCP) {
113+
export function registerLogsTools(agent: ObservabilityMCP) {
31114
// Register the worker logs analysis tool by worker name
32115
agent.server.tool(
33116
'worker_logs_by_worker_name',
@@ -125,12 +208,13 @@ export function registerLogsTools(agent: MyMCP) {
125208
shouldFilterErrors,
126209
rayId,
127210
})
211+
const events = logs?.events?.events ?? []
128212
return {
129213
content: [
130214
{
131215
type: 'text',
132216
text: JSON.stringify({
133-
logs,
217+
events: extractRelevantLogInfo(events),
134218
stats: {
135219
timeRange: {
136220
from,

apps/workers-observability/worker-configuration.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ declare namespace Cloudflare {
77
CLOUDFLARE_CLIENT_SECRET: string;
88
ENVIRONMENT: string;
99
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
10-
MCP_OBJECT: DurableObjectNamespace<import("./src/index").MyMCP>;
10+
MCP_OBJECT: DurableObjectNamespace<import("./src/index").ObservabilityMCP>;
1111
}
1212
}
1313
interface Env extends Cloudflare.Env {}

apps/workers-observability/wrangler.jsonc

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
"name": "mcp-cloudflare-workers-observability-dev",
1111
"migrations": [
1212
{
13-
"new_sqlite_classes": ["MyMCP"],
13+
"new_sqlite_classes": ["ObservabilityMCP"],
1414
"tag": "v1"
1515
}
1616
],
@@ -20,7 +20,7 @@
2020
"durable_objects": {
2121
"bindings": [
2222
{
23-
"class_name": "MyMCP",
23+
"class_name": "ObservabilityMCP",
2424
"name": "MCP_OBJECT"
2525
}
2626
]
@@ -47,7 +47,7 @@
4747
"durable_objects": {
4848
"bindings": [
4949
{
50-
"class_name": "MyMCP",
50+
"class_name": "ObservabilityMCP",
5151
"name": "MCP_OBJECT"
5252
}
5353
]
@@ -69,7 +69,7 @@
6969
"durable_objects": {
7070
"bindings": [
7171
{
72-
"class_name": "MyMCP",
72+
"class_name": "ObservabilityMCP",
7373
"name": "MCP_OBJECT"
7474
}
7575
]

packages/mcp-common/src/api/workers-logs.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { fetchCloudflareApi } from '../cloudflare-api'
2-
import { zKeysResponse, zReturnedQueryRunResult } from '../schemas/workers-logs-schemas'
2+
import { zKeysResponse, zReturnedQueryRunResult } from '../types/workers-logs-schemas'
33
import { V4Schema } from '../v4-api'
44

55
/**

packages/mcp-common/src/schemas/workers-logs-schemas.ts renamed to packages/mcp-common/src/types/workers-logs-schemas.ts

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -115,8 +115,27 @@ export const zStatistics = z.object({
115115
bytes_read: z.number(),
116116
})
117117

118+
const zCloudflareMiniEventDetailsRequest = z.object({
119+
url: z.string().optional(),
120+
method: z.string().optional(),
121+
path: z.string().optional(),
122+
search: z.record(z.string()).optional(),
123+
})
124+
125+
const zCloudflareMiniEventDetailsResponse = z.object({
126+
status: z.number().optional(),
127+
})
128+
129+
const zCloudflareMiniEventDetails = z.object({
130+
request: zCloudflareMiniEventDetailsRequest.optional(),
131+
response: zCloudflareMiniEventDetailsResponse.optional(),
132+
rpcMethod: z.string().optional(),
133+
rayId: z.string().optional(),
134+
executionModel: z.string().optional(),
135+
})
136+
118137
export const zCloudflareMiniEvent = z.object({
119-
event: z.record(z.string(), z.unknown()).optional(),
138+
event: zCloudflareMiniEventDetails,
120139
scriptName: z.string(),
121140
outcome: z.string(),
122141
eventType: z.enum([
@@ -161,10 +180,21 @@ export const zCloudflareEvent = zCloudflareMiniEvent.extend({
161180
cpuTimeMs: z.number(),
162181
})
163182

183+
const zSourceSchema = z.object({
184+
exception: z
185+
.object({
186+
stack: z.string().optional(),
187+
name: z.string().optional(),
188+
message: z.string().optional(),
189+
timestamp: z.number().optional(),
190+
})
191+
.optional(),
192+
})
193+
164194
export const zReturnedTelemetryEvent = z.object({
165195
dataset: z.string(),
166196
timestamp: z.number().int().positive(),
167-
source: z.union([z.string(), z.object({})]),
197+
source: z.union([z.string(), zSourceSchema]),
168198
$workers: z.union([zCloudflareMiniEvent, zCloudflareEvent]).optional(),
169199
$metadata: z.object({
170200
id: z.string(),
@@ -198,6 +228,7 @@ export const zReturnedTelemetryEvent = z.object({
198228
}),
199229
})
200230

231+
export type zReturnedQueryRunEvents = z.infer<typeof zReturnedQueryRunEvents>
201232
export const zReturnedQueryRunEvents = z.object({
202233
events: z.array(zReturnedTelemetryEvent).optional(),
203234
fields: z

0 commit comments

Comments
 (0)