Serilog.Sinks.InMemory 0.11.0
dotnet add package Serilog.Sinks.InMemory --version 0.11.0
NuGet\Install-Package Serilog.Sinks.InMemory -Version 0.11.0
<PackageReference Include="Serilog.Sinks.InMemory" Version="0.11.0" />
paket add Serilog.Sinks.InMemory --version 0.11.0
#r "nuget: Serilog.Sinks.InMemory, 0.11.0"
// Install Serilog.Sinks.InMemory as a Cake Addin #addin nuget:?package=Serilog.Sinks.InMemory&version=0.11.0 // Install Serilog.Sinks.InMemory as a Cake Tool #tool nuget:?package=Serilog.Sinks.InMemory&version=0.11.0
Serilog.Sinks.InMemory
用于测试的 Serilog 内存中接收器。支持 FluentAssertions,便于编写断言。
构建状态
用法
要仅使用接收器,请添加 Serilog.Sinks.InMemory
NuGet 包
dotnet
CLI
dotnet add package Serilog.Sinks.InMemory
PowerShell
Install-Package Serilog.Sinks.InMemory
但最好使用断言,因此您还希望添加 Serilog.Sinks.InMemory.Assertions
NuGet 包
dotnet
CLI
dotnet add package Serilog.Sinks.InMemory.Assertions
PowerShell
Install-Package Serilog.Sinks.InMemory.Assertions
示例
假设您有一个包含实现一些复杂业务逻辑的方法的类
public class ComplicatedBusinessLogic
{
private readonly ILogger _logger;
public ComplicatedBusinessLogic(ILogger logger)
{
_logger = logger;
}
public string FirstTenCharacters(string input)
{
return input.Substring(0, 10);
}
}
有一个请求要求记录包含的字符数的消息。因此,为了测试这一点,您可以创建 ILogger
的模拟并断言该方法已调用,但是模拟设置很快就会变得很复杂(真的,这是我的看法!)并且在开始验证参数值时,对模拟的断言也有同样的问题。
因此,让我们使用 Serilog 和专门的测试接收器
public class WhenExecutingBusinessLogic
{
public void GivenInputOfFiveCharacters_MessageIsLogged()
{
var logger = new LoggerConfiguration()
.WriteTo.InMemory()
.CreateLogger();
var logic = new ComplicatedBusinessLogic(logger);
logic.FirstTenCharacters("12345");
// Use the static Instance property to access the in-memory sink
InMemorySink.Instance
.Should()
.HaveMessage("Input is {count} characters long");
}
}
现在测试将失败,因为 Expected a message to be logged with template \"Input is {count} characters long\" but didn't find any
现在更改实现如下
public string FirstTenCharacters(string input)
{
_logger.Information("Input is {count} characters long", input.Length);
return input.Substring(0, 10);
}
再次运行测试,现在它通过了。但如何确保此消息只记录一次呢?
为此,创建一个新的测试如下
public void GivenInputOfFiveCharacters_MessageIsLoggedOnce()
{
/* omitted for brevity */
InMemorySink.Instance
.Should()
.HaveMessage("Input is {count} characters long")
.Appearing().Once();
}
要验证消息是否多次记录,请使用 Appearing().Times(int numberOfTimes)
因此,现在您需要验证属性 count
是否具有预期值。这建立在之前的测试的基础上
public void GivenInputOfFiveCharacters_CountPropertyValueIsFive()
{
/* omitted for brevity */
InMemorySink.Instance
.Should()
.HaveMessage("Input is {count} characters long")
.Appearing().Once()
.WithProperty("count")
.WithValue(5);
}
断言一条消息出现多次
假设你在循环中有一个日志消息,你想要验证这一点
public void GivenLoopWithFiveItems_MessageIsLoggedFiveTimes()
{
/* omitted for brevity */
InMemorySink.Instance
.Should()
.HaveMessage("Input is {count} characters long")
.Appearing().Times(5);
}
断言消息具有特定的级别
除了消息被记录之外,你还想验证它是否具有正确的级别。你可以使用WithLevel()
断言来实现这一点
public void GivenLoopWithFiveItems_MessageIsLoggedFiveTimes()
{
/* omitted for brevity */
InMemorySink.Instance
.Should()
.HaveMessage("Input is {count} characters long")
.Appearing().Once()
.WithLevel(LogEventLevel.Information);
}
这也适用于多个消息
public void GivenLoopWithFiveItems_MessageIsLoggedFiveTimes()
{
logger.Warning("Test message");
logger.Warning("Test message");
logger.Warning("Test message");
InMemorySink.Instance
.Should()
.HaveMessage("Test message")
.Appearing().Times(3)
.WithLevel(LogEventLevel.Information);
}
这将失败并显示以下消息:预期日志消息 "Hello, world!" 的实例具有 Information 级别,但找到了 3 个具有 Warning 级别的实例
使用模式断言消息
除了匹配确切的消息之外,还可以使用Containing()
断言来匹配某种特定的模式
InMemorySink.Instance
.Should()
.HaveMessage()
.Containing("some pattern")
.Appearing().Once();
它会匹配到日志消息
这是一个模式
消息中的某个模式
消息中的某个模式
断言消息已被记录(或没有!)
当你想断言一条消息已被记录但不关心具体信息时,可以使用HaveMessage
和Appearing
InMemorySink.Instance
.Should()
.HaveMessage()
.Appearing().Times(3); // Expect three messages to be logged
当然,当你期望没有消息被记录时,也可以使用相反的方法
InMemorySink.Instance
.Should()
.NotHaveMessage();
或者特定的消息不应该被记录
InMemorySink.Instance
.Should()
.NotHaveMessage("a specific message");
对消息断言属性
当你想断言消息具有属性时,可以使用WithProperty
断言
InMemorySink.Instance
.Should()
.HaveMessage("Message with {Property}")
.Appearing().Once()
.WithProperty("Property");
然后你可以使用WithValue
来断言它具有特定的值
InMemorySink.Instance
.Should()
.HaveMessage("Message with {Property}")
.Appearing().Once()
.WithProperty("Property")
.WithValue("property value");
使用And
约束可以断言消息具有多个属性
InMemorySink.Instance
.Should()
.HaveMessage("Message with {Property1} and {Property2}")
.Appearing().Once()
.WithProperty("Property1")
.WithValue("value 1")
.And
.WithProperty("Property2")
.WithValue("value 2");
当你有一个出现多次的日志消息,并且你想断言日志属性值的期望值时,可以使用WithValues
断言
InMemorySink.Instance
.Should()
.HaveMessage("Message with {Property1} and {Property2}")
.Appearing().Times(3)
.WithProperty("Property1")
.WithValue("value 1", "value 2", "value 3")
注意:
WithValue
接受一个值数组。
有时你可能想要使用像BeLessThanOrEqual()
或HaveLength()
这样的断言,在这些情况下,WithValue
可能不是很有用。相反,你可以使用WhichValue<T>
来访问日志属性的值
InMemorySink.Instance
.Should()
.HaveMessage()
.Appearing().Once()
.WithProperty("PropertyOne")
.WhichValue<string>()
.Should()
.HaveLength(3);
如果日志属性值的类型与泛型类型参数不匹配,则WhichValue<T>
方法将抛出异常。
注意: 这只适用于标量值。当你以对象作为属性值记录日志消息时,Serilog将其转换为字符串。
使用解构对象断言属性
如果你使用对象解构
var someObject = new { Foo = "bar", Baz = "quux" };
logger.Information("Hello {@SomeObject}", someObject);
并且想断言解构对象的属性,你可以使用HavingADestructuredObject()
断言,如下所示
InMemorySink.Instance
.Should()
.HaveMessage("Hello {@SomeObject}")
.Appearing().Once()
.WithProperty("SomeObject")
.HavingADestructuredObject()
.WithProperty("Foo")
.WithValue("bar");
当属性SomeObject
不包含解构对象时,断言将失败并显示以下消息:"预期消息 "Hello {NotDestructured}" 拥有属性 "NotDestructured",该属性包含一个解构对象,但找到的是标量值"
在测试之间清除日志事件
根据你的测试框架和测试设置,你可能想要确保通过InMemorySink
捕获的日志事件被清除,这样测试就不会相互干扰。为此,InMemorySink
实现了IDisposable
接口。当调用Dispose()
时,LogEvents
集合会清空。
这将取决于你的测试框架或测试是否需要此功能。对于xUnit,此功能不是必需的,因为它在每个测试类的实例中隔离每个测试,这意味着它们都有自己的InMemorySink
实例。然而,MSTest采用不同的方法,在那里你可能想要使用此功能,如下所述
[TestClass]
public class WhenDemonstratingDisposableFeature
{
private Logger _logger;
[TestInitialize]
public void Initialize()
{
_logger?.Dispose();
_logger = new LoggerConfiguration()
.WriteTo.InMemory()
.CreateLogger();
}
[TestMethod]
public void GivenAFoo_BarIsBlah()
{
_logger.Information("Foo");
InMemorySink.Instance
.Should()
.HaveMessage("Foo");
}
[TestMethod]
public void GivenABar_BazIsQuux()
{
_logger.Information("Bar");
InMemorySink.Instance
.Should()
.HaveMessage("Bar");
}
}
此方法确保状態化对象CharterABar_BazIsQuux在先前的测试中不会看到任何日志消息。
创建记录器
通过LoggerConfiguration对象创建记录器。默认初始化如下
var logger = new LoggerConfiguration()
.WriteTo.InMemory()
.CreateLogger();
输出模板
基于文本的输出使用输出模板来控制格式化。这可以通过outputTemplate参数进行修改
var logger = new LoggerConfiguration()
.WriteTo.InMemory(outputTemplate: "{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj}{NewLine}{Exception}")
.CreateLogger();
默认模板,如上例所示,使用内置属性如Timestamp和Level。有关进一步配置和这些属性的说明,请参阅官方文档。
最低级别
在此示例中,只有信息级别日志及其以上才会写入InMemorySink。
var logger = new LoggerConfiguration()
.WriteTo.InMemory(restrictedToMinimumLevel: Events.LogEventLevel.Information)
.CreateLogger();
默认级别 - 如果未指定MinimumLevel,则将处理详细信息级别事件和相关更高级别的事件。
动态级别
如果一个应用程序需要动态级别切换,在配置记录器时首先要创建LoggingLevelSwitch实例
var levelSwitch = new LoggingLevelSwitch();
此对象将当前最低级别默认设置为信息,所以为了使日志更加限制性,请提前设置其最低级别
levelSwitch.MinimumLevel = LogEventLevel.Warning;
配置记录器时,使用MinimumLevel.ControlledBy()提供切换器
var log = new LoggerConfiguration()
.MinimumLevel.ControlledBy(levelSwitch)
.WriteTo.InMemory()
.CreateLogger();
现在,写入记录器的事件将根据切换器的MinimumLevel属性进行过滤。
要在运行时将级别提高或降低,例如响应通过网络发送的命令,请更改属性
levelSwitch.MinimumLevel = LogEventLevel.Verbose;
log.Verbose("This will now be logged");
产品 | 版本 兼容的附加计算的target framework版本 |
---|---|
.NET | net5.0 已计算。 net5.0-windows 已计算。 net6.0 已计算。 net6.0-android 已计算。 net6.0-ios 已计算。 net6.0-maccatalyst 已计算。 net6.0-macos 已计算。 net6.0-tvos 已计算。 net6.0-windows 已计算。 net7.0 已计算。 net7.0-android 已计算。 net7.0-ios 已计算。 net7.0-maccatalyst 已计算。 net7.0-macos 已计算。 net7.0-tvos 已计算。 net7.0-windows 已计算。 net8.0 已计算。 net8.0-android 已计算。 net8.0-browser 已计算。 net8.0-ios 已计算。 net8.0-maccatalyst 已计算。 net8.0-macos 已计算。 net8.0-tvos 已计算。 net8.0-windows 已计算。 |
.NET Core | netcoreapp2.0 已计算。 netcoreapp2.1 已计算。 netcoreapp2.2 已计算。 netcoreapp3.0 已计算。 netcoreapp3.1 已计算。 |
.NET Standard | netstandard2.0 兼容。 netstandard2.1 已计算。 |
.NET Framework | net461 已计算。 net462 已计算。 net463 已计算。 net47 已计算。 net471 已计算。 net472 已计算。 net48 已计算。 net481 已计算。 |
MonoAndroid | monoandroid 已计算。 |
MonoMac | monomac 已计算。 |
MonoTouch | monotouch 已计算。 |
Tizen | tizen40 已计算。 tizen60 已计算。 |
Xamarin.iOS | xamarinios 已计算。 |
Xamarin.Mac | xamarinmac 已计算。 |
Xamarin.TVOS | xamarintvos 已计算。 |
Xamarin.WatchOS | xamarinwatchos 已计算。 |
-
.NETStandard 2.0
- Serilog (>= 2.12.0)
NuGet 包 (4)
显示依赖 Serilog.Sinks.InMemory 的前 4 个 NuGet 包
包 | 下载 |
---|---|
Serilog.Sinks.InMemory.Assertions
与 Serilog.Sinks.InMemory 包一起使用的 FluentAssertions 扩展 |
|
Swisschain.Extensions.Testing
包描述 |
|
Eshva.Common.Testing
Eshva 的常用代码集合 |
|
Swisschain.ExtensionsV2.Testing
包描述 |
GitHub 代码仓库 (5)
显示依赖 Serilog.Sinks.InMemory 的前 5 个热门 GitHub 代码仓库
代码仓库 | 星标数 |
---|---|
EventStore/EventStore
EventStoreDB,一个面向事件的本机数据库。专为事件存储、事件驱动和微服务架构设计
|
|
dafny-lang/dafny
Dafny 是一种具备验证意识的编程语言
|
|
nkdAgility/azure-devops-migration-tools
Azure DevOps 迁移工具允许您将团队、待办事项、任务、测试用例以及计划和方案从 Azure DevOps / TFS 中的一个项目迁移到另一个项目,无论是在同一组织内还是在不同组织之间
|
|
serilog-contrib/serilog-ui
适用于多个输出的简单 Serilog 日志查看器 UI
|
|
serilog-contrib/Serilog.Enrichers.Sensitive
一个 Serilog LogEvent 增强器,它可以掩码敏感数据
|