File: TagProviderTests.cs
Web Access
Project: src\test\Generators\Microsoft.Gen.Logging\Generated\Microsoft.Gen.Logging.Generated.Tests.csproj (Microsoft.Gen.Logging.Generated.Tests)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Collections.Generic;
using System.Globalization;
using FluentAssertions;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Testing;
using Microsoft.Shared.Text;
using TestClasses;
using Xunit;
 
namespace Microsoft.Gen.Logging.Test;
 
public class TagProviderTests
{
    private readonly FakeLogger _logger = new();
 
    internal class ClassToBeLogged
    {
        public string MyStringProperty { get; set; } = "TestString";
        public override string ToString() => "ClassToLog string representation";
    }
 
    [Fact]
    public void LogsWithObject()
    {
        object obj = new ClassToBeLogged();
        TagProviderWithObjectExtensions.OneParam(_logger, obj);
 
        Assert.Equal(1, _logger.Collector.Count);
 
        var latestRecord = _logger.Collector.LatestRecord;
        Assert.Equal(LogLevel.Warning, latestRecord.Level);
        Assert.Equal($"Custom provided properties for {obj}.", latestRecord.Message);
 
        var expectedState = new Dictionary<string, string?>
        {
            ["Param"] = obj.ToString(),
            ["param.ToString"] = obj + " ProvidePropertiesCall",
            ["{OriginalFormat}"] = "Custom provided properties for {Param}."
        };
 
        latestRecord.StructuredState.Should().NotBeNull().And.Equal(expectedState);
    }
 
    [Fact]
    public void LogsWhenNonStaticClass()
    {
        const string StringParamValue = "Value for a string";
 
        var classToLog = new ClassToLog { MyIntProperty = 0 };
        new NonStaticTestClass(_logger).LogPropertiesWithProvider(StringParamValue, classToLog);
 
        Assert.Equal(1, _logger.Collector.Count);
        var latestRecord = _logger.Collector.LatestRecord;
        Assert.Equal(LogLevel.Information, latestRecord.Level);
        Assert.Equal($"LogProperties with provider: {StringParamValue}, {classToLog}", latestRecord.Message);
 
        var expectedState = new Dictionary<string, string?>
        {
            ["P0"] = StringParamValue,
            ["P1"] = classToLog.ToString(),
            ["p1.MyIntProperty"] = classToLog.MyIntProperty.ToInvariantString(),
            ["p1.Custom_property_name"] = classToLog.MyStringProperty,
            ["{OriginalFormat}"] = "LogProperties with provider: {P0}, {P1}"
        };
 
        latestRecord.StructuredState.Should().NotBeNull().And.Equal(expectedState);
    }
 
    [Fact]
    public void LogsWhenDefaultAttrCtorInNonStaticClass()
    {
        const string StringParamValue = "Value for a string";
 
        var classToLog = new ClassToLog { MyIntProperty = ushort.MaxValue };
        new NonStaticTestClass(_logger).DefaultAttrCtorLogPropertiesWithProvider(LogLevel.Debug, StringParamValue, classToLog);
 
        Assert.Equal(1, _logger.Collector.Count);
        var latestRecord = _logger.Collector.LatestRecord;
        Assert.Null(latestRecord.Exception);
        Assert.NotEqual(0, latestRecord.Id.Id);
        Assert.Equal(LogLevel.Debug, latestRecord.Level);
        Assert.Equal(string.Empty, latestRecord.Message);
 
        var expectedState = new Dictionary<string, string?>
        {
            ["p0"] = StringParamValue,
            ["p1.MyIntProperty"] = classToLog.MyIntProperty.ToInvariantString(),
            ["p1.Custom_property_name"] = classToLog.MyStringProperty
        };
 
        latestRecord.StructuredState.Should().NotBeNull().And.Equal(expectedState);
    }
 
    [Fact]
    public void LogsWhenDefaultAttrCtorInStaticClass()
    {
        var classToLog = new ClassToLog { MyIntProperty = ushort.MaxValue };
        TagProviderExtensions.DefaultAttributeCtor(_logger, LogLevel.Trace, classToLog);
 
        Assert.Equal(1, _logger.Collector.Count);
        var latestRecord = _logger.Collector.LatestRecord;
        Assert.Null(latestRecord.Exception);
        Assert.NotEqual(0, latestRecord.Id.Id);
        Assert.Equal(LogLevel.Trace, latestRecord.Level);
        Assert.Equal(string.Empty, latestRecord.Message);
 
        var expectedState = new Dictionary<string, string?>
        {
            ["param.MyIntProperty"] = classToLog.MyIntProperty.ToInvariantString(),
            ["param.Custom_property_name"] = classToLog.MyStringProperty
        };
 
        latestRecord.StructuredState.Should().NotBeNull().And.Equal(expectedState);
    }
 
