Skip to main content
Glama
krzko

Google Cloud MCP Server

by krzko

gcp-logging-query-logs

Query Google Cloud Logs using custom filters to search across payload types and metadata fields. Retrieve specific log entries efficiently with defined limits.

Instructions

Query Google Cloud Logs with custom filters. Searches across all payload types (text, JSON, proto) and metadata fields.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
filterYesThe filter to apply to logs (Cloud Logging query language)
limitNoMaximum number of log entries to return

Implementation Reference

  • The main execution logic for the 'gcp-logging-query-logs' tool. Queries GCP Logging API using the filter, limits results, formats entries with formatLogEntry, and returns markdown-formatted response or error.
    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, }; } },
  • Input schema validation using Zod for the tool parameters: filter (required string) and limit (optional number, default 50, range 1-1000). Includes tool title and description.
    { 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"), },
  • Registration of the 'gcp-logging-query-logs' tool on the MCP server using server.registerTool, including name, schema, and handler reference.
    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, }; } }, );
  • Helper function to initialize and return the Google Cloud Logging client instance, used in the tool handler.
    export function getLoggingClient(): Logging { return new Logging({ projectId: process.env.GOOGLE_CLOUD_PROJECT, }); }
  • Helper function that formats a GCP LogEntry into a detailed Markdown string, displaying all available fields comprehensively. Used to process 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; }
  • TypeScript interface defining the structure of a GCP LogEntry, used for type safety in formatting and handling.
    export interface LogEntry { timestamp: string; severity: string; resource: { type: string; labels: Record<string, string>; }; logName: string; textPayload?: string; jsonPayload?: Record<string, unknown>; protoPayload?: Record<string, unknown>; labels?: Record<string, string>; insertId?: string; trace?: string; spanId?: string; traceSampled?: boolean; sourceLocation?: { file?: string; line?: number; function?: string; }; httpRequest?: { requestMethod?: string; requestUrl?: string; requestSize?: string; status?: number; responseSize?: string; userAgent?: string; remoteIp?: string; referer?: string; latency?: string; cacheLookup?: boolean; cacheHit?: boolean; cacheValidatedWithOriginServer?: boolean; cacheFillBytes?: string; protocol?: string; }; operation?: { id?: string; producer?: string; first?: boolean; last?: boolean; }; receiveTimestamp?: string; [key: string]: unknown; }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/krzko/google-cloud-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server