NLog dynamically change filename using NLog.config - c#

How to dynamically change the FileName using a variable from C#? My idea is to create a log file like Log_<UserId_From_DB>_${date:format=yyyy-MM-dd}.log.
Any ideas?

Another option is to use the Global Diagnostic Context - $(GDC):
Set the value in C#
GlobalDiagnosticsContext.Set("UserId_From_DB","42");
In the config (nlog.config):
<target type="file" filename="Log_${gdc:item=UserId_From_DB}_${date:format=yyyy-MM-dd}.log" ..>
Please avoid modifying NLog Variables at runtime (See previous answer below). They should be seen as readonly, because they are not threadsafe. NLog Variables will also be affected if LoggingConfiguration is reloaded.
Previous answer with NLog Variables:
Set the value in C#
LogManager.Configuration.Variables["UserId_From_DB"] = "42";
In the config (nlog.config):
<target type="file" filename="Log_${var:UserId_From_DB}_${date:format=yyyy-MM-dd}.log" ..>
If the value is set again, the filename will automatically changed.

While the posted answer works, it suffers from concurrency issues. That variable is a global variable and you may end up with conflicts.
There is a better solution available. There is a way to pass event properties to NLog.
Link to the relevant NLog documentation.
Let's assume you want to log an error message:
Logger myLog = LogManager.GetLogger(name);
LogLevel level = LogLevel.Error;
string message = "This is an error message!";
You turn this information into a LogEventInfo object:
LogEventInfo logEvent = new LogEventInfo(level , myLog.Name, message);
You can then add properties to this event (the string indexes are free to choose):
logEvent.Properties["MySpecialValue"] = "SPECIAL";
And then you write to the log:
myLog.Log(logEvent);
The interesting thing here is that in your NLog configuration, you can use this custom property in any field that the Nlog documentation refers to as a "Layout" value.
You use ${event-properties:item=MySpecialValue} in the layout to access the property. For example:
<target xsi:type="File"
name="file"
fileName="${basedir}/logs/${event-properties:item=MySpecialValue}/my_${event-properties:item=MySpecialValue}_file.log"
layout="${event-properties:item=MySpecialValue} ${message}" />
Following the posted example, you will get a folder named SPECIAL, inside of which is a log file named my_SPECIAL_file.log in which you find the message SPECIAL This is an error message!. Just to prove the point that you can use this custom value in many different ways and shapes.
I commonly use this to make entity-specific logging (where the filename of the log equals the entity's ID value), which is essentially the same as you want to do here.
As a quick tip, I tend to wrap the NLog Logger in a class of my own:
public class UserLogger
{
private readonly Logger _log;
private readonly User _user;
public UserLogger(User u)
{
_user = u;
_log = LogManager.GetCurrentClassLogger();
}
public void Error(string message)
{
LogEventInfo logEvent =
new LogEventInfo(LogLevel.Error, _log.Name, message);
logEvent.Properties["UserId"] = _user.Id;
_log.Log(logEvent);
}
}
This is just a simple example to get you started. The cool feature I'm using here is that I've defined the log's filename (in the Nlog.Config target) using the UserId value and thus can ensure that each user gets logged to their own unique log file.
This way, you can enforce that the user ID is known when you want to log to the "user log" target. As an additional bonus, it also neatly decouples NLog dependencies from your calling code.

Assuming you have a log file called mylogfile.log in your nlog.config file
FileTarget target = LogManager.Configuration.FindTargetByName("mylogfile.log") as FileTarget;
String customlog = "Log_" + GetUserId(UserId_From_DB) + "_" + DateTime.Now.ToString("yyyy-MM-dd") + ".log";
target.FileName = customlog;

Related

c# logging framework - logfiles with explicit names

I´m searching for a logging framework for c# where I can set the logfile name easy from code.
My program starts several background workers which all should log into their own log files.
They use the same classes for their work and sometimes in different threads.
e.g.
backgroundworker1:
var aboImport = new CSVImport(file1); <-- logs into backgroundWorker1.log
aboImport.Execute();
ArchiveFile(file1);
backgroundworker2:
var addressImport = new CSVImport(file2); <-- logs into backgroundWorker2.log
addressImport.Execute();
ArchiveFile(file2);
e.g.
backgroundWorker1.log
backgroundWorker2.log
backgroundWorker3.log
...
heartbeat.log <-- I´can see my workers or main thread is running an working
I´m using using log4net at the moment, but this does not seem to fit my needs.
I found (Create multiple Logfiles with dynamic Names with log4net) where they create dynamic appenders in code.
But I do not understand whow this works, because the log4net definitions are normally tied to the namespaces.
Can other logging frameworks do this more elegant? Which logging framework do you recommend for this?
Stay away from Log4Net. It's not an actively developed project. Last release was almost 1 yr ago (12/2015). NLog is much better supported.
With that being said, all the logging frameworks let you do everything template driven with a bunch of different "macros"... you might base the filename on threadId or some other dynamic value... or just pass it in through a key/value pair. You have to look at all the "macros" available and pick the one that works in your scenario.
With nlog I could easy create loggers with different file targets.
private static void Main(string[] args)
{
var config = LogManager.Configuration;
AddLogger(config, "logger1", #"C:\Temp\Logfile\Log1.txt");
AddLogger(config, "logger2", #"C:\Temp\Logfile\Log2.txt");
LogManager.Configuration = config;
var logger1 = LogManager.GetLogger("logger1");
logger1.Debug("Test1");
var logger2 = LogManager.GetLogger("logger2");
logger2.Debug("Test2");
}
private static void AddLogger(LoggingConfiguration config, string loggerName, string fileName)
{
var fileTarget = new FileTarget();
config.AddTarget(loggerName, fileTarget);
fileTarget.FileName = fileName;
var rule = new LoggingRule(loggerName, LogLevel.Debug, fileTarget);
config.LoggingRules.Add(rule);
}

Add an appender for a portion of code

For a certain portion of my code, I need to log all messages to a second file, specific to each instance of the action completed in two classes.
These classes (Parser and Importer) both have the following logger definition:
private static ILog _logger = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
Both also have this method:
public void AddAppender(IAppender appender)
{
((Logger)_logger.Logger).AddAppender(appender);
}
The whole project (ASP.NET WebForms) logging is initialized in the AssemblyInfo.cs:
[assembly: XmlConfigurator]
Now, when the user triggers a certain action, this happens:
var logFile = new FileInfo(ConfigurationManager.AppSettings["uploadDir"] + "/" + importId + "_log.txt");
var appender = new FileAppender() {
Threshold = Level.Info,
Layout = new PatternLayout("%message%newline"),
File = logFile.FullName
};
var parser = new Parser(...);
parser.AddAppender(appender);
var candidates = parser.Parse(File.ReadAllLines(uploadFile.FullName));
var importer = new Importer(...);
importer.AddAppender(appender);
importer.Import(candidates, false, ignoreWarnings.Checked);
My expectation would be, that a file will be created at the logFile location, that would contain all INFO and above message from either of the two classes.
This, however, is not the case - no file is created at all.
What am I doing wrong and how can I make this work?
Per the documentation of FileAppender:
This appender will first try to open the file for writing when
ActivateOptions is called. This will typically be during
configuration. If the file cannot be opened for writing the appender
will attempt to open the file again each time a message is logged to
the appender. If the file cannot be opened for writing when a message
is logged then the message will be discarded by this appender.
This documentation is slightly ambiguous, since it seems to imply the file will be opened when you attempt to log a message in all cases, but this isn't true -- unless you call ActivateOptions, the appender is never considered ready for appending.

Change Location and Name of Log file at runtime, based on GUID passed in QueryString

Right now whenever I run application It creates a file (file name specified in Web.Config Logging Section).
What I want is to change the path and name of the file (based on GUID passing as query sting) at run time.
I did some research, but examples available are not working for me. Will appreciate if someone can provide a code sample.
As you've discovered, your requirements are not a natural fit for the configuration based approach of Enterprise Library. That's not to say that it can't be done.
One way to accomplish what you want is to use programmatic configuration of the logging block.
One approach would be to create an IDictionary to hold a mapping of IDs to LogWriters. When an ID comes in check to see if a LogWriter already exists in the IDictionary. If it does then use it and if it doesn't then create a new LogWriter.
Here is some sample code that uses the Logging Fluent Interface to configure a UnityContainer and then resolves a LogWriter and saves it to a Dictionary:
int id = 123123;
Dictionary<int, LogWriter> loggers = new Dictionary<int, LogWriter>();
ConfigurationSourceBuilder builder = new ConfigurationSourceBuilder();
builder.ConfigureLogging()
.WithOptions
.DoNotRevertImpersonation()
.SpecialSources.LoggingErrorsAndWarningsCategory.SendTo.FlatFile("Flat File Listener").ToFile(#"trace.log")
.LogToCategoryNamed("General")
.WithOptions.SetAsDefaultCategory()
.SendTo.FlatFile("AppSpecificFlatFile" + id)
.ToFile("logging" + id + ".log")
;
DictionaryConfigurationSource configSource = new DictionaryConfigurationSource();
builder.UpdateConfigurationWithReplace(configSource);
coreExtension = new EnterpriseLibraryCoreExtension(configSource);
IUnityContainer container = new UnityContainer();
container.AddExtension(coreExtension);
var logger = container.Resolve<LogWriter>();
loggers[id] = logger;
This is just a sample and is not thread safe or abstracted but should hopefully help.
If you just need to change the trace log file at runtime, you can simply remove the current tracelistener using
System.Diagnostics.Trace.Listener.Remove("Listener1");
Then add a new Listener with the new log file name as,
System.Diagnostics.Trace.Listener.Add(new System.Diagnostics.TextWriterTraceListener("NewLogFileName.txt","Listener2");
Now your logs will be recorded to NewLogFileName.txt

NLog - Save ${message} in multiple database columns

I'd like to save the logged message ${message} in several columns in a database, as shown on the following example:
My logger messages will follow the path User|Action, e.g:
logger.Info("John Doe|Logged in application"}
logger.Info("Mike Doe|Deleted a file"}
Now I'd like to save User in a column in my database, e.g logsTable.user, and Action in another column, e.g logsTable.action.
Is there any way to parse the ${message} with a regex or some other rules (to separate messages according to a specific character, in my example it's a "|") to save into parameters (in my case, i'd like the first part of the message to go in #user parameter and the second part in #action parameter)?
According to NLog documentation it shouldn't be too complicated to add your own properties to a log event. Then you could do an extension method on the correct NLog interface and write something like this (uncompiled):
public void LogSomething(this ILog logger, string username, string message)
{
LogEventInfo myEvent = new LogEventInfo(LogLevel.Debug, "", message);
myEvent.LoggerName = logger.Name;
myEvent.Properties.Add("User", username);
logger.Log(myEvent);
}
Now you should be able to refer to ${event-context:item=User}

Create a ReadOnly log file with Log4Net

I'm developing application in c# that creates 2 log files (.txt files): one for errors and another for modifications made by users. This two files are created with log4net. The issue I see is that this files can be edited, and so altered by mistake.
I would like to set this files to readonly, and that log4net still could write on them. Because if I just change the property in the file, the next log wont be written.
There is a way to do that?
Also, the user of the app can open this logs file from within the app. For that I use the next code:
System.IO.FileInfo finfo = new System.IO.FileInfo("path");
if (finfo.Exists)
{
//finfo.Attributes = System.IO.FileAttributes.ReadOnly;
// I don't use the previous line at the moment, because it blocks the followings logs.
System.Diagnostics.Process.Start("path");
}
And this is the code to create and call the logger:
public static class CLogger
{
private static readonly ILog logger = LogManager.GetLogger(typeof(CLogger));
static CLogger()
{
XmlConfigurator.Configure(new System.IO.FileInfo("path to .config file"));
}
public static void WriteLog(ELogLevel logLevel, String log)
{
if (logLevel.Equals(ELogLevel.DEBUG))
{
logger.Debug(log);
}
else if (logLevel.Equals(ELogLevel.ERROR))
.
.
.
else if (logLevel.Equals(ELogLevel.WARN))
{
logger.Warn(log);
}
}
}
Calling to the logger:
CLogger.WriteLog(ELogLevel.ERROR, ex.ToString());
Basically, if the application runs under the rights of the user, than the user ultimately must have the rights to access the files: if he doesn't, the application wouldn't be able to write into this files either.
I would change the rights on the folder where the logs are being written. The web server granted Read/Write, all other only Read.

Categories

Resources