问题
I have 2 NLog instances where one needs a special ValueFormatter to serialize parameters. The ValueFormatter is set with this code :
NLog.Config.ConfigurationItemFactory.Default.ValueFormatter = new NLogValueFormatter();
So as you can see it will be applied to all loggers. I can´t find any property on the NLogger itself that might take a ValueFormatter.
Is there any way to bind this ValueFormatter just to one of the loggers?
Edit 1:
private CommunicationFormatProvider provider = new CommunicationFormatProvider();
public void LogCommunication(string message, params object[] args)
{
_comLogger.Log(LogLevel.Info, provider, message, args);
}
public class CommunicationFormatProvider : IFormatProvider, ICustomFormatter
{
public string Format(string format, object arg, IFormatProvider formatProvider)
{
StringBuilder strBuilder = new StringBuilder();
strBuilder.Append(format);
var myTarget = LogManager.Configuration.FindTargetByName("communicationTarget");
myTarget = ((myTarget as NLog.Targets.Wrappers.WrapperTargetBase)?.WrappedTarget) ?? myTarget;
var jsonLayout = (myTarget as NLog.Targets.TargetWithLayout)?.Layout as NLog.Layouts.JsonLayout;
if (jsonLayout?.MaxRecursionLimit > 0)
strBuilder.Append(JsonConvert.SerializeObject(arg, new JsonSerializerSettings() { MaxDepth = jsonLayout?.MaxRecursionLimit }));
return strBuilder.ToString();
}
object IFormatProvider.GetFormat(Type formatType)
{
return (formatType == typeof(ICustomFormatter)) ? this : null;
}
}
EDIT 2 :
public bool FormatValue(object value, string format, CaptureType captureType, IFormatProvider formatProvider, StringBuilder builder)
{
if (value.GetType() == typeof(LogData))
return false;
builder.Append(format);
try
{
var myTarget = LogManager.Configuration.FindTargetByName("communicationTarget");
myTarget = ((myTarget as NLog.Targets.Wrappers.WrapperTargetBase)?.WrappedTarget) ?? myTarget;
var jsonLayout = (myTarget as NLog.Targets.TargetWithLayout)?.Layout as NLog.Layouts.JsonLayout;
if (jsonLayout?.MaxRecursionLimit > 0)
{
var jsonSettings = new JsonSerializerSettings() { MaxDepth = jsonLayout?.MaxRecursionLimit };
using (var stringWriter = new StringWriter())
{
using (var jsonWriter = new JsonTextWriterMaxDepth(stringWriter, jsonSettings))
JsonSerializer.Create(jsonSettings).Serialize(jsonWriter, value);
builder.Append(stringWriter.ToString());
}
}
else
value = null;
}
catch(Exception ex)
{
builder.Append($"Failed to serlize {value.GetType()} : {ex.ToString()}");
}
return true;
}
JSON Serializer from here : json.net limit maxdepth when serializing
回答1:
NLog JsonLayout has two options that are important for serialization of LogEvent Properties:
- IncludeAllProperties
- MaxRecursionLimit
The default parameters is like this:
<layout type="JsonLayout" includeAllProperties="false" maxRecursionLimit="0">
<attribute name="time" layout="${longdate}" />
<attribute name="level" layout="${level}"/>
<attribute name="message" layout="${message}" />
</layout>
But you can also activate inclusion of LogEvent properties like this:
<layout type="JsonLayout" includeAllProperties="true" maxRecursionLimit="10">
<attribute name="time" layout="${longdate}" />
<attribute name="level" layout="${level}"/>
<attribute name="message" layout="${message}" />
</layout>
If you have a custom object like this:
public class Planet
{
public string Name { get; set; }
public string PlanetType { get; set; }
public override string ToString()
{
return Name; // Will be called in normal message-formatting
}
}
Then you can log the object like this:
logger.Info("Hello {World}", new Planet() { Name = "Earth", PlanetType = "Water Planet" });
The default JsonLayout will just include the default attributes, where message
-attribute says Hello Earth
.
But the JsonLayout with includeAllProperties="true"
will include any additional LogEvent-properties. And will include World
-propety that has been serialized fully.
The idea is that one should not care about how NLog Targets are configured when logging. It is the Logging-Rules + Target-Configuration + Layout-Configuration that decides how things should be finally written.
If you really want the object to be serialized into ${message}
, then you can also do this:
logger.Info("Hello {@World}", new Planet() { Name = "Earth", PlanetType = "Water Planet" });
If you don't want the LogEvent-properties mixed together with your default properties, then you can do this:
<layout type="JsonLayout" maxRecursionLimit="10">
<attribute name="time" layout="${longdate}" />
<attribute name="level" layout="${level}"/>
<attribute name="message" layout="${message}" />
<attribute name="properties" encode="false">
<layout type="JsonLayout" includeAllProperties="true" maxRecursionLimit="10" />
</attribute>
</layout>
If you have an object that mixes fields with properties, then you can tell NLog to perform custom reflection for that type:
LogManager.Setup().SetupSerialization(s =>
s.RegisterObjectTransformation<GetEntityViewRequest>(obj =>
return Newtonsoft.Json.Linq.JToken.FromObject(obj) // Lazy and slow
)
);
回答2:
You could make sure that the object to be formatted inherits from IFormattable
. https://docs.microsoft.com/en-us/dotnet/api/system.iformattable
Then you can implement different formatters, where each Logger can choose their favorite when logging your special object.
Similar to this formatter that provides special formatting of Exception-objects:
https://github.com/NLog/NLog/blob/dev/src/NLog/Internal/ExceptionMessageFormatProvider.cs
Then just make sure to use the IFormatProvider formatProvider
-parameter when calling methods on the Logger, when logging your special object.
I guess you could make own custom NLogValueFormatter
perform the default formatting. And the Logger that needs special additional formattting can then provide its own custom IFormatProvider
.
Update Answer Do you know that you can force NLog to automatically perform JsonSerialization of an object using the message-template:
This will perform the default ToString-operation:
logger.Info("Hello {World}", new { Name = "Earth", Type = "Water Planet" });
This will signal that the object is safe for reflection (Notice the @
):
logger.Info("Hello {@World}", new { Name = "Earth", Type = "Water Planet" });
See also: https://github.com/NLog/NLog/wiki/How-to-use-structured-logging
来源:https://stackoverflow.com/questions/61281449/set-valueformatter-for-specific-nlog-instance