Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Reqnroll.Microsoft.Extensions.Logging.ReqnrollPlugin #321

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>netstandard2.0;net8.0</TargetFrameworks>
<Nullable>enable</Nullable>
<LangVersion>latest</LangVersion>
<AssemblyOriginatorKeyFile>$(Reqnroll_KeyFile)</AssemblyOriginatorKeyFile>
<SignAssembly>$(Reqnroll_EnableStrongNameSigning)</SignAssembly>
<PublicSign>$(Reqnroll_PublicSign)</PublicSign>

<PackageId>Reqnroll.Microsoft.Extensions.Logging</PackageId>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<NuspecFile>$(MSBuildThisFileDirectory)Reqnroll.Microsoft.Extensions.Logging.nuspec</NuspecFile>

<RootNamespace>Reqnroll.Microsoft.Extensions.Logging</RootNamespace>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\..\Reqnroll\Reqnroll.csproj" />
</ItemGroup>

<ItemGroup Condition="'$(TargetFramework)' == 'net8.0' ">
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.0" />
</ItemGroup>

<ItemGroup Condition="'$(TargetFramework)' != 'net8.0' ">
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="6.0.0" />
<PackageReference Include="Polyfill" Version="7.2.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?xml version="1.0"?>
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata>
<id>Reqnroll.Microsoft.Extensions.Logging</id>
<version>$version$</version>
<title>Reqnroll Microsoft.Extensions.Logging integration plugin</title>
<authors>Stef Heyenrath, $author$</authors>
<owners>Stef Heyenrath, $owner$</owners>
<description>Reqnroll plugin that enables to use Microsoft.Extensions.Logging for writing logging to the IReqnrollOutputHelper.</description>
<summary>Reqnroll plugin that enables to use Microsoft.Extensions.Logging for writing logging to the IReqnrollOutputHelper.</summary>
<language>en-US</language>
<projectUrl>https://www.reqnroll.net</projectUrl>
<repository type="git" url="https://github.com/reqnroll/Reqnroll.git" branch="$branch$" commit="$commit$" />
<icon>images\reqnroll-icon.png</icon>
<copyright>Copyright © Stef Heyenrath, $author$</copyright>
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<license type="expression">BSD-3-Clause</license>
<tags>reqnroll microsoft extensions logging logger</tags>
<dependencies>
<group targetFramework=".NETStandard2.0">
<dependency id="Reqnroll" version="$CompatibilityVersionRange$" />
<dependency id="Microsoft.Extensions.Logging.Abstractions" version="6.0.0" />
</group>
</dependencies>
</metadata>

<files>
<file src="build\**\*" target="build" />
<file src="bin\$config$\netstandard2.0\Reqnroll.Microsoft.Extensions.Logging.ReqnrollPlugin.dll" target="lib\netstandard2.0" />
<file src="bin\$config$\netstandard2.0\Reqnroll.Microsoft.Extensions.Logging.ReqnrollPlugin.pdb" target="lib\netstandard2.0" />

<file src="$SolutionDir$LICENSE" target="LICENSE" />
<file src="$SolutionDir$reqnroll-icon.png" target="images\" />
</files>
</package>
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Extensions.Logging;

namespace Reqnroll.Microsoft.Extensions.Logging;

