Should I test that?

Should I test logging?

Verdict

Yes

Write one test that your main error path logs the failure with its request ID, and do not assert on ordinary info and debug log lines.

Why

Test logging minimally: one test for the log line an incident depends on, none for the rest. For diagnostic logging, Blast radius is internal, because a missing line slows your team down and no customer sees it. Detectability is eventually: nobody notices a missing line until someone searches for it during an incident. Change frequency is regularly, since log lines move with the code around them, and Reversibility is with effort, because the fix is a hotfix and the entries from before it never exist. Test cost is moderate, since a log assertion needs a capture fixture and breaks when someone rewords a message.

When the decision changes
WhenDecisionWhy
The log is an audit trail that a regulation or a contract requiresTest mandatory: assert that every covered action writes one audit entryBlast radius rises to safety-or-legal and Reversibility to impossible, because a missing audit entry cannot be written later
The request logger can see passwords, tokens or card numbersTest mandatory: assert that the logger redacts passwords, tokens and card numbersBlast radius rises to safety-or-legal, Detectability to never and Reversibility to impossible, because a leaked secret raises no error and cannot be taken back
An alert or a metric matches the text of a log lineTest the log line: assert its level and the event name that the alert depends onBlast radius rises to users and Detectability to never, because a reworded message silences the alert
The log lines are debug output that production filters outDo not test debug log lines; delete the ones nobody readsBlast radius falls to none, because nobody but the developer sees debug output
The logger is a static global that a test cannot capture without a refactorTest logging differently: alert when an expected log event stops appearing in staging or productionTest cost rises to heavy, so a signal from the running system is cheaper

What breaks if you don't test

A refactor moves a try block, and the except branch that logged the payment provider's error code now swallows the exception silently. CI stays green. During the next outage, the on-call engineer opens the logs and finds a gap where the error code should be. The incident runs longer, because the team has to add the line back, deploy, and wait for the failure to happen again.

What you lose if you over-test

Asserting on every log statement ties the suite to wording. A test that expects Processing order 42 for user 7 fails when someone fixes a typo, and the fix is to paste the new string into the test, which then checks nothing new. Across hundreds of log lines, each refactor becomes a round of test edits. Tests that mock the logger and verify each call also bind you to one logging API, so a move to structured logging rewrites the tests too.

How to test

Test at the unit level with the capture tool your stack already has: the pytest caplog fixture in Python, or a Logback ListAppender in Java. The minimum set is one test for the error path an incident depends on: trigger the failure, then assert on the level and on the fields an engineer needs, such as the request ID. Assert on an event name or structured fields instead of message prose, so rewording does not break the test. Add a regression test when a missing log line has already cost you an incident.

When the answer changes

  • Someone builds an alert or a dashboard on a log line, which turns that line into an interface for another system.
  • Your logs start to hold user data, or an auditor asks for them. Follow the OWASP Logging Cheat Sheet and add a redaction test.
  • An error tracker already reports every exception with its stack trace, so the error log stops being the only place a failure shows.

Real incident + Code example

The alert that matched a reworded message

On a payments service I worked on, the on-call alert counted log lines containing card declined by issuer. During a refactor, a developer changed that message to Issuer declined the card. No test covered the line, and the alert matched nothing for nine days. We found out when an issuer outage declined a batch of renewals and customers wrote to support first. The test we added checks an event name, which nobody rewrites for style:

import logging

def test_declined_charge_logs_event(caplog):
    caplog.set_level(logging.WARNING, logger="payments")

    charge(order_id="ord_42", card=DECLINED_CARD)

    events = [r for r in caplog.records
              if getattr(r, "event", None) == "payment_declined"]
    assert len(events) == 1
    assert events[0].levelname == "WARNING"
    assert events[0].order_id == "ord_42"

The production code passes extra={"event": "payment_declined", "order_id": order_id} to logger.warning, and the alert now matches the event field. Anyone can reword the message; only renaming the event fails the build.

FAQ

Should we unit test logging?

Unit test logging only where someone depends on the output: the error path an incident relies on, audit entries, and lines that feed alerts. Ordinary info and debug lines do not repay a test, because a missing one costs your team time and no customer sees it.

Should log statements be tested?

A log statement needs a test when a person or a system reads it as a contract, such as an auditor or an alert rule. Assert on an event name or structured fields, so that rewording a message does not break the suite.

Should I test that passwords are not logged?

A test that passwords and tokens never reach the logs is mandatory whenever your request logging can see them, because a secret in log storage gets copied into backups and cannot be taken back. Send a request with a fake password through the logging middleware and assert that the captured output does not contain it.