├── .editorconfig ├── .gitattributes ├── .gitignore ├── LICENSE ├── README.md ├── azure-pipelines.yml ├── serilog-sinks-xunit.sln ├── src └── Serilog.Sinks.XUnit │ ├── MessageSinkExtensions.cs │ ├── Serilog.Sinks.XUnit.csproj │ ├── Sinks │ └── XUnit │ │ └── TestOutputSink.cs │ ├── TestOutputHelperExtensions.cs │ └── XUnitLoggerConfigurationExtensions.cs └── test └── Serilog.Sinks.XUnit.Tests ├── CodeCoverage.runsettings ├── GlobalSuppressions.cs ├── MessageSinkExtensionsTests.cs ├── SampleFixture.cs ├── SampleTests.cs ├── Serilog.Sinks.XUnit.Tests.csproj ├── TestOutputHelperExtensionsTests.cs ├── TestOutputSinkTests.cs └── xunit.runner.json /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 4 6 | 7 | [*.{yml,yaml,json,csproj}] 8 | indent_size = 2 9 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | 3 | * text=auto -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | bld/ 21 | [Bb]in/ 22 | [Oo]bj/ 23 | 24 | # Visual Studio 2015 cache/options directory 25 | .vs/ 26 | # Uncomment if you have tasks that create the project's static files in wwwroot 27 | #wwwroot/ 28 | 29 | # MSTest test Results 30 | [Tt]est[Rr]esult*/ 31 | [Bb]uild[Ll]og.* 32 | 33 | # NUNIT 34 | *.VisualState.xml 35 | TestResult.xml 36 | 37 | # Build Results of an ATL Project 38 | [Dd]ebugPS/ 39 | [Rr]eleasePS/ 40 | dlldata.c 41 | 42 | # DNX 43 | project.lock.json 44 | artifacts/ 45 | 46 | *_i.c 47 | *_p.c 48 | *_i.h 49 | *.ilk 50 | *.meta 51 | *.obj 52 | *.pch 53 | *.pdb 54 | *.pgc 55 | *.pgd 56 | *.rsp 57 | *.sbr 58 | *.tlb 59 | *.tli 60 | *.tlh 61 | *.tmp 62 | *.tmp_proj 63 | *.log 64 | *.vspscc 65 | *.vssscc 66 | .builds 67 | *.pidb 68 | *.svclog 69 | *.scc 70 | 71 | # Chutzpah Test files 72 | _Chutzpah* 73 | 74 | # Visual C++ cache files 75 | ipch/ 76 | *.aps 77 | *.ncb 78 | *.opendb 79 | *.opensdf 80 | *.sdf 81 | *.cachefile 82 | 83 | # Visual Studio profiler 84 | *.psess 85 | *.vsp 86 | *.vspx 87 | *.sap 88 | 89 | # TFS 2012 Local Workspace 90 | $tf/ 91 | 92 | # Guidance Automation Toolkit 93 | *.gpState 94 | 95 | # ReSharper is a .NET coding add-in 96 | _ReSharper*/ 97 | *.[Rr]e[Ss]harper 98 | *.DotSettings.user 99 | 100 | # JustCode is a .NET coding add-in 101 | .JustCode 102 | 103 | # TeamCity is a build add-in 104 | _TeamCity* 105 | 106 | # DotCover is a Code Coverage Tool 107 | *.dotCover 108 | 109 | # NCrunch 110 | _NCrunch_* 111 | .*crunch*.local.xml 112 | nCrunchTemp_* 113 | 114 | # MightyMoose 115 | *.mm.* 116 | AutoTest.Net/ 117 | 118 | # Web workbench (sass) 119 | .sass-cache/ 120 | 121 | # Installshield output folder 122 | [Ee]xpress/ 123 | 124 | # DocProject is a documentation generator add-in 125 | DocProject/buildhelp/ 126 | DocProject/Help/*.HxT 127 | DocProject/Help/*.HxC 128 | DocProject/Help/*.hhc 129 | DocProject/Help/*.hhk 130 | DocProject/Help/*.hhp 131 | DocProject/Help/Html2 132 | DocProject/Help/html 133 | 134 | # Click-Once directory 135 | publish/ 136 | 137 | # Publish Web Output 138 | *.[Pp]ublish.xml 139 | *.azurePubxml 140 | # TODO: Comment the next line if you want to checkin your web deploy settings 141 | # but database connection strings (with potential passwords) will be unencrypted 142 | *.pubxml 143 | *.publishproj 144 | 145 | # NuGet Packages 146 | *.*nupkg 147 | # The packages folder can be ignored because of Package Restore 148 | **/packages/* 149 | # except build/, which is used as an MSBuild target. 150 | !**/packages/build/ 151 | # Uncomment if necessary however generally it will be regenerated when needed 152 | #!**/packages/repositories.config 153 | # NuGet v3's project.json files produces more ignoreable files 154 | *.nuget.props 155 | *.nuget.targets 156 | 157 | # Microsoft Azure Build Output 158 | csx/ 159 | *.build.csdef 160 | 161 | # Microsoft Azure Emulator 162 | ecf/ 163 | rcf/ 164 | 165 | # Microsoft Azure ApplicationInsights config file 166 | ApplicationInsights.config 167 | 168 | # Windows Store app package directory 169 | AppPackages/ 170 | BundleArtifacts/ 171 | 172 | # Visual Studio cache files 173 | # files ending in .cache can be ignored 174 | *.[Cc]ache 175 | # but keep track of directories ending in .cache 176 | !*.[Cc]ache/ 177 | 178 | # Others 179 | ClientBin/ 180 | ~$* 181 | *~ 182 | *.dbmdl 183 | *.dbproj.schemaview 184 | *.pfx 185 | *.publishsettings 186 | node_modules/ 187 | orleans.codegen.cs 188 | 189 | # RIA/Silverlight projects 190 | Generated_Code/ 191 | 192 | # Backup & report files from converting an old project file 193 | # to a newer Visual Studio version. Backup files are not needed, 194 | # because we have git ;-) 195 | _UpgradeReport_Files/ 196 | Backup*/ 197 | UpgradeLog*.XML 198 | UpgradeLog*.htm 199 | 200 | # SQL Server files 201 | *.mdf 202 | *.ldf 203 | 204 | # Business Intelligence projects 205 | *.rdl.data 206 | *.bim.layout 207 | *.bim_*.settings 208 | 209 | # Microsoft Fakes 210 | FakesAssemblies/ 211 | 212 | # GhostDoc plugin setting file 213 | *.GhostDoc.xml 214 | 215 | # Node.js Tools for Visual Studio 216 | .ntvs_analysis.dat 217 | 218 | # Visual Studio 6 build log 219 | *.plg 220 | 221 | # Visual Studio 6 workspace options file 222 | *.opt 223 | 224 | # Visual Studio LightSwitch build output 225 | **/*.HTMLClient/GeneratedArtifacts 226 | **/*.DesktopClient/GeneratedArtifacts 227 | **/*.DesktopClient/ModelManifest.xml 228 | **/*.Server/GeneratedArtifacts 229 | **/*.Server/ModelManifest.xml 230 | _Pvt_Extensions 231 | 232 | # Paket dependency manager 233 | .paket/paket.exe 234 | 235 | # FAKE - F# Make 236 | .fake/ 237 | 238 | .ionide -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Todd Benning 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://dev.azure.com/benning/Serilog.Sinks.XUnit/_apis/build/status/trbenning.serilog-sinks-xunit?branchName=master)](https://dev.azure.com/benning/Serilog.Sinks.XUnit/_build/latest?definitionId=2&branchName=master) 2 | [![NuGet Version](https://img.shields.io/nuget/v/Serilog.Sinks.XUnit.svg?style=flat)](https://www.nuget.org/packages/Serilog.Sinks.XUnit/) 3 | 4 | # serilog-sinks-xunit 5 | The xunit test output sink for Serilog 6 | 7 | ### What is it? 8 | It's a package that will allow you to use Serilog for test output. 9 | 10 | ### Installation 11 | 12 | ``` 13 | Install-Package Serilog.Sinks.XUnit 14 | ``` 15 | 16 | ### Example usage 17 | ```csharp 18 | using System; 19 | using Xunit; 20 | using Xunit.Abstractions; 21 | 22 | public class Samples 23 | { 24 | ILogger _output; 25 | 26 | public Samples(ITestOutputHelper output) 27 | { 28 | // Pass the ITestOutputHelper object to the TestOutput sink 29 | _output = new LoggerConfiguration() 30 | .MinimumLevel.Verbose() 31 | .WriteTo.TestOutput(output, Events.LogEventLevel.Verbose) 32 | .CreateLogger() 33 | .ForContext(); 34 | } 35 | 36 | [Fact] 37 | public void ExampleUsage() 38 | { 39 | // Use ILogger as you normally would. These messages will show up in the test output 40 | _output.Information("Test output to Serilog!"); 41 | 42 | Action sketchy = () => throw new Exception("I threw up."); 43 | var exception = Record.Exception(sketchy); 44 | 45 | _output.Error(exception, "Here is an error."); 46 | Assert.NotNull(exception); 47 | } 48 | } 49 | ``` 50 | -------------------------------------------------------------------------------- /azure-pipelines.yml: -------------------------------------------------------------------------------- 1 | # Starter pipeline 2 | # Start with a minimal pipeline that you can customize to build and deploy your code. 3 | # Add steps that build, run tests, deploy, and more: 4 | # https://aka.ms/yaml 5 | 6 | name: 3.0$(rev:.r) 7 | 8 | trigger: 9 | branches: 10 | include: 11 | - master 12 | paths: 13 | include: 14 | - src/* 15 | - azure-pipelines.yml 16 | 17 | pool: 18 | vmImage: 'windows-latest' 19 | 20 | variables: 21 | buildConfiguration: 'Release' 22 | verbosity: 'Normal' 23 | 24 | steps: 25 | - task: UseDotNet@2 26 | displayName: 'Install .NET Core SDK 3.1.x' 27 | inputs: 28 | packageType: 'sdk' 29 | version: '3.1.x' 30 | 31 | - task: UseDotNet@2 32 | displayName: 'Install .NET SDK 6.x' 33 | inputs: 34 | packageType: 'sdk' 35 | version: '6.x' 36 | 37 | - task: UseDotNet@2 38 | displayName: 'Install .NET SDK 8.x' 39 | inputs: 40 | packageType: 'sdk' 41 | version: '8.x' 42 | 43 | - task: UseDotNet@2 44 | displayName: 'Install .NET SDK 9.x' 45 | inputs: 46 | packageType: 'sdk' 47 | version: '9.x' 48 | installationPath: $(Agent.ToolsDirectory)/dotnet 49 | 50 | - task: DotNetCoreCLI@2 51 | displayName: 'Restore Dependencies' 52 | inputs: 53 | command: 'restore' 54 | projects: '**/*.csproj' 55 | feedsToUse: 'select' 56 | verbosityRestore: $(verbosity) 57 | 58 | - task: DotNetCoreCLI@2 59 | displayName: 'Run Unit Tests' 60 | condition: succeeded() 61 | inputs: 62 | command: test 63 | projects: 'test/**/*.csproj' 64 | arguments: '--configuration $(BuildConfiguration) --verbosity $(verbosity) --filter "Category!=Sample" --collect "Code Coverage" --settings "test/Serilog.Sinks.XUnit.Tests/CodeCoverage.runsettings"' 65 | publishTestResults: true 66 | 67 | - task: DotNetCoreCLI@2 68 | displayName: 'Build All Projects (master)' 69 | inputs: 70 | command: build 71 | projects: 'src/**/*.csproj' 72 | arguments: '--configuration $(BuildConfiguration) --verbosity $(verbosity)' 73 | condition: and(succeeded(), ne(variables['Build.Reason'], 'PullRequest'), eq(variables['Build.SourceBranch'], 'refs/heads/master')) 74 | 75 | - task: DotNetCoreCLI@2 76 | displayName: 'Create NuGet Packages' 77 | inputs: 78 | command: 'pack' 79 | packagesToPack: 'src/**/*.csproj' 80 | versioningScheme: 'byBuildNumber' 81 | verbosityPack: $(verbosity) 82 | condition: and(succeeded(), ne(variables['Build.Reason'], 'PullRequest'), eq(variables['Build.SourceBranch'], 'refs/heads/master')) 83 | 84 | - task: NuGetCommand@2 85 | inputs: 86 | command: 'push' 87 | packagesToPush: '$(Build.ArtifactStagingDirectory)/**/*.nupkg' 88 | nuGetFeedType: 'external' 89 | publishFeedCredentials: 'NuGet' 90 | verbosityPush: $(verbosity) 91 | condition: and(succeeded(), ne(variables['Build.Reason'], 'PullRequest'), eq(variables['Build.SourceBranch'], 'refs/heads/master')) 92 | -------------------------------------------------------------------------------- /serilog-sinks-xunit.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.28803.452 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{3BC5FFDD-0FC9-47E4-8C4E-81662BD307EC}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{25D521E7-8018-4D27-B7D8-613D98616E49}" 9 | EndProject 10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Serilog.Sinks.XUnit", "src\Serilog.Sinks.XUnit\Serilog.Sinks.XUnit.csproj", "{5B84BDCD-C15C-4FF4-9694-39A4B3794AE1}" 11 | EndProject 12 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Serilog.Sinks.XUnit.Tests", "test\Serilog.Sinks.XUnit.Tests\Serilog.Sinks.XUnit.Tests.csproj", "{7F5A7AF6-9B88-421E-932C-930105461392}" 13 | EndProject 14 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{F9AF5931-719F-435F-8385-F9D92AF58306}" 15 | ProjectSection(SolutionItems) = preProject 16 | .editorconfig = .editorconfig 17 | .gitattributes = .gitattributes 18 | .gitignore = .gitignore 19 | azure-pipelines.yml = azure-pipelines.yml 20 | LICENSE = LICENSE 21 | README.md = README.md 22 | EndProjectSection 23 | EndProject 24 | Global 25 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 26 | Debug|Any CPU = Debug|Any CPU 27 | Release|Any CPU = Release|Any CPU 28 | EndGlobalSection 29 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 30 | {5B84BDCD-C15C-4FF4-9694-39A4B3794AE1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 31 | {5B84BDCD-C15C-4FF4-9694-39A4B3794AE1}.Debug|Any CPU.Build.0 = Debug|Any CPU 32 | {5B84BDCD-C15C-4FF4-9694-39A4B3794AE1}.Release|Any CPU.ActiveCfg = Release|Any CPU 33 | {5B84BDCD-C15C-4FF4-9694-39A4B3794AE1}.Release|Any CPU.Build.0 = Release|Any CPU 34 | {7F5A7AF6-9B88-421E-932C-930105461392}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 35 | {7F5A7AF6-9B88-421E-932C-930105461392}.Debug|Any CPU.Build.0 = Debug|Any CPU 36 | {7F5A7AF6-9B88-421E-932C-930105461392}.Release|Any CPU.ActiveCfg = Release|Any CPU 37 | {7F5A7AF6-9B88-421E-932C-930105461392}.Release|Any CPU.Build.0 = Release|Any CPU 38 | EndGlobalSection 39 | GlobalSection(SolutionProperties) = preSolution 40 | HideSolutionNode = FALSE 41 | EndGlobalSection 42 | GlobalSection(NestedProjects) = preSolution 43 | {5B84BDCD-C15C-4FF4-9694-39A4B3794AE1} = {3BC5FFDD-0FC9-47E4-8C4E-81662BD307EC} 44 | {7F5A7AF6-9B88-421E-932C-930105461392} = {25D521E7-8018-4D27-B7D8-613D98616E49} 45 | EndGlobalSection 46 | GlobalSection(ExtensibilityGlobals) = postSolution 47 | SolutionGuid = {F38B9487-3E48-4839-BB06-72B0E99ECDFA} 48 | EndGlobalSection 49 | EndGlobal 50 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.XUnit/MessageSinkExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace Serilog 2 | { 3 | using System; 4 | using Core; 5 | using Events; 6 | using Formatting; 7 | using Xunit.Abstractions; 8 | 9 | /// 10 | /// Provides extension methods that create Serilog loggers 11 | /// directly from objects. 12 | /// 13 | public static class MessageSinkExtensions 14 | { 15 | /// 16 | /// Initializes a new Serilog logger that writes to xunit's . 17 | /// 18 | /// The that will be written to. 19 | /// The minimum level for 20 | /// events passed through the sink. Ignored when is specified. 21 | /// A message template describing the format used to write to the sink. 22 | /// the default is "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj}{NewLine}{Exception}". 23 | /// Supplies culture-specific formatting information, or null. 24 | /// A switch allowing the pass-through minimum level to be changed at runtime. 25 | /// The Serilog logger that logs to . 26 | /// Thrown when is null. 27 | public static Logger CreateTestLogger( 28 | this IMessageSink messageSink, 29 | LogEventLevel restrictedToMinimumLevel = LevelAlias.Minimum, 30 | string outputTemplate = TestOutputLoggerConfigurationExtensions.DefaultConsoleOutputTemplate, 31 | IFormatProvider formatProvider = null, 32 | LoggingLevelSwitch levelSwitch = null) 33 | { 34 | return new LoggerConfiguration() 35 | .WriteTo.TestOutput( 36 | messageSink, 37 | restrictedToMinimumLevel, 38 | outputTemplate, 39 | formatProvider, 40 | levelSwitch) 41 | .CreateLogger(); 42 | } 43 | 44 | /// 45 | /// Initializes a new Serilog logger that writes to xunit's . 46 | /// 47 | /// The that will be written to. 48 | /// Controls the rendering of log events into text, for example to log JSON. To 49 | /// control plain text formatting, use the overload that accepts an output template. 50 | /// The minimum level for 51 | /// events passed through the sink. Ignored when is specified. 52 | /// A switch allowing the pass-through minimum level 53 | /// to be changed at runtime. 54 | /// The Serilog logger that logs to . 55 | /// Thrown when 56 | /// or is null. 57 | public static Logger CreateTestLogger( 58 | this IMessageSink messageSink, 59 | ITextFormatter formatter, 60 | LogEventLevel restrictedToMinimumLevel = LevelAlias.Minimum, 61 | LoggingLevelSwitch levelSwitch = null) 62 | { 63 | return new LoggerConfiguration() 64 | .WriteTo.TestOutput( 65 | messageSink, 66 | formatter, 67 | restrictedToMinimumLevel, 68 | levelSwitch) 69 | .CreateLogger(); 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.XUnit/Serilog.Sinks.XUnit.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | The xUnit test output sink for Serilog 5 | 1.0.0 6 | $(BUILD_BUILDNUMBER) 7 | Serilog.Sinks.XUnit 8 | Serilog.Sinks.XUnit 9 | logging Serilog xUnit output ITestOutputHelper 10 | Copyright © 2017 Todd Benning 11 | Todd Benning 12 | net462;netstandard2.0;net8.0;net9.0 13 | true 14 | true 15 | true 16 | snupkg 17 | true 18 | MIT 19 | https://github.com/trbenning/serilog-sinks-xunit 20 | https://github.com/trbenning/serilog-sinks-xunit 21 | false 22 | 23 | 24 | 25 | true 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.XUnit/Sinks/XUnit/TestOutputSink.cs: -------------------------------------------------------------------------------- 1 | namespace Serilog.Sinks.XUnit 2 | { 3 | using System; 4 | using System.IO; 5 | using Core; 6 | using Events; 7 | using Formatting; 8 | using Xunit.Abstractions; 9 | using Xunit.Sdk; 10 | 11 | /// 12 | /// A sink to direct Serilog output to the XUnit test output 13 | /// 14 | public class TestOutputSink : ILogEventSink 15 | { 16 | private readonly IMessageSink _messageSink; 17 | private readonly ITestOutputHelper _testOutputHelper; 18 | private readonly ITextFormatter _textFormatter; 19 | 20 | /// 21 | /// Creates a new instance of 22 | /// 23 | /// An implementation that can be used to provide test output 24 | /// The used when rendering the message 25 | public TestOutputSink(IMessageSink messageSink, ITextFormatter textFormatter) 26 | { 27 | _messageSink = messageSink ?? throw new ArgumentNullException(nameof(messageSink)); 28 | _textFormatter = textFormatter ?? throw new ArgumentNullException(nameof(textFormatter)); 29 | } 30 | 31 | /// 32 | /// Creates a new instance of 33 | /// 34 | /// An implementation that can be used to provide test output 35 | /// The used when rendering the message 36 | public TestOutputSink(ITestOutputHelper testOutputHelper, ITextFormatter textFormatter) 37 | { 38 | _testOutputHelper = testOutputHelper ?? throw new ArgumentNullException(nameof(testOutputHelper)); 39 | _textFormatter = textFormatter ?? throw new ArgumentNullException(nameof(textFormatter)); 40 | } 41 | 42 | /// 43 | /// Emits the provided log event from a sink 44 | /// 45 | /// The event being logged 46 | public void Emit(LogEvent logEvent) 47 | { 48 | if (logEvent == null) throw new ArgumentNullException(nameof(logEvent)); 49 | 50 | var renderSpace = new StringWriter(); 51 | _textFormatter.Format(logEvent, renderSpace); 52 | var message = renderSpace.ToString().Trim(); 53 | _messageSink?.OnMessage(new DiagnosticMessage(message)); 54 | _testOutputHelper?.WriteLine(message); 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.XUnit/TestOutputHelperExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace Serilog 2 | { 3 | using System; 4 | using Core; 5 | using Events; 6 | using Formatting; 7 | using Xunit.Abstractions; 8 | 9 | /// 10 | /// Provides extension methods that create Serilog loggers 11 | /// directly from objects. 12 | /// 13 | public static class TestOutputHelperExtensions 14 | { 15 | /// 16 | /// Initializes a new Serilog logger that writes to xunit's . 17 | /// 18 | /// The that will be written to. 19 | /// The minimum level for 20 | /// events passed through the sink. Ignored when is specified. 21 | /// A message template describing the format used to write to the sink. 22 | /// the default is "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj}{NewLine}{Exception}". 23 | /// Supplies culture-specific formatting information, or null. 24 | /// A switch allowing the pass-through minimum level to be changed at runtime. 25 | /// The Serilog logger that logs to . 26 | /// Thrown when is null. 27 | public static Logger CreateTestLogger( 28 | this ITestOutputHelper testOutputHelper, 29 | LogEventLevel restrictedToMinimumLevel = LevelAlias.Minimum, 30 | string outputTemplate = TestOutputLoggerConfigurationExtensions.DefaultConsoleOutputTemplate, 31 | IFormatProvider formatProvider = null, 32 | LoggingLevelSwitch levelSwitch = null) 33 | { 34 | return new LoggerConfiguration() 35 | .WriteTo.TestOutput( 36 | testOutputHelper, 37 | restrictedToMinimumLevel, 38 | outputTemplate, 39 | formatProvider, 40 | levelSwitch) 41 | .CreateLogger(); 42 | } 43 | 44 | /// 45 | /// Initializes a new Serilog logger that writes to xunit's . 46 | /// 47 | /// The that will be written to. 48 | /// Controls the rendering of log events into text, for example to log JSON. To 49 | /// control plain text formatting, use the overload that accepts an output template. 50 | /// The minimum level for 51 | /// events passed through the sink. Ignored when is specified. 52 | /// A switch allowing the pass-through minimum level 53 | /// to be changed at runtime. 54 | /// The Serilog logger that logs to . 55 | /// Thrown when 56 | /// or is null. 57 | public static Logger CreateTestLogger( 58 | this ITestOutputHelper testOutputHelper, 59 | ITextFormatter formatter, 60 | LogEventLevel restrictedToMinimumLevel = LevelAlias.Minimum, 61 | LoggingLevelSwitch levelSwitch = null) 62 | { 63 | return new LoggerConfiguration() 64 | .WriteTo.TestOutput( 65 | testOutputHelper, 66 | formatter, 67 | restrictedToMinimumLevel, 68 | levelSwitch) 69 | .CreateLogger(); 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.XUnit/XUnitLoggerConfigurationExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace Serilog 2 | { 3 | using System; 4 | using Configuration; 5 | using Core; 6 | using Events; 7 | using Formatting; 8 | using Formatting.Display; 9 | using Sinks.XUnit; 10 | using Xunit.Abstractions; 11 | 12 | /// 13 | /// Adds the WriteTo.TestOutput() extension method to . 14 | /// 15 | public static class TestOutputLoggerConfigurationExtensions 16 | { 17 | internal const string DefaultConsoleOutputTemplate = "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj}{NewLine}{Exception}"; 18 | 19 | /// 20 | /// Writes log events to . 21 | /// 22 | /// Logger sink configuration. 23 | /// The that will be written to. 24 | /// The minimum level for 25 | /// events passed through the sink. Ignored when is specified. 26 | /// A message template describing the format used to write to the sink. 27 | /// the default is "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj}{NewLine}{Exception}". 28 | /// Supplies culture-specific formatting information, or null. 29 | /// A switch allowing the pass-through minimum level to be changed at runtime. 30 | /// Configuration object allowing method chaining. 31 | public static LoggerConfiguration TestOutput( 32 | this LoggerSinkConfiguration sinkConfiguration, 33 | IMessageSink messageSink, 34 | LogEventLevel restrictedToMinimumLevel = LevelAlias.Minimum, 35 | string outputTemplate = DefaultConsoleOutputTemplate, 36 | IFormatProvider formatProvider = null, 37 | LoggingLevelSwitch levelSwitch = null) 38 | { 39 | if (sinkConfiguration == null) throw new ArgumentNullException(nameof(sinkConfiguration)); 40 | if (messageSink == null) throw new ArgumentNullException(nameof(messageSink)); 41 | 42 | var formatter = new MessageTemplateTextFormatter(outputTemplate, formatProvider); 43 | 44 | return sinkConfiguration.Sink(new TestOutputSink(messageSink, formatter), restrictedToMinimumLevel, levelSwitch); 45 | } 46 | 47 | /// 48 | /// Writes log events to . 49 | /// 50 | /// Logger sink configuration. 51 | /// The that will be written to. 52 | /// Controls the rendering of log events into text, for example to log JSON. To 53 | /// control plain text formatting, use the overload that accepts an output template. 54 | /// The minimum level for 55 | /// events passed through the sink. Ignored when is specified. 56 | /// A switch allowing the pass-through minimum level 57 | /// to be changed at runtime. 58 | /// Configuration object allowing method chaining. 59 | public static LoggerConfiguration TestOutput( 60 | this LoggerSinkConfiguration sinkConfiguration, 61 | IMessageSink messageSink, 62 | ITextFormatter formatter, 63 | LogEventLevel restrictedToMinimumLevel = LevelAlias.Minimum, 64 | LoggingLevelSwitch levelSwitch = null) 65 | { 66 | if (sinkConfiguration == null) throw new ArgumentNullException(nameof(sinkConfiguration)); 67 | if (formatter == null) throw new ArgumentNullException(nameof(formatter)); 68 | 69 | return sinkConfiguration.Sink(new TestOutputSink(messageSink, formatter), restrictedToMinimumLevel, levelSwitch); 70 | } 71 | 72 | /// 73 | /// Writes log events to . 74 | /// 75 | /// Logger sink configuration. 76 | /// The that will be written to. 77 | /// The minimum level for 78 | /// events passed through the sink. Ignored when is specified. 79 | /// A message template describing the format used to write to the sink. 80 | /// the default is "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj}{NewLine}{Exception}". 81 | /// Supplies culture-specific formatting information, or null. 82 | /// A switch allowing the pass-through minimum level to be changed at runtime. 83 | /// Configuration object allowing method chaining. 84 | public static LoggerConfiguration TestOutput( 85 | this LoggerSinkConfiguration sinkConfiguration, 86 | ITestOutputHelper testOutputHelper, 87 | LogEventLevel restrictedToMinimumLevel = LevelAlias.Minimum, 88 | string outputTemplate = DefaultConsoleOutputTemplate, 89 | IFormatProvider formatProvider = null, 90 | LoggingLevelSwitch levelSwitch = null) 91 | { 92 | if (sinkConfiguration == null) throw new ArgumentNullException(nameof(sinkConfiguration)); 93 | if (testOutputHelper == null) throw new ArgumentNullException(nameof(testOutputHelper)); 94 | 95 | var formatter = new MessageTemplateTextFormatter(outputTemplate, formatProvider); 96 | 97 | return sinkConfiguration.Sink(new TestOutputSink(testOutputHelper, formatter), restrictedToMinimumLevel, levelSwitch); 98 | } 99 | 100 | /// 101 | /// Writes log events to . 102 | /// 103 | /// Logger sink configuration. 104 | /// The that will be written to. 105 | /// Controls the rendering of log events into text, for example to log JSON. To 106 | /// control plain text formatting, use the overload that accepts an output template. 107 | /// The minimum level for 108 | /// events passed through the sink. Ignored when is specified. 109 | /// A switch allowing the pass-through minimum level 110 | /// to be changed at runtime. 111 | /// Configuration object allowing method chaining. 112 | public static LoggerConfiguration TestOutput( 113 | this LoggerSinkConfiguration sinkConfiguration, 114 | ITestOutputHelper testOutputHelper, 115 | ITextFormatter formatter, 116 | LogEventLevel restrictedToMinimumLevel = LevelAlias.Minimum, 117 | LoggingLevelSwitch levelSwitch = null) 118 | { 119 | if (sinkConfiguration == null) throw new ArgumentNullException(nameof(sinkConfiguration)); 120 | if (formatter == null) throw new ArgumentNullException(nameof(formatter)); 121 | 122 | return sinkConfiguration.Sink(new TestOutputSink(testOutputHelper, formatter), restrictedToMinimumLevel, levelSwitch); 123 | } 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /test/Serilog.Sinks.XUnit.Tests/CodeCoverage.runsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | .*\Serilog.Sinks.XUnit.dll 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /test/Serilog.Sinks.XUnit.Tests/GlobalSuppressions.cs: -------------------------------------------------------------------------------- 1 | // This file is used by Code Analysis to maintain SuppressMessage 2 | // attributes that are applied to this project. 3 | // Project-level suppressions either have no target or are given 4 | // a specific target and scoped to a namespace, type, member, etc. 5 | 6 | [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Redundancies in Symbol Declarations", "RECS0154:Parameter is never used", Justification = "", Scope = "type", Target = "~T:AutoGeneratedProgram")] 7 | -------------------------------------------------------------------------------- /test/Serilog.Sinks.XUnit.Tests/MessageSinkExtensionsTests.cs: -------------------------------------------------------------------------------- 1 | namespace Serilog.Sinks.XUnit.Tests 2 | { 3 | using System; 4 | using System.IO; 5 | using Core; 6 | using Events; 7 | using FluentAssertions; 8 | using Formatting; 9 | using NSubstitute; 10 | using Xunit; 11 | using Xunit.Abstractions; 12 | using Xunit.Sdk; 13 | 14 | public static class MessageSinkExtensionsTests 15 | { 16 | [Fact] 17 | public static void CreateTestLogger_WithDefaultParameters() 18 | { 19 | var outputMock = Substitute.For(); 20 | var logger = outputMock.CreateTestLogger(); 21 | 22 | const string message = "This is a test message"; 23 | logger.Information(message); 24 | 25 | outputMock.Received(1).OnMessage(Arg.Is(dm => dm.Message.Contains($" INF] {message}"))); 26 | } 27 | 28 | [Fact] 29 | public static void CreateTestLogger_WithCustomMessageTemplate() 30 | { 31 | var outputMock = Substitute.For(); 32 | var logger = outputMock.CreateTestLogger(outputTemplate: "Game of Thrones {Level}: {Message}"); 33 | 34 | const string message = "That's what I do. I drink and I know things."; 35 | logger.Warning(message); 36 | 37 | outputMock.Received(1).OnMessage(Arg.Is(dm => dm.Message.Equals($"Game of Thrones Warning: {message}"))); 38 | } 39 | 40 | [Fact] 41 | public static void CreateTestLogger_WithCustomMessageTemplateWithExtraNewline() 42 | { 43 | var outputMock = Substitute.For(); 44 | var logger = outputMock.CreateTestLogger(outputTemplate: "Game of Thrones {Level}: {Message}{NewLine}{Exception}"); 45 | 46 | const string message = "That's what I do. I drink and I know things."; 47 | logger.Warning(message); 48 | 49 | outputMock.Received(1).OnMessage(Arg.Is(dm => dm.Message.Equals($"Game of Thrones Warning: {message}"))); 50 | } 51 | 52 | [Fact] 53 | public static void CreateTestLogger_WithCustomMinimumLogLevel() 54 | { 55 | var outputMock = Substitute.For(); 56 | var logger = outputMock.CreateTestLogger(LogEventLevel.Error); 57 | 58 | const string message = "Foo"; 59 | logger.Debug(message); 60 | logger.Information(message); 61 | logger.Warning(message); 62 | 63 | outputMock.DidNotReceive().OnMessage(Arg.Any()); 64 | } 65 | 66 | [Fact] 67 | public static void CreateTestLogger_WithCustomSwitch() 68 | { 69 | var outputMock = Substitute.For(); 70 | var levelSwitch = new LoggingLevelSwitch(LogEventLevel.Warning); 71 | var logger = outputMock.CreateTestLogger(levelSwitch: levelSwitch); 72 | 73 | const string message = "Bar"; 74 | logger.Verbose(message); 75 | logger.Debug(message); 76 | logger.Information(message); 77 | outputMock.DidNotReceive().OnMessage(Arg.Any()); 78 | 79 | levelSwitch.MinimumLevel = LogEventLevel.Information; 80 | 81 | logger.Information(message); 82 | 83 | outputMock.Received(1).OnMessage(Arg.Is(dm => dm.Message.Contains(message))); 84 | } 85 | 86 | [Fact] 87 | public static void CreateTestLogger_WithCustomTextFormatter() 88 | { 89 | var outputMock = Substitute.For(); 90 | var textFormatter = Substitute.For(); 91 | var logger = outputMock.CreateTestLogger(textFormatter); 92 | 93 | const string message = "Baz"; 94 | logger.Error(message); 95 | 96 | textFormatter.Received(1).Format( 97 | Arg.Is(logEvent => logEvent.Level == LogEventLevel.Error && logEvent.MessageTemplate.Text == message), 98 | Arg.Any()); 99 | } 100 | 101 | [Fact] 102 | public static void CreateTestLogger_ShouldThrowIfMessageSinkIsNull() 103 | { 104 | Action act = () => default(IMessageSink).CreateTestLogger(); 105 | 106 | act.Should().Throw() 107 | .And.ParamName.Should().Be("messageSink"); 108 | } 109 | 110 | [Fact] 111 | public static void CreateTestLogger_ShouldThrowIfFormatterIsNull() 112 | { 113 | Action act = () => Substitute.For().CreateTestLogger(default(ITextFormatter)); 114 | 115 | act.Should().Throw() 116 | .And.ParamName.Should().Be("formatter"); 117 | } 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /test/Serilog.Sinks.XUnit.Tests/SampleFixture.cs: -------------------------------------------------------------------------------- 1 | namespace Serilog.Sinks.XUnit.Tests 2 | { 3 | using System; 4 | using System.Threading.Tasks; 5 | using Xunit; 6 | using Xunit.Abstractions; 7 | 8 | public sealed class SampleFixture : IDisposable, IAsyncLifetime 9 | { 10 | private readonly ILogger _log; 11 | 12 | public SampleFixture(IMessageSink messageSink) 13 | { 14 | // Pass the IMessageSink object to the TestOutput sink 15 | _log = new LoggerConfiguration() 16 | .MinimumLevel.Verbose() 17 | .WriteTo.TestOutput(messageSink, Events.LogEventLevel.Verbose) 18 | .CreateLogger() 19 | .ForContext(); 20 | 21 | _log.Information("Sample fixture constructor called."); 22 | // Check the test output window. You should see the above message. 23 | } 24 | 25 | public void Dispose() 26 | { 27 | _log.Information("Sample fixture dispose called."); 28 | // Check the test output window. You should see the above message. 29 | } 30 | 31 | public Task InitializeAsync() 32 | { 33 | _log.Information("Sample fixture initialize async called."); 34 | // Check the test output window. You should see the above message. 35 | return Task.CompletedTask; 36 | } 37 | 38 | public Task DisposeAsync() 39 | { 40 | _log.Information("Sample fixture dispose async called."); 41 | // Check the test output window. You should see the above message. 42 | return Task.CompletedTask; 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /test/Serilog.Sinks.XUnit.Tests/SampleTests.cs: -------------------------------------------------------------------------------- 1 | namespace Serilog.Sinks.XUnit.Tests 2 | { 3 | using System; 4 | using System.Threading.Tasks; 5 | using Xunit; 6 | using Xunit.Abstractions; 7 | 8 | public sealed class SampleTests : IDisposable, IAsyncLifetime, IClassFixture 9 | { 10 | private readonly ILogger _log; 11 | 12 | public SampleTests(SampleFixture fixture, ITestOutputHelper output) 13 | { 14 | // Pass the ITestOutputHelper object to the TestOutput sink 15 | _log = new LoggerConfiguration() 16 | .MinimumLevel.Verbose() 17 | .WriteTo.TestOutput(output, Events.LogEventLevel.Verbose) 18 | .CreateLogger() 19 | .ForContext(); 20 | 21 | _log.Information("Sample test constructor called."); 22 | // Check the test output window. You should see the above message. 23 | } 24 | 25 | public void Dispose() 26 | { 27 | _log.Information("Sample test dispose called."); 28 | // Check the test output window. You should see the above message. 29 | } 30 | 31 | public Task InitializeAsync() 32 | { 33 | _log.Information("Sample test initialize async called."); 34 | // Check the test output window. You should see the above message. 35 | return Task.CompletedTask; 36 | } 37 | 38 | public Task DisposeAsync() 39 | { 40 | _log.Information("Sample test dispose async called."); 41 | // Check the test output window. You should see the above message. 42 | return Task.CompletedTask; 43 | } 44 | 45 | [Fact] 46 | [Trait("Category", "Sample")] 47 | public void ExampleUsage() 48 | { 49 | // Use ILogger as you normally would. These messages will show up in the test output 50 | _log.Information("Test output to Serilog!"); 51 | 52 | Action sketchy = () => throw new Exception("I threw up."); 53 | var exception = Record.Exception(sketchy); 54 | 55 | _log.Error(exception, "Here is an error."); 56 | Assert.NotNull(exception); 57 | } 58 | 59 | [Fact] 60 | [Trait("Category", "Sample")] 61 | public void VerifyDebugLevelOutput() 62 | { 63 | _log.Debug("Test output to Serilog!"); 64 | // Check the test output window. You should see the above message. 65 | } 66 | 67 | [Fact] 68 | [Trait("Category", "Sample")] 69 | public void VerifyVerboseLevelOutput() 70 | { 71 | _log.Verbose("Test output to Serilog!"); 72 | // Check the test output window. You should see the above message. 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /test/Serilog.Sinks.XUnit.Tests/Serilog.Sinks.XUnit.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net462;netcoreapp3.1;net6.0;net8.0;net9.0 5 | true 6 | false 7 | true 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | all 21 | runtime; build; native; contentfiles; analyzers; buildtransitive 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | PreserveNewest 36 | 37 | 38 | PreserveNewest 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /test/Serilog.Sinks.XUnit.Tests/TestOutputHelperExtensionsTests.cs: -------------------------------------------------------------------------------- 1 | namespace Serilog.Sinks.XUnit.Tests 2 | { 3 | using System; 4 | using System.IO; 5 | using Core; 6 | using Events; 7 | using FluentAssertions; 8 | using Formatting; 9 | using NSubstitute; 10 | using Xunit; 11 | using Xunit.Abstractions; 12 | 13 | public static class TestOutputHelperExtensionsTests 14 | { 15 | [Fact] 16 | public static void CreateTestLogger_WithDefaultParameters() 17 | { 18 | var outputMock = Substitute.For(); 19 | var logger = outputMock.CreateTestLogger(); 20 | 21 | const string message = "This is a test message"; 22 | logger.Information(message); 23 | 24 | outputMock.Received(1).WriteLine(Arg.Is(text => text.Contains($" INF] {message}"))); 25 | } 26 | 27 | [Fact] 28 | public static void CreateTestLogger_WithCustomMessageTemplate() 29 | { 30 | var outputMock = Substitute.For(); 31 | var logger = outputMock.CreateTestLogger(outputTemplate: "Game of Thrones {Level}: {Message}"); 32 | 33 | const string message = "That's what I do. I drink and I know things."; 34 | logger.Warning(message); 35 | 36 | outputMock.Received(1).WriteLine(Arg.Is(text => text.Equals($"Game of Thrones Warning: {message}"))); 37 | } 38 | 39 | [Fact] 40 | public static void CreateTestLogger_WithCustomMessageTemplateWithExtraNewline() 41 | { 42 | var outputMock = Substitute.For(); 43 | var logger = outputMock.CreateTestLogger(outputTemplate: "Game of Thrones {Level}: {Message}{NewLine}{Exception}"); 44 | 45 | const string message = "That's what I do. I drink and I know things."; 46 | logger.Warning(message); 47 | 48 | outputMock.Received(1).WriteLine(Arg.Is(text => text.Equals($"Game of Thrones Warning: {message}"))); 49 | } 50 | 51 | [Fact] 52 | public static void CreateTestLogger_WithCustomMinimumLogLevel() 53 | { 54 | var outputMock = Substitute.For(); 55 | var logger = outputMock.CreateTestLogger(LogEventLevel.Error); 56 | 57 | const string message = "Foo"; 58 | logger.Debug(message); 59 | logger.Information(message); 60 | logger.Warning(message); 61 | 62 | outputMock.DidNotReceive().WriteLine(Arg.Any()); 63 | } 64 | 65 | [Fact] 66 | public static void CreateTestLogger_WithCustomSwitch() 67 | { 68 | var outputMock = Substitute.For(); 69 | var levelSwitch = new LoggingLevelSwitch(LogEventLevel.Warning); 70 | var logger = outputMock.CreateTestLogger(levelSwitch: levelSwitch); 71 | 72 | const string message = "Bar"; 73 | logger.Verbose(message); 74 | logger.Debug(message); 75 | logger.Information(message); 76 | outputMock.DidNotReceive().WriteLine(Arg.Any()); 77 | 78 | levelSwitch.MinimumLevel = LogEventLevel.Information; 79 | 80 | logger.Information(message); 81 | 82 | outputMock.Received(1).WriteLine(Arg.Is(text => text.Contains(message))); 83 | } 84 | 85 | [Fact] 86 | public static void CreateTestLogger_WithCustomTextFormatter() 87 | { 88 | var outputMock = Substitute.For(); 89 | var textFormatter = Substitute.For(); 90 | var logger = outputMock.CreateTestLogger(textFormatter); 91 | 92 | const string message = "Baz"; 93 | logger.Error(message); 94 | 95 | textFormatter.Received(1).Format( 96 | Arg.Is(logEvent => logEvent.Level == LogEventLevel.Error && logEvent.MessageTemplate.Text == message), 97 | Arg.Any()); 98 | } 99 | 100 | [Fact] 101 | public static void CreateTestLogger_ShouldThrowIfTestOutputHelperIsNull() 102 | { 103 | Action act = () => default(ITestOutputHelper).CreateTestLogger(); 104 | 105 | act.Should().Throw() 106 | .And.ParamName.Should().Be("testOutputHelper"); 107 | } 108 | 109 | [Fact] 110 | public static void CreateTestLogger_ShouldThrowIfFormatterIsNull() 111 | { 112 | Action act = () => Substitute.For().CreateTestLogger(default(ITextFormatter)); 113 | 114 | act.Should().Throw() 115 | .And.ParamName.Should().Be("formatter"); 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /test/Serilog.Sinks.XUnit.Tests/TestOutputSinkTests.cs: -------------------------------------------------------------------------------- 1 | namespace Serilog.Sinks.XUnit.Tests 2 | { 3 | using System; 4 | using System.IO; 5 | using Events; 6 | using FluentAssertions; 7 | using Formatting; 8 | using NSubstitute; 9 | using Xunit; 10 | using Xunit.Abstractions; 11 | using Xunit.Sdk; 12 | 13 | public class TestOutputSinkTests 14 | { 15 | [Fact] 16 | public void Constructor_ForMessageSink_ShouldThrowIfMessageSinkIsNull() 17 | { 18 | var ex = Record.Exception(() => new TestOutputSink((IMessageSink)null, Substitute.For())); 19 | ex.Should().BeOfType(); 20 | } 21 | 22 | [Fact] 23 | public void Constructor_ForMessageSink_ShouldThrowIfTextFormatterIsNull() 24 | { 25 | var ex = Record.Exception(() => new TestOutputSink(Substitute.For(), null)); 26 | ex.Should().BeOfType(); 27 | } 28 | 29 | [Fact] 30 | public void Constructor_ForTestOutputHelper_ShouldThrowIfTestOutputHelperIsNull() 31 | { 32 | var ex = Record.Exception(() => new TestOutputSink((ITestOutputHelper)null, Substitute.For())); 33 | ex.Should().BeOfType(); 34 | } 35 | 36 | [Fact] 37 | public void Constructor_ForTestOutputHelper_ShouldThrowIfTextFormatterIsNull() 38 | { 39 | var ex = Record.Exception(() => new TestOutputSink(Substitute.For(), null)); 40 | ex.Should().BeOfType(); 41 | } 42 | 43 | [Fact] 44 | public void Emit_ShouldThrowIfEventIsNull() 45 | { 46 | var underTest = new TestOutputSink( 47 | Substitute.For(), 48 | Substitute.For()); 49 | 50 | var ex = Record.Exception(() => underTest.Emit(null)); 51 | 52 | ex.Should().BeOfType(); 53 | } 54 | 55 | [Fact] 56 | public void Emit_ForMessageSink_ShouldWriteFormattedEventToTestOutput() 57 | { 58 | var messageSink = Substitute.For(); 59 | var formatter = Substitute.For(); 60 | 61 | var logger = new LoggerConfiguration() 62 | .MinimumLevel.Verbose() 63 | .WriteTo.TestOutput(messageSink, formatter) 64 | .CreateLogger(); 65 | 66 | const string message = "Hello"; 67 | logger.Information(message); 68 | 69 | messageSink.Received(1).OnMessage(Arg.Any()); 70 | 71 | formatter.Received(1).Format( 72 | Arg.Is(ev => ev.Level == LogEventLevel.Information && ev.MessageTemplate.Text == message), 73 | Arg.Any()); 74 | } 75 | 76 | [Fact] 77 | public void Emit_ForTestOutputHelper_ShouldWriteFormattedEventToTestOutput() 78 | { 79 | var outputHelper = Substitute.For(); 80 | var formatter = Substitute.For(); 81 | 82 | var logger = new LoggerConfiguration() 83 | .MinimumLevel.Verbose() 84 | .WriteTo.TestOutput(outputHelper, formatter) 85 | .CreateLogger(); 86 | 87 | const string message = "Hello"; 88 | logger.Information(message); 89 | 90 | outputHelper.Received(1).WriteLine(Arg.Any()); 91 | 92 | formatter.Received(1).Format( 93 | Arg.Is(ev => ev.Level == LogEventLevel.Information && ev.MessageTemplate.Text == message), 94 | Arg.Any()); 95 | } 96 | 97 | [Theory] 98 | [InlineData(LogEventLevel.Debug, new[] { LogEventLevel.Verbose })] 99 | [InlineData(LogEventLevel.Information, new[] { LogEventLevel.Verbose, LogEventLevel.Debug })] 100 | [InlineData(LogEventLevel.Warning, new[] { LogEventLevel.Verbose, LogEventLevel.Debug, LogEventLevel.Information })] 101 | [InlineData(LogEventLevel.Error, new[] { LogEventLevel.Verbose, LogEventLevel.Debug, LogEventLevel.Information, LogEventLevel.Warning })] 102 | [InlineData(LogEventLevel.Fatal, new[] { LogEventLevel.Verbose, LogEventLevel.Debug, LogEventLevel.Information, LogEventLevel.Warning, LogEventLevel.Error })] 103 | public void Emit_ShouldHonorTheRestrictedToMinimumLevelParameter(LogEventLevel minLevel, LogEventLevel[] levelsToWrite) 104 | { 105 | var outputHelper = Substitute.For(); 106 | var formatter = Substitute.For(); 107 | 108 | var logger = new LoggerConfiguration() 109 | .MinimumLevel.Verbose() 110 | .WriteTo.TestOutput(outputHelper, formatter, minLevel) 111 | .CreateLogger(); 112 | 113 | const string message = "Hello"; 114 | foreach (var level in levelsToWrite) 115 | { 116 | logger.Write(level, message); 117 | } 118 | 119 | outputHelper.DidNotReceive().WriteLine(Arg.Any()); 120 | 121 | formatter.DidNotReceive().Format( 122 | Arg.Any(), 123 | Arg.Any()); 124 | } 125 | 126 | [Fact] 127 | public void Emit_ShouldWriteDebugEventWhenMinimumLevelSetToDebug() 128 | { 129 | var outputHelper = Substitute.For(); 130 | var formatter = Substitute.For(); 131 | 132 | var logger = new LoggerConfiguration() 133 | .MinimumLevel.Verbose() 134 | .WriteTo.TestOutput(outputHelper, formatter, LogEventLevel.Debug) 135 | .CreateLogger(); 136 | 137 | const string message = "Hello"; 138 | logger.Debug(message); 139 | 140 | outputHelper.Received(1).WriteLine(Arg.Any()); 141 | 142 | formatter.Received(1).Format( 143 | Arg.Is(ev => ev.Level == LogEventLevel.Debug && ev.MessageTemplate.Text == message), 144 | Arg.Any()); 145 | } 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /test/Serilog.Sinks.XUnit.Tests/xunit.runner.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://xunit.github.io/schema/current/xunit.runner.schema.json", 3 | "diagnosticMessages": true 4 | } --------------------------------------------------------------------------------