public class ReqnrollLogger : ILogger
{
private static readonly Dictionary<LogLevel, string> LogLevelStrings = new()
{
{ LogLevel.Trace, nameof(LogLevel.Trace) },
{ LogLevel.Debug, nameof(LogLevel.Debug) },
{ LogLevel.Information, nameof(LogLevel.Information) },
{ LogLevel.Warning, nameof(LogLevel.Warning) },
{ LogLevel.Error, nameof(LogLevel.Error) },
{ LogLevel.Critical, nameof(LogLevel.Critical) }
};

private readonly IReqnrollOutputHelper _outputHelper;
private readonly string? _categoryName;
private readonly ReqnrollLoggerOptions _options;
private readonly LoggerExternalScopeProvider _scopeProvider;

public static ILogger CreateLogger(IReqnrollOutputHelper outputHelper) => new ReqnrollLogger(outputHelper, new LoggerExternalScopeProvider(), string.Empty);

public static ILogger<T> CreateLogger<T>(IReqnrollOutputHelper outputHelper) => new ReqnrollLogger<T>(outputHelper, new LoggerExternalScopeProvider());

public ReqnrollLogger(IReqnrollOutputHelper outputHelper, LoggerExternalScopeProvider scopeProvider, string? categoryName, bool appendScope)
: this(outputHelper, scopeProvider, categoryName, options: new ReqnrollLoggerOptions { IncludeScopes = appendScope })
{
}

public ReqnrollLogger(IReqnrollOutputHelper outputHelper, LoggerExternalScopeProvider scopeProvider, string? categoryName, ReqnrollLoggerOptions? options = null)
{
_outputHelper = outputHelper;
_scopeProvider = scopeProvider;
_categoryName = categoryName;
_options = options ?? new();
}

public bool IsEnabled(LogLevel logLevel) => logLevel != LogLevel.None;

#if NET8_0
public IDisposable? BeginScope<TState>(TState state) where TState : notnull => _scopeProvider.Push(state);
#else
public IDisposable BeginScope<TState>(TState state) => _scopeProvider.Push(state);
#endif

public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter)
{
if (!IsEnabled(logLevel))
{
return;
}

var sb = new StringBuilder();

if (_options.TimestampFormat is not null)
{
var now = _options.UseUtcTimestamp ? DateTimeOffset.UtcNow : DateTimeOffset.Now;
var timestamp = now.ToString(_options.TimestampFormat, _options.Culture);
sb.Append(timestamp).Append(' ');
}

if (_options.IncludeLogLevel)
{
sb.Append(GetLogLevelString(logLevel)).Append(' ');
}

if (_options.IncludeCategory)
{
sb.Append('[').Append(_categoryName).Append("] ");
}

sb.Append(formatter(state, exception));

if (exception is not null)
{
sb.Append('\n').Append(exception);
}

// Append scopes
if (_options.IncludeScopes)
{
_scopeProvider.ForEachScope((scope, st) =>
{
st.Append("\n => ");
st.Append(scope);
}, sb);
}

try
{
_outputHelper.WriteLine(sb.ToString());
}
catch
{
// This can happen when the test is not active
}
}

