diff --git a/.env.example b/.env.example index 2c25119..85a7e04 100644 --- a/.env.example +++ b/.env.example @@ -26,6 +26,10 @@ R2_SECRET_ACCESS_KEY="" # R2 API token Secre R2_BUCKET="vash-esports-images" # Bucket name, e.g. vash-esports-images R2_PUBLIC_URL="https://cdn.esports.vash.software" # Public URL for the bucket, e.g. https://cdn.esports.vash.software +# Dozzle log viewer (https://dozzle.dev) +DOZZLE_USERNAME="admin" +DOZZLE_PASSWORD="" # Password for the Dozzle web UI + # Sentry error tracking (https://sentry.io) PUBLIC_SENTRY_DSN="" # DSN is write-only, safe to expose client-side SENTRY_AUTH_TOKEN="" # Only needed in CI for source map uploads diff --git a/docker-compose.yml b/docker-compose.yml index 08adb36..612dc6e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -85,6 +85,18 @@ services: retries: 5 start_period: 30s + dozzle: + image: amir20/dozzle:latest + restart: unless-stopped + ports: + - '127.0.0.1:8080:8080' # Proxy via nginx at e.g. logs.vash.software + volumes: + - /var/run/docker.sock:/var/run/docker.sock:ro + environment: + DOZZLE_AUTH_PROVIDER: simple + DOZZLE_USERNAME: ${DOZZLE_USERNAME:-admin} + DOZZLE_PASSWORD: ${DOZZLE_PASSWORD} + volumes: pgdata: umamidata: diff --git a/src/hooks.server.ts b/src/hooks.server.ts index 3200598..0890b10 100644 --- a/src/hooks.server.ts +++ b/src/hooks.server.ts @@ -39,6 +39,9 @@ if (!building) { } // ── Request logging middleware ── +// Paths that are too noisy to log every time (healthchecks, polling) +const SILENT_PATHS = new Set(['/api/health', '/api/queue']); + const handleRequestLogging: Handle = async ({ event, resolve }) => { const requestId = crypto.randomUUID(); const start = performance.now(); @@ -48,26 +51,33 @@ const handleRequestLogging: Handle = async ({ event, resolve }) => { const response = await resolve(event); const duration = Math.round(performance.now() - start); - const userId = event.locals.user?.id; - - log.http.info( - { - requestId, - method: event.request.method, - path: event.url.pathname, - status: response.status, - duration, - userId: userId ?? null, - ip: event.getClientAddress() - }, - `${event.request.method} ${event.url.pathname} ${response.status} ${duration}ms` - ); + const path = event.url.pathname; + const status = response.status; + // Always count metrics, but only log interesting requests metrics.requestCount++; metrics.totalResponseTime += duration; + const isSilent = SILENT_PATHS.has(path) && status < 400; + + if (!isSilent) { + const userId = event.locals.user?.id; + log.http.info( + { + requestId, + method: event.request.method, + path, + status, + duration, + userId: userId ?? null, + ip: event.getClientAddress() + }, + `${event.request.method} ${path} ${status} ${duration}ms` + ); + } + if (duration > 1000) { - log.http.warn({ requestId, path: event.url.pathname, duration }, 'Slow request'); + log.http.warn({ requestId, path, duration }, 'Slow request'); metrics.slowRequests++; }