Chapter2:CONFIGURING A LOGGING SERVICE

2 Configuring a Logging Service

Why does logging messages matter so much during application development? While our application is in the development stage, it’s easy to debug the code and find out what happened. But debugging in a production environment is not that easy.
为什么在应用程序开发过程中记录消息如此重要?虽然我们的应用程序处于开发阶段,但很容易调试代码并找出发生了什么。但是在生产环境中进行调试并不容易。‌‌

That’s why log messages are a great way to find out what went wrong and why and where the exceptions have been thrown in our code in the production environment. Logging also helps us more easily follow the flow of our application when we don’t have access to the debugger.
这就是为什么日志消息是找出问题所在以及为什么以及在生产环境中的代码中抛出异常的原因和位置的好方法。日志记录还有助于我们在无法访问调试器时更轻松地跟踪应用程序的流程。

.NET Core has its own implementation of the logging mechanism, but in all our projects we prefer to create our custom logger service with the external logger library NLog.
.NET Core 有自己的日志记录机制实现,但在我们所有的项目中,我们更喜欢使用外部记录器库 NLog 创建自定义记录器服务。

That is exactly what we are going to do in this chapter.
这正是我们将在本章中要做的。

2.1 Creating the Required Projects

Let’s create two new projects. In the first one named Contracts, we are going to keep our interfaces. We will use this project later on too, to define our contracts for the whole application. The second one, LoggerService, we are going to use to write our logger logic in.
让我们创建两个新项目。在第一个名为Contracts,我们将保留我们的接口。我们稍后也将使用此项目来定义整个应用程序的协定。第二个是 LoggerService ,我们将用它来编写我们的记录器逻辑。

To create a new project, right-click on the solution window, choose Add and then NewProject. Choose the Class Library (.NET Core) project:
若要创建新项目,请右键单击解决方案窗口,选择“添加”,然后选择“新建项目”。选择“类库(.NET Core)”项目:

alt

Finally, name it Contracts. Do the same thing for the second project and name it LoggerService. Now that we have these projects in place, we need to reference them from our main project.
最后,将其命名为 Contracts 。对第二个项目执行相同的操作,并将其命名为 LoggerService 。现在我们已经有了这些项目,我们需要从我们的主项目中引用它们。

To do that, navigate to the solution explorer. Then in the LoggerService project, right-click on Dependencies and choose the AddReference option. Under Projects, click Solution and check the Contracts project.
为此,请导航到解决方案资源管理器。然后在 LoggerService 项目中,右键单击“依赖项”并选择“添加引用”选项。在“项目”下,单击“解决方案”并选中“Contracts”项目。

Now, in the main project right click on Dependencies and then click on Add Reference. Check the LoggerService checkbox to import it. Since we have referenced the Contracts project through the LoggerService, it will be available in the main project too.
现在,在主项目中,右键单击“依赖项”,然后单击“添加引用”。选中“ LoggerService ”复选框以将其导入。由于我们已经通过LoggerService引用了Contracts项目,因此它也将在主项目中提供。

2.2 Creating the lLoggerManager Interface and Installing Nlog

Our logger service will contain four methods for logging our messages:
我们的记录器服务将包含四种用于记录消息的方法:

  • Info messages 信息消息
  • Debug messages调试消息
  • Warning messages警告消息
  • Error messages错误消息

To achieve this, we are going to create an interface named ILoggerManager inside the Contracts project containing those four method definitions.
为了实现这一点,我们将创建一个接口。在包含这四个方法定义的 Contracts 项目中命名为 ILoggerManager

So, let’s do that first:
所以,让我们先这样做:

namespace Contracts
{
    public interface ILoggerManager
    {
        void LogInfo(string message);
        void LogWarn(string message);
        void LogDebug(string message);
        void LogError(string message);
    }
}

Before we implement this interface inside the LoggerService project, we need to install the NLog library in our LoggerService project. NLog is a logging platform for .NET which will help us create and log our messages.
LoggerService 项目中实现此接口之前,我们需要在 LoggerService 项目中安装 NLog 库。NLog是.NET的日志记录平台,它将帮助我们创建和记录消息。

We are going to show two different ways of adding the NLog library to our project.
我们将展示将 NLog 库添加到项目中的两种不同方法。

1.In the LoggerService project, right-click on the Dependencies and choose Manage NuGet Packages. After the NuGet Package Manager window appears, just follow these steps:
LoggerService 项目中,右键单击“依赖项”,然后选择“管理 NuGet 包”。出现“NuGet 包管理器”窗口后,只需执行以下步骤:

alt

2.From the View menu, choose Other Windows and then click on the Package Manager Console. After the console appears, type: Install-Package NLog.Extensions.Logging -Version 1.6.5
从“视图”菜单中,选择“其他窗口”,然后单击“程序包管理器控制台”。控制台出现后,键入:
Install-Package NLog.Extensions.Logging -Version 1.6.5

