Logging in Xamarin with Serilog

This article introduces briefly the design of the logging infrastructure in general. Although some part of the article is written in the the context of Xamarin with MvvmCross, Serilog is available across .NET Framework and .NET Standard, so most of the following content can apply to the whole .NET ecosystem in general.

Logging Infrastructure

Below are the common components involved when you do logging.

  • Logger This is the object your logic code hold to interact with the logging infrastructure. The object is normally an ILogger (for .NET Standard) or MvxLog (for MvvmCross).
    It normally has the following functions: Trace/Verbose, Debug, Info, Warning, Error, Fatal. Those functions are actually a shorthand for a common Log function with a corresponding Log Level parameter (more on Log Level in the next subsection).
    Application logic never creates a logger by itself. Instead, it gets a logger created by a Log Provider (usually via a dependency injection).
  • Log Sink A sink indicate the actual destination of the logs (e.g. debug output, console, file, etc.). It is usually configured in the Log Provider instead of embedded inside the logger logic. This separation of Sinks from Logger is extremely powerful, allowing you to change your log destination easily by swapping (or adding) Sinks without ever touching your Logger. You can even setup different filters and limits for each sinks (see example below).
  • Log Provider This component connects everything together based on the requested configuration and creates an appropriate logger object. It is usually configured at application start up.

Log Level

Log Level is a way for developer to indicate the criticality of each log entry, allowing appropriate minimum level filtering for each sink. Below are my own convention for using Log Level.

  • Trace/Verbose This level is usually used to trace each step of your logic implementation. I rarely log at this level, and usually reserve this for logs from the underlying platform.
  • Debug This level can be used as a replacement for any Debug.WriteLine() or Console.WriteLine() in your code. Keep in mind that this level is usually used for logs that should be excluded from production/release build.
  • Information This level is similar to Debug level with the exception that those logs should still show up in production/release build. Those are the logs that should help you in troubleshooting production issue.
  • Warning I usually use this level to indicate recoverable problem, for example, networking issue with a retry later.
  • Error This is usually used to indicate a more critical problem, especially those that need user intervention, e.g. database corruption. You can think of this level as something that should automatically send an email to support team.
  • Fatal This should be only used to indicate a crash.

In general, lower log level should give you more in-depth details of the application logic, however, can generate a large amount of log entries. So you should take care of your filters so as to not exhaust your sinks.

Structured Logs

If you are logging formatted string like below, you might not fully utilize the capability of your logging framework:

Instead, try to do this:

Basically you don’t pre-format your log, and instead give the template and all the parameters to the logger directly.

The logger can create the same formatted string on its own, so you don’t lose any feature. In addition, it passes all the parameters to your sinks. If you setup a sink that support structure logs, you can easily filter your logs later on using those parameter value (e.g. userId) instead of have to use Regex on your formatted strings.

Most of the logging framework nowadays support structured logging, so there are really no reason not to use it.

Serilog

Serilog provides us with a Log Provider that works well with both ASP.NET Core for your server and MvvmCross for your Xamarin App. Serilog also has a large number of sinks available as Nuget packages.

Serilog also has some additional facilities that you should learn about and utilize:

  • Enricher: add context information into each log entry
  • Filter: fine tune which log entry can go to each sink

Below is an example configuring Serilog for MvvmCross:

More examples can be found in the reference links at the end.

Some Recommendations

  • Log Level and log Filter allow you to control how much log is actually stored without touching your logging calls. Hence, you should not hesitate to log frequently in the app. Just remember to set appropriate log level and use structured log instead of formatted string.
  • If you are building an Xamarin/mobile app without Internet access to logging services such as Azure Event Hubs or Application Insights, you can write your logs into file (should be rolling to prevent filling up storage) and provide a button in app to automatically zip up the logs, open a new email composer and attach the zip file. You might also automatically filling in appropriate information for the user such as support email, subject, app versions. This should also happen after the app is restarted from a crash.
  • If you are writing mobile app, do NOT write logs into your application database, even for error logs. On a bad day, a bad bug in the clients (or even a surge of users) may generate huge amount of logs and affect your server. If you have ample disk space, you can consider using Rolling File sink.
    An even better choice is to use external services on a separate server/container for your log sink, especially if you want to analyze your low level logs. One very good service that can be installed on-premise is https://www.splunk.com.
  • Filter level switching is another useful feature of Serilog https://nblumhardt.com/2017/10/logging-filter-switch/. This allows turning on more detailed logging as and when needed without the need of restarting the application.

Links

Azure Notification Hub SDK on .NET Standard and WNS Raw notification

Update (3 Oct 2018): Thanks to the beloved-by-community Jon Galloway, I can finally connect with the engineers working on the library. Hopefully, the team can push out a fix soon.

Update (13 Oct 2018): Microsoft team has quickly release version 2.0.1 with proper fix for the issue. Bravo for the team!

I have been using Azure Notification Hub for a very long time to send push notifications to my UWP apps on Windows 10. Since the introduction of .NET Core 1.0, I have gradually been moving my projects from .NET Framework to .NET Core. However, Microsoft.Azure.NotificationHubs was always the blockers due to the lack of support for .NET Standard.

Tl;dr version:

  • 2.0.0-preview1 and preview2 have issue sending WNS Raw message.
  • After 8 months, 2.0.0 was released with the same issue.
  • There was no place to report the issue.
  • There was no source code.
  • The fix is only 1 LOC, but I spent much more time to make the decompiled code compile back.
  • And now I have still no way to report the issue nor contribute the fix.
Read More

Interesting notes on Docker Windows containers

In this post, I collected some useful links and notes when trying out Docker on Windows with Windows containers.

Useful images

Useful commands

Some notes

  • You do not see the Mount/Disk setting in Docker when using Windows container instead of Linux container because this is not necessary according to the documentation. However, you have to pass in correct parameter for the volume; for instance, C:\data_in_host:C:\data_in_container  or C:/data_in_host:C:/data_in_container  instead of C:/data_in_host:/data_in_container . Check the following links for more information: https://docs.microsoft.com/en-us/virtualization/windowscontainers/manage-containers/container-storage.
  • You cannot test with http://localhost:{host_port} on Windows. You will have to use either http://{host_name}:{host_port} or http://{container_ip}:{container_port}. To find out container IP, you can use docker inspect --format '{{ .NetworkSettings.Networks.nat.IPAddress }}' container_name.

Links & Examples

https://docs.microsoft.com/en-us/virtualization/windowscontainers

https://anthonychu.ca/post/dockerizing-aspnet-4x-windows-containers

Deploying ASP.NET Core applications to Docker containers

Migrating Legacy UUID of MongoDB to standard UUID

MongoDB has a legacy format for UUID which causes problem where the UUID is interpreted differently on different platforms, and the standard UUID solves that exact problem.
However, the C# driver defaults to use legacy format and requires switching to standard format explicitly.

Moreover, after switching to standard format, you might get Exception due to existing UUID. You can fix that Exception by using the Python script below to migrate legacy UUID to standard UUID while maintaining the same C# GUID translation:

After running the script, you can then replace ‘my_collection’ by ‘my_collection_new’.
Based on your actual need, you may also replace CSHARP_LEGACY with JAVA_LEGACY or PYTHON_LEGACY or other legacy format (http://api.mongodb.org/python/current/api/bson/binary.html).

Hope this help!