In our project, we have some class properties that we want to censor when being logged. Each property that is to be censored is attributed with a custom-defined attribute; let's call it CensorMeAttribute
.
Hence, any property we want to censor may be implemented as:
[CensorMe]
public string Secret { get; set; }
We currently override NLog's built-in JSON formatter/converter in order to censor the value of the CensorMe
-attributed properties by following the guidelines provided by NLog's wiki article on How to use structured logging:
NLog.IJsonConverter
with the following signature:public class CensorJsonConverter : NLog.IJsonConverter
and the appropriate logic for our use-case.
Program.cs
, we currently override the JSON converter and enable NLog as logging provider in this static method:private static void AddNLog(HostBuilderContext context, ILoggingBuilder loggingBuilder)
{
NLog.Config.ConfigurationItemFactory.Default.JsonConverter = new CensorJsonConverter();
var config = new NLogLoggingConfiguration(context.Configuration.GetSection("NLog"));
loggingBuilder.AddNLog(config);
}
Here, the call to loggingBuilder.AddNLog(config)
is a call to ILoggingBuilder.AddNLog(LoggingConfiguration)
.
With this implementation, Secret
is logged as \"Secret\":\"***\"
rather than \"Secret\":\"actual secret value\"
. This is the desired logging output, which we want to keep.
Upon upgrading the NLog package version to ≥ v5.0, NLog.Config.ConfigurationItemFactory.Default.JsonConverter
became obsolete. The workaround message says:
Instead use LogFactory.ServiceRepository.ResolveInstance(typeof(IJsonConverter)).
I cannot find any ResolveInstance()
method on the LogFactory.ServiceRepository
property. There is the RegisterService()
method, which registers instance of singleton object for use in NLog (ServiceRepository
docs). What I hence have tried is to replace the line
NLog.[...].JsonConverter = new CensorJsonConverter();
with a call to
config.LogFactory.ServiceRepository.RegisterService([...]);
as follows:
private static void AddNLog(HostBuilderContext context, ILoggingBuilder loggingBuilder)
{
var config = new NLogLoggingConfiguration(context.Configuration.GetSection("NLog"));
config.LogFactory.ServiceRepository.RegisterService(
typeof(CensorJsonConverter),
new CensorJsonConverter());
loggingBuilder.AddNLog(config);
}
With this implementation, Secret
is logged as \"Secret\":\"actual secret value\"
. This is not the desired logging output.
How do I override the built-in JSON converter without setting the now (NLog ≥ v5.0) obsolete NLog.Config.ConfigurationItemFactory.Default.JsonConverter
?
Or is there perhaps another way to achieve a similar handling of the CensorMe
-attributed properties, with or without the use of NLog?
You can do this:
private static void AddNLog(HostBuilderContext context, ILoggingBuilder loggingBuilder)
{
LogManager.Setup()
.SetupExtensions(e => e.RegisterNLogWeb().RegisterConfigSettings(context.Configuration))
.SetupSerialization(s => s.RegisterJsonConverter(new CensorJsonConverter())
.LoadConfigurationFromSection(context.Configuration);
loggingBuilder.AddNLogWeb();
}
Notice that if you wrap your secrets in a custom type that inherits from IFormattable
, then NLog will by default ignore properties and just do ToString()
(No need for custom JsonConverter)
public struct SecretValue : IFormattable
{
public string Value;
string IFormattable.ToString(string format, IFormatProvider formatProvider) => ToString();
public override string ToString() => string.IsNullOrEmpty(Value) ? "" : "*****";
}
See also: https://github.com/NLog/NLog/wiki/How-to-use-structured-logging#customize-object-reflection