Capture and change all logging message - c#

I have an ASP.NET Core 5 application. There are some log providers that I have as from box. Other words I cannot configure theese providers. But I need to add additional info to every logging message.
For example, code invoke:
logger.LogInfo("Hello World");
But logging provider must get, for example "UserId: 123; Hello World"
How can I reach this goal?

you can create an extension method for this like this,
public static class LogExtensions
{
public static void CustomLogInformation(this ILogger log, string message)
{
log.LogInformation($"prefix{message}");
}
}
you could also pass Exception exception as a parameter if you require it.
In this example, prefix is added, but you can change the value as per your requirements.
If the prefix values changes then you can pass that as well by adding one more parameter to this method. If it's the same for all scenarios then you can define it in some config file.
and if you look at the source code, LogInformation itself is an extension method.
namespace Microsoft.Extensions.Logging
{
public static class LoggerExtensions
{
public static void LogInformation(this ILogger logger, string message, params object[] args)
{
logger.Log(LogLevel.Information, message, args);
}
}
}

NLog Layout enables you to capture additional NLog Context besides what is provided with the actual logevent.
You could update your NLog Target Layout to include ${aspnet-user-identity} in the output:
<target xsi:type="console"
name="console"
layout="${aspnet-user-identity} ${level} ${message}" />
Another alternative could be ${aspnet-user-claim:ClaimTypes.Name}. If your user-information comes from custom location, then you can also implement a custom layout-renderer.

Related

ASP.NET Core Logging with Func() argument

