CK-Monitoring is the log sink for ActivityMonitors.
ℹ️If you are not already familliar with the ActivityMonitor, i'll suggest to read its documentation first.
Package Name | Release | CI |
---|---|---|
CK.Monitoring | ||
CK.Monitoring.Hosting |
A GrandOutput is a collector for logs sent to ActivityMonitors. Even if, technically, it is not a singleton,
we always use in practice the static GrandOutput.Default
property.
Most of the times, you will need only one GrandOutput. You can get one by:
Using the .NET Generic Host
The Generic Host is a great base for any app, this is what you will probably use most of the time. You will need the CK.Monitoring.Hosting NuGet package. Now, you can add this line:
using Microsoft.Extensions.Hosting;
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
+ .UseCKMonitoring()
.ConfigureServices((hostContext, services) =>
{
services.AddHostedService<Worker>();
});
}
Place this line so it run before any ActivityMonitor is instantiated. This will configures the GrandOutput.Default and provides a scoped IActivityMonitor to the DI.
ℹ️ An activity monitor is available on
IHostBuilder
andHostBuilderContext
: simply call theGetBuilderMonitor()
extension method on them. This monitor will write its logs as soon as the GrandOuptout and its configured handlers will be available.
⚠ This is an advanced usage, skip this part if you want to configure your GrandOutput.
Simply call
GrandOutput.EnsureActiveDefault();
before any ActivityMonitor is instantiated.
The GrandOutput will output the logs in it's configured handlers. CK.Monitoring.Hosting allow you to configure the GrandOutput with a configuration file or any other means thanks to standard configuration providers.
If you do not use CK.Monitoring.Hosting, you will have to manually configure it.
The standard handlers (included in CK.Monitoring assembly) are:
Handlers | Write logs to | Usages | Metadata |
---|---|---|---|
BinaryFile | binary file (extension .ckmon ), optionally compressed. |
To be programmatically read. | All of it. |
TextFile | a text files. | To read when no console can be shown, or when persistence is needed. When developing or in production. | log, date, exceptions, monitor ID, and loglevel. |
Console | the console. | To read the program output when developing. Doesn't persist the logs. | log, date, exceptions, monitor ID, and loglevel. |
Now, you can configure your GrandOutput:
UseCKMonitoring()
uses the configuration section named "CK-Monitoring".
Using the json configuration provider, a typical configuration is:
{
"CK-Monitoring": {
"GrandOutput": {
"MinimalFilter": "Debug",
"Handlers": {
"Console": true,
"TextFile": {
"Path": "Text"
}
}
}
}
}
This is a configuration we often use, this logs onto the Console and to "Logs/Text" timed folders. You can read a fully explained configuration file in appsettings.json in the CK-Sample-Monitoring.
ℹ️
UseMonitoring()
support dynamically changing configuration.
ℹ️ CK-Sample-Monitoring is a sample repository that shows how an application can be configured with CK.Monitoring.Hosting. The demo application dynamically reacts to the change of the appsettings.
⚠ This is an advanced usage.
As we saw earlier, if you instantiate the GrandOutput yourself, you should call EnsureActiveDefault()
.
When EnsureActiveDefault()
is called without configuration, the default configuration of the GrandOutput.Default
is equivalent to:
new GrandOutputConfiguration().AddHandler(
new Handlers.TextFileConfiguration()
{
Path = "Text"
})
You can parameterize where the root path of the log folders.
For this, set LogFile.RootLogPath
that is initially null and can be set only once.
You should do that before calling GrandOutput.EnsureActiveDefault()
:
// Sets the absolute root of the log folder.
// It must be an absolute path and is typically a subfolder of the current application.
LogFile.RootLogPath = "/RootLogPath";
GrandOutput.EnsureActiveDefault();
From now on, any new ActivityMonitor logs will be routed into text files inside "/RootLogPath/Text" directory.
The GrandOutput can be reconfigured at any time (and can also be disposed - the GrandOutput.Default
static properties is then reset to null).
Reconfigurations handles create/update/delete of currently running handlers based on a key (an identity) that
depends on the type of each handlers (for "file handlers" for instance, the Path
is the key).
// Sets the absolute root of the log folder.
// It must be an absolute path and is typically a subfolder of the current application.
LogFile.RootLogPath = System.IO.Path.Combine( AppContext.BaseDirectory, "Logs" );
// Creates a configuration object.
var conf = new GrandOutputConfiguration()
.SetTimerDuration( TimeSpan.FromSeconds(1) ) // 500ms is the default value.
.AddHandler( new Handlers.BinaryFileConfiguration()
{
Path = "OutputGzip",
UseGzipCompression = true
})
.AddHandler( new Handlers.BinaryFileConfiguration()
{
Path = "OutputRaw",
UseGzipCompression = false
}).AddHandler( new Handlers.TextFileConfiguration()
{
Path = "Text",
MaxCountPerFile = 500
});
// Initializes the GrandOutput.Default singleton with the configuration object.
GrandOutput.EnsureActiveDefault( conf );
⚠ This is an advanced usage.
The IGrandOutputHandler
that all handlers implement is a simple interface:
/// <summary>
/// Handler interface.
/// Object implementing this interface must expose a public constructor that accepts
/// its associated <see cref="IHandlerConfiguration"/> object.
/// </summary>
public interface IGrandOutputHandler
{
/// <summary>
/// Prepares the handler to receive events.
/// This is called before any event will be received.
/// </summary>
/// <param name="m">The monitor to use.</param>
/// <returns>True on success, false on error (this handler will not be added).</returns>
ValueTask<bool> ActivateAsync( IActivityMonitor m );
/// <summary>
/// Called on a regular basis.
/// Enables this handler to do any required housekeeping.
/// </summary>
/// <param name="m">The monitor to use.</param>
/// <param name="timerSpan">Indicative timer duration.</param>
ValueTask OnTimerAsync( IActivityMonitor m, TimeSpan timerSpan );
/// <summary>
/// Handles a log event.
/// </summary>
/// <param name="m">The monitor to use.</param>
/// <param name="logEvent">The log event.</param>
ValueTask HandleAsync( IActivityMonitor m, InputEntry logEvent );
/// <summary>
/// Attempts to apply configuration if possible.
/// The handler must check the type of the given configuration and any key configuration
/// before accepting it and reconfigures it (in such case, true must be returned).
/// If this handler considers that this new configuration does not apply to itself, it must return false.
/// </summary>
/// <param name="m">The monitor to use.</param>
/// <param name="c">Configuration to apply.</param>
/// <returns>True if the configuration applied.</returns>
ValueTask<bool> ApplyConfigurationAsync( IActivityMonitor m, IHandlerConfiguration c );
/// <summary>
/// Closes this handler.
/// This is called after the handler has been removed.
/// </summary>
/// <param name="m">The monitor to use.</param>
ValueTask DeactivateAsync( IActivityMonitor m );
}
Handler configurations must fulfill this even simpler contract:
/// <summary>
/// Configuration interface.
/// </summary>
public interface IHandlerConfiguration
{
/// <summary>
/// Must return a deep clone of this configuration object.
/// </summary>
/// <returns>A clone of this object.</returns>
IHandlerConfiguration Clone();
}
GrandOutput configuration and handler configurations must not be serialized and exchanged with the external world. They must remain local, like a hidden implementation detail of the running host.
If a kind of "remote log configuration feature" is needed, it must be done though specific code and only strictly controlled changes must be allowed.
To ease configuration, choose a relevant name for the handler: for instance "MailAlerter".
- The assembly that implements the handler and its configuration must be: "CK.Monitoring.MailAlerterHandler" (file
CK.Monitoring.MailAlerterHandler.dll
). - The handler and its configuration must both be in "CK.Monitoring.Handlers" namespace.
- The configuration type name must be: "MailAlerterConfiguration" (full name: "CK.Monitoring.Handlers.MailAlerterConfiguration").
- The handler type name must be: "MailAlerter" (full name: "CK.Monitoring.Handlers.MailAlerter").
namespace CK.Monitoring.Handlers
{
public class MailAlerterConfiguration : IHandlerConfiguration
{
public string? Email { get; set; }
//...
}
public class MailAlerter : IGrandOutputHandler
{
MailAlerterConfiguration _config;
public DemoSinkHandler( MailAlerterConfiguration c )
{
_config = c;
}
//...
}
}
By following these conventions, the following configuration (using CK.Monitorig.Hosting with json configuration provider):
{
"CK-Monitoring": {
"GrandOutput": {
"Handlers": {
"Console": true,
"MailAlerter": {
"Email": "[email protected]"
}
}
}
}
}
Will automatically tries to load the "CK.Monitoring.MailAlerterHandler" assembly (it must be in the application's binary folder of course), instantiate the configuration, the handler and activates it.
Sample code: MailAlerterConfiguration and MailAlerter.