Skip to content

urgcorp/Serilog.Sinks.YandexCloud

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

50 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Serilog.Sinks.YandexCloud

Flexible Serilog Sink for Yandex Cloud logging

Generate structured exception details for logged exceptions and unwrap nested exceptions.

Setup

Using IAM token file

You can download token file from your Cloud Console and use it directly when configuring

builder.Host.UseSerilog((context, services, configuration) => configuration
    .ReadFrom.Configuration(context.Configuration)
    /* ... */
    .WriteTo.YandexCloud("Secrets/key.json", new YandexCloudSinkSettings(), batching =>
    {
        batching.BatchSizeLimit = 200;
        batching.Period = TimeSpan.FromSeconds(10);
    }));

Using appsettings.json:

{
  "Serilog": {
    "Using": [ "Serilog.Sinks.YandexCloud" ],
    "WriteTo": [
      {
        "Name": "YandexCloud",
        "Args": {
          "Formatter": "Serilog.Formatting.Json.JsonFormatter",
          "FolderId": "<...>",
          "LogGroupId": "<...>",
          "ResourceId": "<...>",
          "ResourceType": "<...>",
          "KeyId": "<...>",
          "ServiceAccountId": "<...>",
          "PrivateKey": "<PRIVATE KEY>"
        }
      }
    ]
  }
}

Pass ILogger Category Name as Yandex Cloud Logger StreamName

Use YcStreamNameCsEnricher when registering sink
It will truncate namespace and leave only class name for which logger was requested

var yandexSink = sinkSettings.CreateYandexCloudSink(keyPath);
builder.Host.UseSerilog((context, services, configuration) =>
{
    configuration
        .ReadFrom.Configuration(context.Configuration)
        .ReadFrom.Services(services)
        .Enrich.FromLogContext()
        .Enrich.With<YcStreamNameCsEnricher>();

    if (yandexSink != null)
        configuration.WriteTo.Sink(yandexSink);
});

To manually define Stream Name use YandexCloudSink.YC_STREAM_NAME_PROPERTY

using (LogContext.PushProperty(YandexCloudSink.YC_STREAM_NAME_PROPERTY, "MyStreamName"))
{
    logger.LogInformation("...");
}

[Obsolete] Separate logger without scoped data

To prevent log context leaking to other parts of the application, you can have different loggers without using scoped data using the same sink.

public interface IAppLogger<out T> : ILogger<T>
{ }

public class AppLoggerAdapter<T> : IAppLogger<T>
{
    private readonly ILogger _logger;

    public AppLoggerAdapter(Serilog.ILogger rootLogger)
    {
        _logger = new SerilogLoggerFactory(rootLogger, dispose: false)
            .CreateLogger(typeof(T).FullName ?? typeof(T).Name);
    }

    public IDisposable? BeginScope<TState>(TState state) where TState : notnull
        => _logger.BeginScope(state);

    public bool IsEnabled(LogLevel logLevel)
        => _logger.IsEnabled(logLevel);

    public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception,
        Func<TState, Exception?, string> formatter)
    {
        using (Serilog.Context.LogContext.Suspend())
        {
            _logger.Log(logLevel, eventId, state, exception, formatter);
        }
    }
}

Then register separate configuration

var yandexSink = YandexCloudSink.CreateBatchingSink(credentialsProvider, sinkSettings);

builder.Host.UseSerilog((context, services, configuration) =>
{
    configuration
        .ReadFrom.Configuration(context.Configuration)
        .ReadFrom.Services(services)
        .Enrich.WithProperty("InstanceId", services.GetRequiredService<IInstanceIdProvider>().InstanceId)
        .Enrich.FromLogContext()
        .WriteTo.Sink(yandexSink);
});

builder.Services.AddSingleton(typeof(IAppLogger<>), typeof(AppLoggerAdapter<>));
builder.Services.AddSingleton(sp =>
{
    // Second logger without FromLogContext, using same sink
    var appLoggerConfig = new LoggerConfiguration()
        .Enrich.WithProperty("InstanceId", sp.GetRequiredService<IInstanceIdProvider>().InstanceId)
        .WriteTo.Sink(yandexSink);

    return appLoggerConfig.CreateLogger();
});

Preview

Simple

Logger.LogInformation($"Test request handle delay is set to {rndDelay} ms.");

Test request handle delay is set to 95 ms.

{
  "ConnectionId": "0HNGC2KE59T58",
  "RequestId": "0HNGC2KE59T58:00000001",
  "RequestPath": "/test"
}

Enrich with property

using (LogContext.PushProperty("Environment", app.Environment.EnvironmentName))
{
    logger.LogInformation("Application started");
}
{
  "ConnectionId": "0HNGC2KE59T58",
  "RequestId": "0HNGC2KE59T58:00000001",
  "RequestPath": "/test",
  "Environment": "Development"
}

Exception

var ex = new HttpRequestException("Requested Test Exception");
_logger.LogError(ex, "Error processing request after {RequestDurationMs} ms.",
    (long)elapsed.TotalMilliseconds);
/* Handler in Middleware */

Error processing request after 274 ms.

{
  "ConnectionId": "0HNGC2KE59T58",
  "RequestDurationMs": 274,
  "RequestId": "0HNGC2KE59T58:00000001",
  "RequestPath": "/test",
  "exceptions": [
    {
      "message": "Requested Test Exception",
      "stack_trace": [
        "at LoggerApp.TEST.TestRequestHandler.Handle(TestRequest e, CancellationToken cancellationToken) in .../LoggerApp/TEST/TestRequestHandler.cs:line 25",
        "at LoggerApp.TEST.TestMiddleware.InvokeAsync(HttpContext context) in .../LoggerApp/TEST/TestMiddleware.cs:line 49"
      ],
      "type": "System.Net.Http.HttpRequestException"
    }
  ]
}

About

Serilog Sink for Yandex Cloud logging

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages

  • C# 98.0%
  • Shell 2.0%