Custom Logging on Azure Blob Storage with Nlog

In this article, I’m going to explain logging in azure blob storage with nlog library.

Firstly, I need to create asp.net core empty web app then create an empty controller. I used swagger to help me btw.

Swagger Configuration (Startup.cs)

https://docs.microsoft.com/en-us/aspnet/core/tutorials/getting-started-with-swashbuckle?view=aspnetcore-2.2&tabs=visual-studio
public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc()
              .SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
            services.AddSwaggerGen(c =>
            {
                c.SwaggerDoc("v1", new Info
                {
                    Version = "v1",
                    Title = "Notification API",
                    Description = "AppCenter Notification API",
                    TermsOfService = "http://ozaksut.com/",
                    Contact = new Contact()
                    {

                        Name = "Yiğit Özaksüt",
                        Email = "yigit@ozaksut.com",
                        Url = "http://ozaksut.com/"
                    }
                });
            });
            services.AddOptions();
        }
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseSwagger();
            app.UseSwaggerUI(c =>
            {
                c.RoutePrefix = string.Empty;
                c.SwaggerEndpoint("/swagger/v1/swagger.json", "AppCenter Notification API V1");
            });

            app.UseMvc();
        }

Now I need to use Nlog

AzureStorage extension for Azure BlobStorage

https://github.com/JDetmar/NLog.Extensions.AzureStorage

Nlog.config

Don’t forget to set copy always!

<?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="C:\Users\Yigit\Desktop\Log\internal-nlog.json"-->

  <extensions>
    <add assembly="NLog.Web.AspNetCore"/>
    <!--AzureStorage extension-->
    <add assembly="NLog.Extensions.AzureStorage" />
  </extensions>
  
  <!--"type" is your log type (AzureBlobStorage or File)-->
  <!--"name" is your log rule name-->
  <!--"blobName" is your file name-->
  <!--"container" is your blob container name-->
  <!--"connectionString" is your storage connection string-->
  <targets async="true">
    <target xsi:type="AzureBlobStorage"
            name="error"
            blobName="Error-${shortdate}.json"
            container="logs"
            connectionString="">
      <layout type='CompoundLayout'>
        <layout xsi:type="JsonLayout" includeAllProperties="true">
          <attribute name="time" layout="${longdate}" />
          <attribute name="level" layout="${level:upperCase=true}"/>
          <attribute name="message" layout="${message}" />
<!--I found better option for additional data-->
<!--https://github.com/NLog/NLog/wiki/MDLC-Layout-Renderer-->
         <!--<attribute name="request" layout="${gdc:request}" />-->
          <attribute name="request" layout="${mdlc:item=request}" />
          <!--<attribute name="response" layout="${gdc:response}" />-->
          <attribute name="response" layout="${mdlc:item=response}" />
          <attribute name="url" layout="${aspnet-request-url}" />
          <attribute name="action" layout="${aspnet-mvc-action}" />
        </layout>
        <layout type='SimpleLayout' text="," />
      </layout>
    </target>
    <target name="info"
            xsi:type="AzureBlobStorage"
            blobName="Info-${shortdate}.json"
            container="logs"
            connectionString="">
      <layout type='CompoundLayout'>
        <layout xsi:type="JsonLayout" includeAllProperties="true">
          <attribute name="time" layout="${longdate}" />
          <attribute name="level" layout="${level:upperCase=true}"/>
          <attribute name="message" layout="${message}" />
<!--<attribute name="request" layout="${gdc:request}" />-->
          <attribute name="request" layout="${mdlc:item=request}" />
          <!--<attribute name="response" layout="${gdc:response}" />-->
          <attribute name="response" layout="${mdlc:item=response}" />
          <attribute name="url" layout="${aspnet-request-url}" />
          <attribute name="action" layout="${aspnet-mvc-action}" />
        </layout>
        <layout type='SimpleLayout' text="," />
      </layout>
    </target>
    <target name="trace"
            xsi:type="AzureBlobStorage"
            blobName="Trace-${shortdate}.json"
            container="logs"
            connectionString="">
      <layout type='CompoundLayout'>
        <layout xsi:type="JsonLayout" includeAllProperties="true">
          <attribute name="time" layout="${longdate}" />
          <attribute name="level" layout="${level:upperCase=true}"/>
          <attribute name="message" layout="${message}" />
<!--<attribute name="request" layout="${gdc:request}" />-->
          <attribute name="request" layout="${mdlc:item=request}" />
          <!--<attribute name="response" layout="${gdc:response}" />-->
          <attribute name="response" layout="${mdlc:item=response}" />
          <attribute name="url" layout="${aspnet-request-url}" />
          <attribute name="action" layout="${aspnet-mvc-action}" />
        </layout>
        <layout type='SimpleLayout' text="," />
      </layout>
    </target>
    <target name="critical"
            xsi:type="AzureBlobStorage"
            blobName="Critical-${shortdate}.json"
            container="logs"
            connectionString="">
      <layout type='CompoundLayout'>
        <layout xsi:type="JsonLayout" includeAllProperties="true">
          <attribute name="time" layout="${longdate}" />
          <attribute name="level" layout="${level:upperCase=true}"/>
          <attribute name="message" layout="${message}" />
