gcp-logging-query-time-range
Query Google Cloud Logs within a specific time range using ISO timestamps or relative times (e.g., '1h', '2d'). Apply filters and set limits for precise log retrieval on Google Cloud MCP Server.
Instructions
Query Google Cloud Logs within a specific time range. Supports relative times (1h, 2d) and ISO timestamps.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| endTime | No | End time in ISO format (defaults to now) | |
| filter | No | Additional filter criteria | |
| limit | No | Maximum number of log entries to return | |
| startTime | Yes | Start time in ISO format or relative time (e.g., "1h", "2d") |
Implementation Reference
- src/services/logging/tools.ts:126-197 (handler)Handler function implementing GCP Logging query by time range: parses times, builds timestamp filter, queries API, formats entries with formatLogEntry, returns markdown results or error.async ({ startTime, endTime, filter, limit }) => { try { const projectId = await getProjectId(); const logging = getLoggingClient(); const start = parseRelativeTime(startTime); const end = endTime ? parseRelativeTime(endTime) : new Date(); // Build filter string let filterStr = `timestamp >= "${start.toISOString()}" AND timestamp <= "${end.toISOString()}"`; if (filter) { filterStr = `${filterStr} AND ${filter}`; } const [entries] = await logging.getEntries({ pageSize: limit, filter: filterStr, }); if (!entries || entries.length === 0) { return { content: [ { type: "text", text: `No log entries found in the specified time range with filter: ${filterStr}`, }, ], }; } 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 Time Range Results\n\nProject: ${projectId}\nTime Range: ${start.toISOString()} to ${end.toISOString()}\nFilter: ${filter || "None"}\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 time range format and try again. Valid formats include: - ISO date strings (e.g., "2025-03-01T00:00:00Z") - Relative time expressions: "1h" (1 hour ago), "2d" (2 days ago), "1w" (1 week ago), etc.`, }, ], isError: true, }; } },
- Zod input schema defining parameters: startTime (required, ISO or relative), endTime (optional), filter (optional), limit (1-1000, default 50).{ title: "Query Logs by Time Range", description: "Query Google Cloud Logs within a specific time range. Supports relative times (1h, 2d) and ISO timestamps.", inputSchema: { startTime: z .string() .describe( 'Start time in ISO format or relative time (e.g., "1h", "2d")', ), endTime: z .string() .optional() .describe("End time in ISO format (defaults to now)"), filter: z.string().optional().describe("Additional filter criteria"), limit: z .number() .min(1) .max(1000) .default(50) .describe("Maximum number of log entries to return"), }, },
- src/services/logging/tools.ts:102-198 (registration)MCP server tool registration for 'gcp-logging-query-time-range' including title, description, input schema, and inline handler."gcp-logging-query-time-range", { title: "Query Logs by Time Range", description: "Query Google Cloud Logs within a specific time range. Supports relative times (1h, 2d) and ISO timestamps.", inputSchema: { startTime: z .string() .describe( 'Start time in ISO format or relative time (e.g., "1h", "2d")', ), endTime: z .string() .optional() .describe("End time in ISO format (defaults to now)"), filter: z.string().optional().describe("Additional filter criteria"), limit: z .number() .min(1) .max(1000) .default(50) .describe("Maximum number of log entries to return"), }, }, async ({ startTime, endTime, filter, limit }) => { try { const projectId = await getProjectId(); const logging = getLoggingClient(); const start = parseRelativeTime(startTime); const end = endTime ? parseRelativeTime(endTime) : new Date(); // Build filter string let filterStr = `timestamp >= "${start.toISOString()}" AND timestamp <= "${end.toISOString()}"`; if (filter) { filterStr = `${filterStr} AND ${filter}`; } const [entries] = await logging.getEntries({ pageSize: limit, filter: filterStr, }); if (!entries || entries.length === 0) { return { content: [ { type: "text", text: `No log entries found in the specified time range with filter: ${filterStr}`, }, ], }; } 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 Time Range Results\n\nProject: ${projectId}\nTime Range: ${start.toISOString()} to ${end.toISOString()}\nFilter: ${filter || "None"}\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 time range format and try again. Valid formats include: - ISO date strings (e.g., "2025-03-01T00:00:00Z") - Relative time expressions: "1h" (1 hour ago), "2d" (2 days ago), "1w" (1 week ago), etc.`, }, ], isError: true, }; } }, );
- src/services/logging/types.ts:73-260 (helper)Helper function to format individual LogEntry into comprehensive markdown display used in tool responses.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; }
- src/services/logging/types.ts:61-65 (helper)Helper to initialize and return the Google Cloud Logging client instance.export function getLoggingClient(): Logging { return new Logging({ projectId: process.env.GOOGLE_CLOUD_PROJECT, }); }