This commit is contained in:
YUN-PC5\user 2023-10-13 16:56:47 +08:00
parent b86343c308
commit e3385e1464
3 changed files with 542 additions and 5 deletions

View File

@ -1334,4 +1334,390 @@ var jobDetail = JobBuilder.Create<SayHelloJob>()
## 介绍 ## 介绍
## 使用 NLog 是一个跨平台的 .Net 日志组件。
## 安装
### Package Manager
```shell
Install-Package NLog
```
### .Net CLI
```shell
dotnet add package NLog
```
## 配置
### 配置方法
NLog 可以通过两种方式进行配置:
- 配置文件
- 程序代码
### 通过配置文件配置
#### 配置文件路径
NLog 启动时会在某些标准路径查找配置文件,并进行自动配置:
- 对于 **ASP.NET** 应用程序,以下文件会被查询:
- 标准 web 应用程序文件 web.config
- web.config 所在目录下的 web.nlog 文件
- 应用程序目录下的 NLog.config 文件
- NLog.dll 所在目录下的 NLog.dll.nlog 文件(only if NLog isn't installed in the GAC)
#### 配置文件格式
NLog 支持两种文件格式:
1. 在标准的 *.exe.config 或 web.config 中嵌入配置
2. 在单独的文件中进行配置
个人觉得单独的配置文件便于在项目中切换配置或日志库。
NLog 配置文件是一个以`nlog`为根节点的 XML 文件。`nlog`节点可以添加命名空间,以开启 Visual Studio 的 Intellisense 功能。
```xml
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
</nlog>
```
以下元素可以作为`nlog`节点的子节点,前两种元素在所有 NLog 配置文件中都必须存在,剩余元数是可选的。
- `targets` - 定义日志目标/输出
- `rules` - 定义日志路由规则
- `extensions` - 定义要加载的 NLog 扩展性 *.dll 文件
- `includes` - 定义要包含的外部配置文件
- `variables` - 设置配置变量的值
`nlog`节点中设置属性`autoReload="true"`, Nlog会监视配置文件并在配置文件发送更改时自动载入配置文件而不需要重启应用程序。该功能支持通过`include`包含的子配置文件。
示例如下
```xml
<nlog autoReload="true">
...
</nlog>
```
##### targets
`targets`节点中定义了一系列日志输出目标,每一个输出目标是一个`target`元素。对于每一个`target`元素,`name`属性和`type`属性是必须要指定的:
- `name` - `target`的名字。路由规则根据该属性将日志信息路由到当前目标。
- `type` - `target`的类型。当使用了 xsi 命名空间时,该属性被命名为 xsi:type。目前 NLog 支持的输出目标列表Targets
不同类型的`target`节点可以接受不同的属性。例如对于`File`目标,`fileName`参数指定了日志文件的文件名;对于`Console`目标,`error`参数指定日志信息是写到标准错误流还是标准输出流。NLog 内置了许多预定义类型,也可以自定义输出目标类型,详见如何自定义输出目标。
以下是 `targets`节点的例子:
```xml
<targets>
<target name="f1" xsi:type="File" fileName="file1.txt"/>
<target name="f2" xsi:type="File" fileName="file2.txt"/>
<target name="n1" xsi:type="Network" address="tcp://localhost:4001"/>
<target name="ds" xsi:type="OutputDebugString"/>
</targets>
```
##### rules
`rules`节点是日志路由规则的集合,由一个或多个`logger`元素组成。每个`logger`元素记录了logger的名字、目标输出以及要处理的日志等级。NLog 从路由规则表的第一个`logger`开始处理,如果当前`logger`有效,则日志信息将被输出到指定的`target`。如果某个`logger`被标记为`final`,那么其后的`logger`都会被忽略。
`logger`包含下列属性:
- `name` - logger的名字(可以使用通配符*)
- `minLevel` - 最小日志等级
- `maxLevel` - 最大日志等级
- `level` - 单一的日志等级
- `levels` - 以逗号分割的日志等级列表
- `writeTo` - 以逗号分割的输出目标列表
- `final` - 标记当前规则为最后一条规则
- `enabled` - 使能当前规则
如果在一条规则中定义了多个日志等级相关的属性(`level`, `levels`, `minLevel``maxLevel`)
按照优先级只生效当前优先级最高的属性。等级相关属性优先级如下
1. `level`
2. `levels`
3. `minLevel``maxLevel`
4. 没有设置(所有等级的日志都会被记录)
以下是`rules`节点的例子:
```xml
<rules>
<logger name="Name.Space.Class1" minlevel="Debug" writeTo="f1" />
<logger name="Name.Space.Class1" levels="Debug,Error" writeTo="f1" />
<logger name="Name.Space.*" writeTo="f3,f4" />
<logger name="Name.Space.*" minlevel="Debug" maxlevel="Error" final="true" />
</rules>
```
1. 命名空间 `Name.Space` 下类 `Class1` 中高于 `Debug` 级别的日志信息将被写入输出目标 `f1`
2. 命名空间`Name.Space` 下类 `Class1` 中级别为 `Debug``Error` 的日志信息将被写入 `target:f1`
3. 命名空间 `Name.Space` 下所有类中的日志信息将被写入 `target:f3, f4`
4. 命名空间 `Name.Space` 下所有类中级别在`Debug``Error` 之间 (`Debug`,`Info`,`Warn``Error`) 的日志信息不被记录(因为没有指定属性`writeTo`)。由于标记了属性 `final`,之后的 `logger` 都会被忽略。
##### extensions
`extensions`节点可以添加额外的NLog元包或自定义功能。`assembly`属性指定的被包含程序集不带后缀`.dll`。示例如下
```xml
<nlog>
<extensions>
<add assembly="MyAssembly" />
</extensions>
<targets>
<target name="a1" type="MyFirst" host="localhost" />
</targets>
<rules>
<logger name="*" minLevel="Info" appendTo="a1" />
</rules>
</nlog>
```
NLog 4.0 之后,与`NLog.dll`同目录下名如`NLog*.dll`的程序集(如`NLog.CustomTarget.dll`)会被自动加载。
##### Includes
`include`节点指定当前配置文件包含多个子配置文件。通过`${}`语法可以使用环境变量,下例展示包含一个名为当前机器名的配置文件。
```xml
<nlog>
...
<include file="${machinename}.config" />
...
</nlog>
```
NLog 4.4.2 之后可以使用通配符`*`指定多个文件。例如,`<include file="nlog-*.config" />`
##### variables
`variable`元素定义了配置文件中需要用到的变量,一般用来表示复杂或者重复的表达式(例如文件名)。变量需要先定义后使用,否则配置文件将初始化失败。定义变量的语法如下:
```xml
<variable name="var" value="xxx" />
```
定义变量之后,可以通过`${var}`语法来使用:
```xml
<nlog>
<variable name="logDirectory" value="logs/${shortdate}" />
<targets>
<target name="file1" xsi:type="File" fileName="${logDirectory}/file1.txt" />
<target name="file2" xsi:type="File" fileName="${logDirectory}/file2.txt" />
</targets>
</nlog>
```
## 记录日志
### 获取`NLog.Logger`实例
通过`NLog.LogManager.GetCurrentClassLogger`方法或`NLog.LogManager.GetLogger`方法可以获得`NLog.Logger`实例。
1. `NLog.LogManager.GetCurrentClassLogger`方法
通过`NLog.LogManager.GetCurrentClassLogger`方法可以创建一个与所在类同名(包括namespace)的`NLog.Logger`的实例。
```c#
var logger = NLog.LogManager.GetCurrentClassLogger();
```
2. `NLog.LogManager.GetLogger`方法
通过`NLog.LogManager.GetLogger("MyLogger")`方法可以显示地指定`NLog.Logger`的名称为MyLogger。
```c#
var logger = NLog.LogManager.GetLogger("MyLogger");
```
### NLog 支持的日志级别
- `Trace` - very detailed logs包含大量的信息例如 protocol payloads。该级别一般仅在开发环境中启用。
- `Debug` - debugging information, 比 `Trance` 级别稍微粗略,一般在生产环境中不启用。
- `Info` - information messages一般在生产环境中启用。
- `Warn` - warning messages一般用于可恢复或临时性错误的非关键问题。
- `Error` - error messages一般是异常信息。
- `Fatal` - 非常严重的错误!
### NLog 日志记录函数方法
在代码中通过以下方法分别进行不同等级的日志记录,
```csharp
NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
logger.Trace("Sample trace message");
logger.Debug("Sample debug message");
logger.Info("Sample informational message");
logger.Warn("Sample warning message");
logger.Error("Sample error message");
logger.Fatal("Sample fatal error message");
```
除此之外,`NLog.Logger` 还可以通过调用 `Log` 方法记录日志,
```csharp
NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
// log level is passed as the parameter.
logger.Log(LogLevel.Trace, "Sample trace message");
logger.Log(LogLevel.Debug, "Sample debug message");
logger.Log(LogLevel.Info, "Sample informational message");
logger.Log(LogLevel.Warn, "Sample warning message");
logger.Log(LogLevel.Error, "Sample error message");
logger.Log(LogLevel.Fatal, "Sample fatal message");
```
### 预定义和模板宏 Layouts and Layout Renderers
#### 模板宏 `Layout Renderers`
Layout 使用模板宏 Layout Renders 自定义日志输出的内容和格式。例如,大多数`target`使用的默认`SimpleLayout`如下
```xml
${longdate}|${level:uppercase=true}|${logger}|${message}
```
通过 `${}` 语法可以使用预定义的模版宏 Layout Renders。
Layout Renders 的列表:[Layout Renders](https://github.com/NLog/NLog/wiki/Layout-Renderers)。
#### **预定义 `Layout`**
\- [CsvLayout](https://link.zhihu.com/?target=https%3A//github.com/NLog/NLog/wiki/CsvLayout) - A specialized layout that renders CSV-formatted events.
\- [JsonLayout](https://link.zhihu.com/?target=https%3A//github.com/NLog/NLog/wiki/JsonLayout) - A specialized layout that renders to JSON.
\- [LayoutWithHeaderAndFooter](https://link.zhihu.com/?target=https%3A//github.com/NLog/NLog/wiki/LayoutWithHeaderAndFooter) - A specialized layout that supports header and footer.
\- [Log4JXmlEventLayout](https://link.zhihu.com/?target=https%3A//github.com/NLog/NLog/wiki/Log4JXmlEventLayout) - A specialized layout that renders Log4j-compatible XML events.
\- [SimpleLayout](https://link.zhihu.com/?target=https%3A//github.com/NLog/NLog/wiki/SimpleLayout) - Represents a string with embedded placeholders that can render contextual information.
\- [CompoundLayout](https://link.zhihu.com/?target=https%3A//github.com/NLog/NLog/wiki/CompoundLayout) - A layout containing one or more nested layouts.
## 最佳实践
#### **`NLog.Logger` 应为类的静态变量**
新建 `NLog.Logger` 对象消耗一定的开销,例如需要获取锁或者分配对象。因此推荐以下方式创建`NLog.Logger`
```csharp
namespace MyNamespace
{
public class MyClass
{
private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
}
}
```
#### **应由 `NLog.Logger` 格式化日志**
尽量避免直接使用字符串进行格式化。例如,
```csharp
var message = "Hello" + "Earch";
logger.Info(message);
```
推荐使用 `NLog.Logger` 对象进行格式化。例如,
```csharp
logger.Info("Hello {0}", "Earth");
```
`NLog.Logger` 对象可以推迟执行格式化操作的时机,从而减少开销。
#### **应将异常对象传递给 `NLog.Logger`**
避免将异常作为字符串格式化的参数,而是显示的将异常作为参数传递给函数。例如,
```csharp
try
{
}
catch (Exception ex)
{
logger.Error(ex, "Something bad happened");
}
```
#### **开启配置文件的有效性验证**
默认情况下NLog 屏蔽了自身的所有异常,因此 NLog 出错时不会使应用程序崩溃。对于大多数应用程序,建议在 `nlog` 元素增加 `throwConfigExceptions="true"` 属性开启初始化配置时的异常捕获功能,以验证配置文件的有效性。
```xml
<?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"
throwConfigExceptions="true">
</nlog>
```
> ⚠️注意:还有一个名为 `throwExceptions` 的属性,不应在生产环境中使用。它是为了单元测试和本地调试设计的。
#### **别忘了 Flush 日志信息**
默认情况下NLog 在程序关闭时会自动 flush。 Windows 限定 .NET 应用程序在程序终止前一定时间内进行关闭操作(一般是 2 秒)。如果 NLog 的 `target` 依赖于网络传输(例如 Http, Mail, Tcp),那么建议手动执行 Flush/Shutdown 操作。
Mono/Linux 上运行的 .NET 应用程序在关闭前需要停止 Thread/Timer。如果未能完成这些操作则会引发未处理异常、段错误以及其他难以预料的行为同样建议手动执行 Flush/Shutdown 操作。
```csharp
NLog.LogManager.Shutdown(); // Flush and close down internal threads and timers
```
# 远程过程调用应用
## 调用 gRPC 服务
### 客户端工厂集成
gRPC 与`HttpClientFactory`的集成提供了一种创建 gRPC 客户端的集中方式。它可用作配置独立 gRPC 客户端实例的替代方法。
工厂具有以下优势:
- 提供了用于配置逻辑 gRPC 客户端实例的中心位置。
- 可管理基础`HttpClientMessageHandler`的生存期。
- 在 ASP.NET Core gRPC 服务中自动传播截止时间和取消。
#### 注册 gRPC 客户端
若要注册 gRPC 客户端,可在`Program.cs`中的应用入口点处的WebApplicationBuilder的实例中使用通用的`AddGrpcClient`扩展方法,并指定 gRPC 类型化客户端类和服务地址:
```c#
builder.Services.AddGrpcClient<Greeter.GreeterClient>(o =>
{
o.Address = new Uri("https://localhost:5001");
})
```
gRPC 客户端类型通过依赖性注入(DI)注册为暂时性。现在可以在由 DI 创建的类型中直接注入和使用客户端。ASP.NET Core MVC 控制器、SignalR 中心和 gRPC 服务是可以自动注入 gRPC 客户端的位置:
```c#
public class AggregatorService : Aggregator.AggregatorBase
{
private readonly Greeter.GreeterClient _client;
public AggregatorService(Greeter.GreeterClient client)
{
_client = client;
}
public override async Task SayHellos(HelloRequest request,
IServerStreamWriter<HelloReply> responseStream, ServerCallContext context)
{
using (var call = _client.SayHellos(request))
{
await foreach (var response in call.ResponseStream.ReadAllAsync())
{
await responseStream.WriteAsync(response);
}
}
}
}
```

34
source/_posts/Spring.md Normal file
View File

@ -0,0 +1,34 @@
---
title: Spring
date: 2023-10-09 14:17:02
tags:
---
# Spring Cloud
## OpenFeign
### 引入pom.xml
```xml
<properties>
<java.version>17</java.version>
<spring-cloud.version>2022.0.1</spring-cloud.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
```

View File

@ -39,10 +39,6 @@ protobuf 采用 `varint` 和 处理负数的 `ZigZag` 两种编码方式使得
第 2 个问题呢,其实需要的就是[每个平台]一套代码生成工具。生成的代码需要覆盖类的定义、对象的序列化/反序列化、服务接口的暴露和远程调用等等必要的模板代码,如此,开发人员只需要负责接口文档的维护和业务代码的实现(很自然的面向接口编程:))。此时,采用 protobuf 的`gRPC`自然而然的映入眼帘,因为对于目前所有主要的编程语言和平台,都有 gRPC 工具和库,包括 .NET、Java、Python、Go、C++、Node.js、Swift、Dart、Ruby 以及 PHP。可以说这些工具和库的提供使得 gRPC 可以跨多种语言和平台一致地工作,成为一个全面的 RPC 解决方案。 第 2 个问题呢,其实需要的就是[每个平台]一套代码生成工具。生成的代码需要覆盖类的定义、对象的序列化/反序列化、服务接口的暴露和远程调用等等必要的模板代码,如此,开发人员只需要负责接口文档的维护和业务代码的实现(很自然的面向接口编程:))。此时,采用 protobuf 的`gRPC`自然而然的映入眼帘,因为对于目前所有主要的编程语言和平台,都有 gRPC 工具和库,包括 .NET、Java、Python、Go、C++、Node.js、Swift、Dart、Ruby 以及 PHP。可以说这些工具和库的提供使得 gRPC 可以跨多种语言和平台一致地工作,成为一个全面的 RPC 解决方案。
## gRPC 在 .NET 中的使用
`gRPC`作为 .NET 平台中的“一等公民”。
## proto文件 ## proto文件
```protobuf ```protobuf
@ -190,5 +186,126 @@ message Person {
| `string` | `google.protobuf.StringValue` | | `string` | `google.protobuf.StringValue` |
| `ByteString` | `google.protobuf.BytesValue` | | `ByteString` | `google.protobuf.BytesValue` |
### 小数
Protobuf 本身不支持 .NET `decimal` 类型,只支持 `double``float`。在 Protobuf 项目中,我们正在探讨这样一种可能性:将标准 decimal 类型添加到已知类型,并为支持它的语言和框架添加平台支持。尚未实现任何内容。
可以创建消息定义来表示 `decmial` 类型,以便在 .NET 客户端和服务器之间实现安全序列化。但其他平台上的开发人员必须了解所使用的格式,并能够实现自己对其的处理。
### 为 Protobuf 创建自定义 decimal 类型
```protobuf
package CustomTypes;
message DecimalValue {
int64 units = 1;
sfixed32 nanos = 2;
}
```
`nanos`字段表示从`0.999_999_999``-0.999_999_999`的值。例如,`decimal``1.5m`将表示`{ units = 1, nanos = 500_000_000 }`。这就是此示例中的 `nanos` 字段使用 `sfixed32` 类型的原因:对于较大的值,其编码效率比 `int32` 更高。 如果 `units` 字段为负,则 `nanos` 字段也应为负。
### 集合
#### 列表
Protobuf 中,在字段上使用`repeated`前缀关键字指定列表。以下示例演示如何创建列表:
```protobuf
message Person {
// ...
repeated string roles = 8;
}
```
在生产的代码中,`repeated`字段由`Google.Protobuf.Collections.RepeatedField<T>`泛型类型表示。
```c#
public class Person
{
// ...
public RepeatedField<string> Roles { get; }
}
```
`RepeatedField<T>`可实现 IList<T>。因此你可使用 LINQ 查询,或者将其转换为数组或列表。`RepeatedField<T>`属性没有公共 setter。项应添加到现有集合中。
```c#
var person = new Person();
person.Roles.Add("user");
var roles = new [] { "admin", "manager" };
person.Roles.Add(roles);
```
#### 字典
.NET IDictionary<TKey, TValue> 类型在 Protobuf 中使用 `map<key_type, value_type>`表示。
```protobuf
message Person {
// ...
map<string, string> attributes = 9;
}
```
在生成的 .NET 代码中,`map`字段由`Google.Protobuf.Collections.MapField<TKey, TValue>`泛型类型表示。
`MapField<TKey, TValue>`可实现 IDictionary<TKey, TValue>。与`repeated`属性一样,`map`属性没有公共 setter。项应添加到现有集合中。
```c#
var person = new Person();
person.Attributes["create_by"] = "James";
var attributes = new Dictionary<string, string>
{
["last_modified"] = DateTime.UtcNow.ToString()
};
person.Attributes.Add(attributes);
```
### 无结构的条件消息
Protobuf 是一种协定优先的消息传递格式。 构建应用时,必须在 `.proto` 文件中指定应用的消息,包括其字段和类型。 Protobuf 的协定优先设计非常适合强制执行消息内容,但可能会限制不需要严格协定的情况:
- 包含未知有效负载的消息。 例如,具有可以包含任何消息的字段的消息。
- 条件消息。 例如,从 gRPC 服务返回的消息可能是成功结果或错误结果。
- 动态值。 例如,具有包含非结构化值集合的字段的消息,类似于 JSON。
Protobuf 提供语言功能和类型来支持这些情况。
#### 任意
利用`Any`类型,可以将消息作为嵌入类型使用,而无需`.proto`定义。若使用`Any`类型,请导入`any.proto`
```protobuf
import "google/protobuf/any.proto";
message Status {
string message = 1;
google.protobuf.Any detail = 2;
}
```
```c#
// Create a status with a Person message set to detail.
var status = new ErrorStatus();
status.Detail = Any.Pack(new Person { FirstName = "James" });
// Read Person message from detail.
if (status.Detail.Is(Person.Desciptor))
{
var person = status.Detail.Unpack<Person>();
// ...
}
```
## Dubbo 3 ## Dubbo 3