diff --git a/source/_posts/ASP.NET Core.md b/source/_posts/ASP.NET Core.md index 09c350a..d54dc9b 100644 --- a/source/_posts/ASP.NET Core.md +++ b/source/_posts/ASP.NET Core.md @@ -1334,4 +1334,390 @@ var jobDetail = JobBuilder.Create() ## 介绍 -## 使用 +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`节点的子节点,前两种元素在所有 NLog 配置文件中都必须存在,剩余元数是可选的。 + +- `targets` - 定义日志目标/输出 +- `rules` - 定义日志路由规则 +- `extensions` - 定义要加载的 NLog 扩展性 *.dll 文件 +- `includes` - 定义要包含的外部配置文件 +- `variables` - 设置配置变量的值 + +在`nlog`节点中设置属性`autoReload="true"`, Nlog会监视配置文件,并在配置文件发送更改时自动载入配置文件而不需要重启应用程序。该功能支持通过`include`包含的子配置文件。 + +示例如下 + +```xml + + ... + +``` + +##### targets + +`targets`节点中定义了一系列日志输出目标,每一个输出目标是一个`target`元素。对于每一个`target`元素,`name`属性和`type`属性是必须要指定的: + +- `name` - `target`的名字。路由规则根据该属性将日志信息路由到当前目标。 +- `type` - `target`的类型。当使用了 xsi 命名空间时,该属性被命名为 xsi:type。目前 NLog 支持的输出目标列表:Targets + +不同类型的`target`节点可以接受不同的属性。例如对于`File`目标,`fileName`参数指定了日志文件的文件名;对于`Console`目标,`error`参数指定日志信息是写到标准错误流还是标准输出流。NLog 内置了许多预定义类型,也可以自定义输出目标类型,详见如何自定义输出目标。 + +以下是 `targets`节点的例子: + +```xml + + + + + + +``` + +##### 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 + + + + + + +``` + +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 4.0 之后,与`NLog.dll`同目录下名如`NLog*.dll`的程序集(如`NLog.CustomTarget.dll`)会被自动加载。 + +##### Includes + +`include`节点指定当前配置文件包含多个子配置文件。通过`${}`语法可以使用环境变量,下例展示包含一个名为当前机器名的配置文件。 + +```xml + + ... + + ... + +``` + +NLog 4.4.2 之后可以使用通配符`*`指定多个文件。例如,`` + +##### variables + +`variable`元素定义了配置文件中需要用到的变量,一般用来表示复杂或者重复的表达式(例如文件名)。变量需要先定义后使用,否则配置文件将初始化失败。定义变量的语法如下: + +```xml + +``` + +定义变量之后,可以通过`${var}`语法来使用: + +```xml + + + + + + + +``` + +## 记录日志 + +### 获取`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 + + + +``` + +> ⚠️注意:还有一个名为 `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(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 responseStream, ServerCallContext context) + { + using (var call = _client.SayHellos(request)) + { + await foreach (var response in call.ResponseStream.ReadAllAsync()) + { + await responseStream.WriteAsync(response); + } + } + } +} +``` + diff --git a/source/_posts/Spring.md b/source/_posts/Spring.md new file mode 100644 index 0000000..fbcad58 --- /dev/null +++ b/source/_posts/Spring.md @@ -0,0 +1,34 @@ +--- +title: Spring +date: 2023-10-09 14:17:02 +tags: +--- + +# Spring Cloud + +## OpenFeign + +### 引入pom.xml + +```xml + + 17 + 2022.0.1 + + + + + org.springframework.cloud + spring-cloud-dependencies + ${spring-cloud.version} + pom + import + + + + + org.springframework.cloud + spring-cloud-starter-openfeign + +``` + diff --git a/source/_posts/gRPC.md b/source/_posts/gRPC.md index 44961b8..e0633b2 100644 --- a/source/_posts/gRPC.md +++ b/source/_posts/gRPC.md @@ -39,10 +39,6 @@ protobuf 采用 `varint` 和 处理负数的 `ZigZag` 两种编码方式使得 第 2 个问题呢,其实需要的就是[每个平台]一套代码生成工具。生成的代码需要覆盖类的定义、对象的序列化/反序列化、服务接口的暴露和远程调用等等必要的模板代码,如此,开发人员只需要负责接口文档的维护和业务代码的实现(很自然的面向接口编程:))。此时,采用 protobuf 的`gRPC`自然而然的映入眼帘,因为对于目前所有主要的编程语言和平台,都有 gRPC 工具和库,包括 .NET、Java、Python、Go、C++、Node.js、Swift、Dart、Ruby 以及 PHP。可以说,这些工具和库的提供,使得 gRPC 可以跨多种语言和平台一致地工作,成为一个全面的 RPC 解决方案。 -## gRPC 在 .NET 中的使用 - -`gRPC`作为 .NET 平台中的“一等公民”。 - ## proto文件 ```protobuf @@ -190,5 +186,126 @@ message Person { | `string` | `google.protobuf.StringValue` | | `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`泛型类型表示。 + +```c# +public class Person +{ + // ... + public RepeatedField Roles { get; } +} +``` + +`RepeatedField`可实现 IList。因此你可使用 LINQ 查询,或者将其转换为数组或列表。`RepeatedField`属性没有公共 setter。项应添加到现有集合中。 + +```c# +var person = new Person(); + +person.Roles.Add("user"); + +var roles = new [] { "admin", "manager" }; +person.Roles.Add(roles); +``` + +#### 字典 + +.NET IDictionary 类型在 Protobuf 中使用 `map`表示。 + +```protobuf +message Person { + // ... + map attributes = 9; +} +``` + +在生成的 .NET 代码中,`map`字段由`Google.Protobuf.Collections.MapField`泛型类型表示。 + +`MapField`可实现 IDictionary。与`repeated`属性一样,`map`属性没有公共 setter。项应添加到现有集合中。 + +```c# +var person = new Person(); + +person.Attributes["create_by"] = "James"; + +var attributes = new Dictionary +{ + ["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(); + // ... +} +``` + + + ## Dubbo 3