    [Fact]
    public void LogsWithNullable()
    {
        TagProviderExtensions.Nullable(_logger, LogLevel.Trace, null);
 
        Assert.Equal(1, _logger.Collector.Count);
        var latestRecord = _logger.Collector.LatestRecord;
        Assert.Null(latestRecord.Exception);
        Assert.NotEqual(0, latestRecord.Id.Id);
        Assert.Equal(LogLevel.Trace, latestRecord.Level);
        Assert.Equal(string.Empty, latestRecord.Message);
        Assert.Empty(latestRecord.StructuredState!);
 
        _logger.Collector.Clear();
        TagProviderExtensions.Nullable(_logger, LogLevel.Trace, 42);
 
        Assert.Equal(1, _logger.Collector.Count);
        latestRecord = _logger.Collector.LatestRecord;
        Assert.Null(latestRecord.Exception);
        Assert.NotEqual(0, latestRecord.Id.Id);
        Assert.Equal(LogLevel.Trace, latestRecord.Level);
        Assert.Equal(string.Empty, latestRecord.Message);
 
        var expectedState = new Dictionary<string, string?>
        {
            ["param.P1"] = "42",
        };
 
        latestRecord.StructuredState.Should().NotBeNull().And.Equal(expectedState);
    }
 
    [Fact]
    public void LogsWhenOmitParamNameIsTrue()
    {
        var props = new LogPropertiesOmitParameterNameExtensions.MyProps
        {
            P0 = 42,
            P1 = "foo"
        };
 
        LogPropertiesOmitParameterNameExtensions.M1(_logger, props);
 
        Assert.Equal(1, _logger.Collector.Count);
        var latestRecord = _logger.Collector.LatestRecord;
        Assert.Equal(LogLevel.Warning, latestRecord.Level);
        Assert.Equal(string.Empty, latestRecord.Message);
 
        var state = latestRecord.StructuredState!;
        Assert.Equal(2, state.Count);
        Assert.Equal("P0", state[0].Key);
        Assert.Equal(props.P0.ToString(CultureInfo.InvariantCulture), state[0].Value);
        Assert.Equal("Custom_property_name", state[1].Key);
        Assert.Equal(props.P1, state[1].Value);
    }
 
    [Fact]
    public void LogsWhenOmitParamNameIsTrueWithDefaultAttrCtor()
    {
        var props = new LogPropertiesOmitParameterNameExtensions.MyProps
        {
            P0 = 42,
            P1 = "foo"
        };
 
        LogPropertiesOmitParameterNameExtensions.M3(_logger, LogLevel.Error, props);
 
        Assert.Equal(1, _logger.Collector.Count);
        var latestRecord = _logger.Collector.LatestRecord;
        Assert.Null(latestRecord.Exception);
        Assert.NotEqual(0, latestRecord.Id.Id);
        Assert.Equal(LogLevel.Error, latestRecord.Level);
        Assert.Equal(string.Empty, latestRecord.Message);
 
        var state = latestRecord.StructuredState!;
        Assert.Equal(2, state.Count);
        Assert.Equal("P0", state[0].Key);
        Assert.Equal(props.P0.ToString(CultureInfo.InvariantCulture), state[0].Value);
        Assert.Equal("Custom_property_name", state[1].Key);
        Assert.Equal(props.P1, state[1].Value);
    }
 
    [Fact]
    public void LogsWithNullObject()
    {
        TagProviderWithObjectExtensions.OneParam(_logger, null!);
 
        Assert.Equal(1, _logger.Collector.Count);
 
        var latestRecord = _logger.Collector.LatestRecord;
        Assert.Equal(LogLevel.Warning, latestRecord.Level);
        Assert.Equal("Custom provided properties for (null).", latestRecord.Message);
 
        var expectedState = new Dictionary<string, string?>
        {
            ["Param"] = null,
            ["{OriginalFormat}"] = "Custom provided properties for {Param}."
        };
 
        latestRecord.StructuredState.Should().NotBeNull().And.Equal(expectedState);
    }
 
    [Fact]
    public void LogsWhenNullStronglyTypedObject()
    {
        TagProviderExtensions.LogMethodCustomPropsProvider(_logger, null!);
 
        Assert.Equal(1, _logger.Collector.Count);
        var latestRecord = _logger.Collector.LatestRecord;
        Assert.Equal(LogLevel.Warning, latestRecord.Level);
        Assert.Equal("Custom provided properties for (null).", latestRecord.Message);
 
        var expectedState = new Dictionary<string, string?>
        {
            ["Param"] = null,
            ["{OriginalFormat}"] = "Custom provided properties for {Param}."
        };
 
        latestRecord.StructuredState.Should().NotBeNull().And.Equal(expectedState);
    }
 
