Observability
Track and monitor your ClickHouse queries with hypequery's logging system
Logging & Observability
hypequery includes built-in logging support to help you track query execution, timing, and status. The logger provides detailed information about your queries, including:
- Query SQL and parameters
- Execution start and end times
- Query duration
- Number of rows processed
- Error details (if any)
- Query-specific logging with unique IDs
Log Levels
The logger supports different log levels to control the verbosity of output:
import { logger } from '@hypequery/clickhouse'; // Available log levels: // - 'debug': Detailed information for debugging // - 'info': General information about query execution // - 'warn': Warning messages // - 'error': Error messages logger.configure({ level: 'debug', // Set the desired log level enabled: true });
Configuring the Logger
import { logger } from '@hypequery/clickhouse'; // Configure logging options logger.configure({ level: 'debug', enabled: true, onQueryLog: (log) => { // Custom logging handler console.log('Query:', log.query); console.log('Duration:', log.duration); console.log('Status:', log.status); } });
Query-Specific Logging
You can subscribe to logs for specific queries using a unique query ID:
import { logger } from '@hypequery/clickhouse'; import { initServe } from '@hypequery/serve'; import { db } from './analytics/client'; const { define, queries, query } = initServe({ context: () => ({ db }) }); export const api = define({ queries: queries({ users: query .describe('Latest active users') .query(async ({ ctx }) => ctx.db.table('users').where('status', 'eq', 'active').limit(25).execute() ), }), }); // Subscribe to logs for a specific query const unsubscribe = logger.subscribeToQuery('users-demo', (log) => { console.log('Query log:', log); }); await api.run('users', { request: { queryId: 'users-demo' } }); unsubscribe();
Global Query Logging
Log all queries executed through the query builder:
import { logger } from '@hypequery/clickhouse'; // Subscribe to all query logs const unsubscribe = logger.onQueryLog((log) => { // Send to your logging service logToDatadog({ query: log.query, duration: log.duration, rows: log.rowCount, error: log.error, }); }); // Later: unsubscribe()
Integration with Logging Services
Datadog
import { logger } from '@hypequery/clickhouse'; logger.configure({ onQueryLog: (log) => { const stats = { query: log.query, duration: log.duration, success: log.status === 'success', timestamp: new Date().toISOString(), }; // Send to Datadog fetch('https://http-intake.logs.datadoghq.com/v1/input/', { method: 'POST', headers: { 'DD-API-KEY': process.env.DATADOG_API_KEY, }, body: JSON.stringify(stats), }); }, });
Custom Logger
import { logger } from '@hypequery/clickhouse'; class CustomLogger { log(level: string, message: string, meta?: any) { // Your custom logging implementation console.log(`[${level.toUpperCase()}] ${message}`, meta); } } logger.configure({ onQueryLog: (log) => { const customLogger = new CustomLogger(); if (log.error) { customLogger.log('error', 'Query failed', { query: log.query, error: log.error.message, duration: log.duration, }); } else { customLogger.log('info', 'Query executed', { query: log.query, duration: log.duration, rows: log.rowCount, }); } }, });
Performance Monitoring
Track slow queries:
import { logger } from '@hypequery/clickhouse'; const SLOW_QUERY_THRESHOLD = 1000; // 1 second logger.configure({ onQueryLog: (log) => { if (log.duration > SLOW_QUERY_THRESHOLD) { console.warn('Slow query detected:', { query: log.query, duration: `${log.duration}ms`, threshold: `${SLOW_QUERY_THRESHOLD}ms`, }); } }, });
Error Tracking
Capture and analyze query errors:
import { logger } from '@hypequery/clickhouse'; logger.configure({ onQueryLog: (log) => { if (log.error) { // Send to error tracking service trackError({ type: 'QueryError', message: log.error.message, query: log.query, stack: log.error.stack, context: { duration: log.duration, params: log.params, }, }); } }, });
Structured Logging
Use structured logging for better analysis:
import { logger } from '@hypequery/clickhouse'; interface QueryLog { timestamp: string; query: string; duration_ms: number; rows: number; status: 'success' | 'error'; error?: string; } logger.configure({ onQueryLog: (log) => { const structuredLog: QueryLog = { timestamp: new Date().toISOString(), query: log.query, duration_ms: log.duration, rows: log.rowCount, status: log.status, error: log.error?.message, }; // Log as JSON for parsing by logging tools console.log(JSON.stringify(structuredLog)); }, });
Lifecycle Hooks with Serve
Combine logging with Serve lifecycle hooks:
import { initServe } from '@hypequery/serve'; const { define, queries } = initServe({ hooks: { onRequestStart: async (event) => { console.log(`[START] ${event.queryKey} at ${new Date().toISOString()}`); }, onRequestEnd: async (event) => { console.log(`[END] ${event.queryKey} took ${event.durationMs}ms`); }, onError: async (event) => { console.error(`[ERROR] ${event.queryKey}:`, event.error); }, }, queries: { // Your queries here }, });