The adapter between PostSharp Logging and the target logging framework is called a back-end in PostSharp terminology. PostSharp comes with ready-made support for the most popular logging frameworks in .NET. If you need to integrate with a different logging solution, you can implement a custom logging back-end.
You need to implement at least 4 classes to implement a custom logging back-end.
Override the TextLoggingBackendOptions class
By design, every logging back-end must implement a class exposing all available run-time options. This is a design decision to expose options on a different class than the back-end class itself. Even if your custom back-end does not expose new options, we suggest you respect this convention and create a new empty class.
Example
using PostSharp.Patterns.Diagnostics.Backends;
namespace PostSharp.Samples.Logging.CustomBackend.ServiceStack
{
public class ServiceStackLoggingBackendOptions : TextLoggingBackendOptions
{
}
}
Override the LoggingTypeSource class
The LoggingTypeSource class corresponds to the ILog
or ILogger
concept of several logging frameworks. Your LoggingTypeSource will typically contain one field of this type, which will be initialized in the constructor.
Additionally to exposing the back-end logger, the LoggingTypeSource class exposes the IsBackendEnabled(LogLevel) method, which determines whether logging is enabled for the specified level and the current type and role.
Your implementation of LoggingTypeSource must be immutable or thread-safe.
Example
using PostSharp.Patterns.Diagnostics;
using ServiceStack.Logging;
using System;
namespace PostSharp.Samples.Logging.CustomBackend.ServiceStack
{
public class ServiceStackLoggingTypeSource : LoggingTypeSource
{
public ServiceStackLoggingTypeSource(LoggingNamespaceSource parent, string name, Type sourceType)
: base(parent, name, sourceType )
{
Log = LogManager.GetLogger(sourceType);
}
public ILog Log { get; }
protected override bool IsBackendEnabled(LogLevel level)
{
switch (level)
{
case LogLevel.Trace:
case LogLevel.Debug:
return Log.IsDebugEnabled;
default:
return true;
}
}
}
}
Override the TextLogRecordBuilder class
The role of the TextLogRecordBuilder is to create a string representing the current log record and finally to emit this string to the target logging framework.
The TextLogRecordBuilder class already contains the logic that creates the string. If you don't need to alter the string formatting logic, all you have to do is to implement the Write(UnsafeString) method.
If you need to customize the formatting of the string, or if you need to implement semantic logging, you will need to override other virtual methods of the TextLogRecordBuilder class. Please refer to the API reference for details.
Your implementation of the TextLogRecordBuilder class does not need to be thread-safe. All threads involved in logging have their own instance of the TextLogRecordBuilder class.
Example
using PostSharp.Patterns.Diagnostics;
using PostSharp.Patterns.Diagnostics.Backends;
using PostSharp.Patterns.Diagnostics.RecordBuilders;
using PostSharp.Patterns.Formatters;
namespace PostSharp.Samples.Logging.CustomBackend.ServiceStack
{
public class ServiceStackLogRecordBuilder : TextLogRecordBuilder
{
public ServiceStackLogRecordBuilder(TextLoggingBackend backend) : base(backend)
{
}
protected override void Write(UnsafeString message)
{
var log = ((ServiceStackLoggingTypeSource) TypeSource).Log;
var messageString = message.ToString();
switch (Level)
{
case LogLevel.None:
break;
case LogLevel.Trace:
case LogLevel.Debug:
if (Exception == null)
{
log.Debug(messageString);
}
else
{
log.Debug(messageString, Exception);
}
break;
case LogLevel.Info:
if (Exception == null)
{
log.Info(messageString);
}
else
{
log.Info(messageString, Exception);
}
break;
case LogLevel.Warning:
if (Exception == null)
{
log.Warn(messageString);
}
else
{
log.Warn(messageString, Exception);
}
break;
case LogLevel.Error:
if (Exception == null)
{
log.Error(messageString);
}
else
{
log.Error(messageString, Exception);
}
break;
case LogLevel.Critical:
if (Exception == null)
{
log.Fatal(messageString);
}
else
{
log.Fatal(messageString, Exception);
}
break;
}
}
}
}
Override the TextLoggingBackend class
The root of a logging back-end is the TextLoggingBackend class. You can consider this class as a factory type that instantiates other facilities needed to log with your custom logging back-end.
Since the TextLoggingBackend is an abstract class, you will have to implement several factory methods
To implement the back-end class:
Create a new class and derive it from the TextLoggingBackend class.
Add a get-only
Options
property and initialize it to a new instance of yourLoggingBackendOptions
class.Implement the GetTextBackendOptions() abstract method so that it returns the value of your
Options
property.Implement the CreateTypeSource(LoggingNamespaceSource, Type) abstract method so that it returns a new instance of your
LoggingTypeSource
class.Implement the CreateRecordBuilder() abstract method so that it returns a new instance of your
LogRecordBuilder
class.
Example
using PostSharp.Patterns.Diagnostics;
using PostSharp.Patterns.Diagnostics.Backends;
using PostSharp.Patterns.Diagnostics.RecordBuilders;
using System;
namespace PostSharp.Samples.Logging.CustomBackend.ServiceStack
{
public class ServiceStackLoggingBackend : TextLoggingBackend
{
public new ServiceStackLoggingBackendOptions Options { get; } = new ServiceStackLoggingBackendOptions();
protected override LoggingTypeSource CreateTypeSourceBySourceName(LoggingNamespaceSource parent, string sourceName)
{
return new ServiceStackLoggingTypeSource(parent, sourceName, null);
}
protected override LoggingTypeSource CreateTypeSource(LoggingNamespaceSource parent, Type type)
{
return new ServiceStackLoggingTypeSource(parent, null, type);
}
public override LogRecordBuilder CreateRecordBuilder()
{
return new ServiceStackLogRecordBuilder(this);
}
protected override TextLoggingBackendOptions GetTextBackendOptions()
{
return Options;
}
}
}
Overriding context classes
Contexts are typically used to represent the execution of a logged method and a custom activity. There are typically two log records per context: the entry record and the exit record. The role of context classes is to expose or store pieces of information that are shared by several records of the same context. Unless you need to store more pieces of information in the context, you do not need to extend the context classes. Most back-end implementations do not extend context classes.
Context classes all derive from the LoggingContext class. The following table lists all context classes. If you choose to derive a context class, you will also need to override the corresponding factory method in your LoggingBackend class.
Context class | Factory method | Description |
---|---|---|
SyncMethodLoggingContext | CreateSyncMethodContext(ThreadLoggingContext) | Normal method (neither async neither iterator). |
AsyncMethodLoggingContext | CreateAsyncMethodContext() | async method. |
IteratorLoggingContext | CreateIteratorContext() | Iterator method. |
SyncCustomActivityLoggingContext | CreateSyncCustomActivityContext(ThreadLoggingContext) | Synchronous custom activity. |
AsyncCustomActivityLoggingContext | CreateAsyncCustomActivityContext() | Asynchronous custom activity. |
ThreadLoggingContext | CreateThreadContext() | A special kind of context that represents the roots of the context tree and contains all thread-static fields. |
EphemeralLoggingContext | CreateEphemeralContext(ThreadLoggingContext) | A degenerated kind of context used when a log record is emitted out of a context, for instance when an exception record is logged without the corresponding entry record. |
See Also
Other Resources
Example project: PostSharp.Samples.Logging.CustomBackend.ServiceStack
Example project: PostSharp.Samples.Logging.Customization