testing ADR - architectural decisions
Some checks failed
Deploy to Test Environment / deploy-to-test (push) Has been cancelled
Some checks failed
Deploy to Test Environment / deploy-to-test (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,85 @@
|
||||
# ADR-004: Standardized Application-Wide Structured Logging
|
||||
|
||||
**Date**: 2025-12-12
|
||||
|
||||
**Status**: Proposed
|
||||
|
||||
## Context
|
||||
|
||||
Our application currently uses a `logger` service, but the implementation of logging varies significantly across different modules. The `errorHandler` middleware produces high-quality, structured JSON logs for errors, but logging within route handlers and service layers is often ad-hoc, using plain strings or inconsistent object structures.
|
||||
|
||||
This inconsistency leads to several problems:
|
||||
1. **Difficult Debugging**: It is hard to trace a single user request through the system or correlate events related to a specific operation.
|
||||
2. **Ineffective Log Analysis**: Inconsistent log formats make it difficult to effectively query, filter, and create dashboards in a log management system (like Datadog, Splunk, or the ELK stack).
|
||||
3. **Security Risks**: There is no enforced standard for redacting sensitive information (like passwords or tokens) in logs outside of the `errorHandler`, increasing the risk of accidental data exposure.
|
||||
4. **Missing Context**: Logs often lack crucial context, such as a unique request ID, the authenticated user's ID, or the source IP address, making them less useful for diagnosing issues.
|
||||
|
||||
## Decision
|
||||
|
||||
We will adopt a standardized, application-wide structured logging policy. All log entries MUST be in JSON format and adhere to a consistent schema.
|
||||
|
||||
1. **Request-Scoped Logger with Context**: We will create a middleware that runs at the beginning of the request lifecycle. This middleware will:
|
||||
* Generate a unique `request_id` for each incoming request.
|
||||
* Create a request-scoped logger instance (a "child logger") that automatically includes the `request_id`, `user_id` (if authenticated), and `ip_address` in every log message it generates.
|
||||
* Attach this child logger to the `req` object (e.g., `req.log`).
|
||||
|
||||
2. **Mandatory Use of Request-Scoped Logger**: All route handlers and any service functions called by them **MUST** use the request-scoped logger (`req.log`) instead of the global logger instance. This ensures all logs for a given request are automatically correlated.
|
||||
|
||||
3. **Standardized Log Schema**: All log messages should follow a base schema. The logger configuration will be updated to enforce this.
|
||||
* **Base Fields**: `level`, `timestamp`, `message`, `request_id`, `user_id`, `ip_address`.
|
||||
* **Error Fields**: When logging an error, the log entry MUST include an `error` object with `name`, `message`, and `stack`.
|
||||
|
||||
4. **Standardized Logging Practices**:
|
||||
* **INFO**: Log key business events, such as `User logged in` or `Flyer processed`.
|
||||
* **WARN**: Log recoverable errors or unusual situations that do not break the request, such as `Client Error: 404 on GET /api/non-existent-route` or `Retrying failed database connection`.
|
||||
* **ERROR**: Log only unhandled or server-side errors that cause a request to fail (typically handled by the `errorHandler`). Avoid logging expected client errors (like 4xx) at this level.
|
||||
* **DEBUG**: Log detailed diagnostic information useful during development, such as function entry/exit points or variable states.
|
||||
|
||||
### Example Usage:
|
||||
|
||||
```typescript
|
||||
// In a new middleware file: logger.middleware.ts
|
||||
import { logger } from '../services/logger.server';
|
||||
import { randomUUID } from 'crypto';
|
||||
|
||||
export const requestLogger = (req, res, next) => {
|
||||
const requestId = randomUUID();
|
||||
// Create a child logger with context for this request
|
||||
req.log = logger.child({
|
||||
request_id: requestId,
|
||||
user_id: req.user?.user_id, // Assumes user is attached by auth middleware
|
||||
ip_address: req.ip,
|
||||
});
|
||||
next();
|
||||
};
|
||||
|
||||
// In server/app setup:
|
||||
// app.use(requestLogger); // Add this early in the middleware chain
|
||||
|
||||
// In a route handler:
|
||||
router.get('/:id', async (req, res, next) => {
|
||||
// Use the request-scoped logger
|
||||
req.log.info({ flyerId: req.params.id }, 'Fetching flyer by ID');
|
||||
try {
|
||||
// ... business logic ...
|
||||
res.json(flyer);
|
||||
} catch (error) {
|
||||
// The error itself will be logged with full context by the errorHandler
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
## Consequences
|
||||
|
||||
### Positive
|
||||
|
||||
* **Enhanced Observability**: Every log line from a single request can be instantly grouped and analyzed, dramatically speeding up debugging.
|
||||
* **Improved Security**: Centralizing the addition of context (like `user_id`) reduces the chance of developers manually logging sensitive data.
|
||||
* **Scalable Log Management**: Consistent JSON logs are easily ingested and indexed by any modern log aggregation tool.
|
||||
* **Clearer Code**: Removes the need to manually pass contextual information (like user ID) down to service functions just for logging purposes.
|
||||
|
||||
### Negative
|
||||
|
||||
* **Refactoring Effort**: Requires adding the `requestLogger` middleware and refactoring all routes and services to use `req.log` instead of the global `logger`.
|
||||
* **Slight Performance Overhead**: Creating a child logger for every request adds a minor performance cost, though this is negligible for most modern logging libraries.
|
||||
Reference in New Issue
Block a user