    [Fact]
    public void LogsWhenNonNullStronglyTypedObject()
    {
        var classToLog = new ClassToLog { MyIntProperty = 0 };
        TagProviderExtensions.LogMethodCustomPropsProvider(_logger, classToLog);
 
        Assert.Equal(1, _logger.Collector.Count);
        var latestRecord = _logger.Collector.LatestRecord;
        Assert.Equal(LogLevel.Warning, latestRecord.Level);
        Assert.Equal($"Custom provided properties for {classToLog}.", latestRecord.Message);
 
        var expectedState = new Dictionary<string, string?>
        {
            ["Param"] = classToLog.ToString(),
            ["param.MyIntProperty"] = classToLog.MyIntProperty.ToInvariantString(),
            ["param.Custom_property_name"] = classToLog.MyStringProperty,
            ["{OriginalFormat}"] = "Custom provided properties for {Param}."
        };
 
        latestRecord.StructuredState.Should().NotBeNull().And.Equal(expectedState);
    }
 
    [Fact]
    public void LogsWhenStruct()
    {
        var structToLog = new StructToLog { MyIntProperty = 0 };
        TagProviderExtensions.LogMethodCustomPropsProviderStruct(_logger, structToLog);
 
        Assert.Equal(1, _logger.Collector.Count);
        var latestRecord = _logger.Collector.LatestRecord;
        Assert.Equal(LogLevel.Debug, latestRecord.Level);
        Assert.Equal($"Custom provided properties for struct.", latestRecord.Message);
 
        var expectedState = new Dictionary<string, string?>
        {
            ["param.MyIntProperty"] = structToLog.MyIntProperty.ToInvariantString(),
            ["param.Custom_property_name"] = structToLog.MyStringProperty,
            ["{OriginalFormat}"] = "Custom provided properties for struct."
        };
 
        latestRecord.StructuredState.Should().NotBeNull().And.Equal(expectedState);
    }
 
    [Fact]
    public void LogsWhenInterface()
    {
        IInterfaceToLog interfaceToLog = new InterfaceImpl { MyIntProperty = 0 };
        TagProviderExtensions.LogMethodCustomPropsProviderInterface(_logger, interfaceToLog);
 
        Assert.Equal(1, _logger.Collector.Count);
        var latestRecord = _logger.Collector.LatestRecord;
        Assert.Equal(LogLevel.Information, latestRecord.Level);
        Assert.Equal($"Custom provided properties for interface.", latestRecord.Message);
 
        var expectedState = new Dictionary<string, string?>
        {
            ["param.MyIntProperty"] = interfaceToLog.MyIntProperty.ToInvariantString(),
            ["param.Custom_property_name"] = interfaceToLog.MyStringProperty,
            ["{OriginalFormat}"] = "Custom provided properties for interface."
        };
 
        latestRecord.StructuredState.Should().NotBeNull().And.Equal(expectedState);
    }
 
    [Fact]
    public void LogsWhenEnumerable()
    {
        var a = new[] { "Zero", "One", "Two" };
        TagProviderExtensions.Enumerable(_logger, LogLevel.Debug, a);
 
        Assert.Equal(1, _logger.Collector.Count);
        var latestRecord = _logger.Collector.LatestRecord;
        Assert.Equal(LogLevel.Debug, latestRecord.Level);
 
        var expectedState = new Dictionary<string, string?>
        {
            ["things.Foo0"] = a[0],
            ["things.Foo1"] = a[1],
            ["things.Foo2"] = a[2],
        };
 
        latestRecord.StructuredState.Should().NotBeNull().And.Equal(expectedState);
    }
 
    [Fact]
    public void LogsWhenProviderCombinedWithLogProperties()
    {
        var classToLog = new ClassToLog { MyIntProperty = 0 };
        TagProviderExtensions.LogMethodCombinePropsProvider(_logger, classToLog, classToLog);
 
        Assert.Equal(1, _logger.Collector.Count);
        var latestRecord = _logger.Collector.LatestRecord;
        Assert.Equal(LogLevel.Warning, latestRecord.Level);
        Assert.Equal("No params.", latestRecord.Message);
 
        var expectedState = new Dictionary<string, string?>
        {
            ["param1.MyIntProperty"] = classToLog.MyIntProperty.ToInvariantString(),
            ["param1.MyStringProperty"] = classToLog.MyStringProperty,
            ["param1.AnotherStringProperty"] = classToLog.AnotherStringProperty,
            ["param2.MyIntProperty"] = classToLog.MyIntProperty.ToInvariantString(),
            ["param2.Custom_property_name"] = classToLog.MyStringProperty,
            ["{OriginalFormat}"] = "No params."
        };
 
        latestRecord.StructuredState.Should().NotBeNull().And.Equal(expectedState);
    }
 
