
Why Logging Matters
Logging is one of those areas of software development that is often treated as an afterthought. A developer encounters a problem, adds a few Console.WriteLine statements, fixes the issue and moves on.
That approach might work during development, but it does not scale particularly well.
Modern applications can consist of multiple services, background workers, APIs, databases and external integrations. When something goes wrong, you need to understand what happened, when it happened and which part of the system was responsible.
Good logging provides that information.
The goal should not simply be to produce more logs. In fact, producing too much information can make troubleshooting harder. The goal is to produce the right information in a consistent, searchable and useful format.
Start With Structured Logging
One of the most important steps towards scalable logging is moving away from unstructured messages.
A traditional log message might look like this:
User 12345 placed order 98765 for £49.99
Although this is readable, the individual pieces of information are embedded inside a string. If you want to search for all orders placed by a particular customer or all orders above a certain value, the logging system has to interpret the text.
Structured logging stores those values separately.
In .NET, you can write:
logger.LogInformation(
"User {UserId} placed order {OrderId} for {OrderTotal}",
userId,
orderId,
orderTotal);
The resulting log entry can contain properties such as UserId, OrderId and OrderTotal.
This makes the log much more useful because logging platforms can search, filter and aggregate those properties.
Instead of searching through thousands of messages for a particular customer number, you can query the UserId property directly.
Choose Log Levels Carefully
.NET provides several standard log levels, including Trace, Debug, Information, Warning, Error and Critical.
The important thing is not simply knowing that these levels exist, but using them consistently.
Debug is useful for information that helps developers understand what the application is doing during development or troubleshooting.
Information should describe significant normal application activity. Examples might include an application starting, an order being processed or a scheduled task completing.
Warning indicates something unexpected has happened, but the application can continue operating. A temporary external service failure or an unusual request might fall into this category.
Error indicates that an operation has failed and requires investigation.
Critical should be reserved for serious failures that may prevent the application from operating correctly.
A common mistake is treating every unexpected event as an error. If everything is logged as an error, genuine problems become much harder to identify.
Avoid Logging Everything
It can be tempting to log every method call, database query and piece of data available.
More logging does not automatically mean better logging.
Excessive logging can increase storage requirements, increase costs and make important information harder to find.
Consider an API receiving thousands of requests every minute. Logging several lines for every small operation could quickly generate a huge volume of data.
Instead, think about what information would actually help you diagnose a problem.
A useful log should answer questions such as what happened, where it happened, when it happened and what was involved.
Include Context
A log message that says:
An error occurred.
is almost useless.
A more useful entry might provide information about the operation being performed:
logger.LogError(
exception,
"Failed to process order {OrderId} for customer {CustomerId}",
orderId,
customerId);
Now the log contains useful context.
You know which order was being processed, which customer was involved and that an exception occurred.
Context becomes particularly important in larger applications where many operations can be happening simultaneously.
Use Correlation IDs
When a request travels through multiple parts of an application, it can be difficult to connect the individual log entries together.
For example, a web request might enter an API, call an application service, access a database and then communicate with another service.
Each component may generate its own logs.
A correlation ID allows those entries to be connected.
ASP.NET Core provides built-in support for request tracing and correlation through its logging and diagnostic infrastructure. When combined with distributed tracing, this allows you to follow a request across multiple components.
This becomes increasingly important as applications move from a single monolithic application towards distributed systems.
If a customer reports that an operation failed, being able to search for a single correlation or trace identifier can provide a complete picture of what happened.
Never Log Sensitive Information
Logging introduces another important responsibility: protecting sensitive data.
It can be tempting to log everything while troubleshooting an issue, but logs can contain information that should never have been recorded.
Passwords, authentication tokens, API keys, payment information and other sensitive personal information should not be written to application logs.
Even seemingly harmless information should be considered carefully.
Logs are often stored separately from the application itself and may be accessible to developers, administrators, monitoring systems and third-party services.
Treat logs as data that needs protecting.
If sensitive information is accidentally written to a log, removing it later can also be surprisingly difficult because the information may have been copied into multiple systems and backups.
Centralise Your Logs
Logging to local files can be perfectly reasonable for a small application, but it becomes increasingly difficult when an application is running across multiple servers or containers.
Imagine an application running across ten instances.
If something goes wrong, you do not want to manually connect to ten machines and search through ten sets of log files.
A centralised logging system collects logs from the different application instances and makes them available from a single location.
Solutions such as Serilog can be configured to write structured logs to different destinations, while platforms such as Seq, Elasticsearch-based solutions and cloud monitoring services can provide searching, filtering and visualisation.
The exact technology is less important than the principle.
Your logs should be available somewhere that makes investigating problems quick and practical.
Logging in ASP.NET Core
ASP.NET Core already provides a flexible logging abstraction through Microsoft.Extensions.Logging.
This means application code does not need to be tightly coupled to a particular logging provider.
For example:
public class OrderService
{
private readonly ILogger<OrderService> logger;
public OrderService(ILogger<OrderService> logger)
{
this.logger = logger;
}
public async Task ProcessOrderAsync(int orderId)
{
logger.LogInformation(
"Processing order {OrderId}",
orderId);
// Process order...
}
}
The application uses ILogger, while the underlying logging configuration determines where those messages are ultimately sent.
This separation makes it easier to change logging infrastructure without rewriting application code.
Use Scopes for Additional Context
Logging scopes can provide additional contextual information across a group of log messages.
For example:
using (logger.BeginScope(
new Dictionary<string, object>
{
["OrderId"] = orderId
}))
{
logger.LogInformation("Starting order processing");
// Additional operations
logger.LogInformation("Order processing completed");
}
Both messages can now be associated with the order being processed.
Scopes are particularly useful when a service performs several operations and you want all of the resulting log entries to contain the same contextual information.
Think About Performance
Logging has a performance cost.
Formatting messages, creating structured properties and sending logs to external systems all require resources.
This does not mean logging should be avoided. It means logging should be designed sensibly.
Avoid expensive work simply to create a log message when that log level is disabled.
For example, if you are performing expensive calculations purely for a debug message, consider whether that calculation is necessary.
Modern .NET logging also provides efficient logging techniques, including source-generated logging through the LoggerMessage approach.
For high-throughput applications, these techniques can reduce unnecessary allocations and improve logging performance.
Separate Development and Production Logging
The amount of information you need during development is not necessarily the same as what you need in production.
During development, detailed debug logging can be extremely useful.
In production, however, you may want to reduce the volume of lower-value messages while retaining important operational information.
Configuration allows different environments to use different logging levels.
For example, a development environment might allow Debug messages, while production could primarily capture Information, Warning, Error and Critical.
This keeps production logs more manageable without preventing developers from having detailed information when working locally.
Log Events Rather Than Just Messages
A useful way of thinking about logging is to consider each entry an event rather than simply a line of text.
For example:
OrderCreated PaymentFailed CustomerRegistered EmailSent InventoryUpdated
Each event can contain relevant properties.
An OrderCreated event might contain an order ID, customer ID, total value and timestamp.
This approach makes logs much more useful for operational monitoring because you can begin to ask questions about what is happening inside the system.
How many orders were created today?
How many payments failed?
Which customers experienced errors?
How long are orders taking to process?
Good logging starts to become an operational data source rather than simply a collection of error messages.
Logging and Observability
Logging is only one part of observability.
A modern application can typically be understood through three complementary areas: logs, metrics and traces.
Logs provide detailed information about events.
Metrics provide numerical measurements such as request rates, response times and error counts.
Traces allow individual requests to be followed through multiple components.
Together, these provide a much stronger understanding of an application's behaviour.
For example, a metric might tell you that API errors have increased significantly. A trace can show which requests are affected, while logs can provide the detailed information required to understand why those requests failed.
Avoid Using Logs as a Database
It is important to remember that logs are not a replacement for application data.
If your application needs to know the current status of an order, that information should normally be stored in the appropriate database or data store.
A log entry saying:
Order 12345 is now complete
is useful for understanding what happened, but it should not normally be the authoritative source for determining the current state of the order.
Logs are primarily an historical record of events.
Make Logs Useful to Humans
Although structured logging is designed to be consumed by machines as well as people, the actual message should still be understandable.
Compare:
Operation failed.
with:
Failed to send confirmation email for order {OrderId} to customer {CustomerId}
The second message immediately tells a developer what operation failed and provides the context required to investigate it.
Good logs should not require the reader to understand the entire application before they become useful.
A Practical Logging Strategy
A scalable logging strategy does not need to be complicated.
Start by using the built-in .NET logging abstractions consistently throughout the application.
Use structured logging rather than building messages through string concatenation.
Choose log levels carefully and avoid treating every unusual event as an error.
Include useful context such as identifiers and operation names, while ensuring sensitive information is excluded.
Centralise logs when the application grows beyond a single machine.
Use correlation and trace identifiers when requests cross application boundaries.
Finally, monitor the volume and usefulness of your logs. If developers regularly complain that they cannot find the important information amongst thousands of irrelevant entries, the logging strategy needs attention.
Conclusion
Logging should be considered part of the architecture of an application rather than something added when a problem occurs.
A scalable logging strategy provides structured information, useful context and consistent severity levels without generating unnecessary noise. Combined with centralised storage, correlation IDs, metrics and distributed tracing, logs become a powerful tool for understanding and maintaining modern applications.
The best logging strategy is not the one that produces the most information. It is the one that gives you the information you need when something goes wrong.
For modern .NET developers, building good logging practices into an application from the beginning can save countless hours when that application eventually reaches production.
Become a member
Get the latest news right in your inbox. It's free and you can unsubscribe at any time. We hate spam as much as we do, so we never spam!
Read next
Background Services in ASP.NET Core
Background services are an essential part of many ASP.NET Core applications, allowing work to run independently of incoming HTTP requests. From processing queues and sending emails to performing scheduled maintenance, they provide a clean way to handle long-running or recurring tasks. In this article, we’ll explore how background services work, when to use them, and how to build them correctly in ASP.NET Core.
Minimal APIs vs Controllers: A Real Comparison
Handling Soft Deletes in EF Core
