serilog / serilog-sinks-email

A Serilog sink that writes events to SMTP email
Apache License 2.0
70 stars 68 forks source link

Not possible to set Subject or Body using JSON configuration when using overload that accepts EmailSinkOptions and PeriodicBatchingSinkOptions #130

Open MrPsi opened 3 months ago

MrPsi commented 3 months ago

It does not seem to be possible to set subject or body when using the overload that accepts EmailSinkOptions and PeriodicBatchingSinkOptions when using JSON configuration.

Program.cs

using Microsoft.Extensions.Configuration;
using Serilog;

var configuration = new ConfigurationBuilder()
    .AddJsonFile("appsettings.json")
    .Build();

Log.Logger = new LoggerConfiguration()
    .ReadFrom.Configuration(configuration)
    .CreateLogger();

var logger = Log.ForContext<TestContext>();
logger.Information("Log 1");
logger.Information("Log 2");
logger.Error(new Exception("the exception"), "Log 3");
await Log.CloseAndFlushAsync();

public class TestContext;

appsettings.json

{
  "Serilog": {
    "MinimumLevel": {
      "Default": "Information"
    },
    "WriteTo": [
      {
        "Name": "Email",
        "Args": {
          "options": {
            "subject": "Serilog test",
            "from": "from@email.com",
            "to": [ "to@email.com" ],
            "host": "localhost",
            "body": "[{Timestamp:HH:mm:ss} {Level}] {SourceContext}{NewLine}{Message}{NewLine}{Exception}{NewLine}"
          },
          "batchingOptions": {
            "batchSizeLimit": 10,
            "period": "00:00:01"
          },
          "restrictedToMinimumLevel": "Information"
        }
      }
    ]
  }
}

The default subject and body will be used instead of the one specified in the JSON. This is probably because Subject and Body in type EmailSinkOptions is of type ITextFormatter, and not string.

MrPsi commented 3 months ago

After some more testing I found the following.

To specify the type like below does not work either.

{
  "Serilog": {
    "MinimumLevel": {
      "Default": "Information"
    },
    "WriteTo": [
      {
        "Name": "Email",
        "Args": {
          "options": {
            "subject": {
              "type": "Serilog.Formatting.Display.MessageTemplateTextFormatter, Serilog",
              "outputTemplate": "Serilog test"
            },
            "from": "from@email.com",
            "to": [ "to@email.com" ],
            "host": "localhost",
            "body": {
              "type": "Serilog.Formatting.Display.MessageTemplateTextFormatter, Serilog",
              "outputTemplate": "[{Timestamp:HH:mm:ss} {Level}] {SourceContext}{NewLine}{Message}{NewLine}{Exception}{NewLine}"
            }
          },
          "batchingOptions": {
            "batchSizeLimit": 10,
            "period": "00:00:01"
          },
          "restrictedToMinimumLevel": "Information"
        }
      }
    ]
  }
}

But if I add a constructor to EmailSinkOptions like below, the above configuration will work. ("to" need to be changed to string instead of list of strings)

    public EmailSinkOptions(
        string from,
        string to,
        string host,
        int port = DefaultPort,
        SecureSocketOptions connectionSecurity = DefaultConnectionSecurity,
        ICredentialsByHost? credentials = null,
        ITextFormatter? subject = null,
        ITextFormatter? body = null)
    {
        From = from;
        To = LoggerConfigurationEmailExtensions.SplitToAddresses(to);
        Host = host;
        Port = port;
        ConnectionSecurity = connectionSecurity;
        Credentials = credentials;
        Subject = subject ?? Subject;
        Body = body ?? Body;
    }
MrPsi commented 3 months ago

Maybe it is better to add the following constructor to allow to just write a string as subject and body?

    public EmailSinkOptions(
        string from,
        string to,
        string host,
        int port = DefaultPort,
        SecureSocketOptions connectionSecurity = DefaultConnectionSecurity,
        ICredentialsByHost? credentials = null,
        string? subject = null,
        string? body = null)
    {
        if (from == null) throw new ArgumentNullException(nameof(from));
        if (to == null) throw new ArgumentNullException(nameof(to));
        if (host == null) throw new ArgumentNullException(nameof(host));

        From = from;
        To = LoggerConfigurationEmailExtensions.SplitToAddresses(to);
        Host = host;
        Port = port;
        ConnectionSecurity = connectionSecurity;
        Credentials = credentials;

        if (subject != null)
        {
            Subject = new MessageTemplateTextFormatter(subject);
        }

        if (body != null)
        {
            Body = new MessageTemplateTextFormatter(body);
        }
    }

Then the JSON can look like this:

{
  "Serilog": {
    "MinimumLevel": {
      "Default": "Information"
    },
    "WriteTo": [
      {
        "Name": "Email",
        "Args": {
          "options": {
            "subject": "Serilog test",
            "from": "from@email.com",
            "to": "to@email.com",
            "host": "localhost",
            "body": "[{Timestamp:HH:mm:ss} {Level}] {SourceContext}{NewLine}{Message}{NewLine}{Exception}{NewLine}"
          },
          "batchingOptions": {
            "batchSizeLimit": 10,
            "period": "00:00:01"
          },
          "restrictedToMinimumLevel": "Information"
        }
      }
    ]
  }
}
MrPsi commented 3 months ago

Created pull request https://github.com/serilog/serilog-sinks-email/pull/131