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

feature: Structured Logging Support #141

Open
wants to merge 4 commits into
base: develop/9.0.0
Choose a base branch
from

Conversation

nicolaspierre1990
Copy link

@nicolaspierre1990 nicolaspierre1990 commented Dec 22, 2024

Move from Arc4u.Diagnostics to Arc4u.Diagnostics.Logger

Summary

This PR moves the logging implementation from the Arc4u.Diagnostics namespace to Arc4u.Diagnostics.Logger, providing significant performance improvements and better alignment with Microsoft.Extensions.Logging patterns.

Key Changes

Namespace Organization

  • Moved from Arc4u.Diagnostics to Arc4u.Diagnostics.Logger for better code organization
  • Clear separation between logging and other diagnostic concerns
  • Better aligned with .NET namespace conventions

Structured Logging Support

  • Full structured log support through LoggerMessage.Define
  • Semantic logging with named placeholders
  • Preserved object structure in logs (using @ operator)
  • Compatible with log aggregation and analysis tools
  • Better query capabilities in log management systems

Example:

// Structured logging with semantic parameters
using Arc4u.Diagnostics.Logger;

logger.Technical<MyClass>()
      .Add("context", new { env = "prod", region = "eu" })
      .LogInformation("Order {OrderId} processed for {CustomerId} with total amount {Amount:C}", 
                     orderId, customerId, amount);

// Output maintains structure for querying:
{
  "OrderId": "12345",
  "CustomerId": "C789",
  "Amount": 299.99,
  "context": {
    "env": "prod",
    "region": "eu"
  }
  ...
}

Performance Improvements

  • Replaced runtime string formatting with compile-time generated logging delegates using LoggerMessage.Define
  • Reduced allocations by eliminating multiple wrapper classes in favor of a single LoggerWrapper
  • Optimized property collection and caching
  • Removed redundant string operations and concatenations

ILogger Integration

  • Direct dependency on Microsoft.Extensions.Logging.ILogger instead of custom abstractions
  • Better support for structured logging through standard ILogger patterns
  • Full compatibility with common logging providers (Serilog, NLog, etc.)

Breaking Changes

  • LoggerBase and derived classes should be replaced with LoggerWrapper
  • Property collection mechanism has been simplified

Migration Path

// Old approach (string interpolation, loses structure)
using Arc4u.Diagnostics;

logger.Technical()
      .From<MyClass>()
      .Information("Processing {0} items", count)
      .Add("extraInfo", value)
      .Log();

// New approach (structured, maintains semantic information)
using Arc4u.Diagnostics.Logger;

logger.Technical<MyClass>()
      .Add("extraInfo", value)
      .LogInformation("Processing {ItemCount} items with {BatchId}", count, batchId);

Benefits for Log Analysis

  • Improved query capabilities in log aggregation systems
  • Better filtering and searching based on structured fields
  • Enhanced analytics and metrics extraction
  • More accurate log correlation
  • Better debugging and troubleshooting capabilities

Future Improvements

  • Consider removing redundant property management code
  • Further optimize high-volume logging scenarios
  • Adding additional code documentation if approved

Summary by CodeRabbit

Summary by CodeRabbit

  • New Features

    • Introduced logging capabilities with the new UseArc4uLogging methods for both ASP.NET Core applications and host builders.
    • Added extension methods for categorized logging, allowing for more structured log entries.
    • Added a new constant for unwrapped exceptions to enhance logging identifiers.
  • Bug Fixes

    • Enhanced error handling for nullable object types in logging methods.
  • Documentation

    • Updated project structure with new projects for better organization.
  • Chores

    • Added new package references for OpenTelemetry support to enhance monitoring capabilities.

Copy link

coderabbitai bot commented Dec 22, 2024

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Walkthrough

The pull request introduces significant enhancements to the Arc4u.Diagnostics library, focusing on advanced logging capabilities and improved project organization. Key changes include the addition of OpenTelemetry package references for .NET 8.0 and 9.0, the creation of extension methods to integrate logging into ASP.NET Core applications, and the implementation of a robust LoggerWrapper class for structured logging. Furthermore, the solution file has been updated to include new projects, enhancing the overall structure and organization of the codebase.

Changes

File Change Summary
Arc4u.Diagnostics.csproj Added OpenTelemetry package references for net8.0 and net9.0, included Microsoft.AspNetCore.App framework reference
Logging/ApplicationBuilderExtensions.cs New static class with UseArc4uLogging extension method for IApplicationBuilder
Logging/HostBuilderExtensions.cs New static class with UseArc4uLogging extension method for IHostBuilder
Logging/ILoggerExtensions.cs Added extension methods for ILogger and ILogger<T> to create categorized logger wrappers
Logging/LoggerWrapper.cs Implemented LoggerWrapper<T> class with advanced logging capabilities and context management
Logging/LoggerWrapperContext.cs Added context management for logging through dependency injection
Logging/LoggerWrapperExtensions.cs Created extension methods for LoggerWrapper<T> to enhance logging functionality
MessageProperty.cs Updated method signatures to support nullable object types
LoggingConstants.cs Added constant for unwrapped exceptions
src/Arc4u.sln Added new projects and organized existing projects under a hierarchical structure
src/Arc4u.UnitTest/Logging/SerilogTests.cs Minor reordering of using directives

