A few days ago I was reviewing a pull request at work and one line of code catch my eye:
At my workplace we are using standard
ILogger interface from
package. Also logged variable name starts with
And so I started to suspect that
the log statement contains an error and instead it should be written as:
Without thinking any further I put a friendly comment, that this logging statement should be fixed. After half an hour, instead of a fix I get the following response:
In this microservice we are using Serilog as third-party logging provider.
@is used as destructuring operator, please see: https://github.com/serilog/serilog/wiki/Structured-Data#preserving-object-structure
Basically this means that the argument will be logged in JSON form.
@ character was put there on purpose. OK, fine.
But there still was something fishy about this code.
On the one hand we are using
Microsoft.Extensions.Logging.Abstractions to decouple ourselves
from any specific logging provider,
on the other hand we are using Serilog specific extensions.
This results in a false sense of security.
We may think that since we are
ILogger, changing logging provider to e.g.
Azure Web App Diagnostics would be as simple as changing
class of our application.
Unfortunately since we coupled ourselves with Serilog
(by Serilog specific extensions to the log message template),
some of our log statements may not work with the new logging provider.
So what is the solution to this problem? We must choose whatever we
want to use Serilog specific features. If we want to use them, then
we should not hide the fact that we are using Serilog. Fortunately for
us Serilog provides it’s own, ready to use
And we should use that interface instead of standard one accross
the entire application.
On the other hand, if we expect that we may need to change logging
provider in the future, we should stick with
ILogger and we should
use only the features that are described in
the official documentation.
If our needs are not fully covered
by the standard
e.g. we must log objects as JSON, then we must implement them
ourselves by e.g. creating wrappers around parameters:
It is really interesting that a similar coupling happens when using
IEnumerable<T> interface as the return type of a method.
How many times have you seen a code similar to:
Again we have here a bad case of hidden coupling to the interface implementation.
We are using
interface but we are assuming that it is backed by
a collection for which multiple enumerations always
return the same elements.
Our code will break
when someone will change
FindAllUsers implementation to
The solution to this problem is honesty. If you have
a value of type
IEnumerable<T>, tread it as
a value of type
IEnumerable<T>. Nothing more, nothing less.
Do not assume that multiple enumerations
will return the same elements.
This is not guaranteed by that interface.
If you want to return a sequence of elements from a method with
this additional guarantee, then please use a more specific
maybe even something from