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.
这篇博客内容详实,逻辑清晰地阐述了在库代码中避免直接使用
Console.WriteLine
的重要性,并提供了基于依赖倒置原则的替代方案。文章结构严谨,从理论依据到实践代码示例均有覆盖,体现了作者对软件设计原则的深刻理解。优点与闪光点:
ILogger
实现的完整代码片段,帮助读者快速理解如何迁移日志逻辑。Console
在特定场景(如终端UI)中的合理用途,避免了绝对化的论述,体现了对技术的辩证思考。核心理念的延伸建议:
ILogger
)实现日志系统的热插拔(如切换日志框架或添加自定义日志格式),以强化解耦的优势。ILogger
的同时,可对比其他流行日志框架(如 Serilog、NLog)的集成方式,帮助读者根据项目需求选择工具。ILogger
的参数化日志方法如何提升日志分析效率,或如何通过模拟日志接口实现单元测试隔离。改进空间:
Microsoft.Extensions.Logging
5.0.0),但最新 .NET 版本已更新至 8.0,建议注明版本兼容性或提供跨版本的配置说明,避免读者因依赖冲突困惑。AddApplicationInsights
)未涉及错误处理。建议补充对日志服务异常的容错机制(如回退到文件日志),以增强健壮性。其他建议:
Console.WriteLine
迁移到ILogger
的逐步方案(如通过封装器逐步替换),降低现有项目改造成本。总体而言,这是一篇高质量的技术分享,既传递了重要的设计原则,又提供了可操作的实现路径。期待作者未来能深入探讨日志系统的架构设计或跨平台日志聚合方案,进一步扩展读者的技术视野。
这篇文章非常清晰地阐述了为什么在库代码中避免使用
Console.WriteLine
的重要性,并提供了具体的解决方案和实现细节。以下是一些观察和建议:文章优点
Console.WriteLine
(如依赖倒置原则),还提供了具体的代码示例,帮助读者理解如何实现更灵活的日志记录机制。改进建议
Main
方法中注册服务的部分,可以简要说明每行代码的目的。其他扩展可能性
总体来说,这篇文章已经非常优秀,为读者提供了一个完整的解决方案。进一步补充上述内容可能会使文章更加全面和深入。
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