<!--<attribute name="request" layout="${gdc:request}" />-->
          <attribute name="request" layout="${mdlc:item=request}" />
          <!--<attribute name="response" layout="${gdc:response}" />-->
          <attribute name="response" layout="${mdlc:item=response}" />
          <attribute name="url" layout="${aspnet-request-url}" />
          <attribute name="action" layout="${aspnet-mvc-action}" />
        </layout>
        <layout type='SimpleLayout' text="," />
      </layout>
    </target>

  </targets>

  <!--Your log levels and rules-->
  <rules>
    <logger name="*" level="Error" writeTo="error" />
    <logger name="*" level="Info" writeTo="info" />
    <logger name="*" level="Trace" writeTo="trace" />
    <logger name="*" level="Fatal" writeTo="critical" />
  </rules>
</nlog>

If you want to inject additional context-data then you should consider using MDLC — Thanks Rolf Kristensen

https://github.com/NLog/NLog/wiki/MDLC-Layout-Renderer

I used CompoundLayout and JsonLayout. Because I wanted to take json list result for some reports. You need to remove last comma character, add a ‘]’ as the last character and add a ‘[‘ as the first character

SimpleLayout is seperator for each row

https://github.com/NLog/NLog/wiki/Tutorial

I need to add my request and response data in my logs that’s why I am adding gdc (GlobalDiagnosticsContext) on my log layout.

I found better option for additional data (MDLC). GDC (GlobalDiagnosticsContext) is for global data.

Program.cs

public class Program
    {
        public static void Main(string[] args)
        {
            var logger = NLogBuilder.ConfigureNLog("nlog.config").GetCurrentClassLogger();
            try
            {
                logger.Debug("init main");
                CreateWebHostBuilder(args).Build().Run();
            }
            catch (Exception ex)
            {
                logger.Error(ex, "Stopped program because of exception");
                throw;
            }
            finally
            {
                LogManager.Shutdown();
            }
        }

        public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
            WebHost.CreateDefaultBuilder(args)
                .UseStartup<Startup>().ConfigureLogging(logging =>
                {
                    logging.ClearProviders();
                    logging.SetMinimumLevel(LogLevel.Trace);
                })
                .UseNLog();
    }

Now, you can create a Blob Storage

NotificationController

[Route("api/[controller]")]
    [ApiController]
    public class NotificationController : ControllerBase
    {
        private readonly IServiceManager _manager;
        private readonly ILogger<NotificationController> _logger;
        public NotificationController(ILogger<NotificationController> logger,
            IServiceManager manager)
        {
            _logger = logger;
            _manager = manager;
        }

        [HttpPost]
        public async Task<ActionResult> Send([FromBody] SendNotificationRequest request)
        {
MappedDiagnosticsLogicalContext.Set("request", JsonConvert.SerializeObject(request));
            //GlobalDiagnosticsContext.Set("request", JsonConvert.SerializeObject(request));

            await _manager.Post<SendNotificationRequest>(request);

            return Ok();
        }
    }

ServiceManager.cs

public class ServiceManager : IServiceManager
    {
        private readonly ILogger<ServiceManager> _logger;

        private static HttpClient _client;
        public static HttpClient Client
        {
            get
            {
                if (_client == null)
                {
                    _client = new HttpClient();
                    _client.DefaultRequestHeaders.Add("Accept", "application/json");
                    _client.DefaultRequestHeaders.Add("X-API-Token", "yout appcenter token");
                }
                return _client;
            }
        }

        public string Url => "your appcenter notification post url";

        public ServiceManager(ILogger<ServiceManager> logger)
        {
            _logger = logger;
        }

        public async Task<T> Get<T>()
        {
            var result = await Client.GetStringAsync(Url);
            return JsonConvert.DeserializeObject<T>(result);
        }

        public async Task Post<K>(K request)
        {
            try
            {
                var req = JsonConvert.SerializeObject(request);
                var encoding = Encoding.UTF8;
                var mediaType = "application/json";
                var content = new StringContent(req, encoding, mediaType);

                var response = await Client.PostAsync(Url, content);
                var result = await response.Content.ReadAsStringAsync();
MappedDiagnosticsLogicalContext.Set("response", JsonConvert.SerializeObject(result));
                //GlobalDiagnosticsContext.Set("response", JsonConvert.SerializeObject(result));

                //TODO
                if (response.IsSuccessStatusCode && response.StatusCode == HttpStatusCode.Accepted)
                {
                    var notificationResponse = JsonConvert.DeserializeObject<NotificationResponse>(result);
                    _logger.LogTrace("Accepted");
                }
                else
                {
                    _logger.LogError("Error");
                }
            }
            catch (Exception ex)
            {
                _logger.LogCritical(ex.Message);
            }
        }
    }

When you try to post you can get logs on your blob storage.

[{ "time": "2019-02-24 16:44:25.8321", "level": "ERROR", "message": "Error",
     "request": "{\"notification_target\":{\"type\":\"string\",\"devices\":[\"string\"]},\"notification_content\":{\"name\":\"string\",\"title\":\"string\",\"body\":\"string\",\"custom_data\":{\"additionalProp1\":\"string\",\"sound\":\"default\",\"badge\":0}}}",
      "response": "\"{\\"error\\":{\\"code\\":400,\\"message\\":\\"unknown target.\\"}}\"", 
      "url": "https:\/\/localhost\/api\/Notification", 
      "action": "Send" }]

Yiğit

Xamarin Developer, Consultant & Architect. Community Leader and Director of Xamarin Türkiye

Post A Reply