This post is about why I am logging in my applications, what I am trying to get out of it and when I'm logging. Hopefully this helps you on your quest with ILogger!
As a developer, I'm always looking for some useful information to help make a better, faster, smoother application for my users. Application logging isn't a silver bullet in that sense, but it does help in gaining some useful insights on the inner workings of the app.
Logging firstly provides a rough map of the code paths taken within the app. Think of the Marauders Map in the Harry Potter series. With application logging, you can gauge where your app is responding from and trace the subsequent functions taken from that point onwards. This aids with optimisation because you are then able to statistically determine the most-used code paths. From there you can refactor the functions in these paths to be as quick and robust as possible. This will help in having the highest impact when introducing updates to your production app.
The second insight is in the data passed around in your application. If you log using structured logs (check out this article), you will be able to see exactly which data is being passed to your functions. This allows you to handle those weird cases that you would've never thought of. You also tend to build up a history of the exact data that breaks your functions. So you could save that data for unit testing your functions in future.
Thirdly you can identify behavioural patterns within your app. This goes one step further than understanding the code paths as described above. With understanding behavioural patterns you will be able to understand how collections of code paths create particular use-case patterns within your application. If your application logs allow you to optimise your app at this level, then you've nailed logging!
The "when to log" question helps put some flags up in the application at the points where we think we can get some useful data. The "when" question is quite broad though because it could be "when in the general application should I log?" or "when in the specific function should I log?".
Imagine your application as a black box of code that does nothing until something triggers it into doing something. Then when it starts doing something, you want to understand the series of events that took place inside your app by simply looking at your logs. I've listed a few locations where you should be logging.
Firstly log the aforementioned trigger points, these are the API endpoints, the button click event handlers, the signal-r functions, etc. These are the points that effectively trigger your app to do something.
The second set of locations to log are things that absolutely need to happen within your application. They should execute just fine, but when they don't you'd like to know exactly why they didn't. These are things like service constructors, timer event handlers, general event handlers, etc.
If you know that services aren't being initialised properly it can save you a lot of headaches.
Thirdly log all the other service related functions in your application. So if you have a service called PaymentHandlingService, all the functions in that service should be adequately logged.
Logging within functions is a lot simpler. There are effectively 3 points where one would typically like to get information from a function.
- At the start (i.e. for event handlers, etc.)
- At success condition points (i.e. just before return statements)
- At failure condition points (i.e. in catch statements)
Here use the LogInformation / LogError functions that are available with the ILogger interface. Some of the failure conditions could likely use LogCritical if necessary.
If a function does some complex stuff, we may want to log those complexities during debugging. So anywhere in the function where we'd like to log those intricate complexities we would use a LogDebug point.
Logging in classes that store data (i.e. the ones found in the Models/ folder) can be unnecessary. These classes often get passed (and parsed) around by functions in services. They can have functions and constructors but these can easily be wrapped in try-catch statements that throw out Exceptions for your services to log.
For example, the PaymentHandlingService will have adequate logging. That way, the data classes that get moved around within the service (i.e. DebitCard, User, BankInfo, etc.) don't need to have very complicated logging capabilities. The service will log what they're up to.
In engineering there's this phrase that goes "measure twice, cut once". Well please don't log in the same place twice, that's stupid. Log useful information, log it often enough and log in the right places. It'll help you build better things.