What to Log and What NEVER to Log
The $101 Million Lesson Nobody Talks About
In 2019, Meta's engineers were doing a routine security review when they found something alarming: hundreds of millions of Facebook and Instagram passwords sitting in internal log files -- in plain text. Not hashed. Not masked. Just sitting there, readable, accessible to roughly 20,000 employees.
Five years later, Ireland's Data Protection Commission handed Meta a EUR 91 million ($101 million) fine. The violation? Storing those passwords in logs without adequate security measures, and failing to report the breach to regulators within GDPR's required 72-hour window. (PCMag, September 2024)
The kicker: it was almost certainly not intentional. Some middleware layer, some logging configuration, some line of code -- probably auto-generated or copy-pasted -- that said "log the whole request body." And suddenly, passwords were flowing into log files for years.
Twitter had a nearly identical incident in 2018. Passwords were written to an internal log before the hashing step completed. (ZDNET, 2018) GitHub had the same thing happen the same week.
These are not small startups run by careless developers. They are some of the most sophisticated engineering organizations on Earth. And yet: the logs ate the secrets.
If it can happen to them, it can absolutely happen to your app.
Why Logging Matters -- And Why It Can Hurt You
Logs are the nervous system of your application. When something breaks at 2am, logs tell you what happened. When a user says "I never did that," logs tell you what actually occurred. When you need to debug a mysterious error, logs are your only witness.
But logs are also a liability. Every log line is a record that:
- Gets written to disk, often unencrypted
- Gets forwarded to a third-party log aggregator (Datadog, Logtail, Axiom, Papertrail)
- Gets retained for 30, 60, sometimes 365 days
- Can be read by anyone with access to your logging platform
- May be subpoenaed, audited, or breached
The moment personal data enters a log file, it becomes subject to GDPR in Europe and CCPA in California. That is not a hypothetical. In 2025, EU regulators issued over EUR 1.2 billion in GDPR fines, with personal data breach reports rising 22% year-over-year. (TechRadar / Yahoo Finance, January 2026)
What You SHOULD Log
Good logs answer operational questions without exposing user data. Stick to this list:
- Timestamps -- when did this happen?
- HTTP method and path --
GET /api/products/123 - HTTP status code --
200,404,500 - Response duration --
142ms - User ID (an internal identifier, never an email) --
user_id: u_8f2a3c - Event names --
payment.initiated,login.success,export.requested - Error type and message --
TypeError: Cannot read property 'id' of undefined - Request ID -- a unique identifier to trace a request through your system
- Environment and service name --
production,api-gateway
None of this tells an attacker anything useful. All of it tells you everything you need to debug problems and monitor your system.
What You Must NEVER Log
Here is the list. Print it out and tape it to your monitor.
- Passwords (in any form -- hashed or plaintext)
- API keys and secret tokens
- JWT tokens or session tokens
- Full credit card numbers (even partial -- only last 4 digits are acceptable, per PCI DSS)
- Social Security Numbers or national ID numbers
- Email addresses (they are PII under GDPR and CCPA)
- Full request bodies (they may contain any of the above)
- Authorization headers (
Authorization: Bearer eyJhb...) - Personally Identifiable Information: full names, addresses, phone numbers, dates of birth
If you log any of these, you have created a compliance liability the moment the log is written -- regardless of whether anyone ever reads it.
The AI Code Problem
Here is something that should concern every vibe coder: AI-generated code has a logging problem.
Veracode's 2025 GenAI Code Security Report found that AI models generate insecure log injection code 88% of the time. (Veracode, September 2025) Apiiro's research found a 40% increase in secrets exposure in AI-assisted codebases. When you ask an AI to "add logging so I can debug this," it frequently logs the entire request object -- which includes headers, body, query params, and anything else that came through.
When you ask an AI to "add error handling with logging," it may log error.message along with whatever context it was passed -- potentially including user data.
This is not the AI being malicious. It is the AI being helpful in a way that does not understand threat models. The AI was trained on millions of public repositories, many of which have bad logging practices. It reproduces those patterns faithfully.
The fix is simple: audit the logging in any AI-generated code before you ship it.
Log Sanitization: A Practical TypeScript Example
The right approach is a logger wrapper that strips sensitive fields before anything gets written. Here is a pattern you can use today:
// logger.ts
import pino from "pino";
const REDACTED_KEYS = [
"password",
"passwd",
"secret",
"token",
"apiKey",
"api_key",
"authorization",
"credit_card",
"creditCard",
"cardNumber",
"ssn",
"email",
"phone",
"address",
];
function redact(obj: Record<string, unknown>): Record<string, unknown> {
if (!obj || typeof obj !== "object") return obj;
return Object.fromEntries(
Object.entries(obj).map(([k, v]) => {
if (REDACTED_KEYS.some((key) => k.toLowerCase().includes(key))) {
return [k, "[REDACTED]"];
}
if (typeof v === "object" && v !== null) {
return [k, redact(v as Record<string, unknown>)];
}
return [k, v];
})
);
}
const base = pino({ level: process.env.LOG_LEVEL || "info" });
export const logger = {
info: (msg: string, data?: Record<string, unknown>) =>
base.info(data ? redact(data) : {}, msg),
error: (msg: string, data?: Record<string, unknown>) =>
base.error(data ? redact(data) : {}, msg),
warn: (msg: string, data?: Record<string, unknown>) =>
base.warn(data ? redact(data) : {}, msg),
};
Now instead of:
// BAD -- AI-generated, logs everything
console.log("Incoming request", req.body);
You write:
// GOOD -- logs only what matters
logger.info("Incoming request", {
method: req.method,
path: req.path,
userId: req.user?.id,
duration: Date.now() - startTime,
status: res.statusCode,
});
The wrapper will also catch anything that accidentally slips through -- if a future developer adds email to that log object, [REDACTED] will appear in the log instead.
Where Logs Go
Many vibe coders think logs just appear in the terminal. In production, they travel much further:
- Console / stdout -- fine for local development, nothing in production
- Structured logging libraries (Pino, Winston) -- write JSON logs to stdout that other tools consume
- Log aggregators (Datadog, Logtail, Axiom, Papertrail) -- collect, store, and index your logs for search and alerting
The moment logs leave your server and go to a third-party aggregator, they are subject to that vendor's data processing terms. Logging user email addresses to Datadog means Datadog is processing PII -- and you need a Data Processing Agreement in place, or you have a GDPR violation.
Check your aggregator's data region settings. If you serve EU users, logs containing any PII must stay within the EU under GDPR's data transfer rules.
Retention and Access Control
Log retention is a silent risk. Logs that you never look at are still a liability if they contain sensitive data.
Set retention policies intentionally:
- Application logs: 30 days is usually sufficient
- Security/audit logs: 90-365 days, depending on your compliance requirements
- Logs containing any PII: subject to GDPR's data minimization principle -- only retain as long as necessary, and document why
On access control: in most startups, every developer can read production logs. That may be fine today. But if your logs contain user IDs, IP addresses, or behavioral data, you have created a system that knows a great deal about your users -- and anyone with log access knows it too.
Limit log access to people who need it. Log access itself should be logged.
Quick Audit: Grep Your Codebase Right Now
Open a terminal in your project and run these commands. If any of them return results, you have work to do:
# Look for full request body logging
grep -r "req\.body" --include="*.ts" --include="*.js" .
# Look for console.log with potentially sensitive objects
grep -r "console\.log.*req\|console\.log.*user\|console\.log.*body" --include="*.ts" --include="*.js" .
# Look for logged authorization headers
grep -r "authorization\|Authorization" --include="*.ts" --include="*.js" . | grep -i "log\|print\|console"
# Look for password appearing in log statements
grep -ri "password" --include="*.ts" --include="*.js" . | grep -i "log\|console\|print"
# Look for email being logged
grep -ri "\.email" --include="*.ts" --include="*.js" . | grep -i "log\|console\|print"
This takes five minutes. It has saved teams from serious breaches and six-figure fines.
Checklist: Secure Logging for Vibe Coders
- Replace all
console.logcalls with a structured logger (Pino or Winston) - Add a redaction wrapper that strips sensitive fields before logging
- Never log
req.bodydirectly -- select only the fields you need - Never log authorization headers, tokens, or API keys
- Never log passwords, SSNs, full card numbers, or email addresses
- Use internal user IDs in logs, not emails
- Run the grep audit above on your codebase
- Set log retention policies in your aggregator (30 days for most app logs)
- Restrict log access to people who genuinely need it
- If you serve EU users, confirm your log aggregator stores data within the EU or has a valid transfer mechanism
- Review any AI-generated logging code before deploying -- assume it logs too much
Ask The Guild
Take five minutes right now and run the grep audit on your project. What did you find?
Share in the comments: Did you find any unexpected logging of sensitive data? Was it in your own code, or in AI-generated code? What did you do to fix it?
The best security communities learn from each other's near-misses. Your story might save someone else from a very expensive lesson.