Skip to content
Merged
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
10 changes: 10 additions & 0 deletions .github/workflows/dotnet-desktop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,19 @@ jobs:
- name: Restore dependencies
run: dotnet restore

- name: Bump & push tag
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
uses: mathieudutour/github-tag-action@v6.2
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
tag_prefix: ""

- name: Build
run: dotnet build --configuration Release --no-restore

- name: Test
run: dotnet test --configuration Release --no-build

- name: Publish Nuget
shell: pwsh
run: |
Expand Down
4 changes: 3 additions & 1 deletion FluentLoggerExtensions.slnx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
<Solution>
<Folder Name="/Solution Items/">
<File Path=".github/workflows/dotnet-desktop.yml" />
<File Path="README.md" />
</Folder>
<Project Path="FluentLoggerExtensions.csproj" />
<Project Path="src/FluentLoggerExtensions.csproj" />
<Project Path="tests/FluentLoggerExtensions.Tests/FluentLoggerExtensions.Tests.csproj" Id="2c496e42-1236-4580-a854-98ce3e6cce11" />
</Solution>
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,22 @@ public static class CallerLogContextExtensions
/// <param name="args">The arguments for the message.</param>
public static void LogInformation(this CallerLogContext context, string message, params object?[] args)
{
string? fileName = Path.GetFileName(context.File);
context.Logger.LogInformation("[{File}:{Member}:{Line}] " + message, PrependCallerArgs(fileName, context.Member, context.Line, args));
string fileName = Path.GetFileName(context.File);

try
{
context.Logger.LogInformation(
"[{File}:{Member}:{Line}] " + message,
PrependCallerArgs(fileName, context.Member, context.Line, args));
}
catch (Exception ex)
{
// Fall back to a safe message; don't rethrow
context.Logger.LogError(
ex,
"[{File}:{Member}:{Line}] Logging failed for template. Template={Template}",
fileName, context.Member, context.Line, message);
}
}

