I need to write event log for every instance of class to separate file. Historically project uses NLog for logging, so I want to try resolving this issue using NLog (I've found similar topic Unique log file for each instance of class , but it's suggests using log4net)
Currently I'm getting instance of logger like this:
public static Logger GetInstanceLogger(string name, bool init = false)
{
if (!LogManager.Configuration.AllTargets.Any(t => t.Name == name))
{
var target = new FileTarget();
target.Name = name;
target.FileName = string.Format("logs/{0}.${{shortdate}}.log", name);
target.Layout =
"${date:format=dd.MM.yyyy HH\\:mm\\:ss.fff} thread[${threadid}] ${logger} (${level:uppercase=true}): ${message}. ${exception:format=ToString}";
var config = init ? new LoggingConfiguration() : LogManager.Configuration;
config.AddTarget(name, target);
var ruleInfo = new LoggingRule("*", LogLevel.Trace, target);
config.LoggingRules.Add(ruleInfo);
LogManager.Configuration = config;
LogManager.ReconfigExistingLoggers();
}
var logger = LogManager.GetLogger(name);
return logger;
}
Right now it's writing same log into all files (I suppose it's caused by the log level). Is there a way to accomplish this task using NLog?
Thanks.
I came out with solution using event properties of layout renderer in the filename. When I'm writing new message to log, I'm adding filename as a property for LogEventInfo
protected virtual void SendEvent(LogLevel level, string message, Exception exception, string memberName = null)
{
var logEvent = new LogEventInfo(level, _name, message);
logEvent.Exception = exception;
foreach (String key in _properties.Keys)
{
logEvent.Properties[key] = _properties[key];
}
_logger.Log(logEvent);
}
And in configuration file in NLog targets section:
<targets async="true">
<target xsi:type="File" name="f" fileName="${basedir}/logs/${shortdate}_${event-properties:item=Name}.log"
layout="${detailedLayout}" />
<target xsi:type="File" name="errorLogFile" fileName="${basedir}/logs/${shortdate}.ERROR_${event-properties:item=Name}.log"
layout="${detailedLayout}" />
</targets>
try var ruleInfo = new LoggingRule(name, LogLevel.Trace, target);
Related
We're using NLog for our application. I have set up the logger in the code using mostly the documentation. Yet the logger object gets disposed of while the app is about to write to it causing an error. I use the logger in .NET CORE 3.1 in the Middlewares that handles exceptions globally and authentication. The funniest part is that the error itself is logged to the file by NLog. Error from the logs:
System.ObjectDisposedException: Cannot access a disposed object.
Object name: 'LoggerFactory'.
at Microsoft.Extensions.Logging.LoggerFactory.CreateLogger(String categoryName)
at Microsoft.Extensions.Logging.Logger`1..ctor(ILoggerFactory factory)
at Microsoft.Extensions.Logging.LoggerFactoryExtensions.CreateLogger[T](ILoggerFactory factory)
Configuration in the code:
public static class NLogConfig
{
public static void CreateLogger(IConfiguration configuration)
{
var logName = configuration.GetSection("LoggerConfig").GetSection("LogName").Get<string>();
var logPath = configuration.GetSection("LoggerConfig").GetSection("Directory").Get<string>();
var infoLogLayout = configuration.GetSection("LoggerConfig").GetSection("InfoLayout").Get<string>();
var errorLogLayout = configuration.GetSection("LoggerConfig").GetSection("ErrorLayout").Get<string>();
var config = new NLog.Config.LoggingConfiguration();
var infoLog = new NLog.Targets.FileTarget("FileLog") { FileName = logPath + logName, Layout = infoLogLayout };
var errorLog = new NLog.Targets.FileTarget("FileLog") { FileName = logPath + logName, Layout = errorLogLayout };
config.AddRule(LogLevel.Trace, LogLevel.Info, infoLog);
config.AddRule(LogLevel.Warn, LogLevel.Fatal, errorLog);
NLog.LogManager.Configuration = config;
}
}
JSON configuration file:
"LoggerConfig": {
"LogName": "MY_API_${shortdate}.log",
"Directory": "C:\\Logs\\MY_API\\",
"InfoLayout": "${longdate} | INFO | ${logger} | ${message}",
"ErrorLayout": "------------------\n${longdate} | ERROR | ${logger} | ${message:withexception=true} \n------------------"
}
In the classes that use the log I do this as it is in documentation:
private static readonly NLog.Logger _logger = NLog.LogManager.GetCurrentClassLogger();
The "disposed" error happens randomly and not frequently, but often enough to be a slight concern. How can I avoid this problem? I've read that NLog should be thread-safe so calls to the API shouldn't cause such a problem. Especially when it was only one person using the API at the time
I use a NLog.config, but some values I want to change from outside. E.g. target:adress could change so I want to set it each time the software starts.
I imagine some thing like
var logger = new LoggerFactory().AddNLog().CreateLogger<Program>();
logger.target.adress = "myNewAdress";
How can I set values to my NLog.config?
You could edit the config in C# like this:
var configuration = LogManager.Configuration;
var fileTarget = configuration.FindTargetByName<FileTarget>("myTargetName");
fileTarget.FileName = "${basedir}/file.log";
LogManager.Configuration = configuration; //apply
Please note that combining the config file (nlog.config) and changing it in code, the reload of nlog.config could undo your changes. If you combine both, then reapply the changes on the reload event. E.g.
public void UpdateConfig()
{
var configuration = LogManager.Configuration;
var fileTarget = configuration.FindTargetByName<FileTarget>("myTargetName");
fileTarget.FileName = "${basedir}/file.log";
LogManager.Configuration = configuration; //apply
}
// On start of your program
UpdateConfig();
LogManager.ConfigurationReloaded += (sender, e) =>
{
//Re apply if config reloaded
UpdateConfig();
};
See also: https://github.com/NLog/NLog/wiki/Configure-from-code
I recommend that one makes use of the NLog context layoutrenderers instead of modifying target properties at runtime. It ofcourse requires that the target-property supports NLog Layout.
Example of NLog.config:
<nlog>
<targets>
<target type="file" name="file" fileName="${gdc:item=LogDir}\LogFile.txt}" />
</targets>
<rules>
<logger minLevel="Trace" writeTo="file" />
</rules>
</nlog>
Then at runtime you can change the GDC-variables:
NLog.GlobalDiagnosticsContext.Set("LogDir", "C:\\Temp");
${gdc} Can also be combined with WhenEmpty, so one can provide a fallback default value, when nothing has been asigned from code.
See also: https://github.com/NLog/NLog/wiki/Context
I have a project which uses the below code to create a NLog instance.
public FileTarget CreateNLogFileTarget(string layout, FileArchivePeriod archiveMode, int maxArchiveFiles,
bool keepFileOpen, bool enableConcurrentWrites, ArchiveNumberingMode archiveNumberingMode, string fileName)
{
FileTarget fileTarget = new FileTarget();
fileTarget.Layout = layout;
fileTarget.ArchiveEvery = archiveMode;
fileTarget.MaxArchiveFiles = maxArchiveFiles;
fileTarget.KeepFileOpen = keepFileOpen;
fileTarget.ConcurrentWrites = enableConcurrentWrites;
fileTarget.ArchiveNumbering = archiveNumberingMode;
fileTarget.FileName = fileName;
return fileTarget;
}
FileTarget infoLogFileTarget = CreateNLogFileTarget(#"${longdate} ${message}",
FileArchivePeriod.Hour, 70, false, true, ArchiveNumberingMode.Rolling, "${basedir}/Logs/" + infoLogName + "/${shortdate}{#}.log");
I am using this project in another project and I need to use this NLog utility class to create my loggers. But I need to override these configurations. How can I override these configurations through the xml file? Any help would be much appreciated.
To use the FileTarget from CreateNLogFileTarget in your XML config, you should first find out the target name of the FileTarget it's probably in other parts of the code. Then you could use the target in your config:
<logger name='*' minlevel="Trace" writeTo='theTarget' />
Maybe by using NLog-variables. Change your CreateNLogFileTarget to setup the parameters to get their value from NLog-variables.
Then on startup check if these NLog variables already exists in the loaded NLog-configuration. If not then they are set by the runtime, before calling CreateNLogFileTarget.
https://github.com/NLog/NLog/wiki/Configuration-file#variables
How do I add an extra log appender in runtime? (all pre-existing appenders must keep on working)
I'm trying it this way:
var layout = new PatternLayout("%utcdate %-5level - %message%newline");
layout.ActivateOptions();
_log4netAppender = new FileAppender
{
Layout = layout,
File = logFilePath,
};
_log4netAppender.ActivateOptions();
BasicConfigurator.Configure(_log4netAppender);
but it doesn't write anything to the file.
You should also add the appender to a logger.
Take a look here Adding Appenders programmatically
If the logger you are using is, for example ILog logger do:
((log4net.Repository.Hierarchy.Logger)logger.Logger).AddAppender(appender)
var patternLayout = new log4net.Layout.PatternLayout
{
ConversionPattern = "%date %level %message%newline"
};
patternLayout.ActivateOptions();
var rollingFileAppender = new log4net.Appender.RollingFileAppender
{
File = "MyApp.log",
Layout = patternLayout
};
rollingFileAppender.ActivateOptions();
var hierarchy = (log4net.Repository.Hierarchy.Hierarchy)log4net.LogManager.GetRepository();
hierarchy.Root.AddAppender(rollingFileAppender);
hierarchy.Root.Level = log4net.Core.Level.All; // Default is Debug
log4net.Config.BasicConfigurator.Configure(hierarchy);
There is some code for logging to file. I dont using app.config
class Program
{
static void Main(string[] args)
{
MyLogger.Write("This is message error", "My Category");
Console.ReadKey();
}
}
public static class MyLogger
{
static readonly LogWriterImpl _writer;
static MyLogger()
{
TextFormatter formatter = new TextFormatter
("Timestamp: {timestamp}{newline}" +
"Message: {message}{newline}" +
"Category: {category}{newline}");
var logFileListener = new Microsoft.Practices.EnterpriseLibrary.Logging.TraceListeners.FlatFileTraceListener
(
"c:\\messages.log", "----------", "----------", formatter
);
LogSource mainLogSource = new LogSource("MainLogSource", SourceLevels.All);
mainLogSource.Listeners.Add(logFileListener);
LogSource nonExistantLogSource = new LogSource("Empty");
IDictionary<string, LogSource> traceSources = new Dictionary<string, LogSource>();
traceSources.Add("Error", mainLogSource);
traceSources.Add("Debug", mainLogSource);
_writer = new LogWriterImpl
(
new Microsoft.Practices.EnterpriseLibrary.Logging.Filters.ILogFilter[0],
traceSources,
nonExistantLogSource,
nonExistantLogSource,
mainLogSource,
"Error",
false,
true
);
}
public static void Write(string message)
{
Write(message, "Error");
}
public static void Write(string message, string category)
{
LogEntry entry = new LogEntry();
entry.Categories.Add(category);
entry.Message = message;
_writer.Write(entry);
}
}
This program work without errors but it don't create log file c:\messages.log and don't write log entity. Where is the error? I don't want using application config file in my project
There could be a couple of reasons (at least!) why you are not seeing any logging:
The categories that are configured for logging are "Error" and "Debug" but when you call MyLogger.Write you are passing a category of "My Category"
There could be a permission problem. Writing to the root of the drive is frequently restricted
As an aside, you should probably store the reference to LogWriterImpl as the base class LogWriter.
As another aside, instead of using the logging classes directly it is preferable to use the Fluent Configuration API which was released as part of version 5.0. It makes this type of configuration much simpler. As an example:
var builder = new ConfigurationSourceBuilder();
builder.ConfigureLogging()
.WithOptions
.DoNotRevertImpersonation()
.LogToCategoryNamed("My Category")
.SendTo.FlatFile("MyMessages")
.FormatWith(new FormatterBuilder()
.TextFormatterNamed("Text Formatter")
.UsingTemplate("Timestamp: {timestamp}...{newline})}"))
.ToFile("c:\\messages.log");
var configSource = new DictionaryConfigurationSource();
builder.UpdateConfigurationWithReplace(configSource);
EnterpriseLibraryContainer.Current
= EnterpriseLibraryContainer.CreateDefaultContainer(configSource);
It's also more maintainable and supportable. E.g. there is less chance that there won't be breaking implementation changes like when LogWriter was made abstract as part of version 5.