132 lines
2.6 KiB
Markdown
132 lines
2.6 KiB
Markdown
# 19. Monitoring & Observability
|
|
|
|
## Structured Logging (Pino)
|
|
|
|
```typescript
|
|
// src/lib/server/logger.ts
|
|
|
|
import pino from 'pino';
|
|
|
|
const isDev = process.env.NODE_ENV === 'development';
|
|
|
|
export const logger = pino({
|
|
level: process.env.LOG_LEVEL ?? (isDev ? 'debug' : 'info'),
|
|
|
|
transport: isDev
|
|
? {
|
|
target: 'pino-pretty',
|
|
options: {
|
|
colorize: true,
|
|
translateTime: 'HH:MM:ss',
|
|
ignore: 'pid,hostname',
|
|
},
|
|
}
|
|
: undefined,
|
|
|
|
formatters: {
|
|
level: (label) => ({ level: label }),
|
|
},
|
|
|
|
base: {
|
|
env: process.env.NODE_ENV,
|
|
version: process.env.APP_VERSION ?? '0.0.0',
|
|
},
|
|
|
|
redact: {
|
|
paths: ['password', 'token', 'authorization'],
|
|
censor: '[REDACTED]',
|
|
},
|
|
});
|
|
|
|
export const dbLogger = logger.child({ module: 'database' });
|
|
export const apiLogger = logger.child({ module: 'api' });
|
|
```
|
|
|
|
## Health Check Endpoint
|
|
|
|
```typescript
|
|
// src/server/functions/health.ts
|
|
|
|
import { createServerFn } from '@tanstack/react-start/server';
|
|
import { db } from '@/lib/server/db';
|
|
|
|
interface HealthStatus {
|
|
status: 'healthy' | 'degraded' | 'unhealthy';
|
|
timestamp: string;
|
|
version: string;
|
|
checks: {
|
|
database: boolean;
|
|
cache: boolean;
|
|
};
|
|
}
|
|
|
|
export const getHealth = createServerFn({ method: 'GET' }).handler(
|
|
async (): Promise<HealthStatus> => {
|
|
const checks = {
|
|
database: false,
|
|
cache: true,
|
|
};
|
|
|
|
try {
|
|
await db.$queryRaw`SELECT 1`;
|
|
checks.database = true;
|
|
} catch {
|
|
// Database check failed
|
|
}
|
|
|
|
const allHealthy = Object.values(checks).every(Boolean);
|
|
|
|
return {
|
|
status: allHealthy ? 'healthy' : 'degraded',
|
|
timestamp: new Date().toISOString(),
|
|
version: process.env.APP_VERSION ?? '0.0.0',
|
|
checks,
|
|
};
|
|
}
|
|
);
|
|
```
|
|
|
|
## Application Metrics
|
|
|
|
```typescript
|
|
// src/lib/server/metrics.ts
|
|
|
|
class MetricsCollector {
|
|
private startTime = Date.now();
|
|
private data = {
|
|
requests: { total: 0, errors: 0 },
|
|
database: { queries: 0, slowQueries: 0 },
|
|
cache: { hits: 0, misses: 0 },
|
|
};
|
|
|
|
incrementRequest(isError = false): void {
|
|
this.data.requests.total++;
|
|
if (isError) this.data.requests.errors++;
|
|
}
|
|
|
|
incrementDbQuery(isSlow = false): void {
|
|
this.data.database.queries++;
|
|
if (isSlow) this.data.database.slowQueries++;
|
|
}
|
|
|
|
incrementCacheHit(): void {
|
|
this.data.cache.hits++;
|
|
}
|
|
|
|
incrementCacheMiss(): void {
|
|
this.data.cache.misses++;
|
|
}
|
|
|
|
getMetrics() {
|
|
return {
|
|
...this.data,
|
|
uptime: Math.floor((Date.now() - this.startTime) / 1000),
|
|
};
|
|
}
|
|
}
|
|
|
|
export const metrics = new MetricsCollector();
|
|
```
|
|
|
|
---
|