TL;DR
The #1 logging security best practice is never log sensitive data like passwords, tokens, or PII. Log security events (auth attempts, access denied, errors) with timestamps and user context. Use structured JSON logging, centralize logs, and set up alerts for suspicious patterns. Proper logging catches attacks early and is essential for incident response.
"Your logs are your security camera footage. Record everything important, but never point the camera at your passwords."
Best Practice 1: Log Security Events 3 min
Capture events that matter for security:
- Authentication attempts (success and failure)
- Authorization failures (access denied)
- Input validation failures
- Account changes (password reset, email change)
- Admin actions and privilege escalations
- Rate limit violations
- Error responses (4xx, 5xx)
import { logger } from './logger';
// Log authentication events
async function login(email, password) {
const user = await findUserByEmail(email);
if (!user) {
logger.warn('auth.login.user_not_found', {
email: maskEmail(email),
ip: req.ip,
userAgent: req.headers['user-agent'],
});
throw new AuthError('Invalid credentials');
}
const valid = await verifyPassword(password, user.passwordHash);
if (!valid) {
logger.warn('auth.login.invalid_password', {
userId: user.id,
ip: req.ip,
attemptCount: await getFailedAttempts(user.id),
});
throw new AuthError('Invalid credentials');
}
logger.info('auth.login.success', {
userId: user.id,
ip: req.ip,
});
return createSession(user);
}
Best Practice 2: Never Log Sensitive Data 2 min
These should never appear in your logs:
| Never Log | Why | Alternative |
|---|---|---|
| Passwords | Credential exposure | Log "password provided: true" |
| API keys/tokens | Auth bypass | Log last 4 characters only |
| Credit card numbers | PCI compliance | Log last 4 digits |
| SSN/Government IDs | Identity theft | Log "ID verified: true" |
| Full emails (sometimes) | Privacy/GDPR | Mask: u***@example.com |
// Utility functions for safe logging
function maskEmail(email) {
const [local, domain] = email.split('@');
return `${local.charAt(0)}***@${domain}`;
}
function maskToken(token) {
if (!token) return 'none';
return `***${token.slice(-4)}`;
}
function maskCardNumber(card) {
return `****${card.slice(-4)}`;
}
// WRONG: Logging sensitive data
logger.info('Payment processed', {
cardNumber: '4111111111111111', // NEVER
cvv: '123', // NEVER
});
// CORRECT: Masked logging
logger.info('payment.processed', {
cardLast4: maskCardNumber(cardNumber),
amount: payment.amount,
userId: user.id,
});
Best Practice 3: Use Structured Logging 2 min
JSON logs are searchable and parseable:
import pino from 'pino';
const logger = pino({
level: process.env.LOG_LEVEL || 'info',
formatters: {
level: (label) => ({ level: label }),
},
timestamp: pino.stdTimeFunctions.isoTime,
base: {
service: 'api',
environment: process.env.NODE_ENV,
},
});
// Add request context
function createRequestLogger(req) {
return logger.child({
requestId: req.id,
ip: req.ip,
path: req.path,
method: req.method,
});
}
// Usage in route
app.post('/api/orders', (req, res) => {
const log = createRequestLogger(req);
log.info('order.create.started', {
userId: req.user.id,
itemCount: req.body.items.length,
});
// ... process order
log.info('order.create.completed', {
orderId: order.id,
total: order.total,
});
});
Best Practice 4: Include Context 2 min
Good logs answer who, what, when, where:
// Good log entry includes:
logger.info('resource.access.denied', {
// WHO
userId: user?.id || 'anonymous',
userRole: user?.role,
// WHAT
action: 'delete',
resource: 'document',
resourceId: documentId,
// WHEN (automatic with structured logger)
// WHERE
ip: req.ip,
userAgent: req.headers['user-agent'],
// WHY (reason for denial)
reason: 'insufficient_permissions',
required: 'admin',
actual: user?.role,
});
// Output:
// {
// "level": "info",
// "time": "2026-01-24T10:30:00.000Z",
// "msg": "resource.access.denied",
// "userId": "user_123",
// "userRole": "member",
// "action": "delete",
// "resource": "document",
// "resourceId": "doc_456",
// "ip": "192.168.1.1",
// "reason": "insufficient_permissions"
// }
Best Practice 5: Set Up Log Alerts 3 min
Monitor for suspicious patterns:
| Alert Condition | Threshold | Indicates |
|---|---|---|
| Failed logins per IP | >10 in 5 min | Brute force attack |
| Failed logins per account | >5 in 5 min | Account targeting |
| Access denied errors | Spike from baseline | Privilege escalation attempt |
| 500 errors | >1% of requests | System issue or attack |
| Unusual admin activity | Outside business hours | Compromised account |
// Alert: Brute force detection
{
"name": "Brute Force Attack",
"query": "logs('auth.login.invalid_password').count() by ip",
"threshold": 10,
"window": "5m",
"severity": "high",
"action": "page_security_team"
}
// Alert: Unusual access patterns
{
"name": "Suspicious Access Denied Spike",
"query": "logs('*.access.denied').count()",
"condition": "> 3x baseline",
"window": "15m",
"severity": "medium"
}
Best Practice 6: Log Retention and Security 2 min
Protect your logs and comply with regulations:
- Encrypt logs at rest and in transit
- Restrict access to log systems (need-to-know)
- Set retention policies (30-90 days active, archive longer)
- Ensure immutability to prevent tampering
- Separate log storage from application servers
Compliance Note: GDPR requires you to delete or anonymize personal data in logs when no longer needed. Set up automated retention policies to handle this.
External Resources: For comprehensive logging security guidance, see the OWASP Logging Cheat Sheet and the OWASP Logging Vocabulary Cheat Sheet for industry-standard security recommendations.
Should I log user actions in detail?
Log actions that matter for security and compliance (logins, data access, admin actions). Avoid logging every click or page view unless you need it for specific analytics. Balance visibility with storage costs and privacy.
How long should I keep logs?
Keep active logs 30-90 days for operational use. Archive security-relevant logs for 1-7 years depending on compliance requirements (PCI, HIPAA, SOC2). Set up automatic rotation and archival.
What logging library should I use?
For Node.js, pino is fast and structured. For Python, structlog or python-json-logger work well. For Go, zap or zerolog. The key is structured JSON output that your log aggregator can parse.