Clerk Observability
Overview
Implement comprehensive monitoring, logging, and observability for Clerk authentication.
Prerequisites
- Clerk integration working
- Monitoring platform (DataDog, New Relic, Sentry, etc.)
- Logging infrastructure
Instructions
Step 1: Authentication Event Logging
// lib/auth-logger.ts import { auth, currentUser } from '@clerk/nextjs/server' interface AuthEvent { type: string userId: string | null timestamp: string metadata: Record<string, any> } class AuthLogger { private events: AuthEvent[] = [] log(type: string, metadata: Record<string, any> = {}) { const event: AuthEvent = { type, userId: null, // Set after auth timestamp: new Date().toISOString(), metadata } this.events.push(event) console.log('[Auth Event]', JSON.stringify(event)) // Send to monitoring service this.sendToMonitoring(event) } async logWithAuth(type: string, metadata: Record<string, any> = {}) { const { userId, sessionId } = await auth() const event: AuthEvent = { type, userId, timestamp: new Date().toISOString(), metadata: { ...metadata, sessionId, hasUser: !!userId } } this.events.push(event) console.log('[Auth Event]', JSON.stringify(event)) this.sendToMonitoring(event) } private sendToMonitoring(event: AuthEvent) { // DataDog example if (process.env.DD_API_KEY) { fetch('https://http-intake.logs.datadoghq.com/v1/input', { method: 'POST', headers: { 'Content-Type': 'application/json', 'DD-API-KEY': process.env.DD_API_KEY }, body: JSON.stringify({ ddsource: 'clerk', ddtags: 'env:production', message: event }) }).catch(console.error) } } } export const authLogger = new AuthLogger()
Step 2: Middleware Monitoring
// middleware.ts import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server' import { NextResponse } from 'next/server' const isPublicRoute = createRouteMatcher(['/', '/sign-in(.*)', '/sign-up(.*)']) export default clerkMiddleware(async (auth, request) => { const start = performance.now() const { userId, sessionId } = await auth() // Log auth check const authDuration = performance.now() - start // Add monitoring headers const response = NextResponse.next() response.headers.set('x-auth-duration', String(authDuration.toFixed(2))) response.headers.set('x-auth-user', userId || 'anonymous') // Log slow auth if (authDuration > 100) { console.warn('[Clerk Perf] Slow auth check:', { duration: authDuration, path: request.nextUrl.pathname, userId }) } // Metrics recordMetric('clerk.auth.duration', authDuration) recordMetric('clerk.auth.success', userId ? 1 : 0) return response }) function recordMetric(name: string, value: number) { // Send to your metrics provider console.log(`[Metric] ${name}: ${value}`) }
Step 3: Session Analytics
// lib/session-analytics.ts import { clerkClient } from '@clerk/nextjs/server' interface SessionMetrics { totalSessions: number activeSessions: number averageSessionDuration: number sessionsByDevice: Record<string, number> } export async function getSessionMetrics(userId: string): Promise<SessionMetrics> { const client = await clerkClient() const sessions = await client.sessions.getSessionList({ userId, status: 'active' }) const sessionsByDevice: Record<string, number> = {} let totalDuration = 0 for (const session of sessions.data) { // Parse user agent for device type const device = parseDeviceType(session.userAgent || '') sessionsByDevice[device] = (sessionsByDevice[device] || 0) + 1 // Calculate duration const duration = Date.now() - new Date(session.createdAt).getTime() totalDuration += duration } return { totalSessions: sessions.totalCount, activeSessions: sessions.data.length, averageSessionDuration: totalDuration / sessions.data.length || 0, sessionsByDevice } } function parseDeviceType(userAgent: string): string { if (/mobile/i.test(userAgent)) return 'mobile' if (/tablet/i.test(userAgent)) return 'tablet' return 'desktop' }
Step 4: Webhook Event Tracking
// app/api/webhooks/clerk/route.ts import { Webhook } from 'svix' import { headers } from 'next/headers' import { WebhookEvent } from '@clerk/nextjs/server' const webhookMetrics = { received: 0, processed: 0, failed: 0, byType: {} as Record<string, number> } export async function POST(req: Request) { webhookMetrics.received++ const start = performance.now() const headerPayload = await headers() const svix_id = headerPayload.get('svix-id')! // ... verification code ... try { const evt = wh.verify(body, headers) as WebhookEvent const eventType = evt.type // Track by type webhookMetrics.byType[eventType] = (webhookMetrics.byType[eventType] || 0) + 1 // Process event await processEvent(evt) webhookMetrics.processed++ // Log success console.log('[Webhook]', { type: eventType, id: svix_id, duration: performance.now() - start, status: 'success' }) return Response.json({ success: true }) } catch (error) { webhookMetrics.failed++ console.error('[Webhook Error]', { id: svix_id, error: error.message, duration: performance.now() - start }) return Response.json({ error: 'Processing failed' }, { status: 500 }) } } // Expose metrics endpoint export async function GET() { return Response.json(webhookMetrics) }
Step 5: Error Tracking with Sentry
// lib/sentry-clerk.ts import * as Sentry from '@sentry/nextjs' import { auth, currentUser } from '@clerk/nextjs/server' export async function initSentryWithClerk() { const { userId, orgId } = await auth() if (userId) { Sentry.setUser({ id: userId, // Don't include email unless necessary for privacy }) Sentry.setContext('clerk', { userId, orgId, hasOrg: !!orgId }) } } // Wrapper for auth operations with error tracking export async function withAuthErrorTracking<T>( operation: () => Promise<T>, context: string ): Promise<T> { try { return await operation() } catch (error) { Sentry.captureException(error, { tags: { component: 'clerk', operation: context } }) throw error } } // Usage const user = await withAuthErrorTracking( () => currentUser(), 'get-current-user' )
Step 6: Health Check Endpoint
// app/api/health/clerk/route.ts import { auth, clerkClient } from '@clerk/nextjs/server' interface HealthStatus { status: 'healthy' | 'degraded' | 'unhealthy' checks: { auth: { status: string; latency: number } api: { status: string; latency: number } webhooks: { status: string; lastReceived: string | null } } timestamp: string } export async function GET() { const health: HealthStatus = { status: 'healthy', checks: { auth: { status: 'unknown', latency: 0 }, api: { status: 'unknown', latency: 0 }, webhooks: { status: 'unknown', lastReceived: null } }, timestamp: new Date().toISOString() } // Check auth function const authStart = performance.now() try { await auth() health.checks.auth = { status: 'ok', latency: performance.now() - authStart } } catch { health.checks.auth = { status: 'error', latency: performance.now() - authStart } health.status = 'degraded' } // Check API connectivity const apiStart = performance.now() try { const client = await clerkClient() await client.users.getUserList({ limit: 1 }) health.checks.api = { status: 'ok', latency: performance.now() - apiStart } } catch { health.checks.api = { status: 'error', latency: performance.now() - apiStart } health.status = 'unhealthy' } return Response.json(health) }
Dashboard Metrics
Track these key metrics:
- Authentication success/failure rate
- Auth latency (p50, p95, p99)
- Active sessions over time
- Webhook processing time
- Error rate by type
Output
- Authentication event logging
- Performance monitoring
- Error tracking integration
- Health check endpoints
Error Handling
| Issue | Monitoring Action |
|---|---|
| High auth latency | Alert on p95 > 200ms |
| Failed webhooks | Alert on failure rate > 1% |
| Session anomalies | Track unusual session patterns |
| API errors | Capture with Sentry context |
Resources
Next Steps
Proceed to clerk-incident-runbook for incident response procedures.