How to hide method in logging chain with log4net? - c#

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!

Related

Capture and change all logging message

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.

Can I pass parameter attributes through a function?

Weird and very specific question. Right now we have a logging interface that uses the CallerMemberNameAttribute like so:
interface ILogger
{
void Info(string message, [CallerMemberName] string memberName = "");
}
Which is all great and perfect and works fine in our implementation. However, a requirement has come up that we need to write some functions that can be invoked in our process and also elsewhere, and that these functions need to use a defined ITracer class that looks like the following:
interface ITracer
{
void TraceInformation(string message);
}
So when run in our environment, these functions should log to our current logging infra, but when run in the other environment, it has it's own ITracer set that does its own thing. So I wrote a shim that just passes the messages through when called in our environment:
class Logger : ILogger
{
public void Info(string message, [CallerMemberName] string memberName = "") => // log some stuff
public ITracer GetTraceWriter() => return new TraceWriter(this);
}
class TraceWriter : ITracer
{
public TraceWriter(ILogger logger) => this.logger = logger;
public void TraceInformation(string message) => this.logger.Info($"{message}");
}
This works fine, but the memberName is part of the log message that is output, and with this implementation, when the TraceWriter starts logging, it always has the memberName equal to TraceInformation. Is there a way I can pass this parameter attribute through the function call somehow? The main issue here is that I cannot change the ITracer interface.
Solutions thought of but can't get to work:
Change the TraceInformation call in ITracer to return a function call to ILogger.Info that could be invoked directly from the method (Cannot do this because I cannot change the ITracer interface)
Something you could do, which may or may not be ideal, would be to use the StackFrame class to look up the stack to the calling function.
You can use this to iterate through the stack to either find (or exclude) specific types, or perhaps types that implement a specific interface - or more simply to just 'up' the stack a specific number of frames.
If you included this in your implementation of void Info(..) you could access method names as
# get the name of the current method (i.e. Info)
new StackFrame(0, false).GetMethod().Name
# get the name of the calling method (i.e. TraceInformation)
new StackFrame(1, false).GetMethod().Name
# get the name of the parent calling method - what you are looking for
new StackFrame(2, false).GetMethod().Name
Of course, you then need to reconcile when the method is called from the ITracer object and when it is called directly. You could also get calling objects, inspect what interfaces they implement, and record appropriate method name.
This all uses reflection so you will need to consider performance impact, however I expect that CallerMemberName also uses reflection / may have similar impact.

NLog - Extending Nlog - callsite - dispose()

I am trying to use NLog to log some things in our system. This is a very complicated system, multi-threading with message queues in different places.
We needed to extend NLog to log some other items that way we needed.
After extending NLog, we found the callsite info was wrong. Some searching on Stack gave me a solution.
My concern now is, with the extending we have done (passing the TypeName) we could have several hundred log instances, and I have a bad feeling that this will get backed up really fast.
I don't see a dispose method for NLog, so I don't know how garbage collection will pick this up.
If I have 100 classes, each running on different threads processing 1000 transactions a second, and each of these is logging and creating a new log instance, I see problems in my future.
I am open for suggestions. Please let me know if this will even be a problem, and if so, possible solutions?
EDIT: Adding code: - Here is the ExtenderClass (takes the class name)
private readonly Logger _logger;
public NLogLogger(Type t)
{
_logger = LogManager.GetLogger(t.FullName);
}
public void Trace(string message, string systemUserID, string transactionID, string description)
{
if (_logger.IsTraceEnabled)
{
Write(LogLevel.Trace, message, systemUserID, transactionID, description);
}
}
Here is a class that uses it: - This class will be instantiated for each thread task, so maybe 500 on a busy day.
public class ThreadTask : TaskBase
{
private readonly NLogLogger _logger = new NLogLogger(typeof(ThreadTask));
public Method(string val)
{
_logger.Trace(message);
}
}
The typical pattern for creating your logger instances with NLog (and with log4net for that matter) is to do something like this in each class:
public class MyClass
{
private static readonly Logger logger = LogManager.GetCurrentClassLogger();
public void DoSomething()
{
logger.Debug("Hello from inside DoSomething");
}
}
This way, there is only one logger instance instantiated per class (that has a logger created as above). So, there should not be an issue of an excessive number of loggers being created simply because a lot of class instances are created. Also, since the typical pattern is to use static logger instances, the loggers will remain in effect throughout the life of the application (or maybe AppDomain), so no Dispose is needed.

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