Securely log to blob storage using NLog with connection string in key vault.

If you do a simple google search on how to log to blob storage using NLog, you can find examples from the project page as well as posts from other developers. However, in most of the examples I have found, the connection string for the blob storage are directly embedded in the nlog.config file, which is not ideal. In this post, I show you another example of using NLog to log to azure blob storage, with the connection string coming from an azure key vault.

About NLog

In case you are new to NLog, it’s one of the most popular logging libraries as of this writing. It’s relatively straightforward to get started with NLog in your .NET core application. Literally, all I had to do to get started with logging to a file is importing a few nuget packages, add a nlog.config file and a few lines of codes in Program.cs. You can find simple tutorial to get started here.

NLog gdc layout renderer

Gdc stands for Global Diagnostics Context. It’s just a dictionary object to hold variables so you can use them in the nlog.config file.

Azure Key Vault

In case you are not familiar with azure key vault, it has all the awesome security features built in to protect your secrets. You can store passwords, connection strings, certificates etc … in the vault, and stop worrying about leaking those secrets in the source codes (for the most part).

Logging to azure blob storage using connection string from key vault.

The approach is simple. We are going to do the following in order:

  1. Make the connection string accessible from nlog.config using gdc layout renderer.
  2. Load nlog configurations.
<PackageReference Include="Microsoft.Extensions.Configuration.AzureKeyVault" Version="3.1.0" /> <PackageReference Include="NLog.Extensions.AzureBlobStorage" Version="2.2.0" /> <PackageReference Include="NLog.Web.AspNetCore" Version="4.9.0" />
using Microsoft.Extensions.Configuration; 
using Microsoft.Extensions.Logging; using NLog; using NLog.Web;
public static IWebHostBuilder CreateWebHostBuilder(string[] args) {
var webhostBuilder = WebHost.CreateDefaultBuilder(args)
.ConfigureAppConfiguration((hostingContext, config) => {
config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath);
if (!hostingContext.HostingEnvironment.IsLocal() && !hostingContext.HostingEnvironment.IsTest()) {
// 1. Load secrets from key vault.
config.SetupKeyVault(hostingContext.HostingEnvironment);
}
// 2 & 3 Set connection string via gdc and load nlog.config file.
UpdateNLogConfig(config.Build(),hostingContext.HostingEnvironment);
})
.ConfigureLogging((host, logging) => {
logging.ClearProviders()
.AddConfiguration(host.Configuration.GetSection("Logging"));
})
.UseNLog()
.UseStartup < Startup > ();
return webhostBuilder;
}
public static IConfigurationBuilder SetupKeyVault(this IConfigurationBuilder builder,
IWebHostEnvironment env)
{
var configuration = builder.Build();
var keyVaultOptions = configuration.GetSection("KeyVault").Get<KeyVaultOption>();
if (!env.IsLocal())
{

// use Identity Management
var azureServiceTokenProvider = new AzureServiceTokenProvider();
var keyVaultClient = new KeyVaultClient(
new KeyVaultClient.AuthenticationCallback(
azureServiceTokenProvider.KeyVaultTokenCallback));
builder.AddAzureKeyVault(keyVaultOptions.URL, keyVaultClient, new DefaultKeyVaultSecretManager());
}
return builder;
}
private static void UpdateNLogConfig(IConfiguration configuration, IWebHostEnvironment env) {
var storageConnectionString = configuration.GetSection("Storage:ConnectionString").Get < string > ();
GlobalDiagnosticsContext.Set("StorageConnectionString", storageConnectionString);
var configFile = env.IsLocal() ? $ "nlog.{env.EnvironmentName}.config" : "nlog.config";
LogManager.Configuration = LogManager.LoadConfiguration(configFile).Configuration;
}
<target xsi:type="AzureBlobStorage" name="azure" layout="${layout}" connectionString="${gdc:item=StorageConnectionString}" container="nlog" blobName="${date:universalTime=true:format=yyyy-MM-dd}/${date:universalTime=true:format=HH}.log" ></target>
<?xml version="1.0" encoding="utf-8" ?>
<nlog
xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" autoReload="true" internalLogLevel="info" internalLogFile="D:\Home\LogFiles\internal-nlog.log" throwExceptions="true">
<extensions>
<add assembly="NLog.Extensions.AzureBlobStorage" />
<add assembly="NLog.Web.AspNetCore"/>
</extensions>
<variable name="logDirectory" value="D:\Home\LogFiles" />
<variable name="layout" value="${longdate}|${level:uppercase=true}|${logger}|${message}|${exception:format=tostring:innerFormat=tostring:maxInnerExceptionLevel=1000}|url: ${aspnet-request-url}|action: ${aspnet-mvc-action}" />
<!-- the targets to write to -->
<targets>
<target xsi:type="AzureBlobStorage" name="azure" layout="${layout}" connectionString="${gdc:item=StorageConnectionString}" container="nlog" blobName="${date:universalTime=true:format=yyyy-MM-dd}/${date:universalTime=true:format=HH}.log" ></target>
<!-- all logs. Uses some ASP.NET core renderers -->
<target xsi:type="File" name="allLogs" fileName="${logDirectory}\nlog-all.log" layout="${layout}" archiveFileName="${logDirectory}\archives\nlog-all-{#}.log" archiveNumbering="Date" archiveDateFormat="yyyyMMdd" maxArchiveFiles="30" archiveEvery="Day"/>
<target xsi:type="Debugger" name="debugger" layout="${layout}" />
</targets>
<!-- rules to map from logger name to target -->
<rules>
<logger name="*" minlevel="Debug" writeTo="debugger" />
<logger name="*" minlevel="Debug" writeTo="azure" />
<logger name="*" minlevel="Debug" writeTo="allLogs" />
</rules>
</nlog>
2020-03-19 21:43:23.4229 Info Loading assembly: NLog.Extensions.AzureBlobStorage 2020-03-19 21:43:23.4229 Info Loading assembly: NLog.Web.AspNetCore 2020-03-19 21:43:23.5335 Info Adding target BlobStorageTarget(Name=azure)

References

NLog AzureStorage extension

Backend developer in .NET core. I enjoy the outdoor, hanging out with good friends, reading and personal development.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store