Skip to content

Commit 6bf2e35

Browse files
committed
Feat: CodeOfChaos.Extensions.Serilog
1 parent 0bfe160 commit 6bf2e35

File tree

8 files changed

+292
-1
lines changed

8 files changed

+292
-1
lines changed

CodeOfChaos.Extensions.sln

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CodeOfChaos.Extensions.AspN
2020
EndProject
2121
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests.CodeOfChaos.Extensions.AspNetCore", "tests\Tests.CodeOfChaos.Extensions.AspNetCore\Tests.CodeOfChaos.Extensions.AspNetCore.csproj", "{BC0AB42E-28A5-47FC-9017-1191C6899645}"
2222
EndProject
23+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CodeOfChaos.Extensions.Serilog", "src\CodeOfChaos.Extensions.Serilog\CodeOfChaos.Extensions.Serilog.csproj", "{DCFDADB7-06BE-49BB-A71F-3124A48B0ECF}"
24+
EndProject
25+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests.CodeOfChaos.Extensions.Serilog", "tests\Tests.CodeOfChaos.Extensions.Serilog\Tests.CodeOfChaos.Extensions.Serilog.csproj", "{8670FBAC-E420-4DC6-82B1-AF0C5BF7F797}"
26+
EndProject
2327
Global
2428
GlobalSection(SolutionConfigurationPlatforms) = preSolution
2529
Debug|Any CPU = Debug|Any CPU
@@ -54,6 +58,14 @@ Global
5458
{BC0AB42E-28A5-47FC-9017-1191C6899645}.Debug|Any CPU.Build.0 = Debug|Any CPU
5559
{BC0AB42E-28A5-47FC-9017-1191C6899645}.Release|Any CPU.ActiveCfg = Release|Any CPU
5660
{BC0AB42E-28A5-47FC-9017-1191C6899645}.Release|Any CPU.Build.0 = Release|Any CPU
61+
{DCFDADB7-06BE-49BB-A71F-3124A48B0ECF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
62+
{DCFDADB7-06BE-49BB-A71F-3124A48B0ECF}.Debug|Any CPU.Build.0 = Debug|Any CPU
63+
{DCFDADB7-06BE-49BB-A71F-3124A48B0ECF}.Release|Any CPU.ActiveCfg = Release|Any CPU
64+
{DCFDADB7-06BE-49BB-A71F-3124A48B0ECF}.Release|Any CPU.Build.0 = Release|Any CPU
65+
{8670FBAC-E420-4DC6-82B1-AF0C5BF7F797}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
66+
{8670FBAC-E420-4DC6-82B1-AF0C5BF7F797}.Debug|Any CPU.Build.0 = Debug|Any CPU
67+
{8670FBAC-E420-4DC6-82B1-AF0C5BF7F797}.Release|Any CPU.ActiveCfg = Release|Any CPU
68+
{8670FBAC-E420-4DC6-82B1-AF0C5BF7F797}.Release|Any CPU.Build.0 = Release|Any CPU
5769
EndGlobalSection
5870
GlobalSection(NestedProjects) = preSolution
5971
{26284571-0E09-4BAF-8C2B-DF87DCC1BA0B} = {8DD280D4-1E14-4D5E-AFE6-58DD8F079DCC}
@@ -63,5 +75,7 @@ Global
6375
{0A198DE2-E404-4BC4-9C6C-A4E1B4397463} = {8DD280D4-1E14-4D5E-AFE6-58DD8F079DCC}
6476
{53BD8191-6E89-4E6D-AD32-5613ED73C422} = {197E72AD-DEAB-4350-AFC3-A3BB38720BF5}
6577
{BC0AB42E-28A5-47FC-9017-1191C6899645} = {8DD280D4-1E14-4D5E-AFE6-58DD8F079DCC}
78+
{DCFDADB7-06BE-49BB-A71F-3124A48B0ECF} = {197E72AD-DEAB-4350-AFC3-A3BB38720BF5}
79+
{8670FBAC-E420-4DC6-82B1-AF0C5BF7F797} = {8DD280D4-1E14-4D5E-AFE6-58DD8F079DCC}
6680
EndGlobalSection
6781
EndGlobal
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net9.0</TargetFramework>
5+
<ImplicitUsings>enable</ImplicitUsings>
6+
<Nullable>enable</Nullable>
7+
</PropertyGroup>
8+
9+
<ItemGroup>
10+
<PackageReference Include="JetBrains.Annotations" Version="2024.3.0" />
11+
<PackageReference Include="Serilog" Version="4.2.0" />
12+
</ItemGroup>
13+
14+
</Project>
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// ---------------------------------------------------------------------------------------------------------------------
2+
// Imports
3+
// ---------------------------------------------------------------------------------------------------------------------
4+
namespace CodeOfChaos.Extensions.Serilog;
5+
6+
// ---------------------------------------------------------------------------------------------------------------------
7+
// Code
8+
// ---------------------------------------------------------------------------------------------------------------------
9+
public class ExitApplicationException(int exitCode, string msg) : Exception(msg) {
10+
public int ExitCode { get; } = exitCode;
11+
}
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
// ---------------------------------------------------------------------------------------------------------------------
2+
// Imports
3+
// ---------------------------------------------------------------------------------------------------------------------
4+
using JetBrains.Annotations;
5+
using Serilog;
6+
using Serilog.Core;
7+
using System.Diagnostics.CodeAnalysis;
8+
9+
namespace CodeOfChaos.Extensions.Serilog;
10+
11+
// ---------------------------------------------------------------------------------------------------------------------
12+
// Code
13+
// ---------------------------------------------------------------------------------------------------------------------
14+
15+
/// <summary>
16+
/// Provides extension methods for the <see cref="ILogger" /> interface.
17+
/// </summary>
18+
public static class LoggerExtensions {
19+
/// <summary>
20+
/// Throws a Error log message, logs the exception, and throws it.
21+
/// </summary>
22+
/// <param name="logger">The logger instance.</param>
23+
/// <param name="messageTemplate">The message template.</param>
24+
/// <param name="propertyValues">The property values.</param>
25+
/// <exception cref="Exception">Thrown exception.</exception>
26+
[MessageTemplateFormatMethod("messageTemplate")]
27+
public static Exception ThrowableError(this ILogger logger, string messageTemplate, params object?[]? propertyValues) {
28+
var exception = (Exception)Activator.CreateInstance(typeof(Exception), messageTemplate)!;
29+
logger.Error(exception, messageTemplate, propertyValues);
30+
return exception;
31+
}
32+
33+
/// <summary>
34+
/// Throws a Error exception and logs it using the logger. The exception is created with the specified message template
35+
/// and property values.
36+
/// </summary>
37+
/// <param name="logger">The logger instance.</param>
38+
/// <param name="messageTemplate">The message template for the exception.</param>
39+
/// <param name="propertyValues">The property values for the exception.</param>
40+
[MessageTemplateFormatMethod("messageTemplate")]
41+
public static TException ThrowableError<TException>(this ILogger logger, string messageTemplate, params object?[]? propertyValues) where TException : Exception, new() {
42+
var exception = (TException)Activator.CreateInstance(typeof(TException), messageTemplate)!;
43+
logger.Error(exception, messageTemplate, propertyValues);
44+
return exception;
45+
}
46+
47+
/// <summary>
48+
/// Throws a fatal log message, logs the exception, and throws it.
49+
/// </summary>
50+
/// <param name="logger">The logger instance.</param>
51+
/// <param name="messageTemplate">The message template.</param>
52+
/// <param name="propertyValues">The property values.</param>
53+
/// <exception cref="Exception">Thrown exception.</exception>
54+
[MessageTemplateFormatMethod("messageTemplate")]
55+
public static Exception ThrowableFatal(this ILogger logger, string messageTemplate, params object?[]? propertyValues) {
56+
var exception = (Exception)Activator.CreateInstance(typeof(Exception), messageTemplate)!;
57+
logger.Fatal(exception, messageTemplate, propertyValues);
58+
return exception;
59+
}
60+
61+
/// <summary>
62+
/// Throws a fatal exception and logs it using the logger. The exception is created with the specified message template
63+
/// and property values.
64+
/// </summary>
65+
/// <param name="logger">The logger instance.</param>
66+
/// <param name="messageTemplate">The message template for the exception.</param>
67+
/// <param name="propertyValues">The property values for the exception.</param>
68+
[MessageTemplateFormatMethod("messageTemplate")]
69+
public static TException ThrowableFatal<TException>(this ILogger logger, string messageTemplate, params object?[]? propertyValues) where TException : Exception, new() {
70+
var exception = (TException)Activator.CreateInstance(typeof(TException), messageTemplate)!;
71+
logger.Fatal(exception, messageTemplate, propertyValues);
72+
return exception;
73+
}
74+
75+
/// <summary>
76+
/// Throws a fatal exception with the specified message template and property values.
77+
/// </summary>
78+
/// <param name="logger">The logger instance.</param>
79+
/// <param name="messageTemplate">The message template to be used for the exception.</param>
80+
/// <param name="exception">The type of expection to be thrown.</param>
81+
/// <param name="propertyValues">The property values to be used for formatting the message.</param>
82+
[MessageTemplateFormatMethod("messageTemplate")]
83+
public static TException ThrowableFatal<TException>(this ILogger logger, TException exception, string messageTemplate, params object?[]? propertyValues) where TException : Exception {
84+
logger.Fatal(exception, messageTemplate, propertyValues);
85+
return exception;
86+
}
87+
88+
/// <summary>
89+
/// Writes a fatal log message and exits the application with the specified exit code.
90+
/// </summary>
91+
/// <param name="logger">The logger.</param>
92+
/// <param name="exitCode">The exit code.</param>
93+
/// <param name="messageTemplate">The message template.</param>
94+
/// <param name="propertyValues">The values to be included in the log message.</param>
95+
/// <remarks>
96+
/// This method writes a fatal log message using the specified <paramref name="logger" /> and
97+
/// <paramref name="messageTemplate" />.
98+
/// It then exits the application with the specified <paramref name="exitCode" />.
99+
/// </remarks>
100+
[MessageTemplateFormatMethod("messageTemplate")]
101+
[DoesNotReturn, AssertionMethod]
102+
public static void ExitFatal(this ILogger logger, int exitCode, string messageTemplate, params object?[]? propertyValues) {
103+
logger.Fatal(messageTemplate, propertyValues);
104+
throw new ExitApplicationException(exitCode, messageTemplate);
105+
}
106+
}

src/Tools.CodeOfChaos.Extensions/Program.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ public async static Task Main(string[] args) {
2525
string projects = string.Join(";",
2626
"CodeOfChaos.Extensions",
2727
"CodeOfChaos.Extensions.EntityFrameworkCore",
28-
"CodeOfChaos.Extensions.AspNetCore"
28+
"CodeOfChaos.Extensions.AspNetCore",
29+
"CodeOfChaos.Extensions.Serilog"
2930
);
3031
string oneLineArgs = InputHelper.ToOneLine(args).Replace("%PROJECTS%", projects);
3132

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
// ---------------------------------------------------------------------------------------------------------------------
2+
// Imports
3+
// ---------------------------------------------------------------------------------------------------------------------
4+
using CodeOfChaos.Extensions.Serilog;
5+
using Moq;
6+
using Serilog;
7+
8+
namespace Tests.CodeOfChaos.Extensions.Serilog;
9+
10+
// ---------------------------------------------------------------------------------------------------------------------
11+
// Code
12+
// ---------------------------------------------------------------------------------------------------------------------
13+
public class LoggerExtensionsTests {
14+
private Mock<ILogger> _mockLogger = new();
15+
16+
[Test]
17+
public async Task ThrowableError_ShouldLogErrorAndThrowException() {
18+
// Arrange
19+
const string messageTemplate = "Error occurred: {ErrorDetail}";
20+
object[] propertyValues = ["An error detail"];
21+
_mockLogger.Setup(logger => logger.Error(It.IsAny<Exception>(), messageTemplate, propertyValues));
22+
23+
// Act
24+
Exception exception = _mockLogger.Object.ThrowableError(messageTemplate, propertyValues);
25+
26+
// Assert
27+
await Assert.That(exception).IsNotNull();
28+
await Assert.That(exception.Message).IsEqualTo(messageTemplate);
29+
}
30+
31+
[Test]
32+
public async Task ThrowableError_TException_ShouldLogErrorAndThrowCustomException() {
33+
// Arrange
34+
const string messageTemplate = "Custom error occurred: {ErrorDetail}";
35+
object[] propertyValues = ["Custom exception property"];
36+
_mockLogger.Setup(logger => logger.Error(It.IsAny<InvalidOperationException>(), messageTemplate, propertyValues));
37+
38+
// Act
39+
Exception exception = _mockLogger.Object.ThrowableError<InvalidOperationException>(messageTemplate, propertyValues);
40+
41+
// Assert
42+
await Assert.That(exception).IsNotNull()
43+
.And.IsTypeOf<InvalidOperationException>();
44+
await Assert.That(exception.Message).IsEqualTo(messageTemplate);
45+
}
46+
47+
[Test]
48+
public async Task ThrowableFatal_ShouldLogFatalAndThrowException() {
49+
// Arrange
50+
string messageTemplate = "Fatal error occurred: {ErrorDetail}";
51+
object[] propertyValues = { "Fatal error detail" };
52+
_mockLogger.Setup(logger => logger.Fatal(It.IsAny<Exception>(), messageTemplate, propertyValues));
53+
54+
// Act
55+
Exception exception = _mockLogger.Object.ThrowableFatal(messageTemplate, propertyValues);
56+
57+
// Assert
58+
await Assert.That(exception).IsNotNull();
59+
await Assert.That(exception.Message).IsEqualTo(messageTemplate);
60+
}
61+
62+
[Test]
63+
public async Task ThrowableFatal_TException_ShouldLogFatalAndThrowCustomException() {
64+
// Arrange
65+
string messageTemplate = "Fatal custom error occurred: {ErrorDetail}";
66+
object[] propertyValues = { "Custom fatal detail" };
67+
_mockLogger.Setup(logger => logger.Fatal(It.IsAny<InvalidOperationException>(), messageTemplate, propertyValues));
68+
69+
// Act
70+
Exception exception = _mockLogger.Object.ThrowableError<InvalidOperationException>(messageTemplate, propertyValues);
71+
72+
// Assert
73+
await Assert.That(exception).IsNotNull()
74+
.And.IsTypeOf<InvalidOperationException>();
75+
await Assert.That(exception.Message).IsEqualTo(messageTemplate);
76+
}
77+
78+
[Test]
79+
public async Task ThrowableFatal_WithExistingException_ShouldLogFatalAndUseProvidedException() {
80+
// Arrange
81+
string messageTemplate = "An existing exception occurred: {ErrorDetail}";
82+
var providedException = new InvalidOperationException("Existing exception");
83+
object[] propertyValues = { "Extra detail" };
84+
_mockLogger.Setup(logger => logger.Fatal(providedException, messageTemplate, propertyValues));
85+
86+
// Act
87+
var exception = _mockLogger.Object.ThrowableFatal(providedException, messageTemplate, propertyValues);
88+
89+
// Assert
90+
await Assert.That(exception).IsNotNull();
91+
await Assert.That(exception.Message).IsEqualTo(providedException.Message);
92+
await Assert.That(exception).IsEqualTo(providedException);
93+
}
94+
95+
[Test]
96+
public async Task ExitFatal_ShouldLogFatalAndThrowExitException() {
97+
// Arrange
98+
int exitCode = 1;
99+
string messageTemplate = "Critical failure - application exiting: {Reason}";
100+
object[] propertyValues = { "Unexpected failure" };
101+
102+
_mockLogger.Setup(logger => logger.Fatal(messageTemplate, propertyValues));
103+
104+
// Act
105+
var exception = Assert.Throws<ExitApplicationException>(() =>
106+
_mockLogger.Object.ExitFatal(exitCode, messageTemplate, propertyValues)
107+
);
108+
109+
// Assert
110+
await Assert.That(exception).IsNotNull()
111+
.And.IsTypeOf<ExitApplicationException>();
112+
await Assert.That(exception.Message).IsEqualTo(messageTemplate);
113+
}
114+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"profiles": {
3+
"Test": {
4+
"commandName": "Project"
5+
}
6+
}
7+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net9.0</TargetFramework>
5+
<LangVersion>latest</LangVersion>
6+
<ImplicitUsings>enable</ImplicitUsings>
7+
<Nullable>enable</Nullable>
8+
9+
<IsPackable>false</IsPackable>
10+
<IsTestProject>true</IsTestProject>
11+
</PropertyGroup>
12+
13+
<ItemGroup>
14+
<PackageReference Include="JetBrains.Annotations" Version="2024.3.0"/>
15+
<PackageReference Include="Moq" Version="4.20.72"/>
16+
<PackageReference Include="TUnit" Version="0.5.22" />
17+
<PackageReference Include="Bogus" Version="35.6.1"/>
18+
</ItemGroup>
19+
20+
<ItemGroup>
21+
<ProjectReference Include="..\..\src\CodeOfChaos.Extensions.Serilog\CodeOfChaos.Extensions.Serilog.csproj" />
22+
</ItemGroup>
23+
24+
</Project>

0 commit comments

Comments
 (0)