In this post, you will learn about a simple library that supports the use on NLog with LinqPad.
Introduction
We all love LinqPad. Well, at least I really like it. I consider it not only a sketchpad but also an indispensable tool when it comes to proof-of-concept code, testing of some scenarios - and of course, debugging.
In a sketch or a POC, using the all-mighty extension method Dump
is straightforward, mostly enough, and even better than any classic logging. Still, if it comes to debugging of code that is already part of a large solution and uses classic logging, one cannot always replace that facility with the tools of LinqPad.
Background
When it comes to logging, I am mostly using NLog. I needed many times to view the logging results near the code I was calling from LinqPad. Searching the web, I stumbled upon several code snippets that created Console logger capable of logging to the Results panel of LinqPad. First, I created a library to avoid the need to include those snippets in my sketches. Then I realized that I would like to add that output as a new target instead of replacing all existing ones. Sometime later, I wanted to see colorful log entries instead of plain text. This is how this library came to life.
Using the Code
If you are interested in the whole code, feel free to visit the GitHub repository: https://github.com/zorgoz/Nlog4LinqPad.
If you want to use this logger, you will need to add the reference to the library to the query. If you have Developer/Premium edition, you can directly install it from NuGet, for all other editions, you will have to download the release package from GitHub, or build it for yourself. Don't forget to add NLog
and System
.Reactive.Core
library as well.
You can use the logger with default settings, which will most likely satisfy most of your needs. The entry point of the library is the Nlog4LinqPad
static
class that houses two methods: the legacy LogToConsoleResults
and the more advanced LogToHtmlResults
. All arguments of both methods are optional, but thanks to the new features in C# 7.2, you can override defaults for any of them without bothering about the others.
Both methods will add a target to the existing configuration or create a new one if none is yet created. Beware: by default, LinqPad keeps the types loaded in its AppDomain
, and as NLog is using static
classes, repeatedly running the same sketch will give strange results. Press Ctrl+Shift+F5 to unload the process after finished.
Console Logger
The console logger has only two arguments: the minimum log level (Trace
by default) and a layout format string according to the specifications of NLog (the default will display a timestamp, the level, the message and the exception if any). This logger will use the default result panel for logging, thus the output will be mixed with all other things dumped or written to the console. Let's give it a try:
HTML Logger
The html logger is a little bit more complex. Let's start with the defaults:
As you can see, the default styles are different for the default and the dark theme. Please notice that the log is written to a new panel called Log window by default. This panel is a WebBrowser
control as well as the Results panel, but it is using a simplified DOM, and (for now) it has no length limit imposed by design. I have plans to introduce a rolling log view feature to really support infinite logging.
The solution is using Reactive Extensions to improve performance. The entries are buffered and the document is updated only at every second or when the queue has 200 elements. Afterwards, the browser window is scrolled to the end.
observer = queue
.Buffer(TimeSpan.FromSeconds(1), 200)
.ObserveOn(browser)
.Where(x => x.Count > 0)
.Subscribe(strings =>
{
browser.SuspendLayout();
foreach (var text in strings)
{
var e = browser.Document.CreateElement("div");
e.InnerHtml = text;
browser.Document.Body.InsertAdjacentElement
(HtmlElementInsertionOrientation.BeforeEnd, e);
}
browser.ResumeLayout();
browser.Document.Body.ScrollIntoView(false);
});
The subject is observed synchronized with the browser, thus the UI thread. Such an elegant way provided by Rx of getting rid of invocation!
The static
method has several arguments, all optional with default value:
minLogLevel
: same as for the console logger, defaults to Trace
ownPanelName
: defaults to "Log window"
, panel name used to create logging window. Default Results panel is used if null
. layout
: the layout to use. It is similar to the standard format, with one difference: only layout elements marked with ${...}
are used, each of which will become a node in the result. Anything outside these is ignored. styling
: CSS styles to use for specific elements. Defaults are used if null
. Styling possibilities are described below.
Styling
There is a class called TargetStyling
that can hold CSS styles for various scenarios. Let's take the default layout: "${date:format=HH\\:mm\\:ss}${level}${message}${exception:format=tostring}"
. This is the pattern for a log entry row. Each layout element marked by ${...}
will represent an item in that row. The style counterparts of these two will always be there. All other styles applied will depend on the content of the entry.
<div class="lqph-row lqph-levelname">
<span class="lqph-item lqph-date">..</span>
<span class="lqph-item lqph-level">..</span>
<span class="lqph-item lqph-message">..</span>
<span class="lqph-item lqph-exception">..</span>
The item, row and log level classes have direct property counterparts in the class.
But there is also a property called Classes
, which is a dictionary you can use to add styles for each NLog renderer and more. Just for illustration, these are used in the default styles resulting in the view from above:
Classes = {
{"date", "width: 100px;"},
{"level", "width: 70px; text-transform: uppercase; font-weight: bold;"},
{"exception:empty", "display:none;"},
{"exception", "display:block; padding: 10px;"}
}
Points of Interest
First, I was using the default Results panel, but after inspecting the code of LinqPad and the resulted html code, I realized that it is not the best for logging uses, because of the overhead represented by the features not needed in this scenario, and because of the limits imposed to support these. Then, I found this piece of code, that I used in version 0.5, but unfortunately, it turned out that it is not really suitable for multithreaded scenarios, neither for high load and because it is using the same writer used in the default panel, it imposes some of the limits and overheads. At that spot, I abandoned this direction and implemented the Rx based approach from above.
History
- 7th August, 2018 (v0.6.1): First version really ready to use