gcp-logging-query-logs
Query Google Cloud logs using custom filters to search across payload types and metadata fields for operational insights and monitoring.
Instructions
Query Google Cloud Logs with custom filters. Searches across all payload types (text, JSON, proto) and metadata fields.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| filter | Yes | The filter to apply to logs (Cloud Logging query language) | |
| limit | No | Maximum number of log entries to return |
Implementation Reference
- src/services/logging/tools.ts:37-97 (handler)The main execution logic for the 'gcp-logging-query-logs' tool. Queries GCP logs using the provided filter, limits results, formats entries with formatLogEntry helper, handles errors gracefully, and returns formatted markdown response.async ({ filter, limit }) => { try { const projectId = await getProjectId(); const logging = getLoggingClient(); const [entries] = await logging.getEntries({ pageSize: limit, filter, }); if (!entries || entries.length === 0) { return { content: [ { type: "text", text: `No log entries found matching filter: ${filter}`, }, ], }; } const formattedLogs = entries .map((entry) => { try { return formatLogEntry(entry as unknown as LogEntry); } catch (err: unknown) { const errorMessage = err instanceof Error ? err.message : "Unknown error"; return `## Error Formatting Log Entry\n\nAn error occurred while formatting a log entry: ${errorMessage}`; } }) .join("\n\n"); return { content: [ { type: "text", text: `# Log Query Results\n\nProject: ${projectId}\nFilter: ${filter}\nEntries: ${entries.length}\n\n${formattedLogs}`, }, ], }; } catch (error: unknown) { const errorMessage = error instanceof Error ? error.message : "Unknown error"; // Return a user-friendly error message instead of throwing return { content: [ { type: "text", text: `# Error Querying Logs An error occurred while querying logs: ${errorMessage} Please check your filter syntax and try again. For filter syntax help, see: https://cloud.google.com/logging/docs/view/logging-query-language`, }, ], isError: true, }; } },
- src/services/logging/tools.ts:23-35 (schema)Zod input schema defining the tool parameters: 'filter' (required string for log query) and 'limit' (optional number, 1-1000, default 50).inputSchema: { filter: z .string() .describe( "The filter to apply to logs (Cloud Logging query language)", ), limit: z .number() .min(1) .max(1000) .default(50) .describe("Maximum number of log entries to return"), },
- src/services/logging/tools.ts:17-98 (registration)Registers the 'gcp-logging-query-logs' tool on the MCP server within the registerLoggingTools function, including name, title, description, input schema, and inline handler.server.registerTool( "gcp-logging-query-logs", { title: "Query Logs", description: "Query Google Cloud Logs with custom filters. Searches across all payload types (text, JSON, proto) and metadata fields.", inputSchema: { filter: z .string() .describe( "The filter to apply to logs (Cloud Logging query language)", ), limit: z .number() .min(1) .max(1000) .default(50) .describe("Maximum number of log entries to return"), }, }, async ({ filter, limit }) => { try { const projectId = await getProjectId(); const logging = getLoggingClient(); const [entries] = await logging.getEntries({ pageSize: limit, filter, }); if (!entries || entries.length === 0) { return { content: [ { type: "text", text: `No log entries found matching filter: ${filter}`, }, ], }; } const formattedLogs = entries .map((entry) => { try { return formatLogEntry(entry as unknown as LogEntry); } catch (err: unknown) { const errorMessage = err instanceof Error ? err.message : "Unknown error"; return `## Error Formatting Log Entry\n\nAn error occurred while formatting a log entry: ${errorMessage}`; } }) .join("\n\n"); return { content: [ { type: "text", text: `# Log Query Results\n\nProject: ${projectId}\nFilter: ${filter}\nEntries: ${entries.length}\n\n${formattedLogs}`, }, ], }; } catch (error: unknown) { const errorMessage = error instanceof Error ? error.message : "Unknown error"; // Return a user-friendly error message instead of throwing return { content: [ { type: "text", text: `# Error Querying Logs An error occurred while querying logs: ${errorMessage} Please check your filter syntax and try again. For filter syntax help, see: https://cloud.google.com/logging/docs/view/logging-query-language`, }, ], isError: true, }; } }, );
- src/index.ts:160-160 (registration)Top-level call to registerLoggingTools which includes the gcp-logging-query-logs tool on the main MCP server instance.registerLoggingTools(server);
- src/services/logging/types.ts:73-260 (helper)Helper function to format a single GCP LogEntry into a detailed, human-readable markdown string including all available fields like metadata, HTTP details, labels, payload (text/JSON/proto), trace info, etc. Used by the handler to format query results.export function formatLogEntry(entry: LogEntry): string { // Safely format the timestamp let timestamp: string; try { if (!entry.timestamp) { timestamp = "No timestamp"; } else { const date = new Date(entry.timestamp); timestamp = !isNaN(date.getTime()) ? date.toISOString() : String(entry.timestamp); } } catch { timestamp = String(entry.timestamp || "Invalid timestamp"); } const severity = entry.severity || "DEFAULT"; const resourceType = entry.resource?.type || "unknown"; const resourceLabels = entry.resource?.labels ? Object.entries(entry.resource.labels) .map(([k, v]) => `${k}=${v}`) .join(", ") : ""; const resource = resourceLabels ? `${resourceType}(${resourceLabels})` : resourceType; // Start building the comprehensive log entry display let result = `## ${timestamp} | ${severity} | ${resource}\n\n`; // Basic metadata if (entry.logName) result += `**Log Name:** ${entry.logName}\n`; if (entry.insertId) result += `**Insert ID:** ${entry.insertId}\n`; if (entry.receiveTimestamp) { try { const receiveTime = new Date(entry.receiveTimestamp).toISOString(); result += `**Receive Time:** ${receiveTime}\n`; } catch { result += `**Receive Time:** ${entry.receiveTimestamp}\n`; } } // Trace context information if (entry.trace) result += `**Trace:** ${entry.trace}\n`; if (entry.spanId) result += `**Span ID:** ${entry.spanId}\n`; if (entry.traceSampled !== undefined) result += `**Trace Sampled:** ${entry.traceSampled}\n`; // Source location if available if (entry.sourceLocation) { result += `**Source Location:**\n`; if (entry.sourceLocation.file) result += ` - File: ${entry.sourceLocation.file}\n`; if (entry.sourceLocation.line) result += ` - Line: ${entry.sourceLocation.line}\n`; if (entry.sourceLocation.function) result += ` - Function: ${entry.sourceLocation.function}\n`; } // HTTP request details if available if (entry.httpRequest) { result += `**HTTP Request:**\n`; if (entry.httpRequest.requestMethod) result += ` - Method: ${entry.httpRequest.requestMethod}\n`; if (entry.httpRequest.requestUrl) result += ` - URL: ${entry.httpRequest.requestUrl}\n`; if (entry.httpRequest.status) result += ` - Status: ${entry.httpRequest.status}\n`; if (entry.httpRequest.userAgent) result += ` - User Agent: ${entry.httpRequest.userAgent}\n`; if (entry.httpRequest.remoteIp) result += ` - Remote IP: ${entry.httpRequest.remoteIp}\n`; if (entry.httpRequest.latency) result += ` - Latency: ${entry.httpRequest.latency}\n`; if (entry.httpRequest.requestSize) result += ` - Request Size: ${entry.httpRequest.requestSize}\n`; if (entry.httpRequest.responseSize) result += ` - Response Size: ${entry.httpRequest.responseSize}\n`; if (entry.httpRequest.referer) result += ` - Referer: ${entry.httpRequest.referer}\n`; if (entry.httpRequest.protocol) result += ` - Protocol: ${entry.httpRequest.protocol}\n`; if (entry.httpRequest.cacheHit !== undefined) result += ` - Cache Hit: ${entry.httpRequest.cacheHit}\n`; if (entry.httpRequest.cacheLookup !== undefined) result += ` - Cache Lookup: ${entry.httpRequest.cacheLookup}\n`; if (entry.httpRequest.cacheValidatedWithOriginServer !== undefined) { result += ` - Cache Validated: ${entry.httpRequest.cacheValidatedWithOriginServer}\n`; } if (entry.httpRequest.cacheFillBytes) result += ` - Cache Fill Bytes: ${entry.httpRequest.cacheFillBytes}\n`; } // Operation details if available if (entry.operation) { result += `**Operation:**\n`; if (entry.operation.id) result += ` - ID: ${entry.operation.id}\n`; if (entry.operation.producer) result += ` - Producer: ${entry.operation.producer}\n`; if (entry.operation.first !== undefined) result += ` - First: ${entry.operation.first}\n`; if (entry.operation.last !== undefined) result += ` - Last: ${entry.operation.last}\n`; } // Labels if they exist if (entry.labels && Object.keys(entry.labels).length > 0) { try { result += `**Labels:**\n`; Object.entries(entry.labels).forEach(([key, value]) => { result += ` - ${key}: ${value}\n`; }); } catch (error: unknown) { const errorMessage = error instanceof Error ? error.message : "Unknown error"; result += `**Labels:** [Error formatting labels: ${errorMessage}]\n`; } } // Add any additional fields that might be present const knownFields = new Set([ "timestamp", "severity", "resource", "logName", "textPayload", "jsonPayload", "protoPayload", "labels", "insertId", "trace", "spanId", "traceSampled", "sourceLocation", "httpRequest", "operation", "receiveTimestamp", ]); const additionalFields: Record<string, unknown> = {}; Object.entries(entry).forEach(([key, value]) => { if (!knownFields.has(key) && value !== undefined && value !== null) { additionalFields[key] = value; } }); if (Object.keys(additionalFields).length > 0) { result += `**Additional Fields:**\n`; try { result += `\`\`\`json\n${JSON.stringify(additionalFields, null, 2)}\n\`\`\`\n`; } catch (error: unknown) { const errorMessage = error instanceof Error ? error.message : "Unknown error"; result += `[Error formatting additional fields: ${errorMessage}]\n`; } } // Format the main payload result += `\n**Payload:**\n`; try { if (entry.textPayload !== undefined && entry.textPayload !== null) { result += `\`\`\`\n${String(entry.textPayload)}\n\`\`\``; } else if (entry.jsonPayload) { result += `\`\`\`json\n${JSON.stringify(entry.jsonPayload, null, 2)}\n\`\`\``; } else if (entry.protoPayload) { result += `\`\`\`json\n${JSON.stringify(entry.protoPayload, null, 2)}\n\`\`\``; } else { // Check for any other payload-like fields const data = entry.data || entry.message || entry.msg; if (data) { if (typeof data === "string") { result += `\`\`\`\n${data}\n\`\`\``; } else { result += `\`\`\`json\n${JSON.stringify(data, null, 2)}\n\`\`\``; } } else { result += `\`\`\`\n[No payload available]\n\`\`\``; } } } catch (error: unknown) { const errorMessage = error instanceof Error ? error.message : "Unknown error"; result += `\`\`\`\n[Error formatting payload: ${errorMessage}]\n\`\`\``; } return result; }