gcp-trace-find-from-logs
Find Google Cloud trace IDs from Cloud Logging entries using filters to investigate issues across services.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| projectId | No | Optional Google Cloud project ID | |
| filter | Yes | Filter for logs (e.g., "severity>=ERROR AND timestamp>"-1d"") | |
| limit | No | Maximum number of logs to check |
Implementation Reference
- src/services/trace/tools.ts:501-754 (handler)Complete inline handler for the 'gcp-trace-find-from-logs' MCP tool. Processes input filter (supporting relative times like -1d), queries GCP Cloud Logging API, extracts trace IDs from log entries using the helper extractTraceIdFromLog, deduplicates traces, and returns a formatted Markdown table with trace details including timestamp, severity, log name, and message preview.// Tool to find traces associated with logs server.tool( "gcp-trace-find-from-logs", { projectId: z .string() .optional() .describe("Optional Google Cloud project ID"), filter: z .string() .describe( 'Filter for logs (e.g., "severity>=ERROR AND timestamp>\"-1d\"")', ), limit: z .number() .min(1) .max(100) .default(10) .describe("Maximum number of logs to check"), }, async ({ projectId, filter, limit }, context) => { try { // Use provided project ID or get the default one from state manager first const actualProjectId = projectId || stateManager.getCurrentProjectId() || (await getProjectId()); // Process the filter to handle relative time formats let processedFilter = filter; // Check for relative time patterns in the filter const relativeTimeRegex = /(timestamp[><]=?\s*["'])(-?\d+[hmd])(["'])/g; processedFilter = processedFilter.replace( relativeTimeRegex, ( match: string, prefix: string, timeValue: string, suffix: string, ) => { logger.debug( `Found relative time in filter: ${match}, timeValue: ${timeValue}`, ); // Parse the relative time const value = parseInt(timeValue.slice(1, -1)); const unit = timeValue.slice(-1); const isNegative = timeValue.startsWith("-"); // Calculate the absolute time const now = new Date(); const targetDate = new Date(now); if (unit === "h") { targetDate.setHours( targetDate.getHours() + (isNegative ? -value : value), ); } else if (unit === "d") { targetDate.setDate( targetDate.getDate() + (isNegative ? -value : value), ); } else if (unit === "m") { targetDate.setMinutes( targetDate.getMinutes() + (isNegative ? -value : value), ); } // Format as RFC3339 const formattedTime = targetDate.toISOString(); logger.debug( `Converted relative time ${timeValue} to absolute time: ${formattedTime}`, ); // Return the updated filter part return `${prefix}${formattedTime}${suffix}`; }, ); logger.debug(`Original filter: ${filter}`); logger.debug(`Processed filter: ${processedFilter}`); // Initialize the logging client const logging = new Logging({ projectId: actualProjectId, }); // Fetch logs with the processed filter const [entries] = await logging.getEntries({ filter: processedFilter, pageSize: limit, }); if (!entries || entries.length === 0) { return { content: [ { type: "text", text: `No logs found matching the filter: "${filter}" in project: ${actualProjectId}`, }, ], }; } // Extract trace IDs from logs const traceMap = new Map< string, { traceId: string; timestamp: string; severity: string; logName: string; message: string; } >(); for (const entry of entries) { const metadata = entry.metadata; const traceId = extractTraceIdFromLog(metadata); if (traceId) { // Convert timestamp to string let timestampStr = "Unknown"; if (metadata.timestamp) { if (typeof metadata.timestamp === "string") { timestampStr = metadata.timestamp; } else { try { // Handle different timestamp formats if ( typeof metadata.timestamp === "object" && metadata.timestamp !== null ) { if ( "seconds" in metadata.timestamp && "nanos" in metadata.timestamp ) { // Handle Timestamp proto format const seconds = Number(metadata.timestamp.seconds); const nanos = Number(metadata.timestamp.nanos || 0); const milliseconds = seconds * 1000 + nanos / 1000000; timestampStr = new Date(milliseconds).toISOString(); } else { // Try to convert using JSON timestampStr = JSON.stringify(metadata.timestamp); } } else { timestampStr = String(metadata.timestamp); } } catch (e) { timestampStr = "Invalid timestamp"; } } } // Convert severity to string let severityStr = "DEFAULT"; if (metadata.severity) { severityStr = String(metadata.severity); } // Convert logName to string let logNameStr = "Unknown"; if (metadata.logName) { logNameStr = String(metadata.logName); } // Extract message let messageStr = "No message"; if (metadata.textPayload) { messageStr = String(metadata.textPayload); } else if (metadata.jsonPayload) { try { messageStr = JSON.stringify(metadata.jsonPayload); } catch (e) { messageStr = "Invalid JSON payload"; } } traceMap.set(traceId, { traceId, timestamp: timestampStr, severity: severityStr, logName: logNameStr, message: messageStr, }); } } if (traceMap.size === 0) { return { content: [ { type: "text", text: `No traces found in the logs matching the filter: "${filter}" in project: ${actualProjectId}`, }, ], }; } // Format the traces for display let markdown = `# Traces Found in Logs\n\n`; markdown += `Project: ${actualProjectId}\n`; markdown += `Log Filter: ${filter}\n`; markdown += `Found ${traceMap.size} unique traces in ${entries.length} log entries:\n\n`; // Table header markdown += "| Trace ID | Timestamp | Severity | Log Name | Message |\n"; markdown += "|----------|-----------|----------|----------|--------|\n"; // Table rows for (const trace of traceMap.values()) { const traceId = trace.traceId; // Handle timestamp formatting safely let timestamp = trace.timestamp; try { if (timestamp !== "Unknown" && timestamp !== "Invalid timestamp") { timestamp = new Date(trace.timestamp).toISOString(); } } catch (e) { // Keep the original timestamp if conversion fails } const severity = trace.severity; const logName = trace.logName.split("/").pop() || trace.logName; const message = trace.message.length > 100 ? trace.message.substring(0, 100) + "..." : trace.message; markdown += `| \`${traceId}\` | ${timestamp} | ${severity} | ${logName} | ${message} |\n`; } markdown += "\n\nTo view a specific trace, use the `get-trace` tool with the trace ID."; return { content: [ { type: "text", text: markdown, }, ], }; } catch (error: any) { // Error handling for find-traces-from-logs tool throw new GcpMcpError( `Failed to find traces from logs: ${error.message}`, error.code || "UNKNOWN", error.statusCode || 500, ); } }, );
- src/services/trace/tools.ts:504-520 (schema)Zod schema defining input parameters for the tool: optional projectId, required filter string for log query, and limit (default 10, max 100).{ projectId: z .string() .optional() .describe("Optional Google Cloud project ID"), filter: z .string() .describe( 'Filter for logs (e.g., "severity>=ERROR AND timestamp>\"-1d\"")', ), limit: z .number() .min(1) .max(100) .default(10) .describe("Maximum number of logs to check"), },
- src/services/trace/types.ts:777-812 (helper)Utility function to extract trace ID from GCP log entry. Checks logEntry.trace, labels['logging.googleapis.com/trace'], jsonPayload.traceId, and jsonPayload['logging.googleapis.com/trace'], parsing the hex ID from the full resource path.export function extractTraceIdFromLog(logEntry: any): string | undefined { // Check for trace in the standard logging.googleapis.com/trace field if (logEntry.trace) { // The trace field is typically in the format "projects/PROJECT_ID/traces/TRACE_ID" const match = logEntry.trace.match(/traces\/([a-f0-9]+)$/i); if (match && match[1]) { return match[1]; } } // Check for trace in labels if (logEntry.labels && logEntry.labels["logging.googleapis.com/trace"]) { const traceLabel = logEntry.labels["logging.googleapis.com/trace"]; const match = traceLabel.match(/traces\/([a-f0-9]+)$/i); if (match && match[1]) { return match[1]; } } // Check for trace in jsonPayload if (logEntry.jsonPayload) { if (logEntry.jsonPayload.traceId) { return logEntry.jsonPayload.traceId; } if (logEntry.jsonPayload["logging.googleapis.com/trace"]) { const tracePayload = logEntry.jsonPayload["logging.googleapis.com/trace"]; const match = tracePayload.match(/traces\/([a-f0-9]+)$/i); if (match && match[1]) { return match[1]; } } } return undefined; }
- src/services/trace/index.ts:18-18 (registration)Calls registerTraceTools(server) which registers the gcp-trace-find-from-logs tool among others.await registerTraceTools(server);