I am using ASP.NET core with NLog, using it as a replacement for the original ASP.NET Core logger with the NLog.Web.AspNetCore nugget package.
NLog contains a useful Func() delegate signature that allows to performs arguments evaluation only if the corresponding logging level is enabled:
static readonly Logger log = LogManager.GetCurrentClassLogger();
log.Trace(() => request.JsonSerializer.Serialize(body));
I am using ASP.NET with NLog, but it sounds like this feature is not available:
private ILogger<MyController> log;
log.Trace(() => request.JsonSerializer.Serialize(body));
Before undertaking to write myself a method, I would like to know if I missed something, I have not find anything about such logging methods with a delegate argument using ASP.NET Core with NLog.
There is no such thing in the Microsoft.Extensions.Logging abstractions, and the way it is built, it isn’t exactly easy to do such a thing. While you can easily add extension methods to it, and actually all log calls are extension methods, the base Log method is what determines whether or not to log somethings since it is the only thing that actually has access to the configured log level.
That being said, the logging abstractions to use something that may make it possible to do something similar to this. For that, consider the signature of the ILogger.Log method:
void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
As you can see, there isn’t actually a string being passed to it, but just a state and a formatter. In the default extension methods, the state is a FormattedLogValues object and the formatter is just a method that calls ToString() on the state, i.e. the FormattedLogValues object.
The FormattedLogValues is what actually builds the formatted string, and that’s also where the structured logging is happening. So it is actually a bad idea to serialize some object in your log message; you can just pass that directly to the logger.
But what you could do here is provide your own overloads to Log that take a function instead which is then wrapped into some state object that executes the function when ToString() is being called.
There is not much change in Nlog Implementation for Asp.net core 2.0.
Setup 1: you need to install Nuget package Click here
Setup 2: you need to create Nlog config file with below configuration.
<nlog>
<!-- the targets to write to -->
<targets>
<!-- write logs to file -->
<target filename="${basedir}/logs/${shortdate}.log" layout="
-----------Time Stamp: ${longdate}----------
Log Level: ${level}${newline}
Logger Name : ${logger}${newline}
Log Message : ${message}${newline}
Exception Message: ${event-context:item=ErrorMessage}${newline}
Browser Detail: ${event-context:item=BrowserDetail}${newline}
Session Id: ${event-context:item=SessionId}" name="file" xsi:type="File">
<target br="" connectionstring="${gdc:item=defaultConnection}" dbprovider="Oracle.ManagedDataAccess.Client.OracleConnection,
Oracle.ManagedDataAccess, Version=2.0.12.0, Culture=neutral, PublicKeyToken=89b483f429c47342" keepconnection="false" name="database" xsi:type="Database">
commandText="INSERT INTO TableName (LOG_LEVEL,LOGGER_NAME,SESSION_ID,BROWSER_DETAIL) values(:LOGLEVEL,:LOGGERNAME,:SESSIONID,:BROWSERDETAIL)">
<parameter layout="${level:uppercase=true}" name="LOGLEVEL">
<parameter layout="${logger}" name="LOGGERNAME">
<parameter layout="${event-context:item=SessionId}" name="SESSIONID">
<parameter layout="${event-context:item=BrowserDetail}" name="BROWSERDETAIL">
</parameter></parameter></parameter></parameter></target>
</target></targets>
<rules>
<!--All logs, including from Microsoft-->
<logger minlevel="Error" name="*" writeto="file">
<logger minlevel="Trace" name="*" writeto="database">
<!--Skip non-critical Microsoft logs and so log only own logs-->
<logger final="true" maxlevel="Info" name="Microsoft.*">
<!-- BlackHole -->
</logger></logger></logger></rules>
</nlog>
Setup 3: Need to update Startup file.
NLog.GlobalDiagnosticsContext.Set("defaultConnection", Connection string); NLog.LogManager.LoadConfiguration(env.ContentRootPath + "\\NLog.config");
Setup 4: We have created custom Nlog manager.
public static class NLogManager {
public static ILogger _logger = NLog.LogManager.GetCurrentClassLogger();
public static void InfoLog(NLogData nLogData) {
LogEventInfo theEvent = new LogEventInfo(LogLevel.Info, NLogManager._logger.Name, nLogData.Message);
SetLogEventInfo(theEvent, nLogData);
_logger.Log(theEvent);
}
public static void DebugLog(NLogData nLogData) {
LogEventInfo theEvent = new LogEventInfo(LogLevel.Debug, NLogManager._logger.Name, nLogData.Message);
SetLogEventInfo(theEvent, nLogData);
_logger.Log(theEvent);
}
public static void ErrorLog(NLogData nLogData) {
LogEventInfo theEvent = new LogEventInfo(LogLevel.Error, NLogManager._logger.Name, nLogData.Message);
SetLogEventInfo(theEvent, nLogData);
_logger.Log(theEvent);
}
}
Custom Event parameter for logging :
private static void SetLogEventInfo(LogEventInfo theEvent, NLogData nLogData) {
theEvent.Properties["SessionId"] = nLogData.SessionId;
theEvent.Properties["BrowserDetail"] = nLogData.BrowserDetail;
}
Model for NLog logging.
public class NLogData {
public string SessionId {
get;
set;
}
public string BrowserDetail {
get;
set;
}
}

IoC logger with parameters