    [Fact]
    public void LogsTwoStronglyTypedParams()
    {
        const string StringParamValue = "Value for a string";
 
        var classToLog1 = new ClassToLog { MyIntProperty = 1 };
        var classToLog2 = new ClassToLog { MyIntProperty = -1 };
        TagProviderExtensions.LogMethodCustomPropsProviderTwoParams(_logger, StringParamValue, classToLog1, classToLog2);
 
        Assert.Equal(1, _logger.Collector.Count);
        var latestRecord = _logger.Collector.LatestRecord;
        Assert.Equal(LogLevel.Warning, latestRecord.Level);
        Assert.Equal($"Custom provided properties for both complex params and {StringParamValue}.", latestRecord.Message);
 
        var expectedState = new Dictionary<string, string?>
        {
            ["StringParam"] = StringParamValue,
            ["param.MyIntProperty"] = classToLog1.MyIntProperty.ToInvariantString(),
            ["param.Custom_property_name"] = classToLog1.MyStringProperty,
            ["param2.Another_property_name"] = classToLog2.MyStringProperty.ToUpperInvariant(),
            ["param2.MyIntProperty_test"] = classToLog2.MyIntProperty.ToInvariantString(),
            ["{OriginalFormat}"] = "Custom provided properties for both complex params and {StringParam}."
        };
 
        latestRecord.StructuredState.Should().NotBeNull().And.Equal(expectedState);
 
        // Changing object and logging again to test that IResettable for props provider works correctly:
        classToLog1.MyIntProperty = int.MaxValue;
        classToLog2.MyIntProperty = int.MinValue;
        TagProviderExtensions.LogMethodCustomPropsProviderTwoParams(_logger, StringParamValue, classToLog1, classToLog2);
 
        Assert.Equal(2, _logger.Collector.Count);
        expectedState["param.MyIntProperty"] = classToLog1.MyIntProperty.ToInvariantString();
        expectedState["param2.MyIntProperty_test"] = classToLog2.MyIntProperty.ToInvariantString();
        _logger.Collector.LatestRecord.StructuredState.Should().NotBeNull().And.Equal(expectedState);
    }
 
    [Fact]
    public void LogsTwoObjectParams()
    {
        const string StringParamValue = "ValueForAString";
 
        object obj1 = new ClassToBeLogged();
        object obj2 = new ClassToBeLogged();
        TagProviderWithObjectExtensions.TwoParams(_logger, StringParamValue, obj1, obj2);
 
        Assert.Equal(1, _logger.Collector.Count);
 
        var latestRecord = _logger.Collector.LatestRecord;
        Assert.Equal(LogLevel.Warning, latestRecord.Level);
        Assert.Equal($"Custom provided properties for both complex params and {StringParamValue}.", latestRecord.Message);
 
        var expectedState = new Dictionary<string, string?>
        {
            ["StringParam"] = StringParamValue,
            ["param.ToString"] = obj1 + " ProvidePropertiesCall",
            ["param2.Type"] = obj2.GetType().ToString(),
            ["param2.ToString"] = obj2 + " ProvideOtherPropertiesCall",
            ["{OriginalFormat}"] = "Custom provided properties for both complex params and {StringParam}."
        };
 
        latestRecord.StructuredState.Should().NotBeNull().And.Equal(expectedState);
    }
 
    [Fact]
    public void LogsTwoNullObjectParams()
    {
        const string StringParamValue = "ValueForAString";
 
        TagProviderWithObjectExtensions.TwoParams(_logger, StringParamValue, null!, null!);
 
        Assert.Equal(1, _logger.Collector.Count);
 
        var latestRecord = _logger.Collector.LatestRecord;
        Assert.Equal(LogLevel.Warning, latestRecord.Level);
        Assert.Equal($"Custom provided properties for both complex params and {StringParamValue}.", latestRecord.Message);
 
        var expectedState = new Dictionary<string, string?>
        {
            ["StringParam"] = StringParamValue,
            ["param2.Type"] = null,
            ["param2.ToString"] = " ProvideOtherPropertiesCall",
            ["{OriginalFormat}"] = "Custom provided properties for both complex params and {StringParam}."
        };
 
        latestRecord.StructuredState.Should().NotBeNull().And.Equal(expectedState);
    }
}