Choose the level by the action it should trigger. Normal, expected security events such as a successful login belong at INFO. Suspicious but handled events, such as a failed login or access denial, belong at WARN. ERROR is for failures of the system itself. Document the mapping so it stays consistent.
What the guidance says
The OWASP Logging Cheat Sheet shows both numeric severity scales, from 0 for emergency to 7 for debug, and named scales such as fatal, error, warning, info, debug and trace. It does not pick a winner. It asks for a documented, consistent classification, and it says levels should be verified periodically.
The same sheet adds a rule people forget: where logging is configurable, required events should not be possible to switch off completely. If someone sets the production level to ERROR to save disk, your authentication trail should not vanish with it.
A mapping you can adopt
| Event | Suggested level | Reason |
|---|---|---|
| Successful login | INFO | Expected, needed for audit |
| Failed login | WARN | Usually a typo, sometimes an attack |
| Account lockout | WARN | Control worked, someone should know |
| Access denied | WARN | Handled, may signal probing |
| Privilege or role change | INFO, with audit flag | Rare and important, not an error |
| Token reuse or impossible travel | WARN or higher | Strong signal, route to alerting |
| Input validation failure | WARN | Treat as suspicious, not proof of attack |
| Logging pipeline failure | ERROR | The system cannot do its job |
| Unhandled exception | ERROR | A defect, not necessarily an attack |
Notice the privilege change row. A role change is not an error and should not be logged at ERROR just because it is important. Importance is carried by the event name and an audit flag, not by the level.
Traps to avoid
Logging a failed login at ERROR floods the error channel on every typo, and the real application errors get lost. Teams then mute the channel, which defeats the purpose.
The reverse is also common. Security events logged at DEBUG disappear in production the moment someone lowers verbosity.
A third trap is using level as the alert rule. Alert on event names and patterns, and use the level as a coarse filter. A WARN that repeats five hundred times from one source is an incident. A single WARN is not.
Steps to put it in place
- List your security events from the standard vocabulary and give each a level.
- Store the table in the repository next to the logging code.
- Add a test that the required events are emitted at the documented level.
- Set the production minimum level to INFO or lower, never above WARN.
- Review the table when you add a feature that touches authentication or permissions.
Keep one more thing separate: audit events. If auditors will ask who changed a role, write it to an audit stream with its own retention, whatever level it carries. See an audit log you can answer questions with.
Quick check
Is a failed login an error?
No. The system behaved correctly. Log it at WARN so it is visible without being confused with application failures.
Can I just use one level for all security events?
You can, but you lose a cheap filter. A small, documented mapping costs little and makes triage faster.
Safeguard's policies and gates can enforce checks in your pipeline, such as blocking a risky dependency, so fewer problems reach production logs at all. Try the free scan at gold.safeguard.sh/scan.
Sources
Last reviewed: 19 October 2026.