How could I use NLog to create a user-specific logfile the file name equivalent to the username? I understand that you can use variables in layouts, but the file name attribute is set at the level of the target. I would like to be able to do something like filename="C:\pathtologfiles\${myApp:user}.txt" and in the calling class, ClassLogger.Debug("user did something", thisUser.Username).
Here's how I ended up doing it (in VB, unfortunately.) You would of course need to implement your own _UsernameIsMeaningfulInThisContext and _GetUsername.
In nlog config
<variable name="AppLogDir" value="C:\inetpub\ApplicationLogging\MyApplication" />
...
<targets>
...
<target name="UserSpecificLogfile"
xsi:type="File"
fileName="${AppLogDir}\Users\AppUser_${event-context:item=AppUsername}.txt"
createDirs="true" />
</targets>
<rules>
...
<logger name="*" minLevel="Trace" maxLevel="Fatal" writeTo="UserSpecificLogfile" />
</rules>
In calling class
Private Property _ClassLogger As Logger = LogManager.GetCurrentClassLogger()
...
Private Sub _LogMaybeUser(ByVal nLogLevel As NLog.LogLevel, ByVal nMsg As String)
{
Dim logfileUser As String = "Unknown"
If _UsernameIsMeaningfulInThisContext() { logfileUser = _GetUsername() }
Dim logInfo As New LogEventInfo(nLogLevel, "", nMsg)
logInfo.Properties("AppUsername") = logfileUser
ClassLogger.Log(logInfo)
}
Related
I have an application in .NET Core Console that is working fine in .NET Core 6. I am testing the conversion of this app in the newly released .NET Core 7 and all works fine except the part where I dynamically get the path of the NLog target.
My NLog.config is like this:
<?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"
internalLogLevel="Warn"
internalLogFile="internal-CimplDataLoader.log">
<extensions>
<add assembly="NLog.Extensions.Logging" />
</extensions>
<targets>
<target xsi:type="File" name="allfile" fileName="${shortdate}.log"
layout="${longdate}|${event-properties:item=EventId.Id}|${logger}|${uppercase:${level}}|${message} ${exception}"
keepFileOpen="false"/>
</targets>
<rules>
<!--All logs, including from Microsoft-->
<logger name="*" minlevel="Trace" writeTo="allfile" />
</rules>
</nlog>
The code that works in .NET Core 6:
if (LogManager.Configuration != null)
{
Target target = LogManager.Configuration.FindTargetByName("allfile");
var logEventInfo = new LogEventInfo { TimeStamp = DateTime.Now };
FileTarget? fileTarget;
// Unwrap the target if necessary.
if (target is not WrapperTargetBase wrapperTarget)
fileTarget = target as FileTarget;
else
fileTarget = wrapperTarget.WrappedTarget as FileTarget;
if (fileTarget != null)
{
string fileName = fileTarget.FileName.Render(logEventInfo);
string LogPath = Path.GetDirectoryName(fileName)!;
/* Work with LogPath */
}
else
{
_logger.LogError("Unable to get NLog \"allfile\" base directory.");
}
}
else
{
_logger.LogError("Unable to read NLog configuration.");
}
This same code in .NET Core 7 fails because LogManager.Configuration is always null. Is there any way I can still dynamically get the path in .NET Core 7?
NLog.config was not present, hence the failure. This was due to the switch from .NET 6 to .NET 7 and nlog.config property "Copy To Output Directory" set to "Do not copy".
I tried to setup my Log4Net dynamical. But some properties in my Appenders wont work.
I.e. in my Program.cs
GlobalContext.Properties["hostname"] = Environment.MachineName;
GlobalContext.Properties["serviceName"] = ServiceProperties.ServiceName;
GlobalContext.Properties["smtpHost"] = LoggingProperties.SMTPServer;
GlobalContext.Properties["maximumFileSize"] = LoggingProperties.MaximumFileSize;
log4net.Config.XmlConfigurator.Configure();
log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
log.Debug("Logger configured.");
log.Fatal("I am a Mail"); // Mail Logging Test
And the part of my app.config
<appender name="SmtpAppender" type="log4net.Appender.SmtpAppender">
<param name="smtpHost" value="%property{smtpHost}" />
<evaluator type="log4net.Core.LevelEvaluator">
<threshold value="FATAL"/>
</evaluator>
....
</appender>
In my SmtpAppender, it literally says "%property{smtpHost}" als String, same with every other property.
Can I use the properties in my config without programmatically set everything or do I have to code the whole appenders?
Regards
Basically I have this config for log4net:
<?xml version="1.0" encoding="utf-8"?>
<log4net>
<root>
<level value="INFO" />
<appender-ref ref="RollingFileAppenderWithDeletion" />
</root>
<appender name="RollingFileAppenderWithDeletion" type="Namespace.RollingFileAppenderWithDeletion">
<file type="log4net.Util.PatternString" value="Logs/%property{LogName}/log.%property{ServiceName}-PID-%processid_%date{yyyyMMdd}.log" />
<appendToFile value="true" />
<rollingStyle value="Date" />
<datePattern value="yyyyMMMdd" />
<maxSizeRollBackups value="10" />
<maximumFileSize value="50MB" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%-5level %date{dd MMM yyyy HH:mm:ss,ffff} [%thread] %exception - %message%newline"/>
</layout>
</appender>
</log4net>
Now I have a problem. Everytime log4net rolls log file, it doesn't change the date part of log file. For example if today's log file is - log.MyServiceName-PID-1234_20131208.log, tomorrow after rolling the file, name of the file won't change, so I will end up having a rolled back log file and active log file like this
log.QAService-PID-17584_20131208.log
log.QAService-PID-17584_20131208.log2013Dec08
What I would like to have is
log.QAService-PID-17584_20131209.log - active log file
log.QAService-PID-17584_20131208.log2013Dec08 - rolled back
Now I came across this post, but it didn't help me. In particular, if I remove date pattern from file, and set preserveLogFileNameExtension to true, I can't see date part on active log file anymore. If I go further and set staticLogFileName to false, I don't have active log files anymore, instead active log files are having the rolled back log file name pattern.
What am I missing here? How can I have right log file names generated after roll back.
I'm using log4net version 1.2.10.0. Unfortunately I can't upgrade it to newer version.
Update
This is custom implementation of RollingFileAppenderWithDeletion. It just cleans up old rolled back files after log file rolling took place.
public class RollingFileAppenderWithDeletion :RollingFileAppender
{
private IFileBurner m_fileBurner;
private const int checkMinutes = 1664;
public RollingFileAppenderWithDeletion()
{
m_fileBurner = FileBurner.Instance;
}
protected override void AdjustFileBeforeAppend()
{
base.AdjustFileBeforeAppend();
string path = base.File;
string directoryPath = Path.GetDirectoryName(path);
IDeletionRequirements requirements = new DeletionRequirements();
requirements.CheckEveryMinutes = checkMinutes;
requirements.DayLimit = MaxSizeRollBackups;
requirements.Directories = new List<string> { directoryPath };
m_fileBurner.ClearLogFiles(requirements);
}
}
You are using the type Namespace.RollingAppenderWithDeletion, which is definately not a standard appender. Try use the type log4net.Appender.RollingFileAppender and see if that works. When you use your custom appender you need to post some code, or find why the filename code is not called.
I want to write a stand alone string (like a horizontal line or delimiter) to the log file.
If I use
string delimiter = "------------------------------------------------";
_logger.Info(delimiter);
then in the log file I get:
2013-01-08 15:58:54.4008 INFO ------------------------------------------------
I do not want the extra information at the beginning of the line.
Is there a way to write a separator like this with NLog? I checked the NLog wiki, but didn't find anything.
See my answer to a similar question here:
Nlog - Generating Header Section for a log file
To summarize, I propose defining another logging target. If you are logging to a file, define a second file target, pointing to the same file, but with a different layout. Define the layout so that it has the format that you want. You could define the layout to be hardcoded to the header value that you want ("---------------" in your case), or your could define the layout to only log the message and then you could pass the layout to it.
Here is the shortest thing that might work. Note, I cut and pasted from the answer linked above and modified slightly for your case. I did not test it.
Define the layouts:
<variable name="HeaderLayout" value="${message}"/>
<variable name="FileLayout" value="${longdate} | ${logger} | ${level} | ${message}" />
Define the targets using the layouts:
<target name="fileHeader" xsi:type="File" fileName="xxx.log" layout="${HeaderLayout}" />
<target name="file" xsi:type="File" fileName="xxx.log" layout="${InfoLayout}" />
Define the rules/loggers:
<rules>
<logger name="headerlogger" minlevel="Trace" writeTo="fileHeader" final="true" />
<logger name="*" minlevel="Trace" writeTo="file" />
</rules>
Use the loggers:
public class MyClass
{
private static Logger logger = LogManager.GetCurrentClassLogger();
private static Logger headerLogger = LogManager.GetLogger("headerlogger");
public void DoSomething()
{
headerLogger.Info("---------------------");
logger.Info("Inside DoSomething");
headerLogger.Info("---------------------");
}
}
Alternatively, you could define the layout such that it has the header definition in it:
<variable name="HeaderLayout" value="-----------------------------"/>
<variable name="FileLayout" value="${longdate} | ${logger} | ${level} | ${message}" />
Then you would use it like this:
Logger headerLogger = LogManager.GetLogger("headerlogger"); //Assuming same rules/loggers definition as above.
headerLogger.Info("It doesn't matter what you put here because the layout has the header message hardcoded");
You could write a helper function so that you don't have to deal explicitly with the header logger:
public void WriteHeader()
{
LogManager.GetLogger("headerlogger").Info("This string does not matter");
}
I think that this should give you some good insight into how you can accomplish what you are trying to do.
Hope this helps!
Good luck!
How can I log to special folders (e.g. %APPDATA%) using the app.config file?
I can do it programmatically, but I need to be able to use the app.config file for configuration. I have seen a post of using %envFolderPath.It is not available in the latest released version, but only in their latest code.
Below is the code that I setting the log to special folders programmatically.
public void ExampleLog
{
XmlConfigurator.Configure();
var fileName = GetFileName();
var appender = new log4net.Appender.RollingFileAppender
{
Layout = new log4net.Layout.PatternLayout("%d - %m%n"),
File = fileName,
MaxSizeRollBackups = 10,
MaximumFileSize = "100MB",
AppendToFile = true,
Threshold = Level.Debug
};
appender.ActivateOptions();
BasicConfigurator.Configure(appender);
}
private static string GetFileName()
{
const string subPath = "MySubFolder";
var path = String.Format(#"{0}\{1}", Environment.GetFolderPath (Environment.SpecialFolder.CommonApplicationData), subPath);
const string logName = "Log.txt";
return Path.Combine(path, logName);
}
Pretty sure the syntax for this is available in the current release.
<file type="log4net.Util.PatternString" value="%env{APPDATA}\\MyApp\\Log.txt" />
If you need something more, you can look into option of subclassing the PatternString class, as described here: Log4Net can’t find %username property when I name the file in my appender
Check out the RollingFileAppender configuration sample on the log4net docs (for all the other parameters).
<appender name="RollingFileAppender" type="log4net.Appender.RollingFileAppender">
<file value="log.txt" />
<appendToFile value="true" />
<rollingStyle value="Size" />
<maxSizeRollBackups value="10" />
<maximumFileSize value="100KB" />
<staticLogFileName value="true" />
<layout type="log4net.Layout.SimpleLayout" />
</layout>
</appender>
I've referenced environment variables (including special folders) with the basic log4net variable format ${NAME}. And the file tag doesn't need to have the PatternLayout specified, it's implied.
<file value="${APPDATA}\log.txt" />