I'm currently playing around with the IoC concept (with a WPF app) and I haven't decided on the tool I'll used with it just yet as I'm still trying to get the grasp of it but I'm confused as to how this would be configured regarding the specific parameters each component.
I understand how you define the relevant library in the config file and how it will determine which one should be used by the app and what its lifespan should be but what about each library requiring its own specific set of parameters.
Where do you get these from and when do you pass them on?
Taking your typical logger for example.
I have the following interface:
public interface ILogger
{
void Write(string message);
}
I have the logger class itself:
public class Logger : ILogger
{
private readonly ILogger _logger;
public Logger (ILogger logger)
{
_logger = logger;
}
public void Write(string message)
{
_logger.Write(message);
}
}
I then define multiple loggers each requiring their own parameter, so I implemented the following:
a) database logger: where a connection string is required so that I can log my message to a database.
public void LoggerDb: ILogger
{
public void Write(string message)
{
}
public ConnectionString {get; set;}
}
b) file logger: where a filename is required so that I can log my message to the relevant log file.
public void LoggerFile: ILogger
{
public void Write(string message)
{
}
public Filename {get; set;}
}
c) console logger: where no parameter is required as I just want to output my message to a console window.
public void LoggerConsole: ILogger
{
public void Write(string message)
{
}
}
In my console test app, I've got the following code in the Program.cs:
static void Main(string[] args)
{
string logTypeId = "d";
ILogger logType;
if (logTypeId == "d")
{
logType = new LoggerDb("Data Source=....");
}
else if (logTypeId == "f"
{
logType = new LoggerFile("c:\\mylog.txt");
}
else
{
logType = new LoggerConsole();
}
Logger logger = new Logger(logType);
logger.Write("Message 1");
logger.Write("Message 2");
logger.Write("Message 3");
}
I understand this is not how the code would be if I used an IoC tool. I'm just trying to highlight what I'm trying to achieve and I'm trying to get answers to the following questions:
Can this be achieved using an IoC tool i.e. pass specific parameter depending on the logger type that's used/defined in the IoC section of the app.config?
Is this the correct approach i.e. Having specific loggers with their own constructors parameters? If not, can you explain why and what should be the correct approach. I don't mind the IoC tool you use. I just want to understand how this should be done.
Where should these additional parameters be stored in the app.config?
First, note that in order to implement DI via an IoC, it is by no means required to configure your container in a configuration file (although it's certainly an option and many containers support it).
Most IoC containers these days also allow you to specify your setup in code. So I guess the answer is: it really depends on the IoC container you plan to use. My opinion: avoid xml-based configuration if you can; it's often a pain to maintain and brings little value if you ask me. In your code-based configuration you can still refer to configuration parameters from app.config or other.
You can also turn the question around: is it a requirement to have the container configuration in a separate file (and why)? If yes, look for a container that supports this well. But most do.
Some examples of configuration using a code-based DSL:
Autofac modules: http://docs.autofac.org/en/latest/configuration/modules.html
StructureMap: http://structuremap.github.io/registration/registry-dsl/
Some examples of xml configuration:
Autofac: http://docs.autofac.org/en/latest/configuration/xml.html
Spring.NET container: http://www.springframework.net/doc-latest/reference/html/objects.html
structuremap: http://docs.structuremap.net/configuring-structuremap/structuremap-xml-configuration/
It depends ;)
I can't speak for all DependencyInjection Tools, but many of them should support this functionality.
I don't see anything that speak against this. If you want to call different Loggers explicitly, you can do this. But you can also use some kind of LogListeners. One for DB, one for File and so on. And your Logger just delegates the LogMessage to all Loggers. But this depends on what you want or need ;)
This also depends on the implementation of the Logger. It's common to store the ConnectionString in the config. The other parameters are too specific, but you you can store them in config, too.

How to hide method in logging chain with log4net?

Say I have extensions to ILog. Currently when I use ILog extensions in my buisness logic I see %method %location %class properties as my Extension class ones. I want tham to be refrences to my business logic. Are there in log4net attributes to make ILog extensions hidden?
For example
using log4net;
namespace Helpers {
public class MyObject {}
public static class LoggerExtensions {
public static void Debug(this ILog log, MyObject obj, string format, params object[] arguments) {
log.DebugFormat(format, arguments);
}
}
}
for this <conversionPattern value="%type %method %m%n" /> would return something like: LoggerExtensions Debug message and log4net would not care from where it was invoked.
I need some attribute to make this extension transparent for log4net stackTrace inspector. Is there any or how to create one?
You should be able to do something like this (note that I don't have time right now to test this)...
using log4net;
namespace Helpers
{
public class MyObject {}
public static class LoggerExtensions
{
public static void Debug(this ILog log, MyObject obj, string format, params object[] arguments)
{
if (!log.IsDebugEnabled) return;
log.Logger.Log(typeof(LoggerExtensions), LogLevel.Debug, string.Format(format, arguments));
}
}
}
The key to getting the right call site logged (i.e. where you call your extension method, not the extension method itself) is to use the "Log" method, passing it the type of your object where your logging extension is implemented. Log4net will traverse up the stack until it gets to a type that is the same as the type passed to Log. Some refer to this type as the boundary type. The next entry up on the stack will be the actual call site in your "real" code, not your logging code.
Note also that I check to see if debug logging is enabled and return early if not. The Log method does not have a signature that takes a format and params arguments, so the message must be formatted before calling Log. We don't want to spend the cost to format the message unless it will actually be logged.
As I said earlier, I have not tested this, but this technique is used when wrapping a log4net logger (for example in Common.Logging .Net) to ensure that the call site is maintained. If you look for lot4net wrappers (or NLog wrappers) here on SO, the vast majority are not written such that the call site is maintained, so beware. If you want to wrap or to hide log4net behind an extension method, you cannot simply delegate to the Debug, Info, etc methods. You must use the Log method so that you can pass the boundary type.
Good luck!

log4net custom logging

Someone please recommend a better title for this question. I'm not sure what to put.
Right now we have a log wrapper around an instance of ILog that prepends some text to the logged messages. What I'd like to do instead is implement either ILayout or ILogger or IAppender, which could then be specified in our configuration XML. The text I want to prepend isn't static. Because it's used in every log entry, we want to implement it once rather than everywhere we make a log message in the code.
Does this make sense? Which interface should I implement? Right now we use the PatternLayout.
It depends on how you plan to reuse it (for example, when using multiple appenders), but since you are changing the text of the log message, ILayout sounds like the best choice.
You could inherit PatternLayout and do your stuff in Format.
I agree with implementing a custom PatternLayoutConverter. Here a couple of examples:
This one adds the System.Diagnostics.Trace.CorrelationManager.ActivityId to the output:
public class ActivityIdLayoutConverter : PatternLayoutConverter
{
protected override void Convert(System.IO.TextWriter writer, LoggingEvent loggingEvent)
{
writer.Write(Trace.CorrelationManager.ActivityId.ToString());
}
}
This one is parameterized (it can be configured with a key which can be used to retrieve a value from a dictionary - similar to the GDC or MDC):
class KeyLookupPatternConverter : PatternLayoutConverter
{
protected override void Convert(System.IO.TextWriter writer, LoggingEvent loggingEvent)
{
string setting;
//Option is the key name specified in the config file
if (SomeDictionaryWithYourValues.TryGetValue(Option, out setting))
{
writer.Write(setting);
}
}
}
Here is a link to a question that I asked about creating a PatternLayoutConverter that can take a key value. It shows how to do it in log4net and NLog as well as how to configure.
Alternatively, you could wrap a log4net logger and in your wrapper's "Log" method, you could modify the input message or your could put your custom values in the GlobalDiagnosticContext.Properties or ThreadDiagnosticContext.Properties and then reference the values in the output via the normal properties token method.
You might want to use dependency injection on your app which you can change the way you are logging later on to whichever you want.

Singleton logger, static logger, factory logger... how to log?

I am wrapping the patterns & practices Enterprise Library Logging Application Block for an application written in .NET.
I want to be able to subclass a logger (i.e to provide domain specific logging).
What is the best way to do this?
For e.g, I have a static Logger class at the moment, but this does not allow me to specialize it for domain specific logging.
For example,
Log(MyDomainObj obj, string msg)
Check out NLog. They use this sort of pattern:
private static Logger myDomainLogger = LogManager.GetCurrentClassLogger();
You can then specialize the output based on the class that myDomainLogger belongs to.
More detail:
class MyDomain
{
private static Logger _logger = LogManager.GetCurrentClassLogger();
private void SomeFunc()
{
_logger.Trace("this is a test");
}
}
Then in your output you can have it output "MyDomain.SomeFunc" as part of the "this is a test" message.
Also, checkout log4net. I never found the EL's logging to be as flexible as log4net. I chose log4net since I was already familiar with using log4j.
protected readonly log4net.ILog LOG = log4net.LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
Doing it this way, I can get logs like this:
2009-07-15 09:48:51,674 [4420] DEBUG
SampleNamespace.SampleClass [(null)] -
Sample message you want to output
You could even do better than that. Write a wrapper class that wraps either Nlog or log4net or whatnot. You can then use that wrapper class (maybe use an interface to it if you really want to decouple things) in your code. This way, if you decide to change logger class, you need to change just one class and not edit all your classes.

Categories