-
-
Save nblumhardt/0e1e22f50fe79de60ad257f77653c813 to your computer and use it in GitHub Desktop.
using System; | |
using System.Diagnostics; | |
using System.Linq; | |
using System.Runtime.CompilerServices; | |
using Serilog; | |
using Serilog.Configuration; | |
using Serilog.Core; | |
using Serilog.Events; | |
namespace ConsoleApp24 | |
{ | |
class CallerEnricher : ILogEventEnricher | |
{ | |
public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory) | |
{ | |
var skip = 3; | |
while (true) | |
{ | |
var stack = new StackFrame(skip); | |
if (!stack.HasMethod()) | |
{ | |
logEvent.AddPropertyIfAbsent(new LogEventProperty("Caller", new ScalarValue("<unknown method>"))); | |
return; | |
} | |
var method = stack.GetMethod(); | |
if (method.DeclaringType.Assembly != typeof(Log).Assembly) | |
{ | |
var caller = $"{method.DeclaringType.FullName}.{method.Name}({string.Join(", ", method.GetParameters().Select(pi => pi.ParameterType.FullName))})"; | |
logEvent.AddPropertyIfAbsent(new LogEventProperty("Caller", new ScalarValue(caller))); | |
return; | |
} | |
skip++; | |
} | |
} | |
} | |
static class LoggerCallerEnrichmentConfiguration | |
{ | |
public static LoggerConfiguration WithCaller(this LoggerEnrichmentConfiguration enrichmentConfiguration) | |
{ | |
return enrichmentConfiguration.With<CallerEnricher>(); | |
} | |
} | |
class Program | |
{ | |
static void Main(string[] args) | |
{ | |
Log.Logger = new LoggerConfiguration() | |
.Enrich.WithCaller() | |
.WriteTo.Console(outputTemplate: "[{Timestamp:HH:mm:ss} {Level:u3}] {Message} (at {Caller}){NewLine}{Exception}") | |
.CreateLogger(); | |
Log.Information("Hello, world!"); | |
SayGoodbye(); | |
Log.CloseAndFlush(); | |
} | |
[MethodImpl(MethodImplOptions.NoInlining)] | |
static void SayGoodbye() | |
{ | |
Log.Information("Goodbye!"); | |
} | |
} | |
} |
@p4tr1ckdup0nt
you can publish this as nuget package
Thank you for this. This is very useful. One change I would suggest making is to call "return" after the caller property is set, so that it stops iterating through the stack.
It's best to add a 'return' just after the second 'AddPropertyIfAbsent'. So the while loop won't keep running after you already added the property.
Also: if you added a decorator/wrapper class for logging, you can exclude the name of this class
So my code:
if (method.DeclaringType.Assembly != typeof(Log).Assembly &&//Exclude serilog methods
method.DeclaringType.Name != "SerilogLogger")//Exclude methods from the SerilogLogger implementation class
{
var caller = $"{method.DeclaringType.FullName}.{method.Name}";
logEvent.AddPropertyIfAbsent(new LogEventProperty("Caller", new ScalarValue(caller)));
return;
}
@nblumhardt Can you make a nuget package for this? If so, can you alter the code so you can pass class names which should be excluded (eg "SerilogLogger" from my example above)
Thanks!
Would love a nuget package aswell!
I don't plan to develop this further in any form, due to the extremely poor performance of this approach, but I'd be happy for someone else to take the code and make a package.
I don't plan to develop this further in any form, due to the extremely poor performance of this approach, but I'd be happy for someone else to take the code and make a package.
I have moved on to use a more complete version of This script
In that case I am curious how you do you your error logging. I used to use a custom error logger that outputs the path, and the function name etc. I find that data to be quite crucial. Right now im trying out serilog. I did use the script to still have the path and line number, and i feel like an exception will give me the full stack trace anyways so i'm trying this approach now.
Curious: to me it seems as though line 30 should then return from the function but it goes through the rest of the call stack?
Also curious: would it be more efficient to cache the result of typeof(Log)
so that it's not being fetched with each iteration?
@JimHume thanks for the note! ... yes, absolutely (the dangers of using sketchy code from Gists :-D heheh). I've updated it now.
@nblumhardt Excellent--glad I could give something back. This gist works great and did a good job of illustrating the concepts--thank you for this!
Ok, thats an old one, nevertheless I follow up. Yes, I know this has poor performance, but in asp net core environment I could not find a better solution:
if (method.DeclaringType.Assembly != typeof(Log).Assembly
&& method.DeclaringType.Name != "SerilogLogger"
&& method.DeclaringType.Assembly != typeof(Microsoft.Extensions.Logging.Logger<>).Assembly)
{
var caller = $"{method.DeclaringType.FullName}.{method.Name}({string.Join(", ", method.GetParameters().Select(pi => pi.ParameterType.FullName))})";
logEvent.AddPropertyIfAbsent(new LogEventProperty("Caller", new ScalarValue(caller)));
return;
}
so I extended the proposal of @ArnaudB88 to work with Serilog in asp net core behind Microsoft Logging.
If someone knows a better solution I'd be happy to switch
In answer to my own question: no, the 'typeof' should not be cached, as that would be considered premature optimization according to this stackoverflow post: https://stackoverflow.com/questions/6215312/does-caching-the-return-value-of-typeofmycontrol-provide-any-optimization
I agree with the comment about storing the result of 'typeof' in a variable for readability purposes, however.
Peter Metz (@pmetz-steelcase) took that snippet of code and turned it into a NuGet package for our convenience.
so - the performance is still ... same, but we have new features in 1.3.0
I stumbled upon this because I needed the namespace, source file, line number, etc. in a structured format. The package posted above is great but throws everything into a single string, making it hard to parse the different values again.
I made a new library based on this code but adds all these attributes separately. It also supports multiple assemblies in multi-project solutions and can figure out the referenced assemblies by itself. You can also configure a prefix for the attribute names to avoid name clashes.
It is available here: https://github.com/pm4net/serilog-enrichers-callerinfo/. Hopefully it is useful for not just my use case :)
@johannesmols This is a great solution! I switched from my home brewn one to yours. Works like a charm. Thx a lot!
@digitalsigi Awesome, great to hear!
@johannesmols, @nblumhardt
Hi, I need to close the nuget and github the next days (...), do you like to take over the nuget
https://www.nuget.org/packages/Serilog.Enrichers.WithCaller
Thanks
@pmetz-steelcase Sure, I can take over the nuget package. Will be difficult to maintain it without the source code though. Do you mind me asking why you deleted it?
@johannesmols sorry, it's public again, I have to delete my account
@pmetz-steelcase okay, I made a fork https://github.com/johannesmols/Serilog.Enrichers.WithCaller to preserve the code. On nuget, you can transfer ownership as well (same username as on github). I won't update the package since I created my own improved version of it, but then we will at least have the code available still if someone needs it or makes a PR.
@johannesmols
I added you as additional owner of nuget, after your approval, I'll remove my name
question, the code https://github.com/pm4net/serilog-enrichers-callerinfo , it is yours, too?
@pmetz-steelcase great, I accepted it. And yes, that is my spin on it that I created to suit my needs better
Ok I make it work correctly, it was because I compiled my app with 4.6.1. I set it to 4.7.1 and now it is ok.
I have a small question, is there a way to compile Serilog to add this extension and not need to add the class in each project I use Serilog?
Thanks!
-Patrick Dupont.