private static string GetLogLevelString(LogLevel logLevel)
{
if (LogLevelStrings.TryGetValue(logLevel, out string? logLevelString))
{
return logLevelString;
}

throw new ArgumentOutOfRangeException(nameof(logLevel));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;

namespace Reqnroll.Microsoft.Extensions.Logging;

public sealed class ReqnrollLoggerOptions
{
/// <summary>
/// Includes scopes when <see langword="true" />.
/// </summary>
public bool IncludeScopes { get; set; }

/// <summary>
/// Includes category when <see langword="true" />.
/// </summary>
public bool IncludeCategory { get; set; }

/// <summary>
/// Includes log level when <see langword="true" />.
/// </summary>
public bool IncludeLogLevel { get; set; }

/// <summary>
/// Gets or sets format string used to format timestamp in logging messages. Defaults to <see langword="null" />.
/// </summary>
[StringSyntax(StringSyntaxAttribute.DateTimeFormat)]
public string? TimestampFormat { get; set; }

/// <summary>
/// Gets or sets indication whether or not UTC timezone should be used to format timestamps in logging messages. Defaults to <see langword="false" />.
/// </summary>
public bool UseUtcTimestamp { get; set; }

private CultureInfo _culture = CultureInfo.InvariantCulture;

/// <summary>
/// Defines the culture to use when formatting timestamps in logging messages. Defaults to <see cref="CultureInfo.InvariantCulture" />.
/// </summary>
public CultureInfo Culture
{
get => _culture;
set => _culture = value ?? throw new ArgumentNullException(nameof(value), "Culture cannot be set to null.");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using Microsoft.Extensions.Logging;

namespace Reqnroll.Microsoft.Extensions.Logging;

public sealed class ReqnrollLoggerProvider : ILoggerProvider
{
private readonly IReqnrollOutputHelper _outputHelper;
private readonly ReqnrollLoggerOptions _options;
private readonly LoggerExternalScopeProvider _scopeProvider = new();

public ReqnrollLoggerProvider(IReqnrollOutputHelper outputHelper, bool appendScope)
: this(outputHelper, new ReqnrollLoggerOptions { IncludeScopes = appendScope })
{
}

public ReqnrollLoggerProvider(IReqnrollOutputHelper outputHelper, ReqnrollLoggerOptions? options = null)
{
_outputHelper = outputHelper;
_options = options ?? new ReqnrollLoggerOptions();
}

public ILogger CreateLogger(string categoryName)
{
return new ReqnrollLogger(_outputHelper, _scopeProvider, categoryName, _options);
}

public void Dispose()
{
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
using Microsoft.Extensions.Logging;

namespace Reqnroll.Microsoft.Extensions.Logging;

public sealed class ReqnrollLogger<T>(IReqnrollOutputHelper outputHelper, LoggerExternalScopeProvider scopeProvider) :
ReqnrollLogger(outputHelper, scopeProvider, typeof(T).FullName), ILogger<T>;
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<Project ToolsVersion="12.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" TreatAsLocalProperty="TaskFolder;TaskAssembly">
<ItemGroup>
<None Include="$(_Reqnroll_MicrosoftExtensionsLoggingPluginPath)" >
<Link>%(Filename)%(Extension)</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<Visible>False</Visible>
</None>
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<Project ToolsVersion="12.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<_Reqnroll_MicrosoftExtensionsLoggingPluginFramework>netstandard2.0</_Reqnroll_MicrosoftExtensionsLoggingPluginFramework>
<_Reqnroll_MicrosoftExtensionsLoggingPluginPath>$(MSBuildThisFileDirectory)\..\lib\$(_Reqnroll_MicrosoftExtensionsLoggingPluginFramework)\Reqnroll.Microsoft.Extensions.Logging.ReqnrollPlugin.dll</_Reqnroll_MicrosoftExtensionsLoggingPluginPath>
</PropertyGroup>
</Project>
7 changes: 7 additions & 0 deletions Reqnroll.sln
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Resources", "Resources", "{
reqnroll.ico = reqnroll.ico
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Reqnroll.Microsoft.Extensions.Logging.ReqnrollPlugin", "Plugins\Reqnroll.Microsoft.Extensions.Logging.ReqnrollPlugin\Reqnroll.Microsoft.Extensions.Logging.ReqnrollPlugin.csproj", "{812E6CEC-472B-477E-B8FB-D9C8B80701D2}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -243,6 +245,10 @@ Global
{6CBEB31D-5F98-460F-B193-578AEEA78CF9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6CBEB31D-5F98-460F-B193-578AEEA78CF9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6CBEB31D-5F98-460F-B193-578AEEA78CF9}.Release|Any CPU.Build.0 = Release|Any CPU
{812E6CEC-472B-477E-B8FB-D9C8B80701D2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{812E6CEC-472B-477E-B8FB-D9C8B80701D2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{812E6CEC-472B-477E-B8FB-D9C8B80701D2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{812E6CEC-472B-477E-B8FB-D9C8B80701D2}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -281,6 +287,7 @@ Global
{C658B37D-FD36-4868-9070-4EB452FAE526} = {A10B5CD6-38EC-4D7E-9D1C-2EBA8017E437}
{6CBEB31D-5F98-460F-B193-578AEEA78CF9} = {8BE0FE31-6A52-452E-BE71-B8C64A3ED402}
{53475D78-4500-4399-9539-0D5403C13C7A} = {577A0375-1436-446C-802B-3C75C8CEF94F}
{812E6CEC-472B-477E-B8FB-D9C8B80701D2} = {8BE0FE31-6A52-452E-BE71-B8C64A3ED402}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {A4D0636F-0160-4FA5-81A3-9784C7E3B3A4}
Expand Down
3 changes: 3 additions & 0 deletions Reqnroll.sln.DotSettings
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=VS/@EntryIndexedValue">VS</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=MS/@EntryIndexedValue">MS</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateStaticReadonly/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb"&gt;&lt;ExtraRule Prefix="_" Suffix="" Style="aaBb" /&gt;&lt;/Policy&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=15b5b1f1_002D457c_002D4ca6_002Db278_002D5615aedc07d3/@EntryIndexedValue">&lt;Policy&gt;&lt;Descriptor Staticness="Static" AccessRightKinds="Private" Description="Static readonly fields (private)"&gt;&lt;ElementKinds&gt;&lt;Kind Name="READONLY_FIELD" /&gt;&lt;/ElementKinds&gt;&lt;/Descriptor&gt;&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb"&gt;&lt;ExtraRule Prefix="_" Suffix="" Style="aaBb" /&gt;&lt;/Policy&gt;&lt;/Policy&gt;</s:String>
<s:String x:Key="/Default/Environment/Hierarchy/PsiConfigurationSettingsKey/CustomLocation/@EntryValue">C:\Users\awi\AppData\Local\JetBrains\Transient\ReSharperPlatformVs15\v09_95175bdf\SolutionCaches</s:String>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/BRACES_FOR_WHILE/@EntryValue">Required</s:String>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/BRACES_FOR_FOREACH/@EntryValue">Required</s:String>
Expand Down Expand Up @@ -86,6 +87,7 @@
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002ECSharpPlaceAttributeOnSameLineMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateBlankLinesAroundFieldToBlankLinesAroundProperty/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateThisQualifierSettings/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EPredefinedNamingRulesToUserRulesUpgrade/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=D7666E82786F244DB44F5684E3CB95B7/@KeyIndexDefined">True</s:Boolean>
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=D7666E82786F244DB44F5684E3CB95B7/Applicability/=Live/@EntryIndexedValue">True</s:Boolean>
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=D7666E82786F244DB44F5684E3CB95B7/Description/@EntryValue">Arrange Act Assert</s:String>
Expand All @@ -100,6 +102,7 @@
&#xD;
//ASSERT</s:String>
<s:Boolean x:Key="/Default/UserDictionary/Words/=analytics/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Heyenrath/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=protobuf/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Remotable/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Reqnroll/@EntryIndexedValue">True</s:Boolean>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
using Microsoft.Extensions.Logging;
using Moq;
using Reqnroll.Microsoft.Extensions.Logging;
using Xunit;

namespace Reqnroll.PluginTests.Microsoft.Extensions.Logging;

public class MicrosoftExtensionsLoggingTests
{
private readonly Mock<IReqnrollOutputHelper> _outputHelperMock = new();

[Fact]
public void ReqnrollLogger_LogWithLevelNone_ShouldNotCallWriteLine()
{
// Arrange
var sut = new ReqnrollLogger(_outputHelperMock.Object, new LoggerExternalScopeProvider(), "TestCategory");

// Act
sut.Log(LogLevel.None, new EventId(1), "test", null, (s, _) => s.ToString());

// Verify
_outputHelperMock.VerifyNoOtherCalls();
}

[Theory]
[InlineData(LogLevel.Trace, "Trace test")]
[InlineData(LogLevel.Debug, "Debug test")]
[InlineData(LogLevel.Information, "Information test")]
[InlineData(LogLevel.Warning, "Warning test")]
[InlineData(LogLevel.Error, "Error test")]
[InlineData(LogLevel.Critical, "Critical test")]
public void ReqnrollLogger_WhenIncludeLogLevel_ShouldIncludeTheLogLevel(LogLevel level, string expectedMessage)
{
// Arrange
var options = new ReqnrollLoggerOptions { IncludeLogLevel = true };
var sut = new ReqnrollLogger(_outputHelperMock.Object, new LoggerExternalScopeProvider(), "TestCategory", options);

// Act
sut.Log(level, new EventId(1), "test", null, (s, _) => s.ToString());

// Verify
_outputHelperMock.Verify(o => o.WriteLine(expectedMessage), Times.Once);
_outputHelperMock.VerifyNoOtherCalls();
}
}
1 change: 1 addition & 0 deletions Tests/Reqnroll.PluginTests/Reqnroll.PluginTests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
<ProjectReference Include="..\..\Plugins\Reqnroll.Microsoft.Extensions.DependencyInjection.ReqnrollPlugin\Reqnroll.Microsoft.Extensions.DependencyInjection.ReqnrollPlugin.csproj" />
<ProjectReference Include="..\..\Plugins\Reqnroll.Autofac.ReqnrollPlugin\Reqnroll.Autofac.ReqnrollPlugin.csproj" />
<ProjectReference Include="..\..\Plugins\Reqnroll.ExternalData\Reqnroll.ExternalData.ReqnrollPlugin\Reqnroll.ExternalData.ReqnrollPlugin.csproj" />
<ProjectReference Include="..\..\Plugins\Reqnroll.Microsoft.Extensions.Logging.ReqnrollPlugin\Reqnroll.Microsoft.Extensions.Logging.ReqnrollPlugin.csproj" />
<ProjectReference Include="..\..\Plugins\Reqnroll.Windsor.ReqnrollPlugin\Reqnroll.Windsor.ReqnrollPlugin.csproj" />
<ProjectReference Include="..\..\Plugins\Reqnroll.MSTest.ReqnrollPlugin\Reqnroll.MSTest.ReqnrollPlugin.csproj" />
<ProjectReference Include="..\..\Plugins\Reqnroll.NUnit.ReqnrollPlugin\Reqnroll.NUnit.ReqnrollPlugin.csproj" />
Expand Down