serilog / serilog-extensions-logging

Serilog provider for Microsoft.Extensions.Logging
Apache License 2.0
313 stars 100 forks source link

How to use this from a Azure function? #127

Closed stridsbr closed 5 years ago

stridsbr commented 5 years ago

Hi,

Is it possible to use this as a sink in azure function? I get an instance of Microsoft.Extentions.Logging.ILogger that writes to application insights (including structured data) and the console in Azure function. However I can't find a sink for it. Is it possible to add this as a sink to serilog?

nblumhardt commented 5 years ago

Hi! I think you might be looking for nuget.org/packages/serilog.sinks.ilogger (I haven't tried this myself). HTH!

stridsbr commented 5 years ago

I did try that one. But it's not adding the properties to application insights... so I'm working on that. :-)

skomis-mm commented 5 years ago

Hi, @stridsbr , won't this work for you?

[assembly: WebJobsStartup(typeof(Startup))]
namespace MyAzureFunc
{
    public static class Function1
    {
        [FunctionName("Function1")]
        public static void Run([TimerTrigger("0 */5 * * * *", RunOnStartup = true)]TimerInfo myTimer, ILogger log)
        {
            log.LogInformation("C# Timer trigger function executed at: {Date}", DateTime.Now);
        }
    }

    internal class Startup : IWebJobsStartup
    {
        public void Configure(IWebJobsBuilder builder)
        {
            builder.Services.AddLogging(
                lb => lb.ClearProviders()
                    .AddSerilog(
                        new LoggerConfiguration()
                            .WriteTo.Console()
                            .WriteTo.ApplicationInsights(...)
                            .CreateLogger(),
                        dispose: true));
        }
    }
}
stridsbr commented 5 years ago

As I understand it thats the other way around. When I write to the injected logger that writes to serilogger.

What I'm after is that when I write to serilogger that writes to the injected ILogger.

skomis-mm commented 5 years ago

I see, seems that above implementation of Serilog.Sinks.Ilogger just renders plain string and doesn't pass properties. MEL supports Dictionary<string, object>, so we need to convert LogEvent's properties to such kind of state bag. Something like:

class ILoggerSink : ILogEventSink
{
    readonly ILogger _logger;
    readonly ITextFormatter _formatter;

    public ILoggerSink(ILogger logger, ITextFormatter formatter = null)
    {
        _logger = logger;
        _formatter = formatter;
    }

    public void Emit(LogEvent logEvent)
    {
        object Convert(LogEventPropertyValue val)
        {
            switch (val)
            {
                case SequenceValue sv:
                    return sv.Elements.Select(x => Convert(x)).ToArray();
                case StructureValue sv:
                    return sv.Properties.ToDictionary(x => x.Name, x => Convert(x.Value));
                case DictionaryValue sv:
                    return sv.Elements.ToDictionary(x => x.Key.ToString(), x => Convert(x.Value));
                default:
                    return val.ToString();
            };
        }

        var logLevel = (LogLevel)(int)logEvent.Level;
        var state = logEvent.Properties.ToDictionary(x => x.Key, x => Convert(x.Value));

        _logger.Log(logLevel, default, state, logEvent.Exception,
            (s, _) =>
            {
                var sw = new StringWriter();
                if (_formatter != null)
                {
                    _formatter.Format(logEvent, sw);
                }
                else
                {
                    logEvent.RenderMessage(sw);
                }

                return sw.ToString();
            });
    }
}