Sequence Diagram

sequenceDiagram
    participant App as Application
    participant HostBuilder as IHostBuilder
    participant LoggerContext as LoggerWrapperContext
    participant Logger as LoggerWrapper

    App->>HostBuilder: UseArc4uLogging()
    HostBuilder-->>App: Configured Logging
    App->>LoggerContext: Initialize(ServiceProvider)
    LoggerContext-->>App: Context Initialized
    App->>Logger: Technical/Business/Monitoring Log
    Logger-->>App: Enriched Log Message
Loading

Poem

🐰 A Rabbit's Logging Tale 🐰
With OpenTelemetry's might,
Our logs now shine so bright!
Contexts dance, messages gleam,
In .NET's logging stream
A wrapper's magic takes flight! 🌟


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR. (Beta)
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@nicolaspierre1990
Copy link
Author

@coderabbitai review

Copy link

coderabbitai bot commented Dec 22, 2024

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🧹 Nitpick comments (13)
src/Arc4u.sln (1)

101-106: Well-structured solution organization!

The addition of "sample", "src", and "tests" solution folders follows best practices for .NET solution organization, providing clear separation of concerns between source code, tests, and examples.

src/Arc4u.Diagnostics/Logging/LoggerWrapper.cs (3)

13-14: Potential Concerns with Embedding Object in Dictionary
The field '_additionalFields' is declared as a Dictionary<string, object?>. You might eventually need more robust handling (e.g., concurrency) if multiple threads log concurrently and update fields simultaneously.


52-75: Leverage Named Templates for Structured Logging
Here, you define separate log templates (TechnicalLog, BusinessLog, MonitoringLog). This is good for structured logging, but consider adding more placeholders (e.g., methodName, className) or using parseable keys for easier indexing in log aggregators.


137-147: Dispose Pattern
The class implements Dispose but does not do any resource clean-up besides flipping '_disposed'. If the underlying '_logger' or related objects require disposing, ensure they're also handled here.

src/Arc4u.Diagnostics/Logging/ApplicationBuilderExtensions.cs (1)

7-11: Initialization Timing
This extension method initializes the LoggerWrapperContext with the ASP.NET Core service provider. Ensure this call is placed at an appropriate point in the pipeline (often after required services are registered) so that loggers are fully functional.

src/Arc4u.Diagnostics/MessageProperty.cs (1)

Line range hint 5-13: Null Handling
In AddIfNotExist(), you skip adding if 'value == null'. This helps avoid storing null, but some logs or systems may want explicit tracking of null. This design choice can be fine, but confirm it aligns with your usage.

src/Arc4u.Diagnostics/Logging/HostBuilderExtensions.cs (1)

8-19: Validate Logging Configuration
Your 'UseArc4uLogging' extension method delegates to 'ConfigureLogging', optionally applying the user-provided delegate 'configure'. Ensure you handle conflicts if the user calls 'ConfigureLogging' multiple times.

src/Arc4u.Diagnostics/Logging/LoggerWrapperContext.cs (1)

8-9: Consider null-safety improvements

The Initialize method could benefit from additional validation and thread-safety.

Consider this implementation:

-public static void Initialize(IServiceProvider serviceProvider)
-    => _scopeFactory = serviceProvider.GetRequiredService<IServiceScopeFactory>();
+public static void Initialize(IServiceProvider serviceProvider)
+{
+    ArgumentNullException.ThrowIfNull(serviceProvider);
+    if (_scopeFactory != null)
+        throw new InvalidOperationException("LoggerContext already initialized");
+    _scopeFactory = serviceProvider.GetRequiredService<IServiceScopeFactory>();
+}
src/Arc4u.Diagnostics/Logging/ILoggerExtensions.cs (2)

8-15: Add XML documentation for public API

These public extension methods would benefit from XML documentation describing their purpose and usage.

Add documentation for better IntelliSense support:

+/// <summary>
+/// Creates a technical category logger wrapper for structured logging.
+/// </summary>
+/// <param name="logger">The logger instance.</param>
+/// <param name="methodName">The calling method name (automatically populated).</param>
+/// <returns>A new LoggerWrapper instance for technical logging.</returns>
public static LoggerWrapper<T> Technical<T>(this ILogger logger, [CallerMemberName] string methodName = "")

8-24: Consider reducing code duplication

The implementation is duplicated between ILogger and ILogger extensions. Consider refactoring to reduce duplication.

Consider implementing the ILogger extensions in terms of the ILogger ones:

public static LoggerWrapper<T> Technical<T>(this ILogger<T> logger, [CallerMemberName] string methodName = "") =>
-    new(logger, MessageCategory.Technical, typeof(T), methodName);
+    Technical<T>((ILogger)logger, methodName);
src/Arc4u.Diagnostics/Logging/LoggerWrapperExtensions.cs (3)

35-40: Consider consolidating duplicate methods

The AddOrReplace method is identical to the Add method in implementation. This duplication might be confusing for users as both methods perform the same operation (dictionary[key] = value always replaces existing values).