After a couple of seconds, NLog is up and running in our application.
几秒钟后,NLog 在我们的应用程序中启动并运行。

2.3 Implementing the Interface and Nlog.Config File

In the LoggerService project, we are going to create a new class: LoggerManager. Now let’s have it implement the ILoggerManager interface we previously defined:
LoggerService 项目中,我们将创建一个新的类:LoggerManager。现在让我们让它实现ILoggerManager我们之前定义的接口:

using Contracts;
using NLog;
namespace LoggerService
{
    public class LoggerManager : ILoggerManager
    {
        private static ILogger logger = LogManager.GetCurrentClassLogger();
        public LoggerManager()
        {
        }
        public void LogDebug(string message)
        {
            logger.Debug(message);
        }
        public void LogError(string message)
        {
            logger.Error(message);
        }
        public void LogInfo(string message)
        {
            logger.Info(message);
        }
        public void LogWarn(string message)
        {
            logger.Warn(message);
        }
    }
}

As you can see, our methods are just wrappers around NLog’s methods. Both ILogger and LogManager are part of the NLog namespace. Now, we need to configure it and inject it into the Startup class in the ConfigureServices method.
如您所见,我们的方法只是围绕NLog方法的包装器。ILoggerLogManager 都是 NLog 命名空间的一部分。现在,我们需要配置它并将其注入到 Startup 类中ConfigureServices 方法。

NLog needs to have information about where to put log files on the file system, what the name of these files will be, and what is the minimum level of logging that we want.
NLog 需要包含有关将日志文件放在文件系统上的位置、这些文件的名称以及我们想要的最低日志记录级别的信息。

We are going to define all these constants in a text file in the main project and name it nlog.config. You’ll need to change the path of the internal log and filename parameters to your own paths.
我们将在主项目的一个文本文件中定义所有这些常量,并将其命名为nlog.config。您需要更改 internal log路径filename 参数到您自己的路径。

<?xml version="1.0" encoding="utf-8" ?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd" 
	  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
	  autoReload="true" internalLogLevel="Trace" 
	  internalLogFile="d:\Projects\CompanyEmployees\Project\internal_logs\internallog.txt">
	<targets>
		<target name="logfile" xsi:type="File" 
				fileName="d:\Projects\CompanyEmployees/Project\logs\${shortdate}_logfile.txt" 
				layout="${longdate} ${level:uppercase=true} ${message}"/>
	</targets>
	<rules>
		<logger name="*" minlevel="Debug" writeTo="logfile" />
	</rules>
</nlog>

2.4 Configuring Logger Service for Logging Messages

Setting up the configuration for a logger service is quite easy. First, we need to update the constructor of the Startup class:
设置记录器服务的配置非常简单。首先,我们需要更新 Startup 类的构造函数 :

public Startup(IConfiguration configuration)
        {
            LogManager.LoadConfiguration(string.Concat(Directory.GetCurrentDirectory(), "/nlog.config")); 
            Configuration = configuration;
        }

Basically, we are using NLog’s LogManager static class with the LoadConfiguration method to provide a path to the configuration file.
基本上,我们使用NLog的LogManager静态类和
LoadConfiguration 方法,用于提供配置文件的路径。

The next thing we need to do is to add the logger service inside the .NET Core’s IOC container. There are three ways to do that:
我们需要做的下一件事是在 .NET Core 的 IOC 容器中添加记录器服务。有三种方法可以做到这一点:

  • By calling the services.AddSingleton method, we can create a service the first time we request it and then every subsequent request will call the same instance of the service. This means that all components share the same service every time they need it and the same instance will be used for every method call.
    通过调用services.AddSingleton方法,我们可以在第一次请求它时创建一个服务,然后每个后续请求将调用该服务的同一实例。这意味着所有组件在每次需要时共享相同的服务,并且每次方法调用都将使用相同的实例。
  • By calling the services.AddScoped method, we can create a service once per request. That means whenever we send an HTTP request to the application, a new instance of the service will be created.
    通过调用 services.AddScoped 方法,我们可以创建每个请求一次服务。这意味着每当我们向应用程序发送HTTP请求时,都会创建服务的新实例。
  • By calling the services.AddTransient method, we can create a service each time the application requests it. This means that if multiple components need the service, it will be created again for every single component request.
    通过调用services.AddTransient方法,我们可以创建一个每次应用程序请求时提供服务。这意味着,如果多个组件需要该服务,则会为每个组件请求再次创建该服务。

So, let’s add a new method in the ServiceExtensions class:
因此,让我们在 ServiceExtensions 类中添加一个新方法:

public static void ConfigureLoggerService(this IServiceCollection services) => 
            services.AddScoped<ILoggerManager, LoggerManager>();

And after that, we need to modify the ConfigureServices method to include our newly created extension method:
之后,我们需要修改 ConfigureServices 方法以包含我们新创建的扩展方法:

