logs-time-range
Extract log entries within a specified time range for Google Cloud services. Define start time, end time, filter criteria, and limit results for targeted log analysis.
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:88-152 (handler)Main execution logic for the 'logs-time-range' tool: parses time range, builds Logging API filter, fetches and formats log entries.async ({ startTime, endTime, filter, limit }, _extra) => { 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/tools.ts:82-87 (schema)Input schema using Zod for validation of tool parameters.{ 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:80-152 (registration)Registration of the 'logs-time-range' tool on the MCP server using server.tool().server.tool( 'logs-time-range', { 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 }, _extra) => { 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:40-124 (helper)Helper function to format individual log entries into readable markdown.export function formatLogEntry(entry: LogEntry): string { // Safely format the timestamp let timestamp: string; try { // Check if timestamp exists and is valid if (!entry.timestamp) { timestamp = 'No timestamp'; } else { const date = new Date(entry.timestamp); timestamp = !isNaN(date.getTime()) ? date.toISOString() : String(entry.timestamp); } } catch (error) { 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; // Format the payload with better error handling let payload: string; try { if (entry.textPayload !== undefined && entry.textPayload !== null) { payload = String(entry.textPayload); } else if (entry.jsonPayload) { payload = JSON.stringify(entry.jsonPayload, null, 2); } else if (entry.protoPayload) { payload = JSON.stringify(entry.protoPayload, null, 2); } else if (entry.data) { payload = JSON.stringify(entry.data, null, 2); } else { payload = '[No payload]'; } } catch (error: unknown) { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; payload = `[Error formatting payload: ${errorMessage}]`; } // Format labels if they exist let labelsStr = ''; if (entry.labels && Object.keys(entry.labels).length > 0) { try { labelsStr = Object.entries(entry.labels) .map(([k, v]) => `${k}=${v}`) .join(', '); } catch (error: unknown) { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; labelsStr = `[Error formatting labels: ${errorMessage}]`; } } // Format operation if it exists let operationStr = ''; if (entry.operation) { try { const op = entry.operation as Record<string, any>; operationStr = [ op.id ? `id=${op.id}` : '', op.producer ? `producer=${op.producer}` : '', op.first ? 'first=true' : '', op.last ? 'last=true' : '' ].filter(Boolean).join(', '); } catch (error: unknown) { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; operationStr = `[Error formatting operation: ${errorMessage}]`; } } // Create a more detailed and markdown-friendly format return `## ${timestamp} | ${severity} | ${resource} ` + (entry.logName ? `**Log:** ${entry.logName}\n` : '') + (entry.insertId ? `**ID:** ${entry.insertId}\n` : '') + (labelsStr ? `**Labels:** ${labelsStr}\n` : '') + (operationStr ? `**Operation:** ${operationStr}\n` : '') + `\n\`\`\`\n${payload}\n\`\`\``; }
- src/utils/time.ts:12-53 (helper)Utility to parse startTime/endTime relative strings (e.g. '1h') or ISO dates into Date objects.export function parseRelativeTime(timeString: string): Date { // If it's an ISO date string, parse it directly if (timeString.includes('T') && timeString.includes(':')) { const date = new Date(timeString); if (!isNaN(date.getTime())) { return date; } } // Parse relative time format (e.g., "1h", "2d", "30m") const match = timeString.match(/^(\d+)([smhdw])$/); if (!match) { throw new GcpMcpError( `Invalid time format: ${timeString}. Use format like "30s", "5m", "2h", "1d", or "1w".`, 'INVALID_ARGUMENT', 400 ); } const value = parseInt(match[1]); const unit = match[2]; const now = new Date(); switch (unit) { case 's': // seconds return new Date(now.getTime() - value * 1000); case 'm': // minutes return new Date(now.getTime() - value * 60 * 1000); case 'h': // hours return new Date(now.getTime() - value * 60 * 60 * 1000); case 'd': // days return new Date(now.getTime() - value * 24 * 60 * 60 * 1000); case 'w': // weeks return new Date(now.getTime() - value * 7 * 24 * 60 * 60 * 1000); default: throw new GcpMcpError( `Invalid time unit: ${unit}. Use s, m, h, d, or w.`, 'INVALID_ARGUMENT', 400 ); } }