Custom logging extensions
A custom logger is a standard C# class thats exposes a static factory method and an implementation.
As an example, consider an imaginary logger that sends logs to some kind of external service. It needs the following elements:
- A builder method
- A logging controller
- An implementation of the
ILoggerinterface
Builder method
The builder method is called as part of the runtime build process and creates a new instance of your logging controller.
This controller is responsible for creating new ILogger instances and managing the interactions with any other logging framework you may be using.
// This definition exposes the installed method for installing the logging
// framework when creating a runtime
public static class MyCustomLoggingInstaller
{
public static IRuntimeFactory UseMyCustomLogging(this IRuntimeFactory runtimeFactory)
{
runtimeFactory.SetupLogging(new MyCustomLoggingImpl());
return runtimeFactory;
}
}
Logging controller
The logging controller could look something like the following.
This version creates a connection to an imaginary remote logging service.
internal class MyCustomLoggingImpl: ILoggingImplementation
{
private MyLoggerLogger rootLogger;
public OpenFin.Net.Adapter.Logging.ILogger GetLogger(Type context = null)
{
if (context != null)
{
return new MyLoggerLogger(myServiceConnection, context);
}
return rootLogger;
}
public void Initialize(LogConfig loggerConfig, string targetFilePath)
{
// TODO: Setup some remote logger service connection
myServiceConnection = ...create connection;
rootLogger = new MyLoggerLogger(myServiceConnection);
}
}
The Initialize() method is responsible for setting up any logging service you may be using, creating files, and so on.
The GetLogger() method is called whenever a new logger for a context is required.
The following example creates a new logger, passing in a context of MainWindow.
var loggerMgr = runtime.GetService<ILogManager>();
var logger = loggerMgr.GetLogger(typeof(MainWindow));
ILogger implementation
A logger implementation handles each of the log levels and writes messages to the required destination.
An implementation of ILogger could look something like the following:
public class MyLoggerLogger : ILogger
{
private LogConfig logConfig;
public MyLoggerLogger(ISomeConnection connection, Type context)
{
Connection = connection;
Context = context;
}
public ISomeConnection Connection { get; }
public Type Context{ get; }
/// Invoked after creation and whenever the logger configuration changes
public void OnLogConfigUpdate(LogConfig logConfig)
{
this.logConfig = logConfig;
}
// Output a message to the logging service
public void Debug(string message)
{
Connection.SendDebug(message);
}
// Output a message and any passed exception to the logging service
public void Debug(string message, Exception exception)
{
Connection.SendDebug(exception, message);
}
// Output a message resulting from a `Func` evaluation to the logging service.
// Note: The `Func` is evaluated only if the logging level matches, in this case, debug.
// This technique allows for more output but minimal impact on performance
// if the log level is not enabled.
public void Debug(Func<string> message)
{
if (LogLevel.Debug >= logConfig.MinLevel)
{
Debug(message());
}
}
public void Error(string message)
{
Connection.SendError(message);
}
public void Error(string message, Exception exception)
{
Connection.SendError(exception, message);
}
public void Error(Func<string> message)
{
if (LogLevel.Error >= logConfig.MinLevel)
{
Error(message());
}
}
public void Fatal(string message)
{
Connection.SendFatal(message);
}
public void Fatal(string message, Exception exception)
{
Connection.SendFatal(exception, message);
}
public void Fatal(Func<string> message)
{
if (LogLevel.Fatal >= logConfig.MinLevel)
{
Fatal(message());
}
}
public void Information(string message)
{
Connection.SendInformation(message);
}
public void Information(string message, Exception exception)
{
Connection.Information(exception, message);
}
public void Information(Func<string> message)
{
if (LogLevel.Information >= logConfig.MinLevel)
{
Information(message());
}
}
public void Verbose(string message)
{
Connection.SendSendVerbose(message);
}
public void Verbose(string message, Exception exception)
{
Connection.SendVerbose(exception, message);
}
public void Verbose(Func<string> message)
{
if (LogLevel.Verbose >= logConfig.MinLevel)
{
Verbose(message());
}
}
public void Warning(string message)
{
Connection.SendWarning(message);
}
public void Warning(string message, Exception exception)
{
Connection.SendWarning(exception, message);
}
public void Warning(Func<string> message)
{
if (LogLevel.Warning >= logConfig.MinLevel)
{
Warning(message());
}
}
}
Use the custom logger
Once you have written the logger and deployed it as an assembly, you can enable it within your applications using the runtime builder.
For example:
runtime = new RuntimeFactory()
.UseMyCustomLogging()
.GetRuntimeInstance(new RuntimeOptions
{
Version = "stable",
UUID = "UUID_STRING",
LicenseKey = "LICENSE_KEY"
});
var loggerMgr = runtime.GetService<ILogManager>();
var logger = loggerMgr.GetLogger(typeof(MainWindow));
logger.Debug("This message goes via my custom logger");