public void ConfigureServices(IServiceCollection services)
        {
            services.ConfigureCors();
            services.ConfigureIISIntegration();
            services.ConfigureLoggerService();

            services.AddControllers();
        }

Every time we want to use a logger service, all we need to do is to inject it into the constructor of the class that needs it. .NET Core will resolve that service and the logging features will be available.
每次我们想要使用记录器服务时,我们需要做的就是将其注入到需要它的类的构造函数中。.NET Core 将解析该服务,并且日志记录功能将可用。

This type of injecting a class is called Dependency Injection and it is built into .NET Core.
这种类型的注入类称为依赖关系注入,它内置于 .NET Core 中。

Let’s learn a bit more about it.
让我们进一步了解它。

2.5 Di,Ioc,and Logger Service Testing

What is Dependency Injection (DI) exactly and what is IoC (Inversion of Control)?
究竟什么是依赖注入(DI),什么是IoC(控制反转)?‌

Dependency injection is a technique we use to achieve the decoupling of objects and their dependencies. It means that rather than instantiating an object explicitly in a class every time we need it, we can instantiate it once and then send it to the class.
依赖注入是我们用来实现对象及其依赖关系解耦的一种技术。这意味着,与其在每次需要时在类中显式实例化对象,不如将其实例化一次,然后将其发送到类。

This is often done through a constructor. The specific approach we utilize is also known as the Constructor Injection.
这通常是通过构造函数完成的。我们使用的特定方法也称为构造函数注入(Constructor Injection)。

In a system that is designed around DI, you may find many classes requesting their dependencies via their constructors. In this case, it is helpful to have a class that manages and provides dependencies to classes through the constructor.
在围绕 DI 设计的系统中,您可能会发现许多类通过其构造函数请求其依赖项。在这种情况下,有一个类通过构造函数管理和提供类的依赖项会很有帮助。

These classes are referred to as containers or more specifically, Inversion of Control containers. An IoC container is essentially a factory that is responsible for providing instances of the types that are requested from it.
这些类称为容器,或者更具体地说,称为控制反转容器。IoC 容器本质上是一个工厂,负责提供从中请求的类型实例。

To test our logger service, we are going to use the default WeatherForecastController. You can find it in the main project in the Controllers folder. It comes with the ASP.NET Core Web API template.
为了测试我们的记录器服务,我们将使用默认的WeatherForecastController。您可以在主项目的“控制器”文件夹中找到它。它附带了 ASP.NET 核心 Web API 模板。

In the Solution Explorer, we are going to open the Controllers folder and locate the WeatherForecastController class. Let’s modify it:
在解决方案资源管理器中,我们将打开控制器文件夹并找到 WeatherForecastController 类。让我们修改它:

// WeatherForecastController.cs
using Contracts;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;

namespace CompanyEmployees
{
    //public class WeatherForecast
    //{
    //    public DateTime Date { get; set; }
    //    public int TemperatureC { get; set; }
    //    public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
    //    public string Summary { get; set; }
    //}

    [Route("[controller]")]
    [ApiController]
    public class WeatherForecastController : ControllerBase
    {
        private ILoggerManager _logger;
        public WeatherForecastController(ILoggerManager logger) { _logger = logger; }
        [HttpGet]
        public IEnumerable<string> Get()
        {
            _logger.LogInfo("Here is info message from our values controller."); 
            _logger.LogDebug("Here is debug message from our values controller.");
            _logger.LogWarn("Here is warn message from our values controller.");
            _logger.LogError("Here is an error message from our values controller."); 
            return new string[] { "value1", "value2" };
        }
    }

}

Now let’s start the application and browse to
现在,让我们启动应用程序并浏览到
https://localhost:5001/weatherforecast

Tip: If you are using Windows 8 and having trouble starting this application on https://localhost:5001…, you have to add a parameter to the appsetings.Development.json file:
提示: 如果您使用的是Windows 8,并且在 https://localhost:5001 上启动此应用程序时遇到问题…,则必须向应用程序集添加一个参数。Development.json file:

"Kestrel": { 
    "EndpointDefaults": { 
        "Protocols": "Http1" 
    } 
}

As a result, you will see an array of two strings. Now go to the folder that you have specified in the nlog.config file, and check out the result. You should see two folders: the internal_logs folder and the logs folder. Inside the logs folder, you should find a file with the following logs:
结果,您将看到一个由两个字符串组成的数组。现在转到您在nlog.config 文件中指定的文件夹,然后查看结果。您应该会看到两个文件夹:internal_logs 文件夹和 logs 文件夹。在 logs 文件夹中,您应该找到一个包含以下logs的文件:

alt

That’s all we need to do to configure our logger for now. We’ll add some messages to our code along with the new features.
这就是我们现在配置记录器所需要做的。我们将在代码中添加一些消息以及新功能。