.Net AWS Lambda logging
Tags: serverless, dotnet, learning, aws
Published: 2023-07-03 16:10:52

## Motivation
Data logging is a process of **collecting** and **storing** data over some period of time in order to monitor, fast identify and fix application problems.

## Lambda logger
Usually we create .dot net lambda project from template, that installed together with AWS Toolkit for Visual Studio. Our lambda function contains trigger and context:
```
public void FunctionHandler(S3Event @event, ILambdaContext context)
{
  context.Logger.LogInformation("This is my log.");
}
```
Using context we can log record from lambda to CloudWatch. Looks pretty easy and simple. CloudWatch will store data and have feature to search in logs. But `Logger` implement `ILambdaLogger` interface that is not our lovely `ILogger`, that we use all the time in .net 😐.
## ILogger
Behind the hood, lambda writes logs to the console, and after that, it appears in CloudWatch. That means we can build a logger with the console provider and continue to use **CloudWatch** with **ILogger**
```
var logger = LoggerFactory.Create(builder => builder.AddConsole())
  .CreateLogger<Function>();

logger.LogInformation("This is my log.");
```
So far so good, but all records in CloudWatch - are just plain text. And it is hard to search through plain text and identify problems. Since 2015 CloudWatch support JSON-formatted logs. So we need update configuration to use JSON format `AddJsonConsole()` and can query/filter logs by filed from record.
```
{
    "EventId": 0,
    "LogLevel": "Information",
    "Category": "AWSLambda1.Function",
    "Message": "This is my log.",
    "State": {
        "Message": "This is my log.",
        "{OriginalFormat}": "This is my log."
    }
}
```

## Scope and structure logging
Two importan feature of **ILogger**
1. Scope - we can define scope fields(for example **TraceId**), that will be in all log records while the scope is visible.
2. Structure logging - we can log a record with a defined field and it will appear as a JSON field. For example **Id**.
```
using var _ = logger.BeginScope(new Dictionary<string, object>
{
  ["TraceId"] = "my-trace-id"
});

logger.LogInformation("This is my log where Id={Id}", 1);
```
Log record look like that:
```
{
    ...
    "Message": "This is my log where Id=1",
    "State": {
        "Message": "This is my log where Id=1",
        "Id": 1,
        "{OriginalFormat}": "This is my log where Id={Id}"
    },
    "Scopes": [
        {
            ...
            "TraceId": "my-trace-id"
        }
    ]
}
```
## Test with xUnit
xUnit is the most popular .net unit tests framework. It provides a mechanism to write output from tests using `ITestOutputHelper`. But what if we want to write output everything from `ILogger`? For example when we have [integration tests](https://dev.to/ohalay/integration-tests-for-aws-serverless-solution-12aj %). For that, we may add a **xUnit** log provider `.AddXUnit(outputHelper)` using [community library](https://github.com/martincostello/xunit-logging).

## Conclusion
1. We use structured JSON logging with `ILogger` abstraction.
2. We use output logs for integration tests.

And finally, the source code in the GitHub repository
{% github ohalay/serverless-containers %}

## Help links
* https://learn.microsoft.com/en-us/dotnet/core/extensions/console-log-formatter#json
* https://dev.to/ohalay/integration-tests-for-aws-serverless-solution-12aj
* https://xunit.net/docs/capturing-output
* https://github.com/martincostello/xunit-logging