diff --git a/.github/template-sync.yml b/.github/template-sync.yml
deleted file mode 100644
index 982577b..0000000
--- a/.github/template-sync.yml
+++ /dev/null
@@ -1,34 +0,0 @@
-additional:
- - analyzer
- - arguments
- - article.benchmarks
- - editorconfig
- - equaduct
- - extensions.hosting.winforms
- - extensions.logging.measurement
- - extensions.logging.xunit
- - extensions.strings
- - extensions.tasks
- - extensions.test
- - guard
- - healthchecks
- - http.correlation
- - sequentialguid
-
-files:
- - "!**/*"
- - ".editorconfig"
- - ".gitattributes"
- - ".gitignore"
- - ".github/CODEOWNERS"
- - ".github/dependabot.yml"
- - ".github/FUNDING.yml"
- - ".github/release-drafter.yml"
- - ".github/ISSUE_TEMPLATE/**/*"
- - ".github/PULL_REQUEST_TEMPLATE/**/*"
- - ".github/workflows/update-license.yml"
-
- # you probably want to exclude these files:
- - "!.github/workflows/dependabot-merge.yml"
- - "!.github/workflows/template-sync.yml"
- - "!.github/template-sync.yml"
diff --git a/.github/workflows/cicd.yml b/.github/workflows/cicd.yml
index 39026ab..db637fe 100644
--- a/.github/workflows/cicd.yml
+++ b/.github/workflows/cicd.yml
@@ -24,9 +24,9 @@ jobs:
name: Build & Tests
uses: dailydevops/pipelines/.github/workflows/cicd-dotnet.yml@0.9.3
with:
- disablePublish: true
+ enableSonarQube: true
dotnet-logging: ${{ inputs.dotnet-logging }}
dotnet-version: |
8.x
- solution: ###SOLUTION###
+ solution: ./Logging.XUnit.sln
secrets: inherit
diff --git a/.github/workflows/template-sync.yml b/.github/workflows/template-sync.yml
deleted file mode 100644
index 3d0acf1..0000000
--- a/.github/workflows/template-sync.yml
+++ /dev/null
@@ -1,24 +0,0 @@
-name: Update template files
-
-on:
- push:
- branches:
- - main
- pull_request:
- branches:
- - main
- workflow_dispatch:
-
-jobs:
- template-sync:
- if: github.actor != 'dependabot[bot]' && github.repository == 'dailydevops/dotnet-template'
- runs-on: ubuntu-latest
-
- steps:
- - uses: actions/checkout@v4
-
- - uses: ahmadnassri/action-template-repository-sync@v2.6.0
- with:
- github-token: ${{ secrets.TEMPLATE_SYNC }}
- dry-run: false
- skip-ci: true
diff --git a/.gitmodules b/.gitmodules
index 33c81aa..a898072 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,4 +1,4 @@
[submodule "eng"]
path = eng
url = https://github.com/dailydevops/dotnet-engineering.git
- update = merge
+ update = rebase
diff --git a/Directory.Build.props b/Directory.Build.props
index 2958b68..b3f0ce0 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -9,4 +9,11 @@
+
+ net6.0;net7.0;net8.0
+ net6.0;net7.0;net8.0
+
+ true
+
+
diff --git a/Directory.Packages.props b/Directory.Packages.props
index 78f453b..7ad1b69 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -1,18 +1,31 @@
-
true
true
-
-
+
-
-
+
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Logging.XUnit.sln b/Logging.XUnit.sln
new file mode 100644
index 0000000..5214a1e
--- /dev/null
+++ b/Logging.XUnit.sln
@@ -0,0 +1,81 @@
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.0.31903.59
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{D9954CB8-DB50-4331-A461-36A4AD4DA06E}"
+ ProjectSection(SolutionItems) = preProject
+ .editorconfig = .editorconfig
+ .filenesting.json = .filenesting.json
+ .gitattributes = .gitattributes
+ .gitignore = .gitignore
+ .gitmodules = .gitmodules
+ Directory.Build.props = Directory.Build.props
+ Directory.Build.targets = Directory.Build.targets
+ Directory.Packages.props = Directory.Packages.props
+ Directory.Solution.props = Directory.Solution.props
+ GitVersion.yml = GitVersion.yml
+ LICENSE = LICENSE
+ new-project.ps1 = new-project.ps1
+ nuget.config = nuget.config
+ README.md = README.md
+ update-solution.ps1 = update-solution.ps1
+ EndProjectSection
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{EFE8181B-6FAB-4E04-BD9B-557C1286B858}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{BD750CCE-0318-424D-89B4-9C66EF329E96}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NetEvolve.Logging.XUnit", "src\NetEvolve.Logging.XUnit\NetEvolve.Logging.XUnit.csproj", "{3F3CD6EC-4636-4B58-8400-09AC2B9BAFDF}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NetEvolve.Logging.XUnit.Tests.Unit", "tests\NetEvolve.Logging.XUnit.Tests.Unit\NetEvolve.Logging.XUnit.Tests.Unit.csproj", "{EAAD2C22-48F4-4E37-9A46-0350C6B84388}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NetEvolve.Logging.XUnit.Tests.Integration", "tests\NetEvolve.Logging.XUnit.Tests.Integration\NetEvolve.Logging.XUnit.Tests.Integration.csproj", "{F2B38EDD-63F9-4027-9EA6-D1E0C6387C12}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {3F3CD6EC-4636-4B58-8400-09AC2B9BAFDF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {3F3CD6EC-4636-4B58-8400-09AC2B9BAFDF}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {3F3CD6EC-4636-4B58-8400-09AC2B9BAFDF}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {3F3CD6EC-4636-4B58-8400-09AC2B9BAFDF}.Release|Any CPU.Build.0 = Release|Any CPU
+ {EAAD2C22-48F4-4E37-9A46-0350C6B84388}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {EAAD2C22-48F4-4E37-9A46-0350C6B84388}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {EAAD2C22-48F4-4E37-9A46-0350C6B84388}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {EAAD2C22-48F4-4E37-9A46-0350C6B84388}.Release|Any CPU.Build.0 = Release|Any CPU
+ {F2B38EDD-63F9-4027-9EA6-D1E0C6387C12}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {F2B38EDD-63F9-4027-9EA6-D1E0C6387C12}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {F2B38EDD-63F9-4027-9EA6-D1E0C6387C12}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {F2B38EDD-63F9-4027-9EA6-D1E0C6387C12}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(NestedProjects) = preSolution
+ {3F3CD6EC-4636-4B58-8400-09AC2B9BAFDF} = {EFE8181B-6FAB-4E04-BD9B-557C1286B858}
+ {EAAD2C22-48F4-4E37-9A46-0350C6B84388} = {BD750CCE-0318-424D-89B4-9C66EF329E96}
+ {F2B38EDD-63F9-4027-9EA6-D1E0C6387C12} = {BD750CCE-0318-424D-89B4-9C66EF329E96}
+ EndGlobalSection
+EndGlobal
+CPU.Build.0 = Release|Any CPU
+ {3F3CD6EC-4636-4B58-8400-09AC2B9BAFDF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {3F3CD6EC-4636-4B58-8400-09AC2B9BAFDF}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {3F3CD6EC-4636-4B58-8400-09AC2B9BAFDF}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {3F3CD6EC-4636-4B58-8400-09AC2B9BAFDF}.Release|Any CPU.Build.0 = Release|Any CPU
+ {EAAD2C22-48F4-4E37-9A46-0350C6B84388}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {EAAD2C22-48F4-4E37-9A46-0350C6B84388}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {EAAD2C22-48F4-4E37-9A46-0350C6B84388}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {EAAD2C22-48F4-4E37-9A46-0350C6B84388}.Release|Any CPU.Build.0 = Release|Any CPU
+ {F2B38EDD-63F9-4027-9EA6-D1E0C6387C12}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {F2B38EDD-63F9-4027-9EA6-D1E0C6387C12}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {F2B38EDD-63F9-4027-9EA6-D1E0C6387C12}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {F2B38EDD-63F9-4027-9EA6-D1E0C6387C12}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(NestedProjects) = preSolution
+ {3F3CD6EC-4636-4B58-8400-09AC2B9BAFDF} = {EFE8181B-6FAB-4E04-BD9B-557C1286B858}
+ {EAAD2C22-48F4-4E37-9A46-0350C6B84388} = {BD750CCE-0318-424D-89B4-9C66EF329E96}
+ {F2B38EDD-63F9-4027-9EA6-D1E0C6387C12} = {BD750CCE-0318-424D-89B4-9C66EF329E96}
+ EndGlobalSection
+EndGlobal
diff --git a/README.md b/README.md
index 0e844c8..dbc0860 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,3 @@
-# template-dotnet
-.NET template for repositories
+# NetEvolve.Logging.XUnit
+
+This library provides a logging implementation for [XUnit](https://xunit.net/). When using this library, you have the ability to access the logs generated while executing your tests. This can be useful for debugging purposes.
\ No newline at end of file
diff --git a/new-project.ps1 b/new-project.ps1
index fe28792..bd80458 100644
--- a/new-project.ps1
+++ b/new-project.ps1
@@ -49,7 +49,7 @@ New-Project `
-DisableTests $DisableTests `
-DisableUnitTests $DisableUnitTests `
-DisableIntegrationTests $DisableIntegrationTests `
- -SolutionFile "###SOLUTION###" `
+ -SolutionFile "./Logging.XUnit.sln" `
-OutputDirectory (Get-Location) `
-EnableProjectGrouping $EnableProjectGrouping `
-DisableArchitectureTests $DisableArchitectureTests
diff --git a/new-solution.ps1 b/new-solution.ps1
deleted file mode 100644
index d517277..0000000
--- a/new-solution.ps1
+++ /dev/null
@@ -1,19 +0,0 @@
-[CmdletBinding()]
-param (
- # Name of the solution to be created.
- [Parameter(Mandatory = $true)]
- [string]
- $SolutionName
-)
-
-Write-Output "Updating submodules ..."
-git submodule update --init --recursive --remote | Out-Null
-
-Write-Output "Creating $SolutionName.sln ..."
-$location = Get-Location
-. .\eng\scripts\new-solution.ps1
-
-New-Solution -SolutionName $SolutionName -Output $location
-Remove-Item -Path "$location\new-solution.ps1" -Force
-Remove-Item -Path "$location\.github\template-sync.yml" -Force
-Remove-Item -Path "$location\.github\workflows\template-sync.yml" -Force
diff --git a/src/NetEvolve.Logging.XUnit/IXUnitLoggerOptions.cs b/src/NetEvolve.Logging.XUnit/IXUnitLoggerOptions.cs
new file mode 100644
index 0000000..5fe98ed
--- /dev/null
+++ b/src/NetEvolve.Logging.XUnit/IXUnitLoggerOptions.cs
@@ -0,0 +1,32 @@
+namespace NetEvolve.Logging.XUnit;
+
+///
+/// Accessor for the options of the .
+///
+public interface IXUnitLoggerOptions
+{
+ ///
+ /// Disables the output of the additional information in the log output. Default .
+ ///
+ bool DisableAdditionalInformation { get; }
+
+ ///
+ /// Disable the log level in the log output. Default .
+ ///
+ bool DisableLogLevel { get; }
+
+ ///
+ /// Disable the scopes in the log output. Default .
+ ///
+ bool DisableScopes { get; }
+
+ ///
+ /// Disables the timestamps in the log output. Default .
+ ///
+ bool DisableTimestamp { get; }
+
+ ///
+ /// The format of the timestamp in the log output. Default "o".
+ ///
+ string TimestampFormat { get; }
+}
diff --git a/src/NetEvolve.Logging.XUnit/NetEvolve.Logging.XUnit.csproj b/src/NetEvolve.Logging.XUnit/NetEvolve.Logging.XUnit.csproj
new file mode 100644
index 0000000..2c4ae7a
--- /dev/null
+++ b/src/NetEvolve.Logging.XUnit/NetEvolve.Logging.XUnit.csproj
@@ -0,0 +1,27 @@
+
+
+
+ $(ProjectTargetFrameworks)
+ enable
+ enable
+
+
+
+ $(MSBuildProjectName)
+ Extensions for `ILogger` implementations to log messages to xUnit test output.
+ https://github.com/dailydevops/logging.xunit.git
+ https://github.com/dailydevops/logging.xunit.git
+ logging;provider;xunit
+ 2024
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/NetEvolve.Logging.XUnit/README.md b/src/NetEvolve.Logging.XUnit/README.md
new file mode 100644
index 0000000..54f5861
--- /dev/null
+++ b/src/NetEvolve.Logging.XUnit/README.md
@@ -0,0 +1,67 @@
+# NetEvolve.Logging.XUnit
+
+This library provides a logging implementation for [XUnit](https://xunit.net/). When using this library, you have the ability to access the logs generated while executing your tests. This can be useful for debugging purposes.
+
+## Installation
+```bash
+dotnet add package NetEvolve.Logging.XUnit
+```
+
+## Usage
+
+You can choose to use the `XUnitLogger` class directly or use the `AddXUnit` extension method on the `ILoggingBuilder` instance.
+
+### Direct usage
+
+```csharp
+using Microsoft.Extensions.Logging;
+using NetEvolve.Logging.XUnit;
+using XUnit;
+
+public class ExampleTests
+{
+ private readonly ITestOutputHelper _output;
+
+ public ExampleTests(ITestOutputHelper output)
+ {
+ _output = output;
+ }
+
+ [Fact]
+ public void Test()
+ {
+ var logger = XUnitLogger.CreateLogger(_output);
+
+ // Arrange
+ ...
+ // Act
+ ...
+ // Assert
+ ...
+
+ Assert.NotEmpty(logger.LoggedMessages);
+ }
+}
+```
+
+### Usage with `ILoggingBuilder.AddXUnit`
+
+Or you can use the `AddXUnit` extension method on the `ILoggingBuilder` instance.
+
+```csharp
+using Microsoft.Extensions.Logging;
+using NetEvolve.Logging.XUnit;
+
+var services = new ServiceCollection();
+services.AddLogging(builder =>
+{
+ // Add the XUnit logging implementation
+ builder.AddXUnit();
+
+ // Or alternatively with options
+ builder.AddXUnit(options =>
+ {
+ options.TimestampFormat = "HH:mm:ss.fff";
+ });
+});
+```
\ No newline at end of file
diff --git a/src/NetEvolve.Logging.XUnit/XUnitLogger.cs b/src/NetEvolve.Logging.XUnit/XUnitLogger.cs
new file mode 100644
index 0000000..809cf08
--- /dev/null
+++ b/src/NetEvolve.Logging.XUnit/XUnitLogger.cs
@@ -0,0 +1,312 @@
+namespace NetEvolve.Logging.XUnit;
+
+using System;
+using System.Globalization;
+using System.Text;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Primitives;
+using NetEvolve.Arguments;
+using NetEvolve.Logging.Abstractions;
+using Xunit.Abstractions;
+
+///
+/// Represents a logger that writes messages to xunit output.
+///
+public class XUnitLogger : ILogger, ISupportExternalScope
+{
+ private readonly IXUnitLoggerOptions _options;
+ private readonly ITestOutputHelper _testOutputHelper;
+ private readonly TimeProvider _timeProvider;
+
+ private readonly List _loggedMessages;
+
+ private const int DefaultCapacity = 1024;
+
+ [ThreadStatic]
+ private static StringBuilder? _builder;
+
+ ///
+ /// Gets the external scope provider.
+ ///
+ public IExternalScopeProvider ScopeProvider { get; private set; }
+
+ ///
+ public IReadOnlyList LoggedMessages => _loggedMessages.AsReadOnly();
+
+ ///
+ /// Creates a new instance of .
+ ///
+ /// The to write the log messages to.
+ /// The to use to get the current time.
+ /// The to use to get the current scope.
+ /// The options to control the behavior of the logger.
+ /// A cached or new instance of .
+ public static XUnitLogger CreateLogger(
+ ITestOutputHelper testOutputHelper,
+ TimeProvider timeProvider,
+ IExternalScopeProvider? scopeProvider = null,
+ IXUnitLoggerOptions? options = null
+ )
+ {
+ Argument.ThrowIfNull(testOutputHelper);
+
+ return new XUnitLogger(testOutputHelper, timeProvider, scopeProvider, options);
+ }
+
+ ///
+ /// Creates a new instance of .
+ ///
+ /// The type who's fullname is used as the category name for messages produced by the logger.
+ /// The to write the log messages to.
+ /// The to use to get the current time.
+ /// The to use to get the current scope.
+ /// The options to control the behavior of the logger.
+ /// A cached or new instance of .
+ public static XUnitLogger CreateLogger(
+ ITestOutputHelper testOutputHelper,
+ TimeProvider timeProvider,
+ IExternalScopeProvider? scopeProvider = null,
+ IXUnitLoggerOptions? options = null
+ )
+ where T : notnull =>
+ new XUnitLogger(testOutputHelper, timeProvider, scopeProvider, options);
+
+ private protected XUnitLogger(
+ ITestOutputHelper testOutputHelper,
+ TimeProvider timeProvider,
+ IExternalScopeProvider? scopeProvider,
+ IXUnitLoggerOptions? options
+ )
+ {
+ Argument.ThrowIfNull(testOutputHelper);
+ Argument.ThrowIfNull(timeProvider);
+
+ ScopeProvider = scopeProvider ?? NullExternalScopeProvider.Instance;
+ _testOutputHelper = testOutputHelper;
+ _timeProvider = timeProvider;
+ _options = options ?? XUnitLoggerOptions.Default;
+
+ _loggedMessages = [];
+ }
+
+ ///
+ public IDisposable? BeginScope(TState state)
+ where TState : notnull => ScopeProvider.Push(state);
+
+ ///
+ public bool IsEnabled(LogLevel logLevel) => logLevel != LogLevel.None;
+
+ ///
+ public void Log(
+ LogLevel logLevel,
+ EventId eventId,
+ TState state,
+ Exception? exception,
+ Func formatter
+ )
+ {
+ Argument.ThrowIfNull(formatter);
+
+ if (!IsEnabled(logLevel))
+ {
+ return;
+ }
+
+ var builder = _builder;
+ _builder = null;
+ builder ??= new StringBuilder(DefaultCapacity);
+
+ try
+ {
+ var message = formatter(state, exception);
+ var now = _timeProvider.GetLocalNow();
+ (builder, var scopes) = CreateMessage(
+ logLevel,
+ state,
+ exception,
+ builder,
+ message,
+ now
+ );
+
+ _loggedMessages.Add(
+ new LoggedMessage(now, logLevel, eventId, message, exception, scopes)
+ );
+ _testOutputHelper.WriteLine(builder.ToString());
+ }
+ catch
+ {
+ // Ignore exception.
+ // Unfortunately, this can happen if the process is terminated before the end of the test.
+ }
+ finally
+ {
+ _ = builder.Clear();
+ if (builder.Capacity > DefaultCapacity)
+ {
+ builder.Capacity = DefaultCapacity;
+ }
+ _builder = builder;
+ }
+ }
+
+ private (StringBuilder, List