Serilog 的 StoredProcedure 自定义接收器

StoredProcedure custom sink for Serilog

我想使用 Serilog 调用存储过程。我知道没有这样的接收器,所以我正在创建一个自定义接收器。我的逻辑是在实现 ILogEventSink 的 StoredProcedureSink 的 Emit 方法中调用存储过程。现在,这个存储过程 returns 一个值。我如何在使用 Log.Information();

时获取此值
class StoredProcedureSink : ILogEventSink
    {
        private string _connectionString;
        public StoredProcedureSink(string connectionString)
        {
            _connectionString = connectionString;
        }

        public void Emit(LogEvent logEvent)
        {
            var conn = new SqlConnection(_connectionString);
            conn.Open();
            SqlCommand cmd = new SqlCommand(logEvent.MessageTemplate.ToString().Substring(0, logEvent.MessageTemplate.ToString().IndexOf('{')), conn);
            cmd.CommandType = CommandType.StoredProcedure;
            var properties = logEvent.Properties.GetValueOrDefault("SqlParams");
            var json = JObject.Parse(properties.ToString().Substring(properties.ToString().IndexOf('{') - 1));
            foreach(var kvp in json)
            {
                cmd.Parameters.Add(new SqlParameter(kvp.Key, ((JValue)kvp.Value).Value));
            }
            cmd.ExecuteNonQuery();
//I would like to read the value returned by the stored proc.
        }
    }

//I have a wrapper DBLogger in which I configure the serilog. I have published DBLogger as a nuget package so I can use it in all my apps.

public class DBLogger()
{

public DBLogger()
{
Log.Logger = new LoggerConfiguration()
            .MinimumLevel.Information()
            .MinimumLevel.Override("Microsoft", LogEventLevel.Warning)
            .Enrich.FromLogContext()
            .WriteTo.StoredProcedureSink(
                "connectionString")
            .CreateLogger();
}

public void Information(string storedProcedureName, T parameters)
        {
            try
            {
               Log.Information(storedProcedureName, parameters);
            }
            catch (Exception ex)
            {
                Log.Fatal(ex, "Host terminated unexpectedly");
            }
        }
}

public class Program
{
   static void Main()
   {
     var logger = new DBLogger();
     logger.Information("storedProcedureName", params); //need the Id returned by the stored proc here.
    }
}

这在使用普通 Serilog 模型的实践中不太可能可行。

标准处理涉及 LogEvent 在主线程上被 捕获 ,然后依次提供给每个接收器 - 通常是异步的,并且经常被缓冲。

另一个问题是,一般来说,sink 肯定不会向调用者传播异常(有审计日志记录,但即便如此也不是为这种通信而设计的)。

在我看来,您要完成的审计类型与 Serilog 的最佳点有一定距离(尽管我可能是错的 - 如果是这样,请在您的问题中添加更多细节)。


如果您绝对必须这样做,您可以在记录日志时添加一个 Enricher,它将回调 Action 隔离到 LogEvent 的属性中,并将其传回。不过,在实际执行此操作之前,请仔细考虑!

I ended up creating a static variable of StoredProcedureSink class and assigning the return value to that variable. Not sure if this would be the best way to do it.

class StoredProcedureSink : ILogEventSink
    {
        private string _connectionString;
        **public static int returnValue;**
        public StoredProcedureSink(string connectionString)
        {
            _connectionString = connectionString;
        }

        public void Emit(LogEvent logEvent)
        {
            var outputParam = new SqlParameter()
            {
                Direction = ParameterDirection.Output
            };
            try
            {
                using (SqlConnection conn = new SqlConnection(_connectionString))
                {
                    using (SqlCommand cmd = new SqlCommand(logEvent.MessageTemplate.ToString().Substring(0, logEvent.MessageTemplate.ToString().IndexOf('{')), conn))
                    {
                        conn.Open();
                        cmd.CommandType = CommandType.StoredProcedure;
                        var properties = logEvent.Properties.GetValueOrDefault("SqlParams");
                        var jsonProp = JObject.Parse(properties.ToString().Substring(properties.ToString().IndexOf('{') - 1).Replace(@"\",""));

                        var lastParam = jsonProp.Last;

                        foreach (var kvp in jsonProp)
                        {
                            if(kvp.Key == lastParam.Path)
                            {
                                outputParam.ParameterName = kvp.Key;
                                outputParam.SqlDbType = SqlDbType.Int;
                                cmd.Parameters.Add(outputParam);
                                break;
                            }
                            cmd.Parameters.Add(new SqlParameter(kvp.Key, ((JValue)kvp.Value).Value));
                        }
                        cmd.ExecuteNonQuery();
                    }
                    **returnValue = (int)outputParam.Value;**
                }
            }
            catch(System.Exception e)
            {
                Debug.WriteLine(e.Message);
            }
        }
    }


public class DBLogger : ILogger
    {
        public DBLogger()
        {
            Log.Logger = new LoggerConfiguration()
            .MinimumLevel.Information()
            .MinimumLevel.Override("Microsoft", LogEventLevel.Warning)
            .Enrich.FromLogContext()
            .WriteTo.StoredProcedureSink(
                "connectionString")
            .CreateLogger();
        }

~DBLogger()
        {
            Log.CloseAndFlush();
        }

        public static IHost CreateHostBuilder(string[] args) =>
        new HostBuilder()
            .UseSerilog() // <- Add this line
            .Build();

public int Information<T>(string storedProcedureName, T parameters)
        {
            try
            {
               Log.Information(storedProcedureName, parameters);
            }
            catch (Exception ex)
            {
                Log.Fatal(ex, "Host terminated unexpectedly");
            }
            **return StoredProcedureSink.returnValue;**
        }
}