I have the following NLOG target in the config file :
<target xsi:type="File" name="communicationTarget" fileName="${basedir}/logs/${shortdate}.log">
<layout xsi:type="JsonLayout" includeAllProperties="true" maxRecursionLimit="10">
<attribute name="message" layout="${message}" />
</layout>
</target>
Is there any way to read the maxRecursionLimit from C# code?
I have tried these :
LogManager.Configuration.AllTargets.Where(c => c.Name == "communicationTarget").FirstOrDefault();
LogManager.Configuration.FindTargetByName<FileTarget>("communicationTarget");
No maxRecursionLimit found so far.
Maybe something like this:
var myTarget = LogManager.Configuration.FindTargetByName("communicationTarget");
var myTarget = ((myTarget as NLog.Targets.Wrappers.WrapperTargetBase)?.WrappedTarget) ?? myTarget;
var jsonLayout = (myTarget as NLog.Targets.TargetWithLayout)?.Layout as NLog.Layouts.JsonLayout;
var maxRecursionLimit = jsonLayout?.MaxRecursionLimit;
This will also handle that you should activate <targets async="true">.
P.S. Have not tried to compile the code :)
Related
I have an application that listens for a hash input, processes that input, and serves it back. I would like to create a separate log directory for each input with the following structure:
- hashcode1
- input1.txt
- output1.txt
- log1.txt
- hashcode2
- input2.txt
- output2.txt
- log2.txt
I've looked into the NLog library but it seems the config requires a hardcoded log location, whereas my implementation requires the hashcode as an input. How can I achieve this with NLog, or any other logging library?
You can create the NLog configuration from code. This allows you to dynamically create targets in your processing loop for the appropriate log outputs:
using NLog;
using NLog.Targets;
// Init the config if we don't have a nlog.config
LogManager.Configuration = new();
// Do the work
var work = new List<string> { "5dd2ec2f", "24e1e841", "05137e70" };
work.ForEach(ProcessHash);
void ProcessHash(string hash)
{
// Create dynamic configuration and logger
var fileTarget = new FileTarget(hash) { FileName = $"{hash}/log.txt" };
LogManager.Configuration.AddRuleForAllLevels(fileTarget, hash);
LogManager.ReconfigExistingLoggers();
var logger = LogManager.GetLogger(hash);
// Process...
logger.Info($"Processing hash {hash}");
/* ... your code here ... */
// Remove the logging target
LogManager.Configuration.RemoveTarget(fileTarget.Name);
}
You can create a scope-property like this:
void ProcessHashCode(string hashInput)
{
using (NLog.MappedDiagnosticsLogicalContext.SetScoped("HashCode", hashInput))
{
NLog.LogManager.GetLogger("HashInput").Info(hashInput);
NLog.LogManager.GetCurrentClassLogger().Info("Received Hashcode");
var hashOutput = hashInput.GetHashCode();
NLog.LogManager.GetLogger("HashOuput").Info(hashOutput);
}
}
Then you can do this in NLog.config
<nlog>
<variable name="logdir" value="${mdlc:HashCode:whenEmpty=${basedir}}" />
<targets>
<target name="hashinput" type="file" filename="${logdir}/input.txt" />
<target name="hashouput" type="file" filename="${logdir}/output.txt" />
<target name="hashlog" type="file" filename="${logdir}/log.txt" />
</target>
<rules>
<logger name="HashInput" writeTo="hashinput" final="true" />
<logger name="HashOuput" writeTo="hashouput" final="true" />
<logger name="*" writeTo="hashlog" />
</rules>
</nlog>
See also: https://github.com/NLog/NLog/wiki/MDLC-Layout-Renderer
Alternative you can use Logger.WithProperty("HashCode", hashInput) like this:
void ProcessHashCode(string hashInput)
{
var inputLogger = NLog.LogManager.GetLogger("HashInput").WithProperty("HashCode", hashInput);
var outputLogger =
NLog.LogManager.GetLogger("HashOutput").WithProperty("HashCode", hashInput);
var hashLogger =
NLog.LogManager.GetCurrentClassLogger.WithProperty("HashCode", hashInput);
inputLogger.Info(hashCode);
hashLogger.Info("Received Hashcode");
var hashOutput = hashInput.GetHashCode();
outputLogger.Info(hashOutput);
}
And instead use variable like this:
<variable name="logdir" value="${event-properties:HashCode:whenEmpty=${basedir}}" />
See also: https://github.com/NLog/NLog/wiki/Context#logevent-properties
I use NLog.Web.AspNetCore 4.10.0 in my ASP Net Core 5.0 application. And I created a simple custom ResultLayoutRenderer (shortened for simplicity):
[LayoutRenderer("result-payload")]
public class ResultLayoutRenderer : AspNetLayoutRendererBase
{
protected override void DoAppend(StringBuilder builder, LogEventInfo logEvent)
{
var httpRequest = HttpContextAccessor?.HttpContext?.Request;
var result = httpRequest.Headers["Result"];
builder.Append(result);
}
}
My NLog config is the following:
<?xml version="1.0" encoding="utf-8" ?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
autoReload="true">
<!-- enable asp.net core layout renderers -->
<extensions>
<add assembly="NLog.Web.AspNetCore"/>
</extensions>
<targets>
<target xsi:type="Null" name="blackhole" />
<target name="database" xsi:type="Database"
dbProvider="Npgsql.NpgsqlConnection, Npgsql"
dbHost="${configsetting:name=NlogConnection.DbHost}"
dbDatabase="${configsetting:name=NlogConnection.Database}"
dbUserName="${configsetting:name=NlogConnection.User}"
dbPassword="${configsetting:name=NlogConnection.Password}"">
<commandText>
INSERT INTO logs (Application, Logged, Level, Message, Logger, Result)
VALUES (#Application, #Logged, #Level, #Message, #Logger, #Result);
</commandText>
<parameter name="#application" layout="AspNetCoreNlog" />
<parameter name="#logged" layout="${date}" />
<parameter name="#level" layout="${level}" />
<parameter name="#message" layout="${message}" />
<parameter name="#logger" layout="${logger}" />
<parameter name="#result" layout="${result-payload}" />
</target>
</targets>
</nlog>
I register the ResultLayoutRenderer in the Programm.cs:
public static void Main(string[] args)
{
var logger = NLogBuilder.ConfigureNLog("nlog.config")
.Setup()
.SetupExtensions(s => s.RegisterLayoutRenderer<InputDataLayout>("result-payload"))
.GetCurrentClassLogger();
..............
}
But the layout doesn't work. The NLog internal log file contains the following information:
Error Error parsing layout input-payload will be ignored. Exception: System.ArgumentException: LayoutRenderer cannot be found: 'input-payload'
at NLog.Config.Factory`2.CreateInstance(String itemName)
at NLog.Layouts.LayoutParser.GetLayoutRenderer(ConfigurationItemFactory configurationItemFactory, String name, Nullable`1 throwConfigExceptions)
You are very close. NLogBuilder.ConfigureNLog(...) is soon obsolete, so instead try this:
var logger = NLog.LogFactory.Setup()
.SetupExtensions(s =>
s.AutoLoadAssemblies(false)
.RegisterNLogWeb()
.RegisterLayoutRenderer<ResultLayoutRenderer>("result-payload"))
)
.LoadConfigurationFromFile("nlog.config")
.GetCurrentClassLogger();
See also: https://github.com/NLog/NLog.Web/blob/master/examples/ASP.NET%20Core%205/ASP.NET%20Core%205%20NLog%20Example/Program.cs
This works in my ASP.NET Core 5 Web API:
public static void Main(string[] args)
{
LayoutRenderer.Register<ElapsedTimeLayoutRenderer>("elapsed-time");
var logger = NLogBuilder.ConfigureNLog("nlog.config").GetCurrentClassLogger();
// ...
}
https://github.com/NLog/NLog/wiki/Getting-started-with-ASP.NET-Core-5
https://www.carlosjanderson.com/log-the-elapsed-time-between-log-entries-using-nlog/
I followed Diagnostic & Logging and install NLog.Config nuget package to Android & iOS project.
on Android project, Setup.cs
public override MvxLogProviderType GetDefaultLogProviderType() => MvxLogProviderType.NLog;
On Xamarin.Forms project,
private static readonly IMvxLog _logger = Mvx.IoCProvider.Resolve<IMvxLogProvider>().GetLogFor<CanvasContainer>();
_logger.Debug($"startScale: {startScale}, currentScale: {currentScale}");
I checked _logger is not null.
Also, I changed NLog.config's Build Action to Embedded Resource.
Here is Android Project's NLog.config,
<?xml version="1.0" encoding="utf-8" ?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.nlog-project.org/schemas/NLog.xsd NLog.xsd"
autoReload="true"
throwExceptions="false"
internalLogFile="c:\temp\nlog-internal.log">
<targets>
<target xsi:type="File"
name="FileTarget"
fileName="${specialfolder:folder=MyDocuments}/logs/${shortdate}.log"
layout="${longdate}|${level:uppercase=true}|${logger}|${message}" />
<target xsi:type="ColoredConsole"
name="ConsoleTarget"
encoding="utf-8"
layout="${longdate}|${level:uppercase=true}|${logger}|${message}"
useDefaultRowHighlightingRules="true" />
</targets>
<rules>
<logger name="*" minlevel="Debug" writeTo="FileTarget" />
<logger name="*" minlevel="Debug" writeTo="ConsoleTarget" />
</rules>
</nlog>
Is there anything wrong with this set up? I can not find sample or tutorial for this...
Think you need to help NLog when having NLog.config as Embedded Resource:
public override MvxLogProviderType GetDefaultLogProviderType() => MvxLogProviderType.NLog;
protected override IMvxLogProvider CreateLogProvider()
{
var nlogConfigFile = GetEmbeddedResourceStream(myAssembly, "NLog.config");
if (nlogConfigFile != null)
{
var xmlReader = System.Xml.XmlReader.Create(nlogConfigFile);
NLog.LogManager.Configuration = new XmlLoggingConfiguration(xmlReader, null);
}
return base.CreateLogProvider();
}
private static Stream GetEmbeddedResourceStream(Assembly assembly, string resourceFileName)
{
var resourcePaths = assembly.GetManifestResourceNames()
.Where(x => x.EndsWith(resourceFileName, StringComparison.OrdinalIgnoreCase))
.ToList();
if (resourcePaths.Count == 1)
{
return assembly.GetManifestResourceStream(resourcePaths.Single());
}
return null;
}
See also: https://github.com/NLog/NLog/wiki/Explicit-NLog-configuration-loading#loading-nlog-configuration-from-xamarin-resource
Our tack is c# .net 4.7 logging through NLog library to a "Graylog" GELF input which of course is storing them in an elasticsearch cluster.
The application utilizes logging heavily, but EVERYTHING is sent through as strings - no matter if it was something other than a string in code.
I want to be able send my custom logger properties as their true data types so that I can aggregate on my statistical number fields.
I DO NOT want to constrain my developers and do this with scripted fields, or pre-mapping an index in elasticsearch - in fact, graylog manages my index for me. Nor do I want to utilize graylog pipeline processor - my poor graylog does enough work. I want them to be able to send stat data as their real data types and have them mapped as "long" or "number".
A code snippet from my stuff:
public void InfoExtended2(String Message, Dictionary<string, int> extrafields, [CallerMemberName] string callerMethodName = "", [CallerFilePath] string callerFilePath = "", [CallerLineNumber] int sourceLineNumber = 0)
{
var fileName = callerFilePath.Substring(callerFilePath.LastIndexOf(#"\") + 1).Replace(".cs", "");
var caller = $"{fileName}.{callerMethodName}()Line:{sourceLineNumber}";
var logger = LogManager.GetLogger(caller);
if (!(extrafields.Count == 0))
{
foreach (var field in extrafields)
{
string mykey = field.Key.ToString();
extrafields.TryGetValue(field.Key, out int myvalue);
//be careful whats in object!
if (!logger.Properties.ContainsKey(mykey)) { logger.Properties.Add(mykey, "{#myvalue}"); } else { logger.Properties[mykey] = "{#myvalue}"; }
}
}
logger.Info()
.Message(Message)
.Property("ComeOnNow3", 87)
.Write();
// Create the LogEventInfo object
//LogEventInfo logEvent = new LogEventInfo();
// Now add the event characteristics
//logEvent.Properties["EventCode"] = 2222;
//logEvent.Level = NLog.LogLevel.Info;
//logEvent.Properties["EventCode]."]
//logEvent.Message = "My Message";
////logEvent.Exception = ex;
//logger.Log(logEvent);
//logger.Info("{#Message}", new Dictionary<string, int> { { "key1", 1 }, { "key2", 2 } }); // dict. Result: Test "key1"=1, "key2"=2
}
I've tried a few different ways there with no luck.
If I make a POST GELF call myself with JSON fields representing ints instead of strings, it works. My field is brand new to the index and when I go check the mapping it comes back as a long. I want my code to do that.
Test GELF Call:
{
"version": "1.1",
"host": "example.org",
"short_message": "A short message that helps you identify what is going on",
"full_message": "Backtrace here\n\nmore stuff",
"timestamp": 1385053862.3072,
"level": 1,
"_user_id": 9001,
"_some_info": "foo",
"_some_env_var": "bar"
}
Resulting mapping for user id:
"user_id" : {
"type" : "long"
What our nlog target currently looks like:
<targets>
<target name="gelftcp"
type="gelftcp"
facility="Custom Facility"
remoteaddress="192.168.95.15"
remoteport="12201"
layout="${longdate}${newline}type: ${level:uppercase=true}${newline}class: ${logger}${newline}stacktrace: ${stacktrace}${newline}error message: ${message}${newline}exception: ${exception:format=tostring,data:maxinnerexceptionlevel=10}">
<parameter name="logged_at" layout="${longdate}" />
<parameter name="type" layout="${level:uppercase=true}" />
<parameter name="class" layout="${logger}" />
<parameter name="CustomInt" layout="${event-properties:item=CustomInt}" as="number" />
<parameter name="stacktrace" layout="${stacktrace}" />
<parameter name="error_message" layout="${message}" />
<parameter name="ComeOnNow3" layout="${event-properties:item=ComeOnNow3} " />
<parameter name="exception" layout="${exception:format=tostring,data:maxinnerexceptionlevel=10}" />
<attribute name="eventProperties" encode="false" >
<layout type='JsonLayout' includeAllProperties="true" maxRecursionLimit="2"/>
</attribute>
<variable name="ComeOnNow2" value ="${event-context:ComeOnNow2}" />
</target>
Take it easy on me, I'm not the original author of this code. Just someone tasked with picking up slack & deploying in its current state.
UPDATE:
Tried the first suggestion. Even added the field I'm testing:
<field name="ComeOnNow3" layout="${threadid}" type="System.Int32" />
Attempted this log entry:
logger.Info()
.Message(Message)
.Property("ComeOnNow3", 87)
.Write();
My mapping still comes back as "keyword".
SOLVED:
The trick was to switch to Http instead of Tcp.
Instead of using "gelftcp". Then you can try out GelfLayout instead:
https://www.nuget.org/packages/NLog.GelfLayout
You can combine it with the TCP network-target:
<nlog>
<extensions>
<add assembly="NLog.Layouts.GelfLayout" />
</extensions>
<targets async="true">
<target type="Network" name="gelftcp" address="tcp://192.168.95.15:12201" newLine="true" lineEnding="Null">
<layout type="GelfLayout" facility="MyFacility">
<field name="threadid" layout="${threadid}" type="System.Int32" />
</layout>
</target>
</targets>
<rules>
<logger name="*" minlevel="Debug" writeTo="gelftcp" />
</rules>
</nlog>
It also has some automatic conversion logic for simple NLog LogEventInfo.Properties (Decimal + Double + Integer + Boolean)
More examples can be found here: https://github.com/farzadpanahi/NLog.GelfLayout
I have class MyLogger, where I use log4net. How can I modify my appender to save log in specific logname (I want to set it by parameter logName).
public void AddEntry(string source, string logName, string contextInfo, string message, EventLogEntryType eventType)
{
log4net.ILog Log = log4net.LogManager.GetLogger(source);
Log.Error(String.Format("Context Info: {0}{1}{2}{3}", contextInfo, Environment.NewLine, Environment.NewLine, message));
}
<log4net>
<root>
<priority value="ALL" />
<appender-ref ref="EventLogAppender" />
</root>
This is myAppender. Now it writes in common logtype Application.
<appender name="EventLogAppender" type="log4net.Appender.EventLogAppender" >
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%logger (%property{myContext}) [%level]- %message%newline" />
</layout>
</appender>
</log4net>
I think what you are looking for is this:
<param name="LogName" value="MyLog" />
Some more information can be found here. If you do something like this you may want to have a look at this issue as well. Basically this is about registering your application so that the eventlog knows about it.
Edit (configuration by code):
Did not test it but that ought to do the trick:
foreach (AppenderSkeleton appender in this.Logger.Repository.GetAppenders())
{
var eventlogAppender = appender as EventLogAppender;
if (eventlogAppender != null)
{
eventlogAppender.LogName = "MyLog";
eventlogAppender.ActivateOptions();
break;
}
}
You could add some tests to verify that there is only one EventLogAppender, but probably you do not need to bother to do so.
this works:
<param name="LogName" type="log4net.Util.PatternString" value="%property{LogName}" />