serilog-contrib / Serilog.Sinks.Postgresql.Alternative

Serilog.Sinks.Postgresql.Alternative is a library to save logging information from https://github.com/serilog/serilog to https://www.postgresql.org/.
MIT License
67 stars 13 forks source link

Passing an enum with type Integer throws an error #52

Closed bingis-khan closed 2 years ago

bingis-khan commented 2 years ago

When using the SinglePropertyColumnWriter with NpgsqlDbType.Integer, there's a following error:

System.InvalidCastException: Can't write CLR type SomeEventTypeThatsAnEnum with handler type Int32Handler
   at Npgsql.Internal.TypeHandlers.NumericHandlers.Int32Handler.ValidateObjectAndGetLength(Object value, NpgsqlLengthCache& lengthCache, NpgsqlParameter parameter)
   at Npgsql.NpgsqlParameter.ValidateAndGetLength()
   at Npgsql.NpgsqlBinaryImporter.Write[T](T value, NpgsqlParameter param, Boolean async, CancellationToken cancellationToken)
   at Serilog.Sinks.PostgreSQL.SinkHelper.WriteToStream(NpgsqlBinaryImporter writer, IEnumerable`1 entities)
   at Serilog.Sinks.PostgreSQL.SinkHelper.ProcessEventsByCopyCommand(IEnumerable`1 events, NpgsqlConnection connection)
   at Serilog.Sinks.PostgreSQL.SinkHelper.Emit(IEnumerable`1 events)
   at Serilog.Sinks.PostgreSQL.PostgreSqlSink.EmitBatchAsync(IEnumerable`1 events)

How would I go about inserting an enum value into the database?


Temporary solution

Casting the enum to an int works. But then this enum would have to appear as a number in other logs, which is annoying.

Related

https://github.com/npgsql/npgsql/issues/2675 <- this is the actual source of this error

SeppPenner commented 2 years ago

I need to check that, but I know that Npgsql has always caused trouble here...

SeppPenner commented 2 years ago

I guess, the easiest way would be to provide to more column writers, one for "enum as value / int" and another one for "enum as text / string"...

SeppPenner commented 2 years ago

Maybe, it's possible in a more simple way, I will test it now.

SeppPenner commented 2 years ago

I fixed it. Quite easy, check: https://github.com/serilog-contrib/Serilog.Sinks.Postgresql.Alternative/commit/7e775f7bc4be3e2f3095eab6b07dfb41ae29e3a5.

Basically, the issue was that in case of an Enum type, the value was returned. However, the value of the log property was the property "name" of the enum, not the "value". This is fixed and tested and a new version will be available soon.

Example code (I used this for testing as well):

/// <summary>
///     This method is used to test the issue of https://github.com/serilog-contrib/Serilog.Sinks.Postgresql.Alternative/issues/52.
/// </summary>
/// <returns>A <see cref="Task"/> representing any asynchronous operation.</returns>
[TestMethod]
public async Task TestIssue52()
{
    const string TableName = "Logs11";
    await this.databaseHelper.RemoveTable(string.Empty, TableName);

    var columnProps = new Dictionary<string, ColumnWriterBase>
    {
        { "Message", new RenderedMessageColumnWriter(NpgsqlDbType.Text) },
        { "Level", new LevelColumnWriter(true, NpgsqlDbType.Varchar) },
        { "TimeStamp", new TimestampColumnWriter(NpgsqlDbType.Timestamp) },
        { "Exception", new ExceptionColumnWriter(NpgsqlDbType.Text) },
        { "EnumTest", new SinglePropertyColumnWriter("EnumTest", PropertyWriteMethod.Raw, NpgsqlDbType.Integer) }
    };

    var logger = new LoggerConfiguration()
      .Enrich.FromLogContext()
      .WriteTo.PostgreSQL(
        ConnectionString,
        TableName,
        columnProps,
        needAutoCreateTable: true,
        needAutoCreateSchema: true,
        failureCallback: e => Console.WriteLine($"Sink error: {e.Message}")
      ).CreateLogger();

    LogContext.PushProperty("EnumTest", DummyEnum.Test1);

    logger.Information("A test error occured.");

    Log.CloseAndFlush();
    await Task.Delay(1000);
}