Andy Furniss
Andy Furniss

Reputation: 3914

Serilog with .NET Core AWS Serverless Web API project

I have an AWS Serverless API application which is built with .NET Core (2.1). I have set this up with Serilog logging which uses the AWS Cloudwatch sink for it's logging destination. This project depends on a shared class library which is where the core logic resides.

I have noticed that not all my logging messages are making it into Cloudwatch. Messages in my API Controllers do get logged succesfully but messages from the shared class library are only logged when I'm running locally, not when using the live API.

Here is my logging setup in Startup.

var awsCredentials = new BasicAWSCredentials(config["AWS:AccessKey"], config["AWS:SecretKey"]);
if (env.IsProduction())
{
    var options = new CloudWatchSinkOptions
    {
        LogGroupName = config["AWS:Cloudwatch:LogGroup"],
        LogStreamNameProvider = new LogStreamNameProvider(),
        TextFormatter = new Serilog.Formatting.Json.JsonFormatter()
    };

    var client = new AmazonCloudWatchLogsClient(awsCredentials, RegionEndpoint.EUWest2);
    Log.Logger = new LoggerConfiguration()
        .Enrich.WithExceptionDetails()
        .WriteTo
        .AmazonCloudWatch(options, client)
        .CreateLogger();
}

services.AddSingleton(Log.Logger);

I've also tried adding services.AddLogging(builder => builder.AddSerilog()); but that hasn't helped.

I am using it like this:

public class TestService
{
    private readonly ILogger logger;

    public TestService(ILogger logger)
    {
        this.logger = logger;
    }

    public void TestMethod()
    {
        this.logger.Information("Test Message");
    }
}

Upvotes: 3

Views: 1944

Answers (1)

Greg
Greg

Reputation: 11480

The default dependency injection framework has several limitations. Inside of a controller the dependencies will automatically load via placement in the constructor. However, in nested classes you have to pass the dependency itself, otherwise the framework will not automatically resolve. The framework works under the presumption of a composition root. In essence:

// Auto Resolved:
public class SampleController : Controller
{
     private readonly ILogger logger;

     public SampleController(ILogger logger) => this.logger = logger;

     public IActionResult Index()
     {
         logger.Information("...");
         ..
     }
}

// Will not auto resolve
public class SampleService
{
     private readonly ILogger logger;

     public SampleService(ILogger logger) => this.logger = logger;

     public void SampleAction()
     {
         logger.Information("...");
         ..
     }
}

For you to correctly implement the logger within the SampleService you would need to do something along these lines:

// Logger as property
Logger = serviceProvider.GetService<ILogger>();
var sampleService = new SampleService(Logger);

// Called directly passed
var sampleService = new SampleService(serviceProvider.GetService<ILogger>());

That has been my experience, otherwise it will fail to use the defined implementation you passed. There could be information I do not have that may also be affecting your code.

Upvotes: 1

Related Questions