TL;DR
Never log sensitive data like passwords, tokens, or PII. Do log security events, auth attempts, access denied, errors, with timestamps and user context. Use structured JSON logging, centralize it, and set up alerts for the patterns that matter. It's what lets you catch an attack while it's happening instead of reading about it in a breach report.
"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
A handful of event types cover most of what you'll ever need to investigate:
- 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
None of this belongs in a log file, not even for debugging:
| 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
Plain text logs are fine until you need to search them at 2am. JSON logs aren't:
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
A log line that just says "access denied" tells you nothing. Answer who, what, when, where, and ideally why:
// 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
Logs you never look at aren't worth writing. Set up alerts for these patterns instead:
| 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
Your logs are a record of everything that happened, including the attack. Protect them like it:
- 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.
Further Reading
Put these practices into action with our step-by-step guides.
Audit Your Logging
Check if your logs expose sensitive data or miss critical events.