ADR-022: Error Handling and Logging Strategy
Status
Section titled “Status”Proposed (aspirational)
Note: This ADR is aspirational guidance, not an enforced rule. Unlike Accepted ADRs — which the agent constitution treats as binding (halt-on-violation) — this one documents a recommended error-handling/logging strategy a cloner may adopt as their app grows. It is intentionally not wired into
quality:cior the halt gate. Promote to Accepted if/when the strategy is implemented in the template itself.
Context
Section titled “Context”The Astro starter template currently lacks a comprehensive error handling and logging strategy. As the application grows, we need consistent patterns for:
- Error handling: How to catch, log, and recover from errors
- Structured logging: What to log, at what level, and in what format
- Observability: How to monitor application health and debug issues
- Security: Ensuring sensitive data isn’t logged
This becomes especially critical for:
- Dynamic routes that may encounter missing content
- Form submissions that may fail
- Build-time operations (token generation, link validation)
- Production deployments where debugging is harder
Decision Drivers
Section titled “Decision Drivers”- Debuggability: Developers need clear error messages to diagnose issues
- Security: Never log sensitive data (tokens, passwords, PII)
- Performance: Logging should have minimal overhead
- Observability: Errors should be trackable in production
- User Experience: Users should see helpful error messages, not stack traces
Considered Options
Section titled “Considered Options”Option 1: Console.log Only (Current State)
Section titled “Option 1: Console.log Only (Current State)”Description: Ad-hoc console.log() and console.error() statements
Pros:
- Simple, no dependencies
- Works in browser and Node.js
Cons:
- No structure, hard to parse
- No log levels (debug vs error)
- No context (timestamps, request IDs)
- Can’t filter or search logs
Option 2: Structured Logging with Pino
Section titled “Option 2: Structured Logging with Pino”Description: Use Pino for structured JSON logging
Pros:
- Fast (5x faster than Winston)
- Structured JSON output
- Log levels (trace, debug, info, warn, error, fatal)
- Child loggers with context
- Production-ready
Cons:
- Additional dependency
- Requires configuration
- JSON logs less readable in development
Option 3: Custom Logger Wrapper
Section titled “Option 3: Custom Logger Wrapper”Description: Build a thin wrapper around console with structure
Pros:
- No dependencies
- Full control over format
- Can add structure gradually
Cons:
- Reinventing the wheel
- Missing features (log rotation, transports)
- Maintenance burden
Decision
Section titled “Decision”We will implement Option 2 (Structured Logging with Pino) for build scripts and server-side code, with the following patterns:
1. Error Handling Patterns
Section titled “1. Error Handling Patterns”// ✅ Graceful error handling in dynamic routesexport async function getStaticPaths() { try { const posts = await getCollection('blog'); return posts.map(post => ({ params: { slug: post.slug }, props: { post }, })); } catch (error) { logger.error({ error, context: 'getStaticPaths' }, 'Failed to load blog posts'); // Fail build - don't deploy broken site throw error; }}
// ✅ Defensive error handling in pagesconst { slug } = Astro.params;const post = await getPost(slug);
if (!post) { logger.warn({ slug }, 'Post not found, redirecting to 404'); return Astro.redirect('/404', 303);}2. Structured Logging
Section titled “2. Structured Logging”import pino from 'pino';
export const logger = pino({ level: process.env.LOG_LEVEL || 'info', transport: { target: 'pino-pretty', options: { colorize: true, ignore: 'pid,hostname', translateTime: 'HH:MM:ss', }, },});
// Usagelogger.info({ userId: 123, action: 'login' }, 'User logged in');logger.error({ error, context: 'database' }, 'Database connection failed');3. Security: Redact Sensitive Data
Section titled “3. Security: Redact Sensitive Data”// ✅ Redact sensitive fieldsconst logger = pino({ redact: { paths: [ 'password', 'token', 'apiKey', 'email', '*.password', '*.token', 'req.headers.authorization', ], censor: '[REDACTED]', },});
// Example: email is redactedlogger.info({ email: 'user@example.com', action: 'signup' }, 'User signed up');// Output: { email: '[REDACTED]', action: 'signup', msg: 'User signed up' }4. Build-Time Logging
Section titled “4. Build-Time Logging”import { logger } from './logger';
export async function buildTokens() { logger.info('Starting token build');
try { const tokens = await loadTokens(); logger.debug({ tokenCount: tokens.length }, 'Loaded tokens');
const validated = validateContrast(tokens); logger.info({ validated: validated.length }, 'Validated token contrast');
await writeTokens(validated); logger.info('Token build complete'); } catch (error) { logger.error({ error }, 'Token build failed'); process.exit(1); }}5. Client-Side Error Handling
Section titled “5. Client-Side Error Handling”// For client-side errors (Preact islands, forms)window.addEventListener('error', (event) => { // Send to error tracking service (Sentry, LogRocket, etc.) console.error('Unhandled error:', event.error);
// Don't log in production console (security) if (import.meta.env.PROD) { event.preventDefault(); }});
// Form error handlingasync function handleSubmit(event: Event) { event.preventDefault();
try { const response = await fetch('/api/contact', { method: 'POST', body: new FormData(event.target as HTMLFormElement), });
if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`); }
// Success window.location.href = '/thank-you'; } catch (error) { // Show user-friendly error showError('Failed to send message. Please try again.');
// Log for debugging (only in dev) if (import.meta.env.DEV) { console.error('Form submission failed:', error); } }}Consequences
Section titled “Consequences”Positive
Section titled “Positive”- Better debugging: Structured logs are searchable and filterable
- Security: Sensitive data is automatically redacted
- Observability: Can integrate with log aggregation tools (Datadog, Splunk)
- Performance: Pino is fast (minimal overhead)
- Consistency: All errors logged in same format
Negative
Section titled “Negative”- Dependency: Adds Pino and pino-pretty to devDependencies
- Learning curve: Team needs to learn structured logging
- JSON logs: Less readable in development (mitigated by pino-pretty)
Neutral
Section titled “Neutral”- Build size: No impact (logging is build-time only)
- Runtime: No client-side logging library (uses native console)
Validation
Section titled “Validation”- Build logs: Check that token build, link validation use structured logging
- Error scenarios: Test 404 handling, form errors, missing content
- Security: Verify sensitive data is redacted in logs
- Performance: Ensure logging doesn’t slow down builds
Implementation Checklist
Section titled “Implementation Checklist”- Install Pino and pino-pretty
- Create
scripts/logger.tswith redaction config - Update
scripts/src/build-tokens.tsto use logger - Update
scripts/check-review-dates.mjsto use logger (amended 2026-08-02: this script was never created — ADR-006, which planned it, was withdrawn) - Add error handling to dynamic routes (
[slug].astro) - Document logging patterns in CONTRIBUTING.md
- Add log level configuration to
.env.example
References
Section titled “References”Related ADRs
Section titled “Related ADRs”- ADR-011: Dynamic Route Error Handling (implements graceful degradation)
- ADR-021: Contact Form Progressive Enhancement (form error handling)
Date: 2025-11-15
Participants: Development Team
Outcome: Proposed
Enforcement
Section titled “Enforcement”Not enforced — this record’s status is Proposed; only Accepted ADRs are binding (see the status table in the ADR README and ADR-039).