{"id":29031818,"url":"https://github.com/serilog-contrib/serilogsinksinmemory","last_synced_at":"2025-06-26T10:05:36.717Z","repository":{"id":46653047,"uuid":"152750094","full_name":"serilog-contrib/SerilogSinksInMemory","owner":"serilog-contrib","description":"In-memory sink for Serilog to use for testing","archived":false,"fork":false,"pushed_at":"2025-05-30T13:24:10.000Z","size":236,"stargazers_count":57,"open_issues_count":7,"forks_count":9,"subscribers_count":5,"default_branch":"master","last_synced_at":"2025-06-26T10:05:35.166Z","etag":null,"topics":["fluentassertions","serilog-sink"],"latest_commit_sha":null,"homepage":null,"language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/serilog-contrib.png","metadata":{"files":{"readme":"README.md","changelog":"Changelog.md","contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"github":"sandermvanvliet"}},"created_at":"2018-10-12T12:52:02.000Z","updated_at":"2025-06-20T04:57:27.000Z","dependencies_parsed_at":"2024-06-20T19:06:01.349Z","dependency_job_id":null,"html_url":"https://github.com/serilog-contrib/SerilogSinksInMemory","commit_stats":null,"previous_names":["sandermvanvliet/serilogsinksinmemory"],"tags_count":12,"template":false,"template_full_name":null,"purl":"pkg:github/serilog-contrib/SerilogSinksInMemory","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/serilog-contrib%2FSerilogSinksInMemory","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/serilog-contrib%2FSerilogSinksInMemory/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/serilog-contrib%2FSerilogSinksInMemory/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/serilog-contrib%2FSerilogSinksInMemory/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/serilog-contrib","download_url":"https://codeload.github.com/serilog-contrib/SerilogSinksInMemory/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/serilog-contrib%2FSerilogSinksInMemory/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":262044449,"owners_count":23249752,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["fluentassertions","serilog-sink"],"created_at":"2025-06-26T10:05:35.671Z","updated_at":"2025-06-26T10:05:36.709Z","avatar_url":"https://github.com/serilog-contrib.png","language":"C#","funding_links":["https://github.com/sponsors/sandermvanvliet"],"categories":[],"sub_categories":[],"readme":"# Serilog.Sinks.InMemory\n\nIn-memory sink for Serilog to use for testing with [FluentAssertions](https://fluentassertions.com/) support for easy to write assertions.\n\n## Build status\n\n[![build-and-test](https://github.com/serilog-contrib/SerilogSinksInMemory/actions/workflows/dotnet.yml/badge.svg)](https://github.com/serilog-contrib/SerilogSinksInMemory/actions/workflows/dotnet.yml)\n[![release](https://github.com/serilog-contrib/SerilogSinksInMemory/actions/workflows/release.yml/badge.svg)](https://github.com/serilog-contrib/SerilogSinksInMemory/actions/workflows/release.yml)\n\n[![NuGet Serilog.Sinks.InMemory](https://buildstats.info/nuget/Serilog.Sinks.InMemory)](https://www.nuget.org/packages/Serilog.Sinks.InMemory/)\n[![NuGet Serilog.Sinks.InMemory.Assertions](https://buildstats.info/nuget/Serilog.Sinks.InMemory.Assertions)](https://www.nuget.org/packages/Serilog.Sinks.InMemory.Assertions/)\n\n## Usage\n\nTo just use the sink, add the `Serilog.Sinks.InMemory` NuGet package:\n\n`dotnet` CLI:\n\n```bash\ndotnet add package Serilog.Sinks.InMemory\n```\n\nPowerShell:\n\n```PowerShell\nInstall-Package Serilog.Sinks.InMemory\n```\n\nBut it's better with assertions so you'll also want to add the `Serilog.Sinks.InMemory.Assertions` NuGet package:\n\n`dotnet` CLI:\n\n```bash\ndotnet add package Serilog.Sinks.InMemory.Assertions\n```\n\nPowerShell:\n\n```PowerShell\nInstall-Package Serilog.Sinks.InMemory.Assertions\n```\n\n## Example\n\nLet's say you have a class with method implementing some complicated business logic:\n\n```csharp\npublic class ComplicatedBusinessLogic\n{\n    private readonly ILogger _logger;\n\n    public ComplicatedBusinessLogic(ILogger logger)\n    {\n        _logger = logger;\n    }\n\n    public string FirstTenCharacters(string input)\n    {\n        return input.Substring(0, 10);\n    }\n}\n```\n\nA request came in to log a message with the number of characters in the input. So to test that you can create a mock of `ILogger` and assert the method to log was called, however mock setups quickly become very messy (true: this is my opinion!) and assertions on mocks have the same problem when you start verifying values of arguments.\n\nSo instead let's use Serilog and a dedicated sink for testing:\n\n```csharp\npublic class WhenExecutingBusinessLogic\n{\n    public void GivenInputOfFiveCharacters_MessageIsLogged()\n    {\n        var logger = new LoggerConfiguration()\n            .WriteTo.InMemory()\n            .CreateLogger();\n\n        var logic = new ComplicatedBusinessLogic(logger);\n\n        logic.FirstTenCharacters(\"12345\");\n\n        // Use the static Instance property to access the in-memory sink\n        InMemorySink.Instance\n            .Should()\n            .HaveMessage(\"Input is {count} characters long\");\n    }\n}\n```\n\nThe test will now fail with `Expected a message to be logged with template \\\"Input is {count} characters long\\\" but didn't find any`\n\nNow change the implementation to:\n\n```csharp\npublic string FirstTenCharacters(string input)\n{\n    _logger.Information(\"Input is {count} characters long\", input.Length);\n\n    return input.Substring(0, 10);\n}\n```\n\nRun the test again and it now passes. But how do we ensure this message is only logged once?\n\nTo do that, create a new test like so:\n\n```csharp\npublic void GivenInputOfFiveCharacters_MessageIsLoggedOnce()\n{\n    /* omitted for brevity */\n\n    InMemorySink.Instance\n        .Should()\n        .HaveMessage(\"Input is {count} characters long\")\n        .Appearing().Once();\n}\n```\n\nTo verify if a message is logged multiple times use `Appearing().Times(int numberOfTimes)`\n\nSo now you'll want to verify that the property `count` has the expected value. This builds upon the previous test:\n\n```csharp\npublic void GivenInputOfFiveCharacters_CountPropertyValueIsFive()\n{\n    /* omitted for brevity */\n\n    InMemorySink.Instance\n        .Should()\n        .HaveMessage(\"Input is {count} characters long\")\n        .Appearing().Once()\n        .WithProperty(\"count\")\n        .WithValue(5);\n}\n```\n\n### Asserting a message appears more than once\n\nLet's say you have a log message in a loop and you want to verify that:\n\n```csharp\npublic void GivenLoopWithFiveItems_MessageIsLoggedFiveTimes()\n{\n    /* omitted for brevity */\n\n    InMemorySink.Instance\n        .Should()\n        .HaveMessage(\"Input is {count} characters long\")\n        .Appearing().Times(5);\n}\n```\n\n### Asserting a message has a certain level\n\nApart from a message being logged, you'll also want to verify it is of the right level. You can do that using the `WithLevel()` assertion:\n\n```csharp\npublic void GivenLoopWithFiveItems_MessageIsLoggedFiveTimes()\n{\n    /* omitted for brevity */\n\n    InMemorySink.Instance\n        .Should()\n        .HaveMessage(\"Input is {count} characters long\")\n        .Appearing().Once()\n        .WithLevel(LogEventLevel.Information);\n}\n```\n\nThis also works for multiple messages:\n\n```csharp\npublic void GivenLoopWithFiveItems_MessageIsLoggedFiveTimes()\n{\n    logger.Warning(\"Test message\");\n    logger.Warning(\"Test message\");\n    logger.Warning(\"Test message\");\n\n    InMemorySink.Instance\n        .Should()\n        .HaveMessage(\"Test message\")\n        .Appearing().Times(3)\n        .WithLevel(LogEventLevel.Information);\n}\n```\n\nThis will fail with a message: `Expected instances of log message \"Hello, world!\" to have level Information, but found 3 with level Warning`\n\n### Asserting messages with a pattern\n\nInstead of matching on the exact message you can also match on a certain pattern using the `Containing()` assertion:\n\n```csharp\nInMemorySink.Instance\n   .Should()\n   .HaveMessage()\n   .Containing(\"some pattern\")\n   .Appearing().Once();\n```\n\nwhich matches on log messages:\n\n- `this is some pattern`\n- `some pattern in a message`\n- `this is some pattern in a message`\n\n### Asserting messages have been logged at all (or not!)\n\nWhen you want to assert that a message has been logged but don't care about what message you can do that with `HaveMessage` and `Appearing`:\n\n```csharp\nInMemorySink.Instance\n    .Should()\n    .HaveMessage()\n    .Appearing().Times(3); // Expect three messages to be logged\n```\n\nand of course the inverse is also possible when expecting no messages to be logged:\n\n```csharp\nInMemorySink.Instance\n    .Should()\n    .NotHaveMessage();\n```\n\nor that a specific message is not be logged\n\n```csharp\nInMemorySink.Instance\n    .Should()\n    .NotHaveMessage(\"a specific message\");\n```\n\n### Asserting properties on messages\n\nWhen you want to assert that a message has a property you can do that using the `WithProperty` assertion:\n\n```csharp\nInMemorySink.Instance\n    .Should()\n    .HaveMessage(\"Message with {Property}\")\n    .Appearing().Once()\n    .WithProperty(\"Property\");\n```\n\nTo then assert that it has a certain value you would use `WithValue`:\n\n```csharp\nInMemorySink.Instance\n    .Should()\n    .HaveMessage(\"Message with {Property}\")\n    .Appearing().Once()\n    .WithProperty(\"Property\")\n    .WithValue(\"property value\");\n```\n\nAsserting that a message has multiple properties can be accomplished using the `And` constraint:\n\n```csharp\nInMemorySink.Instance\n    .Should()\n    .HaveMessage(\"Message with {Property1} and {Property2}\")\n    .Appearing().Once()\n    .WithProperty(\"Property1\")\n    .WithValue(\"value 1\")\n    .And\n    .WithProperty(\"Property2\")\n    .WithValue(\"value 2\");\n```\n\nWhen you have a log message that appears a number of times and you want to assert that the value of the log property has the expected values you can do that using the `WithValues` assertion:\n\n```csharp\nInMemorySink.Instance\n    .Should()\n    .HaveMessage(\"Message with {Property1} and {Property2}\")\n    .Appearing().Times(3)\n    .WithProperty(\"Property1\")\n    .WithValue(\"value 1\", \"value 2\", \"value 3\")\n```\n\n\u003e **Note:** `WithValue` takes an array of values.\n\nSometimes you might want to use assertions like `BeLessThanOrEqual()` or `HaveLength()` and in those cases `WithValue` is not very helpful.\nInstead you can use `WhichValue\u003cT\u003e()`  to access the value of the log property:\n\n```csharp\nInMemorySink.Instance\n    .Should()\n    .HaveMessage()\n    .Appearing().Once()\n    .WithProperty(\"PropertyOne\")\n    .WhichValue\u003cstring\u003e()\n    .Should()\n    .HaveLength(3);\n```\n\nIf the type of the value of the log property does not match the generic type parameter the `WhichValue\u003cT\u003e` method will throw an exception.\n\n\u003e **Note:** This only works for scalar values. When you pass an object as the property value when logging a message Serilog converts that into a string.\n\n### Asserting a property with a destructured object\n\nIf you use [object destructuring](https://github.com/serilog/serilog/wiki/Structured-Data#preserving-object-structure):\n\n```csharp\nvar someObject = new { Foo = \"bar\", Baz = \"quux\" };\nlogger.Information(\"Hello {@SomeObject}\", someObject);\n```\n\nand want to assert on properties of the _destructured object_ you can use the `HavingADestructuredObject()` assertion like so:\n\n```csharp\nInMemorySink.Instance\n    .Should()\n    .HaveMessage(\"Hello {@SomeObject}\")\n    .Appearing().Once()\n    .WithProperty(\"SomeObject\")\n    .HavingADestructuredObject()\n    .WithProperty(\"Foo\")\n    .WithValue(\"bar\");\n```\n\nWhen the property `SomeObject` doesn't hold a destructured object the assertion will fail with the message: `\"Expected message \"Hello {NotDestructured}\" to have a property \"NotDestructured\" that holds a destructured object but found a scalar value\"`\n\n## Clearing log events between tests\n\nDepending on your test framework and test setup you may want to ensure that the log events captured by the `InMemorySink` are cleared so tests\nare not interfering with eachother. To enable this, the `InMemorySink` implements the [`IDisposable`](https://docs.microsoft.com/en-us/dotnet/api/system.idisposable?view=netstandard-2.0) interface.\nWhen `Dispose()` is called the `LogEvents` collection is cleared.\n\nIt will depend on the test framework or your test if you need this feature. With xUnit this feature is not necessary as it isolates each test in its own instance of the test class which means that they all\nhave their own instance of the `InMemorySink`. MSTest however has a different approach and there you may want to use this feature as follows:\n\n```csharp\n[TestClass]\npublic class WhenDemonstratingDisposableFeature\n{\n    private Logger _logger;\n\n    [TestInitialize]\n    public void Initialize()\n    {\n        _logger?.Dispose();\n\n        _logger = new LoggerConfiguration()\n            .WriteTo.InMemory()\n            .CreateLogger();\n    }\n\n    [TestMethod]\n    public void GivenAFoo_BarIsBlah()\n    {\n        _logger.Information(\"Foo\");\n\n        InMemorySink.Instance\n            .Should()\n            .HaveMessage(\"Foo\");\n    }\n\n    [TestMethod]\n    public void GivenABar_BazIsQuux()\n    {\n        _logger.Information(\"Bar\");\n\n        InMemorySink.Instance\n            .Should()\n            .HaveMessage(\"Bar\");\n    }\n}\n```\n\nthis approach ensures that the `GivenABar_BazIsQuux` does not see any messages logged in a previous test.\n\n## Creating a logger\n\nLoggers are created using a LoggerConfiguration object.\nA default initiation would be as follows:\n\n```csharp\nvar logger = new LoggerConfiguration()\n    .WriteTo.InMemory()\n    .CreateLogger();\n```\n\n### Output templates\n\nText-based sinks use output templates to control formatting. this can be modified through the outputTemplate parameter:\n\n```csharp\nvar logger = new LoggerConfiguration()\n    .WriteTo.InMemory(outputTemplate: \"{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj}{NewLine}{Exception}\")\n    .CreateLogger();\n```\n\nThe default template, shown in the example above, uses built-in properties like `Timestamp` and `Level`. Refer to the [offcial documentation](https://github.com/serilog/serilog/wiki/Configuration-Basics#output-templates) for further configuration and explanation of these properties.\n\n### Minimum level\n\nIn this example only Information level logs and higher will be written to the InMemorySink.\n\n```csharp\nvar logger = new LoggerConfiguration()\n    .WriteTo.InMemory(restrictedToMinimumLevel: Events.LogEventLevel.Information)\n    .CreateLogger();\n\n```\n\n**Default Level** - if no MinimumLevel is specified, then Verbose level events and [higher](https://github.com/serilog/serilog/wiki/Configuration-Basics#minimum-level) will be processed.\n\n### Dynamic levels\n\nIf an app needs dynamic level switching, the first step is to create an instance of LoggingLevelSwitch when the logger is being configured:\n\n```csharp\nvar levelSwitch = new LoggingLevelSwitch();\n```\n\nThis object defaults the current minimum level to Information, so to make logging more restricted, set its minimum level up-front:\n\n```csharp\nlevelSwitch.MinimumLevel = LogEventLevel.Warning;\n```\n\nWhen configuring the logger, provide the switch using MinimumLevel.ControlledBy():\n\n```csharp\nvar log = new LoggerConfiguration()\n    .MinimumLevel.ControlledBy(levelSwitch)\n    .WriteTo.InMemory()\n    .CreateLogger();\n```\n\nNow, events written to the logger will be filtered according to the switch’s MinimumLevel property.\n\nTo turn the level up or down at runtime, perhaps in response to a command sent over the network, change the property:\n\n```csharp\nlevelSwitch.MinimumLevel = LogEventLevel.Verbose;\nlog.Verbose(\"This will now be logged\");\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fserilog-contrib%2Fserilogsinksinmemory","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fserilog-contrib%2Fserilogsinksinmemory","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fserilog-contrib%2Fserilogsinksinmemory/lists"}