Consider either:

  1. Removing AddOrReplace and documenting that Add always replaces existing values
  2. Renaming Add to AddOrReplace to be more explicit about the behavior

59-80: Enhance validation documentation and error messages

The key validation logic could be improved in two ways:

  1. Add XML documentation to explain the reserved keys and their purpose
  2. Provide a more descriptive ArgumentNullException message
+/// <summary>
+/// Validates that the provided key is not null, empty, or a reserved logging key.
+/// </summary>
+/// <param name="key">The key to validate</param>
+/// <returns>The validated key</returns>
+/// <exception cref="ArgumentNullException">Thrown when key is null or empty</exception>
+/// <exception cref="ReservedLoggingKeyException">Thrown when key is a reserved logging key</exception>
 private static string ValidateKey(string key)
 {
     if (string.IsNullOrWhiteSpace(key))
     {
-        throw new ArgumentNullException(nameof(key));
+        throw new ArgumentNullException(nameof(key), "Logging key cannot be null or empty");
     }

1-81: Well-structured implementation of fluent logging interface

The implementation provides a clean and consistent API for structured logging that aligns well with the PR objectives. The fluent interface allows for easy chaining of logging configurations, and the validation ensures proper usage of logging keys.

Some architectural considerations:

  1. The extension methods provide a good abstraction layer for logging configuration
  2. The fluent interface pattern is consistently applied
  3. Key validation helps prevent conflicts with system-reserved keys

Consider adding integration tests to verify the behavior of these extension methods with different logging providers (Serilog, NLog) to ensure compatibility.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 528cbec and c512e47.

📒 Files selected for processing (10)
  • src/Arc4u.Diagnostics/Arc4u.Diagnostics.csproj (1 hunks)
  • src/Arc4u.Diagnostics/Logging/ApplicationBuilderExtensions.cs (1 hunks)
  • src/Arc4u.Diagnostics/Logging/HostBuilderExtensions.cs (1 hunks)
  • src/Arc4u.Diagnostics/Logging/ILoggerExtensions.cs (1 hunks)
  • src/Arc4u.Diagnostics/Logging/LoggerWrapper.cs (1 hunks)
  • src/Arc4u.Diagnostics/Logging/LoggerWrapperContext.cs (1 hunks)
  • src/Arc4u.Diagnostics/Logging/LoggerWrapperExtensions.cs (1 hunks)
  • src/Arc4u.Diagnostics/MessageProperty.cs (2 hunks)
  • src/Arc4u.UnitTest/Logging/SerilogTests.cs (1 hunks)
  • src/Arc4u.sln (2 hunks)
✅ Files skipped from review due to trivial changes (1)
  • src/Arc4u.UnitTest/Logging/SerilogTests.cs
🔇 Additional comments (9)
src/Arc4u.Diagnostics/Logging/LoggerWrapper.cs (4)

81-85: Avoid Short-Circuiting in Log Method
You return immediately if logging is disabled for the current level. Typically fine, but if you rely on any side-effects (e.g., metrics) prior to the check, you might skip them. Ensure you intend to skip all side-effects.


86-135: Exception Handling within Logging
Catching and re-logging exceptions could mask their original stack trace if done improperly. Currently, after catching the exception in line 130, you call '_logger.Log(...)' with the same level. This is typically okay, but ensure there's no duplication or unintentional infinite loop if the exception is triggered by the logger itself.


205-242: Check for Potential Recursion in GetAllExceptions()
When dealing with nested AggregateException or InnerExceptions, ensure there's no cyclical reference that might cause infinite recursion.


26-35: Ensure Environment.ProcessId Works on All Targeted Platforms
Accessing 'Environment.ProcessId' can throw on non-supported platforms, hence the try-catch. However, confirm you have test coverage or fallback logic for the scenarios where it returns -1.

✅ Verification successful

Based on the search results, I can generate the final response as I have enough context about how ProcessId is used throughout the codebase:

ProcessId Fallback is Properly Handled Across the Codebase
The -1 fallback value for ProcessId is consistently handled across the codebase. The value is used primarily for logging purposes in various formatters and sinks, where it's padded and displayed as part of log messages. No critical logic depends on the actual process ID value, making the fallback safe and appropriate.

Key observations:

  • ConsoleTraceListener, FileLoggerTraceListener, and SimpleTextFormatter all handle ProcessId by just formatting it as a string
  • LoggerMessage and LoggerWrapper both implement the same try-catch pattern for ProcessId
  • The value is consistently treated as a display/diagnostic field rather than a critical identifier
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Searching for usage or checks around 'ProcessId' usage to ensure fallback logic
rg -A 3 "ProcessId"

Length of output: 13664

src/Arc4u.Diagnostics/MessageProperty.cs (1)

15-18: Argument Checking
Throwing an ArgumentNullException for a null 'value' in AddOrReplace() ensures no null data is stored. If a user explicitly wants to store null, they'll be forced to code differently. Confirm this is intended.

src/Arc4u.Diagnostics/Arc4u.Diagnostics.csproj (2)

17-17: LGTM: Framework reference aligns with PR objectives

The addition of Microsoft.AspNetCore.App framework reference aligns with the PR's goal of enhancing logging capabilities and integration with Microsoft.Extensions.Logging.


9-9: Verify OpenTelemetry version compatibility

The OpenTelemetry package versions differ between frameworks (1.9.0 for net8.0 vs 1.10.0 for net9.0). While this follows the framework versioning pattern, we should verify compatibility.

Also applies to: 14-14

✅ Verification successful

OpenTelemetry package versions are appropriate for respective frameworks

The package versions are correctly aligned with their target frameworks:

  • net8.0: OpenTelemetry.Extensions.Hosting 1.9.0 with Microsoft.Extensions.Hosting 8.0.1
  • net9.0: OpenTelemetry.Extensions.Hosting 1.10.0 with Microsoft.Extensions.Hosting 9.0.0

No security vulnerabilities were found for these versions, and the versioning follows a consistent pattern with other framework dependencies.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check for any known compatibility issues or breaking changes between versions
gh api graphql -f query='
{
  securityVulnerabilities(first: 5, ecosystem: NUGET, package: "OpenTelemetry.Extensions.Hosting") {
    nodes {
      advisory {
        summary
        severity
        publishedAt
      }
      vulnerableVersionRange
      firstPatchedVersion {
        identifier
      }
    }
  }
}'

Length of output: 367


Script:

#!/bin/bash
# Check for OpenTelemetry package references across the solution
rg -l "OpenTelemetry\.Extensions\.Hosting" --type csproj

# Check the target frameworks in the project file
cat src/Arc4u.Diagnostics/Arc4u.Diagnostics.csproj

Length of output: 1111

src/Arc4u.Diagnostics/Logging/LoggerWrapperContext.cs (1)

11-18: LGTM: Proper error handling for scope creation

The CreateScope method correctly handles the null case and throws an appropriate exception.

src/Arc4u.Diagnostics/Logging/LoggerWrapperExtensions.cs (1)

1-6: Verify namespace choice for architectural consistency

The extension methods are placed in the Microsoft.Extensions.DependencyInjection namespace while using Arc4u.Diagnostics types. While this is a common practice for extension methods to be discoverable, please verify if this aligns with the project's architectural guidelines.

✅ Verification successful

Namespace choice is architecturally consistent

The namespace choice of Microsoft.Extensions.DependencyInjection for LoggerWrapperExtensions is consistent with the project's architectural patterns. This is evidenced by:

  1. Multiple extension classes in Arc4u.Diagnostics namespace that extend Microsoft's functionality
  2. Similar extension patterns across the codebase where functionality is added to Microsoft's DI container
  3. The presence of numerous other files using Microsoft.Extensions.DependencyInjection, indicating tight integration with Microsoft's DI ecosystem
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check for similar extension method patterns in the codebase
fd -e cs -x grep -l "namespace Microsoft.Extensions.DependencyInjection" {} \; | xargs grep -l "static class.*Extensions"

Length of output: 185


Script:

#!/bin/bash
# Let's check for other extension classes in Arc4u.Diagnostics namespace
fd -e cs -x grep -l "namespace Arc4u.Diagnostics" {} \; | xargs grep -l "static class.*Extensions"

# Also check if there are any other DI-related extensions in the codebase
fd -e cs -x grep -l "using Microsoft.Extensions.DependencyInjection" {} \;

Length of output: 6748

Comment on lines +965 to +1008
GlobalSection(NestedProjects) = preSolution
{CCA589E1-100F-47A6-A34B-FD46FDFDAA2A} = {B9419F5B-9CF3-4247-9929-7AF2362FD8E3}
{511B1EB2-F853-4C5C-80A7-0825AECF1BBE} = {B9419F5B-9CF3-4247-9929-7AF2362FD8E3}
{B270FC8C-6B28-40FA-BA78-B73F720DB98D} = {B9419F5B-9CF3-4247-9929-7AF2362FD8E3}
{705ADAFC-9C5C-4DCB-93C5-B71D49371074} = {B9419F5B-9CF3-4247-9929-7AF2362FD8E3}
{C1048831-D985-481B-BA50-2840315EFFEA} = {B9419F5B-9CF3-4247-9929-7AF2362FD8E3}
{29DA3E34-4769-457F-B583-8BCFF5958E41} = {B9419F5B-9CF3-4247-9929-7AF2362FD8E3}
{531058F7-1DD4-4E51-8727-A2D038B57274} = {B9419F5B-9CF3-4247-9929-7AF2362FD8E3}
{1AD8095D-CEF1-486F-912B-CB9C6DCE0A20} = {B9419F5B-9CF3-4247-9929-7AF2362FD8E3}
{4AC131B6-9FF2-4F66-A2DE-BE66C3CA366B} = {B9419F5B-9CF3-4247-9929-7AF2362FD8E3}
{42B7DC08-23E2-4854-A5DB-71E9FB3BAF6D} = {B9419F5B-9CF3-4247-9929-7AF2362FD8E3}
{51D25BBB-E6E5-46AF-81A1-B854F715F2C0} = {B9419F5B-9CF3-4247-9929-7AF2362FD8E3}
{150D036C-4B66-4A99-BAA9-28AC0A81E2D7} = {B9419F5B-9CF3-4247-9929-7AF2362FD8E3}
{F333A9E4-52F7-473D-AFCE-46A4FCFE844C} = {B9419F5B-9CF3-4247-9929-7AF2362FD8E3}
{9B9ABE45-A069-4E97-BAEF-5C273CF4C8AB} = {B9419F5B-9CF3-4247-9929-7AF2362FD8E3}
{0F2EEF43-174D-436D-A5B1-DA92D3EF9B25} = {B9419F5B-9CF3-4247-9929-7AF2362FD8E3}
{C10B3D6C-E5E4-4365-B5E5-826A2C7A44AE} = {B9419F5B-9CF3-4247-9929-7AF2362FD8E3}
{62867DEB-C095-4D34-8C56-3F0C3A0FE3B7} = {B9419F5B-9CF3-4247-9929-7AF2362FD8E3}
{39E14B92-11E6-433E-8B5C-082485F16DA9} = {B9419F5B-9CF3-4247-9929-7AF2362FD8E3}
{F0D96654-0415-4540-A224-44C2585F825E} = {B9419F5B-9CF3-4247-9929-7AF2362FD8E3}
{86290E0E-3450-4CFD-80B3-8EED35E2A121} = {B9419F5B-9CF3-4247-9929-7AF2362FD8E3}
{64CBEAB5-41B7-47CE-95E7-62CE3ECFE2FC} = {B9419F5B-9CF3-4247-9929-7AF2362FD8E3}
{F0EF3965-11F5-4F8E-8D8C-8A1BC32DFFE0} = {B9419F5B-9CF3-4247-9929-7AF2362FD8E3}
{E954455E-AB54-4144-B784-1E68B6548482} = {B9419F5B-9CF3-4247-9929-7AF2362FD8E3}
{1959B659-8A69-4D68-A198-C627464C2143} = {B9419F5B-9CF3-4247-9929-7AF2362FD8E3}
{5157EBFA-D53F-49A3-BDC6-B4887D4F6E2F} = {B9419F5B-9CF3-4247-9929-7AF2362FD8E3}
{FCFE7961-7B48-42BF-BD72-67A1C0858BB1} = {B9419F5B-9CF3-4247-9929-7AF2362FD8E3}
{FACC7AF6-8AAF-41B9-A0EF-0E6285EE1CA7} = {B9419F5B-9CF3-4247-9929-7AF2362FD8E3}
{F854BCF4-D7DF-46D3-809A-0EF53586EC00} = {B9419F5B-9CF3-4247-9929-7AF2362FD8E3}
{F018AC7E-2799-48E3-BAC0-79C94452085A} = {B9419F5B-9CF3-4247-9929-7AF2362FD8E3}
{46D4C073-FD32-4C23-B22A-306E69122BF6} = {B9419F5B-9CF3-4247-9929-7AF2362FD8E3}
{30D7D397-A29D-4148-AD66-EF26495A5BC0} = {E8E0B4BA-5659-40E6-94C8-CA6D9F339101}
{EA273269-7720-48DE-8866-FC82E2C9490F} = {B9419F5B-9CF3-4247-9929-7AF2362FD8E3}
{6ED0D79F-96D4-418A-9C75-69E165FA5682} = {B9419F5B-9CF3-4247-9929-7AF2362FD8E3}
{FB64F43E-F22E-4202-9F33-840CB7F44AA1} = {B9419F5B-9CF3-4247-9929-7AF2362FD8E3}
{EB7B7062-7366-4262-8686-3DDAA19141B4} = {B9419F5B-9CF3-4247-9929-7AF2362FD8E3}
{B16F1734-B286-4898-8B15-EFA0D17DA912} = {B9419F5B-9CF3-4247-9929-7AF2362FD8E3}
{06393D15-0DA9-40FA-84A1-9A822B627B52} = {B9419F5B-9CF3-4247-9929-7AF2362FD8E3}
{1910732C-CF4C-4411-B29F-F75185DD2F9B} = {B9419F5B-9CF3-4247-9929-7AF2362FD8E3}
{2B61C53F-ECFC-471D-AB98-0D23FF2D071A} = {B9419F5B-9CF3-4247-9929-7AF2362FD8E3}
{6FD0A0B7-A94C-4DBD-AF62-CE3B5359F088} = {B9419F5B-9CF3-4247-9929-7AF2362FD8E3}
{AE2BA9A5-AFCD-4D98-87DC-71D6DCBC2C21} = {B9419F5B-9CF3-4247-9929-7AF2362FD8E3}
{2C83BE21-3C11-46B2-9831-5B84E4912BFB} = {B9419F5B-9CF3-4247-9929-7AF2362FD8E3}
EndGlobalSection
Copy link

Choose a reason for hiding this comment

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

💡 Codebase verification

Remove unused "sample" folder section from solution file

The "sample" folder exists only as an empty section in the solution file without any actual projects or files associated with it. Since this PR focuses on logging changes and there are no sample projects demonstrating these changes, the empty sample section should be removed from the solution file to maintain a clean project structure.

🔗 Analysis chain

Verify the need for the empty "sample" folder

While the project organization is good, with source code and test projects properly separated, the "sample" folder remains empty. Consider either:

  1. Adding sample projects demonstrating the new structured logging implementation, or
  2. Removing the "sample" folder if it's not needed for this PR.

This is particularly relevant as the PR introduces significant logging changes that could benefit from example implementations.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check if there are any sample projects in the repository that should be included
fd -t f -e csproj . | grep -i "sample"

Length of output: 38


Script:

#!/bin/bash
# Let's check the solution file for any sample-related sections or folders
cat src/Arc4u.sln | grep -i "sample"

# Also check if there are any sample-related folders in the repository
fd -t d "sample"

Length of output: 166

Comment on lines 155 to 180
using var scope = LoggerWrapperContext.CreateScope();
var providers = scope.ServiceProvider
.GetServices<IAddPropertiesToLog>();

properties = new Dictionary<string, object?>(AdditionalFields);
var definedProperties = providers.Select(x => x.GetProperties()).SelectMany(x => x);
if (definedProperties != null)
{
foreach (var property in definedProperties)
{
if (property.Value != null)
{
this.AddIfNotExist(property.Key, property.Value);
}
}

return properties;
}

return new Dictionary<string, object?>(AdditionalFields);
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Error getting property providers. Logging without additional properties.");
return new Dictionary<string, object?>(AdditionalFields);
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Potential Performance Implication of GetServices<IAddPropertiesToLog>()
You're calling 'GetServices()' every time you add additional properties, which can create overhead. Consider caching these services or retrieving them once in the constructor if the list of providers is static or changes infrequently.

namespace Arc4u.Diagnostics.Logging;
internal class LoggerWrapperContext
{
private static IServiceScopeFactory? _scopeFactory;
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider thread-safe initialization pattern

The static field _scopeFactory could benefit from a thread-safe initialization pattern to prevent potential race conditions in concurrent scenarios.

Consider using Lazy<T> for thread-safe initialization:

-private static IServiceScopeFactory? _scopeFactory;
+private static readonly Lazy<IServiceScopeFactory> _scopeFactory = new();

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines 53 to 57
public static LoggerWrapper<T> AddStackTrace<T>(this LoggerWrapper<T> logger)
{
logger.IncludeStackTrace = true;
return logger;
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add disposal check for consistency

The AddStackTrace method is missing the disposal check that other methods have.

 public static LoggerWrapper<T> AddStackTrace<T>(this LoggerWrapper<T> logger)
 {
+    logger.ThrowIfDisposed();
     logger.IncludeStackTrace = true;
     return logger;
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public static LoggerWrapper<T> AddStackTrace<T>(this LoggerWrapper<T> logger)
{
logger.IncludeStackTrace = true;
return logger;
}
public static LoggerWrapper<T> AddStackTrace<T>(this LoggerWrapper<T> logger)
{
logger.ThrowIfDisposed();
logger.IncludeStackTrace = true;
return logger;
}

Comment on lines 24 to 33
public static LoggerWrapper<T> AddIfNotExist<T>(this LoggerWrapper<T> logger, string key, object? value)
{
if (value == null || logger.AdditionalFields.ContainsKey(key))
{
return logger;
}

logger.AdditionalFields[ValidateKey(key)] = value;
return logger;
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add disposal check and optimize key validation

The AddIfNotExist method has several potential issues:

  1. Missing disposal check that other methods have
  2. Key validation happens after the null and existence checks
  3. Potential race condition between ContainsKey check and dictionary addition

Consider this safer implementation:

 public static LoggerWrapper<T> AddIfNotExist<T>(this LoggerWrapper<T> logger, string key, object? value)
 {
+    logger.ThrowIfDisposed();
+    var validKey = ValidateKey(key);
     if (value == null || logger.AdditionalFields.ContainsKey(validKey))
     {
         return logger;
     }

-    logger.AdditionalFields[ValidateKey(key)] = value;
+    logger.AdditionalFields[validKey] = value;
     return logger;
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public static LoggerWrapper<T> AddIfNotExist<T>(this LoggerWrapper<T> logger, string key, object? value)
{
if (value == null || logger.AdditionalFields.ContainsKey(key))
{
return logger;
}
logger.AdditionalFields[ValidateKey(key)] = value;
return logger;
}
public static LoggerWrapper<T> AddIfNotExist<T>(this LoggerWrapper<T> logger, string key, object? value)
{
logger.ThrowIfDisposed();
var validKey = ValidateKey(key);
if (value == null || logger.AdditionalFields.ContainsKey(validKey))
{
return logger;
}
logger.AdditionalFields[validKey] = value;
return logger;
}

@nicolaspierre1990
Copy link
Author

@coderabbitai review

Copy link

coderabbitai bot commented Dec 22, 2024

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (3)
src/Arc4u.Diagnostics/Logging/LoggerWrapper.cs (3)

157-185: Simplify AddAdditionalProperties method

The method has redundant checks and could be simplified for better maintainability.

     private Dictionary<string, object?> AddAdditionalProperties()
     {
-        Dictionary<string, object?> properties;
-
         try
         {
-            properties = new Dictionary<string, object?>(AdditionalFields);
+            var properties = new Dictionary<string, object?>(AdditionalFields);
             var definedProperties = _providers.Value.Select(x => x.GetProperties()).SelectMany(x => x);
-            if (definedProperties != null)
-            {
-                foreach (var property in definedProperties)
-                {
-                    if (property.Value != null)
-                    {
-                        this.AddIfNotExist(property.Key, property.Value);
-                    }
-                }
-
-                return properties;
-            }
-
-            return new Dictionary<string, object?>(AdditionalFields);
+            foreach (var property in definedProperties.Where(p => p.Value != null))
+            {
+                properties.AddIfNotExist(property.Key, property.Value);
+            }
+            return properties;
         }
         catch (Exception ex)
         {
             _logger.LogWarning(ex, "Error getting property providers. Logging without additional properties.");
             return new Dictionary<string, object?>(AdditionalFields);
         }
     }

209-225: Add XML documentation for exception formatting methods

Adding XML documentation would improve maintainability and help other developers understand the formatting pattern.

+    /// <summary>
+    /// Formats an exception and its inner exceptions into a hierarchical string representation.
+    /// </summary>
+    /// <param name="exception">The exception to format.</param>
+    /// <returns>A formatted string containing the exception hierarchy.</returns>
     internal static string ToFormattedstring(this Exception exception)

9-9: Add XML documentation for public class

Public types should be documented according to .NET guidelines.

+/// <summary>
+/// Provides structured logging capabilities with support for additional properties and exception formatting.
+/// </summary>
+/// <typeparam name="T">The type associated with the logger instance.</typeparam>
 public sealed class LoggerWrapper<T> : ILogger<T>
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c512e47 and 0c3049d.

📒 Files selected for processing (5)
  • src/Arc4u.Diagnostics/Logging/LoggerWrapper.cs (1 hunks)
  • src/Arc4u.Diagnostics/Logging/LoggerWrapperContext.cs (1 hunks)
  • src/Arc4u.Diagnostics/Logging/LoggerWrapperExtensions.cs (1 hunks)
  • src/Arc4u.Diagnostics/LoggingConstants.cs (1 hunks)
  • src/Arc4u.sln (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/Arc4u.Diagnostics/Logging/LoggerWrapperExtensions.cs
🔇 Additional comments (4)
src/Arc4u.Diagnostics/LoggingConstants.cs (1)

22-23: LGTM!

The new constant UnwrappedException follows the established naming convention and aligns with the structured logging objectives.

src/Arc4u.Diagnostics/Logging/LoggerWrapper.cs (1)

53-58: 🛠️ Refactor suggestion

Consider caching the scope during initialization

Creating a new scope for each provider retrieval could lead to resource leaks. Consider initializing providers during construction.

-        _providers = new Lazy<IReadOnlyList<IAddPropertiesToLog>>(() =>
-        {
-            using var scope = LoggerWrapperContext.CreateScope();
-            return scope.ServiceProvider.GetServices<IAddPropertiesToLog>().ToList();
-        });
+        using var scope = LoggerWrapperContext.CreateScope();
+        _providers = new Lazy<IReadOnlyList<IAddPropertiesToLog>>(
+            () => scope.ServiceProvider.GetServices<IAddPropertiesToLog>().ToList());

Likely invalid or redundant comment.

src/Arc4u.sln (2)

101-104: LGTM! Good solution organization.

The addition of "src" and "tests" solution folders improves the project structure by clearly separating source code from test code, following standard .NET solution organization practices.


963-1006: Project organization looks good, verify no orphaned projects.

The organization of projects under "src" and "tests" folders is logical and well-structured. Let's verify that all projects in the solution are properly organized.

✅ Verification successful

Project organization is correctly structured

All projects are properly organized in the solution structure:

  • 39 projects are correctly placed under the "src" folder ({B9419F5B-9CF3-4247-9929-7AF2362FD8E3})
  • 1 test project (Arc4u.UnitTest) is correctly placed under the "tests" folder ({E8E0B4BA-5659-40E6-94C8-CA6D9F339101})
  • No orphaned projects found - all projects in the solution are assigned to a folder
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for any projects that are not organized under solution folders

# Get all project GUIDs from the solution
echo "Projects in solution:"
rg -o '{[A-F0-9-]{36}}' src/Arc4u.sln | sort | uniq

echo -e "\nProjects organized in solution folders:"
rg -A 1 'NestedProjects = preSolution' -A 50 src/Arc4u.sln | rg '{[A-F0-9-]{36}}'

# Note: Compare the outputs manually to identify any projects that appear in the first list
# but not in the second list (these would be orphaned projects not organized in any folder)

Length of output: 874


Script:

#!/bin/bash
# Description: Check for any projects that are not organized under solution folders

# Get all project GUIDs from the solution
echo "Projects in solution:"
rg "\{[A-F0-9]{8}-[A-F0-9]{4}-[A-F0-9]{4}-[A-F0-9]{4}-[A-F0-9]{12}\}" src/Arc4u.sln | sort | uniq

echo -e "\nProjects organized in solution folders:"
rg "NestedProjects" -A 50 src/Arc4u.sln | rg "\{[A-F0-9]{8}-[A-F0-9]{4}-[A-F0-9]{4}-[A-F0-9]{4}-[A-F0-9]{12}\}"

Length of output: 77829


Script:

#!/bin/bash
# Get all project GUIDs and their organization
echo "Checking project organization..."

# Extract all project GUIDs from Project lines
echo "All projects in solution:"
rg "^Project.*= \"{.*\"" src/Arc4u.sln | cut -d'"' -f4

echo -e "\nProjects organized in folders:"
rg "^\t\t{.*} = {.*}$" src/Arc4u.sln

Length of output: 526


Script:

#!/bin/bash
# Get all project GUIDs and their organization
echo "Checking project organization..."

# Extract all project GUIDs from Project lines
echo "All projects in solution:"
rg "^Project.*= \".*\"" src/Arc4u.sln

echo -e "\nProjects organized in folders:"
rg "^\s+\{[A-F0-9-]+\}\s+=\s+\{[A-F0-9-]+\}" src/Arc4u.sln

Length of output: 11238

Comment on lines 22 to 25
if (_scopeFactory.IsValueCreated)
{
throw new InvalidOperationException("LoggerContext not initialized");
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Critical: Logic error in CreateScope method

The condition is reversed. The method throws when the factory IS created, but it should throw when it's NOT created.

Apply this fix:

-        if (_scopeFactory.IsValueCreated)
+        if (!_scopeFactory.IsValueCreated)
         {
             throw new InvalidOperationException("LoggerContext not initialized");
         }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (_scopeFactory.IsValueCreated)
{
throw new InvalidOperationException("LoggerContext not initialized");
}
if (!_scopeFactory.IsValueCreated)
{
throw new InvalidOperationException("LoggerContext not initialized");
}


public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter)
{
Console.WriteLine("Log<TState> is called.");
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Remove debug Console.WriteLine statement

Debug statements should not be present in production code.

-        Console.WriteLine("Log<TState> is called.");

@nicolaspierre1990
Copy link
Author

@coderabbitai review

Copy link

coderabbitai bot commented Dec 22, 2024

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (4)
src/Arc4u.Diagnostics/Logging/LoggerWrapper.cs (4)

18-18: Consider optimizing the Lazy initialization

The current initialization creates an empty list eagerly. Consider using a simpler initialization:

-    private readonly Lazy<IReadOnlyList<IAddPropertiesToLog>> _providers = new(Enumerable.Empty<IAddPropertiesToLog>().ToList());
+    private readonly Lazy<IReadOnlyList<IAddPropertiesToLog>> _providers = new();

23-36: Consider using a named constant for the fallback ProcessId value

Define a constant for better maintainability and clarity of intent:

+    private const int FALLBACK_PROCESS_ID = -1;
     internal static int ProcessId
     {
         get
         {
             try
             {
                 return Environment.ProcessId;
             }
             catch (PlatformNotSupportedException)
             {
-                return -1;
+                return FALLBACK_PROCESS_ID;
             }
         }
     }

114-120: Consider extracting timestamp format to configuration

The timestamp is currently hardcoded to UTC. Consider making the timestamp format configurable to support different timezone requirements:

+    private readonly DateTimeKind _timestampKind;
     var enrichedState = new
     {
         Caller = _caller,
-        TimeStamp = DateTime.UtcNow,
+        TimeStamp = _timestampKind == DateTimeKind.Utc ? DateTime.UtcNow : DateTime.Now,
         AdditionalFields = properties,
         Level = level
     };

204-243: Add XML documentation for exception handling utilities

Consider adding XML documentation to improve maintainability:

+    /// <summary>
+    /// Provides utilities for formatting exceptions into human-readable strings.
+    /// </summary>
     internal static class DumpException
     {
+        /// <summary>
+        /// Formats an exception and its inner exceptions into a hierarchical string representation.
+        /// </summary>
+        /// <param name="exception">The exception to format.</param>
+        /// <returns>A formatted string containing the exception hierarchy.</returns>
         internal static string ToFormattedstring(this Exception exception)
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0c3049d and b568a18.

📒 Files selected for processing (2)
  • src/Arc4u.Diagnostics/Logging/LoggerWrapper.cs (1 hunks)
  • src/Arc4u.Diagnostics/Logging/LoggerWrapperContext.cs (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/Arc4u.Diagnostics/Logging/LoggerWrapperContext.cs
🔇 Additional comments (2)
src/Arc4u.Diagnostics/Logging/LoggerWrapper.cs (2)

38-83: Well-structured logging implementation!

The implementation follows Microsoft's recommended practices:

  • Proper use of LoggerMessage.Define for high-performance logging
  • Well-organized event IDs with clear categories
  • Proper null validation in constructor

1-244: Verify test coverage for edge cases

While the implementation is solid, please ensure comprehensive test coverage for:

  • Platform-specific ProcessId handling
  • Exception formatting with deep hierarchies
  • Memory usage with large numbers of additional properties
  • Thread safety of the logging operations

src/Arc4u.Diagnostics/Logging/LoggerWrapper.cs Outdated Show resolved Hide resolved
src/Arc4u.Diagnostics/Logging/LoggerWrapper.cs Outdated Show resolved Hide resolved
@nicolaspierre1990
Copy link
Author

@GFlisch any comments ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant