KuiperZone.Utility.Yaal

Yet another application logger


Keywords
c-sharp, dotnet, eventlog, logger, rfc-3164, rfc-5424, syslog
License
Other
Install
Install-Package KuiperZone.Utility.Yaal -Version 1.1.0

Documentation

yaal - An RFC 5424 Logger for .NET

Yaal is yet another application logger for .NET. On Linux, it writes to syslog in RFC 5424 format by default and, on Windows, to EventLog. It supports other sinks and formats, including a file logger with rotation and auto-removal.

NUGET PACKAGE

Yaal is licensed under LGPL-3.0-or-later. It has been tested on Linux and Windows, but not on other platforms.

Features

Features include:

  • Local syslog by default in RFC 5424 format on Linux
  • Support for RFC 5424 structured data
  • EventLog on Windows
  • Supports BSD RFC 3164 format
  • Log severity levels and a threshold setting
  • Writes stack trace information in DEBUG build
  • Supports file logging, with file rotation and old file removal
  • Each thread can have own log file
  • Other sinks available to write to Console and a in-memory buffer

Getting Started

Install the nuget package. See also the DemoApp application in the source code, as it provides examples of how to use many of the features of the Yaal Logger class.

Once installed, simply:

using KuiperZone.Utility.Yaal;
...
Logger.Global.Write($"Hello world");

Or for debug:

Logger.Global.Debug($"Value: {value} = 0x{value:X8}");

These two examples, out of the box, will write to syslog on Linux and EventLog on Windows. It is possible to change these "log sinks" or add new ones.

The latter example, above, writes to the log only in a DEBUG build (it is omitted in RELEASE). Moreover, on Linux, it writes in RFC 5424 format with stack trace as structured data:

<15>1 2022-10-21T19:54:42.754408+01:00 vendetta DemoApp 17709 - [DGB@00000000 FUNC="KuiperZone.Utility.Yaal.DemoApp.Program.CallingMethod(Int32 value)" LINE="109" SEVERITY="debug" THREAD="17709-MAINTHREAD"] Value: 668 = 0x0000029C

Here, the calling method is recorded as the SD-PARAM "FUNC" and "LINE". Note that "THREAD" gives: "{pid}-{thread name or id}". This allows debug output to filtered on the application PID and calling thread. Hint: KSystemLog makes a good syslog viewer on Linux, even under Gnome.

On Windows, the same code above writes to EventLog:

In writing to EventLog, the message SeverityLevel value is translated to a matching EventLogEntryType value. By default, the LogName used is "Application" and Source is ".NET Runtime". This combination allows for messages to be written out of the box, without having to register the event source.

The fact that the use of custom "Source" requires their registration in Administrator mode may prove a PITA on Windows. Therefore, the FileSink may be considered as an alternative (see below).

Thread Safety

The Yaal Logger class is thread-safe, and the Logger.Global instance can be used by any thread. Other instances of Logger can be created, however.

Logging Severity and Threshold

Every log message has an associated SeverityLevel value which follows the RFC 5424 specification. Namely, in order of decreasing priority: Emergency, Alert, Critical, Error, Warning, Notice, Info, Debug. If unspecified, the default message severity is SeverityLevel.Info.

Now, the Logger class has a "threshold" setting: Logger.Threshold, such that only messages with a priority equal or higher than this are written (those with a lower priority are ignored). The default value is SeverityLevel.Debug. This setting can be changed at any time, providing a way to restrict messages to high priorities or disable logging entirely.

Example:

Logger.Global.Threshold = SeverityLevel.Error;

Additionally, we can disable all logging as follows:

Logger.Global.Threshold = SeverityLevel.Disabled;

The FileSink

Yaal supports the concept of "sinks", such that multiple sinks can be added to a Logger instance. It is best to add sinks at application start-up (although it can be done at any time).

To add a file sink:

Logger.Global.AddSink(new FileSink());

The logger will now write to both Syslog (or EventLog), and files. The default file output directory is located under the user's home. By default, each thread writes to a separate file:

Wherever logging is written using Logger.Debug(), the caller method and line information is prefxed to the output as follows:

Sink specific options can be specified at the time of its creation. Here, for example, we can auto remove old log files after 30 days:

var opts = new FileSinkOptions();
opts.StaleLife = TimeSpan.FromDays(30);
Logger.Global.AddSink(new FileSink(opts));

The logging directory and filename can also be specified. Moreover, a number of placeholder variables can be used which are expanded at the time the file is opened.

Including the "{THD}" (thread name) placeholder in the filename will cause the sink to create a different file for each calling thread:

var opts = new FileSinkOptions();
opts.DirectoryPattern = "{DOCDIR}/Logs/{ASM}";
opts.FilePattern = "{APP}-{PID}-{THD}-{[yyyyMMddTHHmmss]}.{PAG}.log";

Logger.Global.AddSink(new FileSink(opts));

The above will cause a logging directory to be created under the user home directory. Each log file will be named after the application, PID, thread name (or ID), date-time, and a "page" counter. If "{THD}" is omitted, logging output from different threads will be written to the same file.

More Sinks

ConsoleSink

To add a Console sink:

Logger.Global.AddSink(new ConsoleSink());

By default, output is colored according to severity.

BufferSink

The BufferSink merely holds recent log messages in memory. Its only purpose is to offer a way to query recent logging and may be useful in testing.

Logger.Global.AddSink(new BufferSink());

Logger.Global.log.Debug("This is logged in DEBUG only");
bool logged = buffer.Contains("logged in DEBUG only");

Console.WriteLine($"Statement was logged: {logged}");

RFC 5424 Structured Data

The LogMessage class can be used to write RFC 5424 structured data.

var msg = new LogMessage(SeverityLevel.Notice, "Contains structured data");

msg.Data.Add("exampleSDID@32473", new SdElement());
msg.Data["exampleSDID@32473"].Add("iut", "9");
msg.Data["exampleSDID@32473"].Add("eventSource", "rawr");
msg.Data["exampleSDID@32473"].Add("eventID", "123");

Logger.Global.Write(msg);

This logs the following:

<13>1 2022-10-22T00:27:32.073321+01:00 vendetta DemoApp 35328 - [exampleSDID@32473 eventID="123" eventSource="rawr" iut="9"] Contains structured data

More Information

Custom Options

It is possible to specify a number of custom option values. This should be done at application start up, prior to logging.

var opts = new LogOptions();
opts.AppName = "CustomAppName";
opts.DebugId = "DBG@32473";
opts.Priority = PriorityKind.Omit;

Logger.Global.Options = opts;

BSD RFC 3164 Format

Do this at application start up:

Logger.Global.Sinks = new ILogSink[] { new SyslogSink(new SyslogSinkOptions(LogFormat.Bsd)) }

This replaces the default sink(s) with a new syslog one, only this one is configured to write in the BSD format.

Approach to Internal Errors

As a rule, the Logger class does not throw exceptions. However, in the event of a failure to log output, the Logger.Error property may prove useful in diagnosing the problem.

Copyright & License

Copyright (C) Andy Thomas, 2022-23. Website: https://kuiper.zone

Yaal Logger is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.

Yaal is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.

Other Projects

Other KuiperZone projects:

Avant Garde is cross-platform XAML previewer for the C# Avalonia Framework. It was the first Avalonia preview solution for Linux. github.com/kuiperzone/AvantGarde

Publish-AppImage for .NET is a simple bash script deployment utility which calls dotnet publish and packages the output as an AppImage file (or zip) with a single command. github.com/kuiperzone/Publish-AppImage