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
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using Microsoft.Extensions.Logging;

namespace BasicUsage.Dynamic;

// <DynamicLogLevel>
public static partial class Log
{
[LoggerMessage(
EventId = 0,
Message = "Could not open socket to `{HostName}`")]
public static partial void CouldNotOpenSocket(
ILogger logger,
LogLevel level,
string hostName);
}
// </DynamicLogLevel>
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using Microsoft.Extensions.Logging;

namespace BasicUsage.Extension;

// <ExtensionLogMethod>
public static partial class Log
{
[LoggerMessage(
EventId = 0,
Level = LogLevel.Critical,
Message = "Could not open socket to `{HostName}`")]
public static partial void CouldNotOpenSocket(
this ILogger logger, string hostName);
}
// </ExtensionLogMethod>
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using Microsoft.Extensions.Logging;

namespace BasicUsage.InstanceField;

// <InstanceLogWithField>
public partial class InstanceLoggingExample
{
private readonly ILogger _logger;

public InstanceLoggingExample(ILogger logger)
{
_logger = logger;
}

[LoggerMessage(
EventId = 0,
Level = LogLevel.Critical,
Message = "Could not open socket to `{HostName}`")]
public partial void CouldNotOpenSocket(string hostName);
}
// </InstanceLogWithField>
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using Microsoft.Extensions.Logging;

namespace BasicUsage.PrimaryConstructor;

// <InstanceLogWithPrimaryCtor>
public partial class InstanceLoggingExample(ILogger logger)
{
[LoggerMessage(
EventId = 0,
Level = LogLevel.Critical,
Message = "Could not open socket to `{HostName}`")]
public partial void CouldNotOpenSocket(string hostName);
}
// </InstanceLogWithPrimaryCtor>
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using Microsoft.Extensions.Logging;

namespace BasicUsage.Static;

// <StaticLogMethod>
public static partial class Log
{
[LoggerMessage(
EventId = 0,
Level = LogLevel.Critical,
Message = "Could not open socket to `{HostName}`")]
public static partial void CouldNotOpenSocket(
ILogger logger, string hostName);
}
// </StaticLogMethod>
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using Microsoft.Extensions.Logging;

namespace LogMethodAnatomy;

// <CaseInsensitiveNames>
public partial class LoggingExample
{
private readonly ILogger _logger;

public LoggingExample(ILogger logger)
{
_logger = logger;
}

[LoggerMessage(
EventId = 10,
Level = LogLevel.Information,
Message = "Welcome to {City} {Province}!")]
public partial void LogMethodSupportsPascalCasingOfNames(
string city, string province);

public void TestLogging()
{
LogMethodSupportsPascalCasingOfNames("Vancouver", "BC");
}
}
// </CaseInsensitiveNames>
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using Microsoft.Extensions.Logging;

namespace LogMethodAnatomy;

public static partial class IndeterminateOrderLog
{
// <IndeterminateParameterOrder>
[LoggerMessage(
EventId = 110,
Level = LogLevel.Debug,
Message = "M1 {Ex3} {Ex2}")]
static partial void LogMethod(
Exception ex,
Exception ex2,
Exception ex3,
ILogger logger);
// </IndeterminateParameterOrder>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using Microsoft.Extensions.Logging;

namespace LogMethodAnatomy;

public static partial class LogMethods
{
// <ValidLogMethod>
// This is a valid attribute usage
[LoggerMessage(
EventId = 110, Level = LogLevel.Debug, Message = "M1 {Ex3} {Ex2}")]
public static partial void ValidLogMethod(
ILogger logger,
Exception ex,
Exception ex2,
Exception ex3);
// </ValidLogMethod>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
using Microsoft.Extensions.Logging;

namespace MoreLoggingExamples;

// <MoreLoggingExamples>
public partial class LoggingSample
{
private readonly ILogger _logger;

public LoggingSample(ILogger logger)
{
_logger = logger;
}

[LoggerMessage(
EventId = 20,
Level = LogLevel.Critical,
Message = "Value is {Value:E}")]
public static partial void UsingFormatSpecifier(
ILogger logger, double value);

[LoggerMessage(
EventId = 9,
Level = LogLevel.Trace,
Message = "Fixed message",
EventName = "CustomEventName")]
public partial void LogWithCustomEventName();

[LoggerMessage(
EventId = 10,
Message = "Welcome to {City} {Province}!")]
public partial void LogWithDynamicLogLevel(
string city, LogLevel level, string province);

public void TestLogging()
{
LogWithCustomEventName();

LogWithDynamicLogLevel("Vancouver", LogLevel.Warning, "BC");
LogWithDynamicLogLevel("Vancouver", LogLevel.Information, "BC");

UsingFormatSpecifier(_logger, 12345.6789);
}
}
// </MoreLoggingExamples>
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Console.WriteLine("Hello, World!");
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
using Microsoft.Extensions.Compliance.Classification;
using Microsoft.Extensions.Compliance.Redaction;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;

namespace RedactingSensitiveInformation;
Comment thread
adegeo marked this conversation as resolved.

// MyTaxonomyClassifications.Private serves two roles:
// - As a DataClassification value passed to SetRedactor().
// - As [MyTaxonomyClassifications.Private] on logging parameters, resolved via the
// nested PrivateAttribute class (C# drops the "Attribute" suffix in attribute syntax).
public static class MyTaxonomyClassifications
{
private static string Name => "MyTaxonomy";

public static DataClassification Private { get; } = new(Name, nameof(Private));

// Accessible as [MyTaxonomyClassifications.Private] due to C# attribute name convention.
public sealed class PrivateAttribute : DataClassificationAttribute
{
public PrivateAttribute() : base(MyTaxonomyClassifications.Private) { }
}
}

public sealed class StarRedactor : Redactor
{
private const string Stars = "*****";

public override int GetRedactedLength(ReadOnlySpan<char> input) => Stars.Length;

public override int Redact(ReadOnlySpan<char> source, Span<char> destination)
{
Stars.CopyTo(destination);
return Stars.Length;
}
}

// <LogPrivateInformation>
public static partial class LogRedactionExample
{
[LoggerMessage(0, LogLevel.Information, "User SSN: {SSN}")]
public static partial void LogPrivateInformation(
this ILogger logger,
[MyTaxonomyClassifications.Private] string SSN);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This is incorrect as reported in #54570
[MyTaxonomyClassifications.Private]

@adegeo adegeo Jul 24, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Hi @laicasaane this does work, it's resolving to PrivateAttribute, even though you type in Private. We have a compiler running as an action that compiles the code snippets and blocks the PR if it won't compile.

I think the reason why it didn't work for you was because you were working with an incomplete snippet and had to cobble it together and you were just missing the actual attribute definition.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Thanks for the information. I will try again.

}
// </LogPrivateInformation>

public static class RedactionSetupExample
{
public static void SetupServices()
{
// <RedactionSetup>
var services = new ServiceCollection();
services.AddLogging(builder =>
{
// Enable redaction.
builder.EnableRedaction();
});

services.AddRedaction(builder =>
{
// Configure redactors for your data classifications.
builder.SetRedactor<StarRedactor>(MyTaxonomyClassifications.Private);
});
// </RedactionSetup>
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
using Microsoft.Extensions.Compliance.Classification;
using Microsoft.Extensions.Compliance.Redaction;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Compliance.Redaction" Version="10.8.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.10" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="10.0.10" />
<PackageReference Include="Microsoft.Extensions.Telemetry" Version="10.8.0" />
</ItemGroup>

</Project>
Loading
Loading