Secure Logging Best Practices: What to Log (and Never Log)

Share

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)
Security event logging
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 LogWhyAlternative
PasswordsCredential exposureLog "password provided: true"
API keys/tokensAuth bypassLog last 4 characters only
Credit card numbersPCI complianceLog last 4 digits
SSN/Government IDsIdentity theftLog "ID verified: true"
Full emails (sometimes)Privacy/GDPRMask: u***@example.com
Masking sensitive data
// 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:

Structured logger setup
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:

Contextual logging
// 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 ConditionThresholdIndicates
Failed logins per IP>10 in 5 minBrute force attack
Failed logins per account>5 in 5 minAccount targeting
Access denied errorsSpike from baselinePrivilege escalation attempt
500 errors>1% of requestsSystem issue or attack
Unusual admin activityOutside business hoursCompromised account
Example alert rules (Datadog/CloudWatch style)
// 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.

Audit Your Logging

Check if your logs expose sensitive data or miss critical events.

Start Free Scan
Best Practices

Secure Logging Best Practices: What to Log (and Never Log)