/// <summary>
Expand All @@ -27,8 +41,22 @@ public static void LogInformation(this CallerLogContext context, string message,
/// <param name="args">The arguments for the message.</param>
public static void LogError(this CallerLogContext context, string message, params object?[] args)
{
string? fileName = Path.GetFileName(context.File);
context.Logger.LogError("[{File}:{Member}:{Line}] " + message, PrependCallerArgs(fileName, context.Member, context.Line, args));
string fileName = Path.GetFileName(context.File);

try
{
context.Logger.LogError(
"[{File}:{Member}:{Line}] " + message,
PrependCallerArgs(fileName, context.Member, context.Line, args));
}
catch (Exception ex)
{
// Fall back to a safe message; don't rethrow
context.Logger.LogError(
ex,
"[{File}:{Member}:{Line}] Logging failed for template. Template={Template}",
fileName, context.Member, context.Line, message);
}
}

/// <summary>
Expand All @@ -39,8 +67,22 @@ public static void LogError(this CallerLogContext context, string message, param
/// <param name="args">The arguments for the message.</param>
public static void LogDebug(this CallerLogContext context, string message, params object?[] args)
{
string? fileName = Path.GetFileName(context.File);
context.Logger.LogDebug("[{File}:{Member}:{Line}] " + message, PrependCallerArgs(fileName, context.Member, context.Line, args));
string fileName = Path.GetFileName(context.File);

try
{
context.Logger.LogDebug(
"[{File}:{Member}:{Line}] " + message,
PrependCallerArgs(fileName, context.Member, context.Line, args));
}
catch (Exception ex)
{
// Fall back to a safe message; don't rethrow
context.Logger.LogDebug(
ex,
"[{File}:{Member}:{Line}] Logging failed for template. Template={Template}",
fileName, context.Member, context.Line, message);
}
}

/// <summary>
Expand All @@ -51,8 +93,22 @@ public static void LogDebug(this CallerLogContext context, string message, param
/// <param name="args">The arguments for the message.</param>
public static void LogWarning(this CallerLogContext context, string message, params object?[] args)
{
string? fileName = Path.GetFileName(context.File);
context.Logger.LogWarning("[{File}:{Member}:{Line}] " + message, PrependCallerArgs(fileName, context.Member, context.Line, args));
string fileName = Path.GetFileName(context.File);

try
{
context.Logger.LogWarning(
"[{File}:{Member}:{Line}] " + message,
PrependCallerArgs(fileName, context.Member, context.Line, args));
}
catch (Exception ex)
{
// Fall back to a safe message; don't rethrow
context.Logger.LogWarning(
ex,
"[{File}:{Member}:{Line}] Logging failed for template. Template={Template}",
fileName, context.Member, context.Line, message);
}
}

/// <summary>
Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<PackageReadmeFile>README.md</PackageReadmeFile>
</PropertyGroup>

<ItemGroup>
Expand All @@ -23,4 +24,8 @@
</PackageReference>
</ItemGroup>

<ItemGroup>
<None Include="..\README.md" Pack="true" PackagePath="\" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
using FluentLogger;
using Microsoft.Extensions.Logging;
using Moq;

namespace FluentLoggerExtensions.Tests;

public class CallerLogContextLogDebugTests
{
[Theory]
[InlineData("This is a test log message")]
[InlineData("This is another test log message")]
[InlineData("This is a really log test message")]
Copy link

Copilot AI Dec 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test description "This is a really log test message" contains a grammatical error. It should be "This is a really long test message" (with "long" instead of "log").

Suggested change
[InlineData("This is a really log test message")]
[InlineData("This is a really long test message")]

Copilot uses AI. Check for mistakes.
public void LogDebug_WithPlainMessage_DoesNotThrow(string message)
{
Mock<ILogger> logger = new();
logger.Object.WithCaller().LogDebug(message);
logger.VerifyLogContains(message, LogLevel.Debug);
}

[Theory]
[InlineData("hello")]
public void LogDebug_WithInterpolatedString_DoesNotThrow(string id)
{
Mock<ILogger> logger = new();

logger.Object.WithCaller().LogDebug($"{id}");
}

[Theory]
[InlineData("hello", "there")]
public void LogDebug_WithMatchingPlaceholdersAndArguments_DoesNotThrow(string id, string id2)
{
Mock<ILogger> logger = new();
logger.Object.WithCaller().LogDebug("{FirstWord} {SecondWord}", id, id2);
}

[Theory]
[InlineData("hello")]
public void LogDebug_WithMissingArguments_DoesNotThrowUntilStateIsEnumerated(string id)
{
Mock<ILogger> logger = new();
logger.Object.WithCaller().LogDebug("{FirstWord} {SecondWord}", id);
}

[Fact]
public void Mismatched_placeholders_throw_when_state_is_enumerated()
Copy link

Copilot AI Dec 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test method name uses snake_case naming convention ("Mismatched_placeholders_throw_when_state_is_enumerated"), which is inconsistent with the PascalCase naming used in other test methods in this file (e.g., "LogDebug_WithPlainMessage_DoesNotThrow"). Consider renaming to "MismatchedPlaceholders_ThrowWhenStateIsEnumerated" for consistency.

Suggested change
public void Mismatched_placeholders_throw_when_state_is_enumerated()
public void MismatchedPlaceholders_ThrowWhenStateIsEnumerated()

Copilot uses AI. Check for mistakes.
{
var logger = new Mock<ILogger>();

logger
.Setup(l => l.Log(
It.IsAny<LogLevel>(),
It.IsAny<EventId>(),
It.IsAny<It.IsAnyType>(),
It.IsAny<Exception?>(),
(Func<It.IsAnyType, Exception?, string>)It.IsAny<object>()))
.Callback(new InvocationAction(invocation =>
{
// state is argument #2
var state = invocation.Arguments[2];

// This is what Serilog does internally: enumerate structured values
foreach (var _ in (IEnumerable<KeyValuePair<string, object?>>)state)
{
// enumeration triggers LogValuesFormatter.GetValue(...)
}
}));

// 2 placeholders, 1 arg -> will throw once enumerated
logger.Object.WithCaller().LogDebug("{FirstWord} {SecondWord}", "hello");
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
using FluentLogger;
using Microsoft.Extensions.Logging;
using Moq;

namespace FluentLoggerExtensions.Tests;

public class CallerLogContextLogErrorTests
{
[Theory]
[InlineData("This is a test log message")]
[InlineData("This is another test log message")]
[InlineData("This is a really log test message")]
Copy link

Copilot AI Dec 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test description "This is a really log test message" contains a grammatical error. It should be "This is a really long test message" (with "long" instead of "log").

Suggested change
[InlineData("This is a really log test message")]
[InlineData("This is a really long test message")]

Copilot uses AI. Check for mistakes.
public void LogError_WithPlainMessage_DoesNotThrow(string message)
{
Mock<ILogger> logger = new();
logger.Object.WithCaller().LogError(message);
logger.VerifyLogContains(message, LogLevel.Error);
}

[Theory]
[InlineData("hello")]
public void LogError_WithInterpolatedString_DoesNotThrow(string id)
{
Mock<ILogger> logger = new();

logger.Object.WithCaller().LogError($"{id}");
}

[Theory]
[InlineData("hello", "there")]
public void LogError_WithMatchingPlaceholdersAndArguments_DoesNotThrow(string id, string id2)
{
Mock<ILogger> logger = new();
logger.Object.WithCaller().LogError("{FirstWord} {SecondWord}", id, id2);
}

[Theory]
[InlineData("hello")]
public void LogError_WithMissingArguments_DoesNotThrowUntilStateIsEnumerated(string id)
{
Mock<ILogger> logger = new();
logger.Object.WithCaller().LogError("{FirstWord} {SecondWord}", id);
}

[Fact]
public void Mismatched_placeholders_throw_when_state_is_enumerated()
Copy link

Copilot AI Dec 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test method name uses snake_case naming convention ("Mismatched_placeholders_throw_when_state_is_enumerated"), which is inconsistent with the PascalCase naming used in other test methods in this file (e.g., "LogError_WithPlainMessage_DoesNotThrow"). Consider renaming to "MismatchedPlaceholders_ThrowWhenStateIsEnumerated" for consistency.

Suggested change
public void Mismatched_placeholders_throw_when_state_is_enumerated()
public void MismatchedPlaceholders_ThrowWhenStateIsEnumerated()

Copilot uses AI. Check for mistakes.
{
var logger = new Mock<ILogger>();

logger
.Setup(l => l.Log(
It.IsAny<LogLevel>(),
It.IsAny<EventId>(),
It.IsAny<It.IsAnyType>(),
It.IsAny<Exception?>(),
(Func<It.IsAnyType, Exception?, string>)It.IsAny<object>()))
.Callback(new InvocationAction(invocation =>
{
// state is argument #2
var state = invocation.Arguments[2];

// This is what Serilog does internally: enumerate structured values
foreach (var _ in (IEnumerable<KeyValuePair<string, object?>>)state)
{
// enumeration triggers LogValuesFormatter.GetValue(...)
}
}));

// 2 placeholders, 1 arg -> will throw once enumerated
logger.Object.WithCaller().LogError("{FirstWord} {SecondWord}", "hello");
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
using FluentLogger;
using Microsoft.Extensions.Logging;
using Moq;

namespace FluentLoggerExtensions.Tests;

public class CallerLogContextLogInformationTests
{
[Theory]
[InlineData("This is a test log message")]
[InlineData("This is another test log message")]
[InlineData("This is a really log test message")]
Copy link

Copilot AI Dec 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test description "This is a really log test message" contains a grammatical error. It should be "This is a really long test message" (with "long" instead of "log").

Suggested change
[InlineData("This is a really log test message")]
[InlineData("This is a really long test message")]

Copilot uses AI. Check for mistakes.
public void LogInformation_WithPlainMessage_DoesNotThrow(string message)
{
Mock<ILogger> logger = new();
logger.Object.WithCaller().LogInformation(message);
logger.VerifyLogContains(message);
}

[Theory]
[InlineData("hello")]
public void LogInformation_WithInterpolatedString_DoesNotThrow(string id)
{
Mock<ILogger> logger = new();

logger.Object.WithCaller().LogInformation($"{id}");
}

[Theory]
[InlineData("hello", "there")]
public void LogInformation_WithMatchingPlaceholdersAndArguments_DoesNotThrow(string id, string id2)
{
Mock<ILogger> logger = new();
logger.Object.WithCaller().LogInformation("{FirstWord} {SecondWord}", id, id2);
}

[Theory]
[InlineData("hello")]
public void LogInformation_WithMissingArguments_DoesNotThrowUntilStateIsEnumerated(string id)
{
Mock<ILogger> logger = new();
logger.Object.WithCaller().LogInformation("{FirstWord} {SecondWord}", id);
}

[Fact]
public void Mismatched_placeholders_throw_when_state_is_enumerated()
Copy link

Copilot AI Dec 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test method name uses snake_case naming convention ("Mismatched_placeholders_throw_when_state_is_enumerated"), which is inconsistent with the PascalCase naming used in other test methods in this file (e.g., "LogInformation_WithPlainMessage_DoesNotThrow"). Consider renaming to "MismatchedPlaceholders_ThrowWhenStateIsEnumerated" for consistency.

Suggested change
public void Mismatched_placeholders_throw_when_state_is_enumerated()
public void MismatchedPlaceholders_ThrowWhenStateIsEnumerated()

Copilot uses AI. Check for mistakes.
{
var logger = new Mock<ILogger>();

logger
.Setup(l => l.Log(
It.IsAny<LogLevel>(),
It.IsAny<EventId>(),
It.IsAny<It.IsAnyType>(),
It.IsAny<Exception?>(),
(Func<It.IsAnyType, Exception?, string>)It.IsAny<object>()))
.Callback(new InvocationAction(invocation =>
{
// state is argument #2
var state = invocation.Arguments[2];

// This is what Serilog does internally: enumerate structured values
foreach (var _ in (IEnumerable<KeyValuePair<string, object?>>)state)
{
// enumeration triggers LogValuesFormatter.GetValue(...)
}
}));

// 2 placeholders, 1 arg -> will throw once enumerated
logger.Object.WithCaller().LogInformation("{FirstWord} {SecondWord}", "hello");
}

}
Loading
Loading