Logging using Serilog
Use one of the Nodinite Serilog sinks with your full framework and .NET Core applications to enable end-to-end tracking. Microsoft Azure functions built with .NET (usually CSharp) are typical. Nodinite sports payload Logging and removes the obstacle the Application insights logging imposes on message size.
The following Nodinite Serilog sinks exist:
# | Sink | Authentication | Reliable Messaging | Message Size | Latest version |
---|---|---|---|---|---|
1. | Log API | Integrated Windows Authentication | >=0B | ||
2. | Azure Event Hub | Managed IdentitiyNew 2.0.0Connection string | <1024KB | ||
3. | Azure Service Bus | Managed IdentitiyNew 2.0.0Connection string | <256KB | ||
4. | Azure Blob Storage ContainerNew 2.0.14 | Managed IdentitiyConnection string | >=0B | NuGet | |
5. | File (SMB) | Integrated Windows Authentication | >=0B | NuGet |
Log Event
The Nodinite Serilog sink produces a Nodinite JSON Log Event which then the Nodinite Pickup Log Events Service Logging Agent consumes.
The JSON Log Event has three distinct parts:
- Event details (also named Additional field values within Nodinite)
- Payload (Body, 0 or more)
- Context properties (Key/Value)
Info
Please review the Nodinite Pickup Service for additional information.
1. Details
The following mandatory fields must be set, either in configuration or with code (values are example data and should be replaced with your logic):
Mandatory | Data Type | Field | Value | Comment |
---|---|---|---|---|
number |
LogAgentValueId | 42 | Who (Log Agents) sent the data | |
string |
EndPointName | "Nodinite.Serilog.ApiSink.Tests" | Name of Endpoint transport | |
string |
EndPointUri | "https://sampleazurefunction1337.azurewebsites.net/api/HttpTrigger1" | URI for Endpoint transport | |
number |
EndPointDirection | 10 (Two-way receive) | Direction for Endpoint transport | |
number |
EndPointTypeId | 86 (HTTPS) | Type of Endpoint transport | |
string |
OriginalMessageTypeName | "https://ACME.com/Customers/1.0#Batch" | Message Type Name |
Info
The specified values are merely example data.
Review the JSON Log Event user guide for details about the optional fields.
2. Payload
If you want to log the payload (body), then please add it as plain text in the context property "body
".
3. Context Properties
The following optional coded context properties are mapped if present to the following Nodinite "additional field values":
Context Key | Description |
---|---|
body | Plain text, the Nodinite Serilog sink internally adds this to the optional Body field |
OriginalMessageTypeName | Name of MessageType, if not set, the default value is https://ACME.com/Customers/1.0#Batch |
ApplicationInterchangeId | Please re-use if possible a scenario wide id, for example the x-ms-client-tracking-id in Azure |
Review the Context Options user guide for additional features overriding the default behaviour.
Automatically assigned
The following properties are automatically assigned by the Nodinite Serilog sink:
Field | Description | Example |
---|---|---|
ServiceInstanceActivityId | A guid unique for each log event | ee2daba2-4807-4ad8-1337-620e2b9c0052 |
LocalInterchangeId | A unique re-used guid set on all logged events within one class instance | ff2daba2-4807-4ad8-1337-620e2b9c0053 |
LogDateTime | UTC | 2020-05-17T13:37:42.001+01:00 |
LogText | The text to log | Hello World |
Log Status Code | The user defined Log Status Code for the logged event | Review the 'Log Status Code translation table' on this page. |
Log Status Code translation table
The Nodinite Log Status Codes are translated as described in the table below from the following Serilog Log Levels:
Serilog Log Level | Nodinite Log Status Code | Nodinite Log Status | Code example |
---|---|---|---|
Debug | 0 | None | log.Debug |
Info | 0 | None | log.Information |
Verbose | 0 | None | log.Verbose |
Error | -1 | Error | log.Error |
Fatal | -2 | Fatal | log.Fatal |
Warning | 1 | Warning | log.Warning |
Step-by-step guide
The example assumes you are adding the Logging to an Azure function. However, it should be pretty straightforward to use this example for other project types.
You must add code and perform operations as follows:
# | Source | Description |
---|---|---|
1. | Add a NuGet reference to one of the Nodinite Sinks | You need to select one that suits your requirements |
2. | In the program startup, for example Program.cs , this is where you add the singleton in use for great performance and optionally loads essential configuration data |
You can opt to load settings from a settings file, or add extra field info during the logging |
3. | Where code with logging runs, for example in the Run(...) method |
Perform the actual Logging where is is needed |
4. | Cleanup | Before exiting the entry point, you should invoke a call to Dispose the singleton logger |
1. Add NuGet Package Reference
From Visual Studio, add to your Solution a package reference to one of the Nodinite Serilog sinks.
NuGet package manager example, filtered by Nodinite packages.
2. Program.cs
In the startup of your project, you need to perform two main tasks:
- Add configuration
- Initialize the Logger
1. Add configuration
You have two options, either add the fields in the code; or have them in the Settings.json.
- Add configuration by code
- Add configuration from the Settings file
Add configuration by code
The following example adds the configuration with code.
var connectionString = "Endpoint=sb://yournamespace.servicebus.windows.net/;SharedAccessKeyName=Default;SharedAccessKey=%hidden%;EntityPath=apieh";
var settings = new NodiniteLogEventSettings()
{
LogAgentValueId = 503,
EndPointDirection = 0,
EndPointTypeId = 0,
EndPointUri = "Nodinite.Serilog.Sink.Tests.Serilog",
EndPointName = "Nodinite.Serilog.Sink.Tests",
OriginalMessageTypeName = "Serilog.LogEvent",
ProcessingUser = "NODINITE",
ProcessName = "Nodinite.Serilog.Sink.Tests",
ProcessingMachineName = "NODINITE-DEV",
ProcessingModuleName = "DOTNETCORE.TESTS",
ProcessingModuleType = "DOTNETCORE.TESTPROJECT"
};
Add configuration from the settings file
The following example adds the configuration using a settings file.
var configuration = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.Build();
Sample appsettings.json
file
Below is an example for the NodiniteEventHubSink
, change as required.
Replace <%NodiniteSinkName
%> with any of the following (depending on which package you opted for):
AzureBlobStorageSink
- Managed IdentityAzureBlobStorageSinkWithConnectionString
NodiniteApiSink
NodiniteFileSink
NodiniteServiceBusSink
NodiniteEventHubSink
{
"Serilog": {
"Using": [ "<placeholder>" ],
"WriteTo": [
{
"Name": "NodiniteEventHubSink",
"Args": {
"ConnectionString": "*******************************",
"Settings": {
"LogAgentValueId": 503,
"EndPointName": "Nodinite.Serilog.Sink.Tests",
"EndPointUri": "Nodinite.Serilog.Sink.Tests.Serilog",
"EndPointDirection": 0,
"EndPointTypeId": 0,
"OriginalMessageTypeName": "Serilog.LogEvent",
"ProcessingUser": "NODINITE",
"ProcessName": "Nodinite.Serilog.Sink.Tests",
"ProcessingMachineName": "NODINITE-DEV",
"ProcessingModuleName": "DOTNETCORE.TESTS",
"ProcessingModuleType": "DOTNETCORE.TESTPROJECT"
}
}
}
]
}
}
2. Initialize the Logger
Now in the Startup.cs
file it is time to create the actual singleton logger.
If you added the configuration by code, please use the following example:
var logger = new LoggerConfiguration()
.WriteTo.NodiniteEventHubSink(connectionString, settings)
.CreateLogger();
builder.Services.AddSingleton<ILogger, Serilog.Core.Logger>(sp => logger);
Otherwise, if you added the configuration from a settings file, please use the following example:
var logger = new LoggerConfiguration()
.ReadFrom.Configuration(configuration)
.CreateLogger();
services.AddSingleton<ILogger, Logger>(sp => logger);
3. Perform Logging
In your code, you must add a call to perform the Logging. The example includes the full payload together with some context data (key-value).
You are supposed to extend this sample with additional fields according to your requirements. Make sure to set a proper name for the Message Type as this is crucial to Nodinite.
private void Log(string correlationId, string payload)
{
_logger.ForContext(new PropertyEnricher("ApplicationInterchangeId", $"{correlationId}"))
.ForContext(new PropertyEnricher("Body", JsonConvert.SerializeObject(payload)))
.ForContext(new PropertyEnricher("OriginalMessageType", "OrderMessage#1.0"))
.ForContext("Body", "{\"id\":1}")
.Information("Hello from Function");
}
New 2.0.18
If you want to log multiple bodies (Attachments), instead of Body, you can use Bodies and send a list of strings like in the following example.
.ForContext("Bodies", "[{\"id\":1},{\"id\":2}]")
Note
If you also pass a payload using the Body field, the Bodies are treated as attachments. Hence, the
OriginalMessageType
is applied on the Body. If you only pass Bodies, theOriginalMessageType
is applied on the 1st entry in the list.
4. Cleanup
Before exiting the thread/run/invocation/... please call the Dispose
method on the Logger as we are utilizing a singleton pattern.
Put this code after the last Logging operation.
((Logger)_logger).Dispose();
.NET Exceptions
New 1.3.0
If you log an Exception, for example using the LogError(
method, the Nodinite sink adds it to the Context. The name of the Context key is ExtendedProperties/1.0#Exception.
From Nodinite, you can then use the Formula plugin to create Search Fields and extract whatever information you seek from the Exception.
Example extracting the Message part of an exception.
JsonPath('..Message', Context('ExtendedProperties/1.0#Exception'))
Tip
The Exception can include nested exceptions.