Azure Function, Logging part I

I always implement logging, this is for me the key to success when shit hits the fan. If your logs are informativ, readable and easy to find you will shorten the downtime and the response to customer queries will be fast.

I always use a minimum of four (4) log properties:

  • IntegrationId – the unique number/value identifying your integration
  • EntityId – the unique value used by customer when requesting information regarding a integraiton
  • inputMessage – complete/part of incoming message
  • outputMessage – complete/part of outgoing message

Any kind of request/response are also logged as requestBody-<message description> responseBody-<message description>Other types of Id that are essential for logging are logged as-is e.g., use the Id name.


Application Insight

Use APPINSIGHTS_CONNECTION_STRING key to register the instance in the Function App Configuration.

{
    "name": "APPINSIGHTS_CONNECTION_STRING",
    "value": "[format('@Microsoft.KeyVault(SecretUri=https://{0}.vault.azure.net/secrets/Shared-Appi-ConnectionString)', parameters('keyVaultURI'))]"
}

Use Key Vault references


Function Name

Implement a standard naming policy, this is essential to be able to find your way in Application Insight. The name is set as an function attribute in your code

[FunctionName("INT0001_Message_System")]
        public async Task RunAsync(
            [ServiceBusTrigger("%sbTopic%", "%sbSubscription%")] ServiceBusReceivedMessage message)

As you may notice there is no connection string to the ServiceBus, Using the app setting AzureWebJobsServiceBus the ServiceBus trigger will use the default connection app setting named AzureWebJobsServiceBus, this makes it possible to skip using the connection string.


Log Levels

I recommend using the built in log levels

  • Information – Available in all environments, only log informtion of use for troubleshooting and avoid *sensitive data.
  • Debug – Turned off by default, avoid log *sensitive information.
  • Warning – Can be usefule to trigger alrets or as a higher level of information.
  • Error – For exception or when receiving a non successful response to a HTTP-service.

host.json

When function host starts, it reads Log Level from host.json and inject ILogger instance with corresponding filter rules.
IncludeScopes must be added manually!

"logging": {
    "IncludeScopes": true,
    "logLevel": {
      "default": "Information",
      "System": "Information",
      "Microsoft": "Information"
}

To control the behavior of Application Insight, Functions host.json


ILogger, C#

Use ILogger beginScope and and Log to create named properties in Application Insight.

BeginScope

All Log entries within a scope will automatically get the scoped named property by default and they will be prefixed with prop__

using (_logger.BeginScope("{INTId}", "INT0001")) //{INTId} is added to customDimension as prop__INTId
using (_logger.BeginScope("{EntityId}", externalSeddel.FinalNoteId)) //{EntityId} is added to customDimension as prop__EntityId
{
    try
    {
    }
    catch(Exception ex)
    {
        _logger.LogError(ex, ex.Message);
        throw;
    }
}
  • Scopes can be nestled
  • Scope property can be used as a filter in Application Insight (performance, failures and Transaction Search) or in a Kusto where-clause

Log

Using Log.Loginfromation we can create custom properties in applicationInsight table Trace within the customDimension column and they will be prefixed with prop__

_logger.LogInformation("inputMessage-http\r\n{inputMessage-http}", inputMessage); //mandatory output log, important to take GDPR in consideration

In this example we create a property in Application Insight table Trace

  • prop__inputMessage-http

With the value of of the http request body


Summery

If we summarize this down to code it will end up looking something like this

[FunctionName("INT0001_Message_ReceivingSystem")]
    public async Task<IActionResult> Run(
        [HttpTrigger(AuthorizationLevel.Function, "post", Route = "invoice")] HttpRequest req)
    {
        using (_logger.BeginScope("{INTId}", "INT0001"))
        {
            try
            {
                var inputMessage = await new StreamReader(req.Body).ReadToEndAsync();
                _logger.LogInformation("inputMessage-http\r\n{inputMessage-http}", inputMessage); //mandatory output log, important to take GDPR in consideration 

                await _handler.HandleMessage(result); //only process data if inputMessage is verfied and accepted

                return new OkObjectResult("200 OK");
            }
            catch (Exception ex)
            {
                _logger.LogError(ex.Message);
                throw; //if InternalServerErrorResult is returned the az func will successfully exit.
                       //Use kusto (requests | where resultCode == 500) to track the 500 response code
                       //If the http-trigger is a backend to a API then the operation in the API will result in a failure if the api is connected to an APPI instance

                //return new InternalServerErrorResult();
            }
        }
    }

Code breakdown

  1. Line 1 – name of the function, we will get back to the importance of this in part 2
  2. Line 5 – utilizing beginscope, this will “tag” every log-entry automatically with INTId a.k.a prop__INTId
    Note! Beginscope can be nested.
  3. Line 10 – loginformation will generate prop__inputMessage-http

In part 2 I will use http-triggered function through a API and demonstrate the result in Application Insight..

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top