Add support for sending NServiceBus logging through Serilog
Already a Patron? skip past this section
It is expected that all developers either become a Patron or have a Tidelift Subscription to use NServiceBusExtensions. Go to licensing FAQ
Support this project by becoming a Sponsor. The company avatar will show up here with a website link. The avatar will also be added to all GitHub repositories under the NServiceBusExtensions organization.
Thanks to all the backing developers. Support this project by becoming a patron.
Support is available via a Tidelift Subscription.
https://nuget.org/packages/NServiceBus.Serilog/
Log.Logger = new LoggerConfiguration()
.WriteTo.File("log.txt")
.CreateLogger();
LogManager.Use<SerilogFactory>();
NServiceBus can write a significant amount of information to the log. To limit this information use the filtering features of the underlying logging framework.
For example to limit log output to a specific namespace.
Here is a code configuration example for adding a Filter.
Log.Logger = new LoggerConfiguration()
.WriteTo.File(
path:"log.txt",
restrictedToMinimumLevel: LogEventLevel.Debug
)
.Filter.ByIncludingOnly(
inclusionPredicate: Matching.FromSource("MyNamespace"))
.CreateLogger();
LogManager.Use<SerilogFactory>();
Writing diagnostic log entries to Serilog. Plugs into the low level pipeline to give more detailed diagnostics.
When using Serilog for tracing, it is optional to use Serilog as the main NServiceBus logger. i.e. there is no need to include LogManager.Use<SerilogFactory>();
.
var tracingLog = new LoggerConfiguration()
.WriteTo.File("log.txt")
.MinimumLevel.Information()
.CreateLogger();
var serilogTracing = configuration.EnableSerilogTracing(tracingLog);
serilogTracing.EnableMessageTracing();
Serilog tracing injects a contextual Serilog.Ilogger
into the NServiceBus pipeline.
NOTE: Saga and message tracing will use the current contextual logger.
There are several layers of enrichment based on the pipeline phase.
All loggers for an endpoint will have the the property ProcessingEndpoint
added that contains the current endpoint name.
When a message is received, the following enrichment properties are added:
- SourceContext will be the message type FullName extracted from the EnclosedMessageTypes header.
UnknownMessageType
will be used if no header exists. The same value will be added to a property namedMessageType
. MessageId
will be the value of the MessageId header.CorrelationId
will be the value of the CorrelationId header if it exists.ConversationId
will be the value of the ConversationId header if it exists.
When a handler is invoked, a new logger is forked from the above enriched physical logger with a new enriched property named Handler
that contains the the FullName of the current handler.
When a message is sent, the same properties as described in "Incoming message enrichment" will be added to the outgoing pipeline. Note that if a handler sends a message, the logger injected into the outgoing pipeline will be forked from the logger instance as described in "Handler enrichment". As such it will contain a property Handler
for the handler that sent the message.
The contextual logger instance can be accessed from anywhere in the pipeline via SerilogTracingExtensions.Logger(this IPipelineContext context)
.
public class HandlerUsingLogger :
IHandleMessages<TheMessage>
{
public Task Handle(TheMessage message, IMessageHandlerContext context)
{
var logger = context.Logger();
logger.Information("Hello from {@Handler}.");
return Task.CompletedTask;
}
}
IPipelineContext
also has extension methods added to expose direct Log*
methods
public class HandlerUsingLog :
IHandleMessages<TheMessage>
{
public Task Handle(TheMessage message, IMessageHandlerContext context)
{
context.LogInformation("Hello from {@Handler}.");
return Task.CompletedTask;
}
}
When an exception occurs in the message processing pipeline, the current pipeline state is added to the exception. When that exception is logged that state can be add to the log entry.
When a pipeline exception is logged, it will be enriched with the following properties:
ProcessingEndpoint
will be the current endpoint name.IncomingMessageId
will be the value of the MessageId header.IncomingTransportMessageId
will be the MessageId from the underlying transport if it exist.IncomingHeaders
will be the value of the Message headers.IncomingMessageType
will be the message type FullName extracted from the EnclosedMessageTypes header.UnknownMessageType
will be used if no header exists.CorrelationId
will be the value of the CorrelationId header if it exists.ConversationId
will be the value of the ConversationId header if it exists.HandlerType
will be type name for the current handler if it exists.IncomingMessage
will be the value of current logical message if it exists.HandlerStartTime
the UTC timestamp for when the handler started.HandlerFailureTime
the UTC timestamp for when the handler threw the exception.
var serilogTracing = configuration.EnableSerilogTracing(logger);
serilogTracing.EnableSagaTracing();
{
logsForTarget: [
{
MessageTemplate: Hello from {@Saga}. Message: {@Message},
Level: Information,
Properties: {
Saga: TheSaga,
Message: {
TypeTag: StartSaga,
Properties: [
{
Property: TheProperty
}
]
},
Handler: TheSaga,
IncomingMessageId: Guid_1,
IncomingMessageType: StartSaga,
CorrelationId: Guid_1,
ConversationId: Guid_2,
SourceContext: StartSaga,
ProcessingEndpoint: SerilogTestsStartSaga
}
},
{
MessageTemplate: Receive message {IncomingMessageType} {IncomingMessageId}.,
Level: Information,
Properties: {
IncomingMessage: {
TypeTag: StartSaga,
Properties: [
{
Property: TheProperty
}
]
},
OriginatingHostId: Guid_3,
MessageIntent: Send,
OriginatingEndpoint: SerilogTestsStartSaga,
OriginatingMachine: TheMachineName,
ReplyToAddress: SerilogTestsStartSaga,
TimeSent: DateTime_1,
IncomingMessageId: Guid_1,
IncomingMessageType: StartSaga,
CorrelationId: Guid_1,
ConversationId: Guid_2,
SourceContext: StartSaga,
ProcessingEndpoint: SerilogTestsStartSaga
}
},
{
MessageTemplate: Saga execution {SagaType} {SagaId}.,
Level: Information,
Properties: {
SagaType: TheSaga,
SagaId: Guid_4,
StartTime: DateTimeOffset_1,
FinishTime: DateTimeOffset_2,
IsCompleted: false,
IsNew: true,
Initiator: {
Elements: {
"IsSagaTimeout": false,
"MessageId": Guid_1,
"OriginatingMachine": TheMachineName,
"OriginatingEndpoint": SerilogTestsStartSaga,
"MessageType": StartSaga,
"TimeSent": DateTime_1,
"Intent": Send
}
},
ResultingMessages: {
Elements: [
{
Elements: {
"Id": Guid_5,
"Type": BackIntoSaga,
"Intent": Send,
"Destination": SerilogTestsStartSaga
}
}
]
},
Entity: {
TypeTag: TheSagaData,
Properties: [
{
Property: TheProperty
},
{
Id: Guid_4
},
{
Originator: SerilogTestsStartSaga
},
{
OriginalMessageId: Guid_1
}
]
},
IncomingMessageId: Guid_1,
IncomingMessageType: StartSaga,
CorrelationId: Guid_1,
ConversationId: Guid_2,
SourceContext: StartSaga,
ProcessingEndpoint: SerilogTestsStartSaga
}
},
{
MessageTemplate: Sent message {OutgoingMessageType} {OutgoingMessageId}.,
Level: Information,
Properties: {
OutgoingMessage: {
TypeTag: StartSaga,
Properties: [
{
Property: TheProperty
}
]
},
UnicastRoutes: {
Elements: [
SerilogTestsStartSaga
]
},
OriginatingHostId: Guid_3,
MessageIntent: Send,
OriginatingEndpoint: SerilogTestsStartSaga,
OriginatingMachine: TheMachineName,
ReplyToAddress: SerilogTestsStartSaga,
OutgoingMessageId: Guid_1,
OutgoingMessageType: StartSaga,
CorrelationId: Guid_1,
ConversationId: Guid_2,
SourceContext: StartSaga,
ProcessingEndpoint: SerilogTestsStartSaga
}
},
{
MessageTemplate: Sent message {OutgoingMessageType} {OutgoingMessageId}.,
Level: Information,
Properties: {
OutgoingMessage: {
TypeTag: BackIntoSaga,
Properties: [
{
Property: TheProperty
}
]
},
UnicastRoutes: {
Elements: [
SerilogTestsStartSaga
]
},
OriginatingHostId: Guid_3,
MessageIntent: Send,
OriginatingEndpoint: SerilogTestsStartSaga,
OriginatingMachine: TheMachineName,
OriginatingSagaId: Guid_4,
OriginatingSagaType: TheSaga,
RelatedTo: Guid_1,
ReplyToAddress: SerilogTestsStartSaga,
OutgoingMessageId: Guid_5,
OutgoingMessageType: BackIntoSaga,
CorrelationId: Guid_1,
ConversationId: Guid_2,
IncomingMessageId: Guid_1,
IncomingMessageType: StartSaga,
SourceContext: StartSaga,
ProcessingEndpoint: SerilogTestsStartSaga
}
}
]
}
Both incoming and outgoing messages will be logged at the Information level. The current message will be included in a property named Message
. For outgoing messages any unicast routes will be included in a property named UnicastRoutes
.
var serilogTracing = configuration.EnableSerilogTracing(logger);
serilogTracing.EnableMessageTracing();
{
logsForTarget: [
{
MessageTemplate: Hello from {@Handler}.,
Level: Information,
Properties: {
Handler: TheHandler,
IncomingMessageId: Guid_1,
IncomingMessageType: StartHandler,
CorrelationId: Guid_1,
ConversationId: Guid_2,
SourceContext: StartHandler,
ProcessingEndpoint: SerilogTestsStartHandler
}
},
{
MessageTemplate: Receive message {IncomingMessageType} {IncomingMessageId}.,
Level: Information,
Properties: {
IncomingMessage: {
TypeTag: StartHandler,
Properties: [
{
Property: TheProperty
}
]
},
OriginatingHostId: Guid_3,
MessageIntent: Send,
OriginatingEndpoint: SerilogTestsStartHandler,
OriginatingMachine: TheMachineName,
ReplyToAddress: SerilogTestsStartHandler,
TimeSent: DateTime_1,
IncomingMessageId: Guid_1,
IncomingMessageType: StartHandler,
CorrelationId: Guid_1,
ConversationId: Guid_2,
SourceContext: StartHandler,
ProcessingEndpoint: SerilogTestsStartHandler
}
},
{
MessageTemplate: Sent message {OutgoingMessageType} {OutgoingMessageId}.,
Level: Information,
Properties: {
OutgoingMessage: {
TypeTag: StartHandler,
Properties: [
{
Property: TheProperty
}
]
},
UnicastRoutes: {
Elements: [
SerilogTestsStartHandler
]
},
OriginatingHostId: Guid_3,
MessageIntent: Send,
OriginatingEndpoint: SerilogTestsStartHandler,
OriginatingMachine: TheMachineName,
ReplyToAddress: SerilogTestsStartHandler,
OutgoingMessageId: Guid_1,
OutgoingMessageType: StartHandler,
CorrelationId: Guid_1,
ConversationId: Guid_2,
SourceContext: StartHandler,
ProcessingEndpoint: SerilogTestsStartHandler
}
}
]
}
Startup diagnostics is, in addition to its default file location, also written to Serilog with the level of Warning
.
class StartupDiagnostics :
FeatureStartupTask
{
public StartupDiagnostics(ReadOnlySettings settings, ILogger logger)
{
this.settings = settings;
this.logger = logger.ForContext<StartupDiagnostics>();
}
protected override Task OnStart(IMessageSession session)
{
var properties = BuildProperties(settings, logger);
MessageTemplateParser templateParser = new();
var messageTemplate = templateParser.Parse("DiagnosticEntries");
LogEvent logEvent = new(
timestamp: DateTimeOffset.Now,
level: LogEventLevel.Warning,
exception: null,
messageTemplate: messageTemplate,
properties: properties);
logger.Write(logEvent);
return Task.CompletedTask;
}
static IEnumerable<LogEventProperty> BuildProperties(
ReadOnlySettings settings,
ILogger logger)
{
var entries = settings.ReadStartupDiagnosticEntries();
foreach (var entry in entries)
{
if (entry.Name == "Features")
{
continue;
}
var name = CleanEntry(entry.Name);
if (logger.BindProperty(name, entry.Data, out var property))
{
yield return property!;
}
}
}
internal static string CleanEntry(string entry)
{
if (entry.StartsWith("NServiceBus."))
{
return entry.Substring(12);
}
return entry;
}
protected override Task OnStop(IMessageSession session)
{
return Task.CompletedTask;
}
ReadOnlySettings settings;
ILogger logger;
}
To log to Seq:
var tracingLog = new LoggerConfiguration()
.WriteTo.Seq("http://localhost:5341")
.MinimumLevel.Information()
.CreateLogger();
The sample illustrates how to customize logging by configuring Serilog targets and rules.
Log.Logger = new LoggerConfiguration()
.WriteTo.Console()
.CreateLogger();
var tracingLog = new LoggerConfiguration()
.WriteTo.Seq("http://localhost:5341")
.MinimumLevel.Information()
.CreateLogger();
var serilogFactory = LogManager.Use<SerilogFactory>();
serilogFactory.WithLogger(tracingLog);
LogManager.Use<SerilogFactory>();
EndpointConfiguration configuration = new("SerilogSample");
EndpointConfiguration configuration = new("SeqSample");
var serilogTracing = configuration.EnableSerilogTracing(tracingLog);
serilogTracing.EnableSagaTracing();
serilogTracing.EnableMessageTracing();
await endpoint.Stop();
Log.CloseAndFlush();
await endpoint.Stop();
Log.CloseAndFlush();
Illustrates customizing Serilog usage to log to Seq.
An instance of Seq running one http://localhost:5341
.
Log.Logger = new LoggerConfiguration()
.WriteTo.Console()
.CreateLogger();
var tracingLog = new LoggerConfiguration()
.WriteTo.Seq("http://localhost:5341")
.MinimumLevel.Information()
.CreateLogger();
var serilogFactory = LogManager.Use<SerilogFactory>();
serilogFactory.WithLogger(tracingLog);
LogManager.Use<SerilogFactory>();
EndpointConfiguration configuration = new("SerilogSample");
EndpointConfiguration configuration = new("SeqSample");
var serilogTracing = configuration.EnableSerilogTracing(tracingLog);
serilogTracing.EnableSagaTracing();
serilogTracing.EnableMessageTracing();
await endpoint.Stop();
Log.CloseAndFlush();
await endpoint.Stop();
Log.CloseAndFlush();
To report a security vulnerability, use the Tidelift security contact. Tidelift will coordinate the fix and disclosure.
Brain designed by Rémy Médard from The Noun Project.