Backend Development 18 min read

How to Write Effective Error Logs for Better Debugging

This article explains why well‑structured error logs are essential for troubleshooting, analyzes common sources of errors, and provides concrete guidelines and code examples to make logs complete, specific, and actionable for developers and operations teams.

Architecture Digest
Architecture Digest
Architecture Digest
How to Write Effective Error Logs for Better Debugging

Logging error information is a key technique for locating and fixing bugs, yet many logs are vague, incomplete, or hard to read, which makes debugging time‑consuming. The article first describes where errors originate: illegal parameters from upper layers, communication failures with lower layers, and internal bugs caused by negligence, insufficient exception handling, tight coupling, algorithm mistakes, parameter order errors, null pointers, network issues, transaction‑concurrency problems, configuration mistakes, unfamiliar business logic, design flaws, unknown details, time‑drift bugs, and hardware failures.

For each cause, practical improvement measures are suggested, such as adding input validation, using static analysis tools, writing short, pure functions, keeping modules loosely coupled, extracting algorithms for unit testing, specifying parameter types, checking for nulls, adding INFO logs at subsystem boundaries, monitoring resource usage, and verifying configurations at startup.

The article then outlines six basic principles for writing error logs:

Be complete : include the scenario, possible reasons, and suggested solutions.

Be specific : identify the exact resource or parameter that failed.

Be direct : allow a reader to understand the cause at a glance.

Integrate experience: embed known fixes and hints directly in the log messages.

Maintain a clean, uniform format.

Highlight key identifiers (time, entity ID, operation name).

Typical log‑format examples are provided, showing how to use String.format or concatenation to embed parameters and context:

if ((storageType == StorageType.dfs1 || storageType == StorageType.dfs2) &&
    (zone.hasStorageType(StorageType.io3) || zone.hasStorageType(StorageType.io4))) {
    // enter dfs1/dfs2 with io3/io4 storage
} else {
    log.info("zone storage type not support, zone: " + zone.getZoneId() +
             ", storageType: " + storageType.name());
    throw new BizException(DeviceErrorCode.ZONE_STORAGE_TYPE_NOT_SUPPORT);
}
catch (Exception ex) {
    log.error("control ip insert failed", ex);
    return new ResultSet
(ControlIpErrorCode.ERROR_CONTROL_IP_INSERT_FAILURE);
}
log.error("[create nc] nc has exist, please check nc parameters. Given nc ip: " + request.getIp());
log.error("[delete nc] some vms [%s] in the nc are not destroyed. nc id: %s", vmNames, request.getNcId());
log.warn(String.format("[query cache status] device cache status conflicts between regiondb and nc, status of device '%s' is %s, but is %s in nc.",
    deviceDO.getId(), deviceDO.getStatus(), status));

Finally, the article discusses when to use different log levels: info for normal operational messages, warn for non‑critical issues, and error for failures that prevent the intended operation. By following these guidelines, developers can produce logs that serve as clear documentation and a powerful debugging aid.

DebuggingoperationsBackend Developmentsoftware engineeringError Logginglogging best practices
Architecture Digest
Written by

Architecture Digest

Focusing on Java backend development, covering application architecture from top-tier internet companies (high availability, high performance, high stability), big data, machine learning, Java architecture, and other popular fields.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.