How to access existing NLog MemoryTarget - c#

I'm reworking an existing logging system to use NLog Instead. I've manually added a reference to NLog in my configuration file:
<targets>
<target xsi:type="File" name="fileLogger" fileName="${basedir}\TRACE\${date:format=yyyy-MM-dd}.log" layout="TimeStamp:[${date}]|${message}" />
<target xsi:type="Memory" name="MemLogger" layout="TimeStamp:[${date}]|${message}|${Type}" />
</targets>
<rules>
<logger name="ApplicationLogger" levels="Info,Warn,Error" writeTo="fileLogger" />
<logger name="ApplicationLogger" levels="Info,Warn,Error" writeTo="fileLogger" />
</rules>
What I want to do is in code pull out the MemLogger Logs and access the different parts (Type, Message, Timestamp). How would I accomplish this? I've seen how to create a new log from scratch, but I don't want to create a new memorytarget, I want to access the existing one in my config, pull the log data out of it, and then clear the memory (so that I don't have a memory leak).
How to I access the MemLogger MemoryTarget in C#?

Unfortunately you cannot access different parts of log. Logs stored as rendered strings, with all layout renderers already replaced with their values. All you can do is manually parse each log string. E.g.
var target =(MemoryTarget)LogManager.Configuration.FindTargetByName("MemLogger");
foreach (string log in target.Logs)
{
var parts = log.Split('|');
var date = parts[0].Replace("TimeStamp:[", "").TrimEnd(']');
var message = parts[1];
var type = parts[2];
//...
}

Something like this:
var target = LogManager.Configuration.FindTargetByName("MemLogger");
Optionally, you can cast received target to MemoryTarget type.

I've just had to do this myself:
Natively:
IList<string> logs = LogManager.Configuration.FindTargetByName<MemoryTarget>("MyTarget").Logs;
or using a Cast:
IList<string> logs = ((MemoryTarget)LogManager.Configuration.FindTargetByName("MyTarget")).Logs;

Related

How to set the logGroup programatically for CloudWatch using Nlog and MDLC

I'm using AWS.Logger.NLog to send logs to CloudWatch.
What I'm trying to do is to assign the logGroup programatically, instead of putting it in the nlog.config. I tested other config parameters like the region and they work. I tested it putting the logGroup "hardcoded" in the nlog.config, like this:
<!-- This works -->
<target type="AWSTarget" name="aws" profile="hmrc" logGroup="TestGroup" region="${mdlc:region}">
But when I try with the logGroup I don't see the log messages in CW.
This is how my code looks:
// ...
var logContext = GetLogContext(context); // Get the logContext from the current Context
using (logger.BeginScope(logContext))
{
MappedDiagnosticsLogicalContext.Set("region", "eu-west-1"); // This works I see the logs in CW
MappedDiagnosticsLogicalContext.Set("loggroup", "TestGroup"); // This doesn't work, no logs
}
And the nlog.config:
<target type="AWSTarget" name="aws" profile="hmrc" logGroup="${mdlc:loggroup}" region="${mdlc:region}">
Am I missing something?

NLog - Write to another target if message starts with certain words

I would like to know if it is possible to write the logs to another target if the message that we want to write begins with certain words, a prefix.
I can't do it at the class level right now, that's why I'm checking if it's possible to get what I want in an easier way than modifying the code
Yes, this is possible.
It could be configured like this:
<logger name="*" writeTo="target-only-if-message-has-prefix">
<filters defaultAction='Ignore'>
<when condition="starts-with('${message}', 'MyPrefix')" action="Log" />
</filters>
</logger>
See
https://github.com/NLog/NLog/wiki/Filtering-log-messages
https://github.com/NLog/NLog/wiki/When-filter

How can you configure an Azure Table Connection String for NLog using Settings variables?

I have an issue where I need to write logs to an Azure table using Nlog, but the connection string could change depending on the environment (i.e. Dev/UAT etc.), so I need to take this from another config file. My Nlog 'targets' section currently looks like the following:
<targets>
<target xsi:type="File" name="allfile" fileName="${var:configDir}\nlog-all.log"
layout="${longdate}|${event-properties:item=EventId.Id}|${logger}|${uppercase:${level}}|${message} ${exception}" />
<target xsi:type="AzureTableStorage"
connectionString="${var:myNLogConnectionString}"
name="NLogAzureTable"
layout="${longdate}|${event-properties:item=EventId.Id}|${logger}|${uppercase:${level}}|${message} ${exception}"
tableName="MyTestLogs"
logTimeStampFormat="O" />
</targets>
My Api looks as follows:
ILoggerFactory logger = new LoggerFactory().AddNLog();
var nlogConfigSection = config.Settings.Sections["MyService_NlogSettings"];
LogManager.LoadConfiguration("nlog.config");
LogManager.Configuration = new XmlLoggingConfiguration("nlog.config");
LogManager.Configuration.Variables["configDir"] = nlogConfigSection.Parameters["FileLocation"].Value;
LogManager.Configuration.Variables["myNLogConnectionString"] = nlogConfigSection.Parameters["environmentNLogConnectionString"].Value;
I can see from debugging that the config.settings values are all being retrieved as required, and even that the variables in the configuration are all being populated appropriately. I've found that if I write locally to the 'allfile' text file, it's managing to retrieve and populate 'configDir', since that's where the text file appears!
However, if I switch to using Azure, I can see as previously mentioned that the variable is being set in the Configuration, but when I look at the Nlog Internal Log file, I can see that it thinks that the connection string is blank.
Is there anything obvious that I'm doing wrong here?!? I've seen solutions for similar problems, but they invariably seem to involve doing what I've already done, but I'm getting no joy!
Try doing this instead:
ILoggerFactory logger = new LoggerFactory().AddNLog();
var nlogConfigSection = config.Settings.Sections["MyService_NlogSettings"];
// Configure global settings before loading NLog.config
NLog.GlobalDiagnosticsContext.Set("configDir", nlogConfigSection.Parameters["FileLocation"].Value);
NLog.GlobalDiagnosticsContext.Set("myNLogConnectionString", nlogConfigSection.Parameters["environmentNLogConnectionString"].Value);
NLog.LogManager.Configuration = new XmlLoggingConfiguration("nlog.config");
With the following NLog.config, replacing ${var with ${gdc:
<targets>
<target xsi:type="File" name="allfile" fileName="${var:configDir}\nlog-all.log"
layout="${longdate}|${event-properties:item=EventId.Id}|${logger}|${uppercase:${level}}|${message} ${exception}" />
<target xsi:type="AzureTableStorage"
connectionString="${gdc:myNLogConnectionString}"
name="NLogAzureTable"
layout="${longdate}|${event-properties:item=EventId.Id}|${logger}|${uppercase:${level}}|${message} ${exception}"
tableName="MyTestLogs"
logTimeStampFormat="O" />
</targets>

NLog dynamically change filename using NLog.config

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;

Log4net output to My Documents

In our C# app, we write files to Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments). Our log4net logfile should go there too, so we've defined application.conf as follows:
<appender name="LogFile" type="log4net.Appender.RollingFileAppender">
<appendToFile value="true"/>
<file value="%USERPROFILE%\My Documents\MyApp\log.txt"/>
...snip...
</appender>
This works, until we run in on a PC which has a non-English Windows. Because then, SpecialFolder.MyDocuments points to the folder Mijn Documenten, while the log still goes to My Documents. Confusion ensues, because now our files are in two places.
I want to write my log to the "real" My Documents folder. How do I do this?
I tried to find an environment variable like %USERPROFILE%, but there doesn't seem to exist one for My Documents.
There's a registry key that defines the true location of My Documents but it's not accessible from application.conf.
I tried to override the File parameter of my appender programmatically, like this:
public static void ConfigureLogger()
{
XmlConfigurator.Configure();
Hierarchy hierarchy = (Hierarchy)log4net.LogManager.GetRepository();
foreach (var appender in hierarchy.Root.Appenders)
{
if (appender is FileAppender)
{
var fileAppender = appender as FileAppender;
var logDirectory = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "MyApp");
var pathElements = fileAppender.File.Split(Path.DirectorySeparatorChar);
var logFileName = pathElements.Last();
var fullLogFilePath = Path.Combine(logDirectory, logFileName);
fileAppender.File = fullLogFilePath;
}
}
}
This doesn't work either: when I inspect the internals of my logger, the File property happily reports Mijn Documenten, but in the mean time the logs still go to My Documents.
I'm running out of ideas!
Change the line
<file value="%USERPROFILE%\My Documents\MyApp\log.txt"/>
to
<file type="log4net.Util.PatternString" value="%envFolderPath{MyDocuments}\MyApp\log.txt" />
The accepted answer is out of date.
You should now use:
<file value="${UserProfile}\Documents\log-messages.log" />
(This should work even on Windows 7, where "Documents" is referred to by the alias "My Documents".)
${UserProfile} will map to C:\Users[UserName], even if you don't see this variable explicitly defined in your environment variable list.
There seem to be at least 2 approaches. The simplest is kind of a hack:
Specify a custom environment variable to indicate the root path in you log4net config:
<file value="%MYAPP_USER_ROOTFOLDER%\MyApp\log.txt"/>
At startup, before initializing logging, set this environment variable value:
Environment.SetEnvironmentVariable("MYAPP_USER_ROOTFOLDER", Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments));
The more complicated but recommended approach is explained here: http://marc.info/?l=log4net-user&m=110142086820117&w=2 and here http://ziqbalbh.com/articles/log4net-another-way-to-change-log-file-location-on-runtime/

Categories

Resources