Logging with Ninject Interception and Serilog - Part 2
Recap
Part 1 of this series was me attempting to use Ninject.Extensions.Interception in combination with Serilog to do AOP logging for times when fine-grained logging isn’t necessary across larger vertical slices of my application.
Picking up where I left off, I had created 3 classes that inherited IInterceptor
:
LogInterceptor
for logging method parameters and return values
public class LogInterceptor : SimpleInterceptor {
private readonly ILogger _logger;
public LogInterceptor(ILogger logger) {
_logger = logger;
}
protected override void BeforeInvoke(IInvocation invocation) {
}
protected override void AfterInvoke(IInvocation invocation) {
_logger.ForContext(invocation.Request.Target.GetType())
.Information("Method: {Name} called with arguments {@Arguments} returned {@ReturnValue}",
invocation.Request.Method.Name,
invocation.Request.Arguments,
invocation.ReturnValue);
}
}
LogErrorInterceptor
for logging all errors
public class LogErrorInterceptor : IInterceptor {
private readonly ILogger _logger;
public LogErrorInterceptor(ILogger logger) {
_logger = logger;
}
public void Intercept(IInvocation invocation) {
try {
invocation.Proceed();
}
catch (Exception exception) {
_logger.ForContext(invocation.Request.Target.GetType())
.Error(exception, "Error at Method: {@Name} Arguments: {@Arguments}",
invocation.Request.Method.Name,
invocation.Request.Arguments);
throw;
}
}
}
TimerInterceptor
for logging time elapsed
public class TimerInterceptor : IInterceptor {
private readonly string _timerName;
private readonly ILogger _logger;
public TimerInterceptor(string timerName, ILogger logger) {
_timerName = timerName;
_logger = logger;
}
public void Intercept(IInvocation invocation) {
using (_logger.ForContext(invocation.Request.Target.GetType())
.BeginTimedOperation(_timerName,
invocation.Request.Method.Name)) {
invocation.Proceed();
}
}
}
As you can see the only differences in the classes that I didn’t use SimpleInterceptor
, is that I wanted more unique behavior in the way that I intercepted the invocation of the method e.g. I wanted to catch any errors thrown or SerilogMetrics required me to wrap the invocation in a using
statement.
TimerInterceptor
for drop-in performance
The ErrorLogger interceptor is great, pretty straightforward and doesn’t really care what method it’s intercepting (as long as it’s virtual per the requirements of the extension). The TimerInterceptor
is also awesome. SerilogMetrics is an extension for Serilog that creates structured log outputs using meters, counters, timers, and gauges. Super easy to use, just like Serilog.
To use this interceptor we add it to our bindings in Ninject
public class ServiceModuleTest : NinjectModule {
public override void Load() {
Bind<ILogger>().ToMethod(x => new LoggerConfiguration()
.WriteTo.Seq(@"http://UrlToSeqSink/")
.CreateLogger());
// Bind that timer interceptor here
Bind<IInterceptedByTimerAttributeTestClass>()
.To<InterceptedByTimerAttributeTestClass>()
.Intercept().With<TimerInterceptor>();
}
Two log entries are created for the start and stop of the method’s timer…
Start timer:
{
"Timestamp": "2015-07-01T11:56:35.6506008-04:00",
"Properties": [
{
"Name": "TimedOperationId",
"Value": "CreatePart"
},
{
"Name": "TimedOperationDescription",
"Value": "CreatePart Timer"
}
],
"MessageTemplateTokens": [
{
"Text": "Beginning operation "
},
{
"PropertyName": "TimedOperationId"
},
{
"Text": ": "
},
{
"PropertyName": "TimedOperationDescription"
}
],
"EventType": "$7151D589",
"Level": "Information",
"Id": "event-a46e8198822d08d22200000000000000",
"Links": {
"Self": "api/events/event-a46e8198822d08d22200000000000000{?download,render}",
"Group": "api/events/resources"
}
}
End timer:
{
"Timestamp": "2015-07-01T11:56:38.8439201-04:00",
"Properties": [
{
"Name": "TimedOperationId",
"Value": "CreatePart"
},
{
"Name": "TimedOperationDescription",
"Value": "CreatePart Timer"
},
{
"Name": "TimedOperationElapsed",
"Value": "00:00:03.1935646"
},
{
"Name": "TimedOperationElapsedInMs",
"Value": 3193.0
}
],
"MessageTemplateTokens": [
{
"Text": "Completed operation "
},
{
"PropertyName": "TimedOperationId"
},
{
"Text": ": "
},
{
"PropertyName": "TimedOperationDescription"
},
{
"Text": " in "
},
{
"PropertyName": "TimedOperationElapsed"
},
{
"Text": " ("
},
{
"PropertyName": "TimedOperationElapsedInMs"
},
{
"Text": " ms)"
}
],
"EventType": "$6A82779B",
"Level": "Information",
"Id": "event-a655c4a1822d08d22300000000000000",
"Links": {
"Self": "api/events/event-a655c4a1822d08d22300000000000000{?download,render}",
"Group": "api/events/resources"
}
}
Now, I have some sweet metric logging that can be filtered in my chosen Serilog sink (Seq is really awesome) to look at the execution times of specific methods in our application over time.
Next part, I’ll talk about the awesomeness of Ninject.Extensions.Interception.Attributes.
If you like this post and want to see more come right to your inbox - be sure to sign up for my newsletter. I always try to cover interesting and helpful topics for .NET developers like you!
Related Posts: