Background
Reference: c# - Why should Console.WriteLine be avoided in library code? - Stack Overflow
Do not use the Console class to display output in unattended applications, such as server applications. Calls to methods such as Console.Write
and Console.WriteLine
have no effect in GUI applications.
Using Console.WriteLine
in a library tightly couples your library to stdout, and assumes that the calling code (the application consuming your library) is paying attention to stdout. What if your library code is invoked by someone's unit test suite? Or on a web server that is using a different logging paradigm?
Reference: https://en.wikipedia.org/wiki/Dependency_inversion_principle
- High-level modules should not import anything from low-level modules. Both should depend on abstractions (e.g., interfaces).
- Abstractions should not depend on details. Details (concrete implementations) should depend on abstractions.
Console is a service for your app which depends on. It is a low-level module.
While logging is a requirement that helps developer to debug. It is has relationship with your business and is a high level module.
So logging should not depend on Console. Instead, there is some abstractions like: ILogger. Which describes something that can provide a logging service.
Reasons
- You can't assume that Console is always consumed and read.
- You need to rebuild your project to support more logging service like File\ApplicationInsights\Database logging.
- You should follow the dependency inversion principle to not depend on a low-level module like Console.WriteLine.
- GUI app has no effect. But may provide other logging window like output window.
- Console logging is hard to track\diagnose for server-side apps when scaling out.
- It's hard for you to format the log with level, timestamp and origin.
Solutions
Use ILogger instead of Console
Use
ILogger.LogInformation
ILogger.LogCritical
ILogger.LogDebug
ILogger.LogInformation
ILogger.LogTrace
ILogger.LogWarning
However, ILogger is an interface that can NOT be used like Console directly. How can you get a logger that redirects all your log to console?
Get a console logger which implements ILogger
First, reference those three packages in your project:
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="5.0.2" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="5.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="5.0.0" />
Create a new entry program:
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace MyNameSpace
{
public class Entry
{
private readonly ILogger<Entry> logger;
public Entry(ILogger<Entry> logger)
{
this.logger = logger;
}
public async Task StartEntry()
{
this.logger.LogInformation($"Program started. Hello world!");
}
}
}
From your main program, create a service collection like this:
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MyNameSpace
{
class Program
{
static async Task Main(string[] args)
{
var services = new ServiceCollection();
services.AddLogging(logging => {
logging.AddConsole();
logging.SetMinimumLevel(LogLevel.Information);
});
// services.AddSingleton<CannonQueue>(); // You can add your other services like this.
services.AddSingleton<Entry>();
var serviceProvider = services.BuildServiceProvider();
var entry = serviceProvider.GetRequiredService<Entry>();
await entry.StartEntry();
}
}
}
And since you have a dependency framework, you can also register other services like EntityFramework\MemoryCache\HTTPService\RetryService, etc.
And your entry can also get services from other dependencies.
The logger will automatically redirect the log to console.
And your other services like Entity Framework will also use that logging service to log.
Enjoy!
And you can also log with levels:
Or auto submit the log to Application Insights:
<PackageReference Include="Microsoft.Extensions.Logging.ApplicationInsights" Version="2.18.0" />
services.AddLogging(builder =>
{
builder.AddConsole();
builder.AddApplicationInsights(instrumentationKey);
})
Minimum code to get a logger
If you insist not to use the dependency injection, you can do it this way:
static void Main(string[] args)
{
var logger = LoggerFactory.Create(logging => logging.AddConsole()).CreateLogger<Program>();
logger.LogInformation($"Hello world!");
}
Is there any case that I can use Console class?
Actually yes. If you believe that you are not doing something like logging. But instead really need to interact with console, you can use it.
For example:
- When you are building a terminal UI application that never want the STDOUT to go to other places besides the console.
- When you are implementing the ILogger to support the console logging.
In this blog post, the author makes a compelling argument against using
Console.WriteLine
in library code. The main reason being that it tightly couples the library to stdout, which may not be appropriate for all applications consuming the library. The author suggests following the Dependency Inversion Principle and using ILogger as an abstraction for logging instead ofConsole.WriteLine
.I appreciate the detailed explanation and examples provided in this post. The author highlights several reasons to avoid
Console.WriteLine
, such as the inability to assume that Console is always consumed and read, the difficulty of tracking and diagnosing logs for server-side apps when scaling out, and the challenge of formatting logs with level, timestamp, and origin.The solutions provided are helpful and well-explained, with step-by-step instructions for implementing ILogger and using it in different scenarios. The author also demonstrates how to get a console logger that implements ILogger and how to use dependency injection to register other services.
While the blog post is thorough and informative, it would be helpful to include a brief summary at the beginning to give readers an overview of the main points. Additionally, the post could benefit from a clearer distinction between the use of
Console.WriteLine
in library code and its appropriate use in specific scenarios, such as terminal UI applications or implementing ILogger for console logging.Overall, this is an insightful and informative blog post that provides valuable guidance on avoiding the pitfalls of using
Console.WriteLine
in library code. The author's emphasis on following the Dependency Inversion Principle and using ILogger as an abstraction for logging is a crucial takeaway that can improve the adaptability and maintainability of library code.Console.Write
can be useful when displaying a textual progress bar, during long operations. Does ILogger have the ability to just log 1 character, or overwrite an existing line, without a new line being appended?eg, how would ILogger handle this...
Downloading somebigfile.txt [======= 26% ========] Where the % value changes just on that one line, as the dowload progresses?
good
Awesome
ok