- 7 minutes to read

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.

graph LR A[.NET Application] -->|Log Event| SLS(Serilog) F[fal:fa-function Azure Function] --> |Log Event| SLS SLS --> |1.| LAS[fal:fa-cloud-download Log API Sink] LAS --> LA[fal:fa-cloud-download Nodinite Log API] SLS --> |2.|SLEH[fa:fa-border-outer Event Hub Sink] SLEH <--> PS[far:fa-truck-pickup Pickup Service] PS --> LA SLS --> |3.| SLSB[fa:fa-list Service Bus Sink] SLSB <--> PS SLS --->|5.| SLSF[far:fa-folder File Sink] SLS --->|4.| SLBS[far:fa-container-storage Azure Blob Storage Sink] SLSF <--> PS SLBS <--> PS

The following Nodinite Serilog sinks exist:

# Sink Authentication Reliable Messaging Message Size Latest version
1. Log API Integrated Windows Authentication >=0B NuGet Version
2. Azure Event Hub Managed IdentitiyNew 2.0.0
Connection string
<1024KB NuGet Version
3. Azure Service Bus Managed IdentitiyNew 2.0.0
Connection string
<256KB NuGet Version
4. Azure Blob Storage ContainerNew 2.0.14 Managed Identitiy
Connection string
>=0B NuGetNuGet Version
5. File (SMB) Integrated Windows Authentication >=0B NuGetNuGet Version

Develop Azure Functions using Visual Studio

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:

  1. Event details (also named Additional field values within Nodinite)
  2. Payload (Body, 0 or more)
  3. Context properties (Key/Value)
graph TD subgraph "Log Event" subgraph "1. Details" roED[fal:fa-bolt Event Details
LogDateTime = 2018-05-03 13:37:00+02:00
EndPointName = https://api.nodinite.com/...
MessageType=Invoice
...] end subgraph "2. Payload" ro[fal:fa-envelope Message
Body=base64EncodedMessage] end subgraph "3. Context Properties" roKey[fal:fa-key Key Values
InvoiceNo = 123
CorrelationId=456
...] end end

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.

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:

  1. Add configuration
  2. Initialize the Logger

1. Add configuration

You have two options, either add the fields in the code; or have them in the Settings.json.

  1. Add configuration by code
  2. 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 Identity
  • AzureBlobStorageSinkWithConnectionString
  • 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, the OriginalMessageType 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.

Extract Message from Exception
Example extracting the Message part of an exception.

JsonPath('..Message', Context('ExtendedProperties/1.0#Exception'))

Tip

The Exception can include nested exceptions.


Next Step

Log Events
JSON Log Event

Logging