diff --git a/DDDCommon.csproj b/DDDCommon.csproj
index 1600054..3ac1488 100644
--- a/DDDCommon.csproj
+++ b/DDDCommon.csproj
@@ -6,7 +6,7 @@
Meteor
true
latest
- 1.0.8
+ 1.2.0
@@ -15,6 +15,7 @@
+
@@ -24,6 +25,12 @@
+
+
+
+
+
+
diff --git a/Domain/Types/ValueObject.cs b/Domain/Types/ValueObject.cs
index 448d51b..b8c1a19 100644
--- a/Domain/Types/ValueObject.cs
+++ b/Domain/Types/ValueObject.cs
@@ -21,7 +21,7 @@ public abstract class ValueObject : IEquatable
return !(obj1 == obj2);
}
- public bool Equals(ValueObject obj)
+ public virtual bool Equals(ValueObject obj)
{
return Equals(obj as object);
}
diff --git a/Infrastructure/Types/NHibernate/PostgreSqlNHibernateHelper.cs b/Infrastructure/Types/NHibernate/PostgreSqlNHibernateHelper.cs
index 6b3cf08..dfbe53f 100644
--- a/Infrastructure/Types/NHibernate/PostgreSqlNHibernateHelper.cs
+++ b/Infrastructure/Types/NHibernate/PostgreSqlNHibernateHelper.cs
@@ -5,6 +5,7 @@
using FluentNHibernate.Conventions.Helpers;
using NHibernate;
using NHibernate.Dialect;
+using NHibernate.Logging.Serilog;
using NHibernate.Spatial.Mapping;
using NHibernate.Spatial.Metadata;
using NHibernate.Tool.hbm2ddl;
@@ -16,6 +17,8 @@ public static class PostgreSqlSessionFactoryHelper
{
public static ISessionFactory Create(DbConfigurations dbConfigurations)
{
+ NHibernateLogger.SetLoggersFactory(new SerilogLoggerFactory());
+
if (dbConfigurations.UseNodaTime) NpgsqlConnection.GlobalTypeMapper.UseNodaTime();
var dbConfigurations1 = dbConfigurations;
@@ -23,8 +26,7 @@ public static ISessionFactory Create(DbConfigurations dbConfigurations)
.Provider()
.Dialect()
.Driver()
- .ConnectionString(dbConfigurations1.ConnectionString)
- .ShowSql();
+ .ConnectionString(dbConfigurations1.ConnectionString);
if (dbConfigurations.UseNetTopologySuite)
{
NpgsqlConnection.GlobalTypeMapper.UseRawPostgis();
diff --git a/Utils/ErrorJsonConverter.cs b/Utils/ErrorJsonConverter.cs
index 893b609..aee2969 100644
--- a/Utils/ErrorJsonConverter.cs
+++ b/Utils/ErrorJsonConverter.cs
@@ -25,9 +25,10 @@ public override void WriteJson(JsonWriter writer, object value, JsonSerializer s
var o = new JObject
{
{ "code", error.Code },
- { "text", error.Text },
- { "details", error.Details }
+ { "message", error.Message }
};
+ if (error.Details != null)
+ o.Add("details", JToken.FromObject(error.Details));
o.WriteTo(writer);
}
}
diff --git a/Utils/Errors.cs b/Utils/Errors.cs
index 2007e42..999a825 100644
--- a/Utils/Errors.cs
+++ b/Utils/Errors.cs
@@ -6,17 +6,19 @@ namespace DDDCommon.Utils
{
public static class Errors
{
- public static Error InternalError(string details = null) =>
- new Error { Code = 1, Text = "internal_error", Details = details };
- public static Error NotFound(string details = null) =>
- new Error { Code = 2, Text = "not_found", Details = details };
- public static Error AlreadyDone(string details = null) =>
- new Error { Code = 3, Text = "already_done", Details = details };
- public static Error InvalidOperation(string details = null) =>
- new Error { Code = 4, Text = "invalid_operation", Details = details };
- public static Error DatabaseError(string details = null) =>
- new Error { Code = 5, Text = "database_error", Details = details };
- public static Error DuplicateKey(string details = null) =>
- new Error { Code = 6, Text = "duplicate_key", Details = details };
+ public static Error InternalError(object details = null) =>
+ new Error(1, "internal_error", details);
+ public static Error NotFound(object details = null) =>
+ new Error(2, "not_found", details);
+ public static Error AlreadyDone(object details = null) =>
+ new Error(3, "already_done", details);
+ public static Error InvalidOperation(object details = null) =>
+ new Error(4, "invalid_operation", details);
+ public static Error DatabaseError(object details = null) =>
+ new Error(5, "database_error", details);
+ public static Error DuplicateKey(object details = null) =>
+ new Error(6, "duplicate_key", details);
+ public static Error AccessDenied(object details = null) =>
+ new Error(7, "access_denied", details);
}
}
diff --git a/Utils/OperationResult.cs b/Utils/OperationResult.cs
index 83799bd..ed3284d 100644
--- a/Utils/OperationResult.cs
+++ b/Utils/OperationResult.cs
@@ -3,6 +3,7 @@
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
+using Serilog;
namespace DDDCommon.Utils
{
@@ -31,6 +32,7 @@ public static async Task Try(Func func)
}
catch (Exception e)
{
+ Log.Error(e, "OperationResult");
return new OperationResult(false,
e as Error ?? Errors.InternalError(e.Message));
}
@@ -44,6 +46,7 @@ public static async Task Try(Func> func)
}
catch (Exception e)
{
+ Log.Error(e, "OperationResult");
return new OperationResult(false,
e as Error ?? Errors.InternalError(e.Message));
}
@@ -73,6 +76,7 @@ public static async Task> Try(Func> func)
}
catch (Exception e)
{
+ Log.Error(e, "OperationResult<{T}>", typeof(T).FullName);
return new OperationResult(false, default,
e as Error ?? Errors.InternalError(e.Message));
}
@@ -86,6 +90,7 @@ public static async Task> Try(Func>>
}
catch (Exception e)
{
+ Log.Error(e, "OperationResult<{T}>", typeof(T).FullName);
return new OperationResult(false, default,
e as Error ?? Errors.InternalError(e.Message));
}
@@ -96,7 +101,13 @@ public static async Task> Try(Func>>
public class Error : Exception
{
public int Code { get; set; }
- public string Text { get; set; }
- public string Details { get; set; }
+ public object Details { get; set; }
+
+ public Error(int code, string message, object details = null, Exception innerException = null)
+ : base(message, innerException)
+ {
+ Code = code;
+ Details = details;
+ }
}
}
diff --git a/Utils/SerilogPostgreSqlSink/ColumnOptions.cs b/Utils/SerilogPostgreSqlSink/ColumnOptions.cs
new file mode 100644
index 0000000..4c8da9e
--- /dev/null
+++ b/Utils/SerilogPostgreSqlSink/ColumnOptions.cs
@@ -0,0 +1,27 @@
+using System.Collections.Generic;
+
+namespace Serilog.Sinks.PostgreSql
+{
+ public class ColumnOptions
+ {
+ public static IDictionary Default => new Dictionary
+ {
+ {DefaultColumnNames.RenderedMesssage, new RenderedMessageColumnWriter()},
+ {DefaultColumnNames.MessageTemplate, new MessageTemplateColumnWriter()},
+ {DefaultColumnNames.Level, new LevelColumnWriter()},
+ {DefaultColumnNames.Exception, new ExceptionColumnWriter()},
+ {DefaultColumnNames.Properties, new PropertiesColumnWriter()},
+ {DefaultColumnNames.CreatedAt, new TimestampColumnWriter()}
+ };
+ }
+
+ public static class DefaultColumnNames
+ {
+ public const string RenderedMesssage = "message";
+ public const string MessageTemplate = "message_template";
+ public const string Level = "level";
+ public const string Exception = "exception";
+ public const string Properties = "properties";
+ public const string CreatedAt = "created_at";
+ }
+}
\ No newline at end of file
diff --git a/Utils/SerilogPostgreSqlSink/ColumnWriterBase.cs b/Utils/SerilogPostgreSqlSink/ColumnWriterBase.cs
new file mode 100644
index 0000000..749fa97
--- /dev/null
+++ b/Utils/SerilogPostgreSqlSink/ColumnWriterBase.cs
@@ -0,0 +1,248 @@
+using System;
+using System.Text;
+using NodaTime;
+using NpgsqlTypes;
+using Serilog.Events;
+using Serilog.Formatting.Json;
+
+namespace Serilog.Sinks.PostgreSql
+{
+ public abstract class ColumnWriterBase
+ {
+ ///
+ /// Column type
+ ///
+ public NpgsqlDbType DbType { get; }
+
+ protected ColumnWriterBase(NpgsqlDbType dbType)
+ {
+ DbType = dbType;
+ }
+
+ ///
+ /// Gets part of log event to write to the column
+ ///
+ ///
+ ///
+ ///
+ public abstract object GetValue(LogEvent logEvent, IFormatProvider formatProvider = null);
+
+ }
+
+ ///
+ /// Writes timestamp part
+ ///
+ public class TimestampColumnWriter : ColumnWriterBase
+ {
+ public TimestampColumnWriter(NpgsqlDbType dbType = NpgsqlDbType.Timestamp) : base(dbType)
+ {
+ }
+
+ public override object GetValue(LogEvent logEvent, IFormatProvider formatProvider = null)
+ {
+ return Instant.FromDateTimeOffset(logEvent.Timestamp);
+ }
+ }
+
+ ///
+ /// Writes message part
+ ///
+ public class RenderedMessageColumnWriter : ColumnWriterBase
+ {
+ public RenderedMessageColumnWriter(NpgsqlDbType dbType = NpgsqlDbType.Text) : base(dbType)
+ {
+ }
+
+ public override object GetValue(LogEvent logEvent, IFormatProvider formatProvider = null)
+ {
+ return logEvent.RenderMessage(formatProvider);
+ }
+ }
+
+ ///
+ /// Writes non rendered message
+ ///
+ public class MessageTemplateColumnWriter : ColumnWriterBase
+ {
+ public MessageTemplateColumnWriter(NpgsqlDbType dbType = NpgsqlDbType.Text) : base(dbType)
+ {
+ }
+
+ public override object GetValue(LogEvent logEvent, IFormatProvider formatProvider = null)
+ {
+ return logEvent.MessageTemplate.Text;
+ }
+ }
+
+ ///
+ /// Writes log level
+ ///
+ public class LevelColumnWriter : ColumnWriterBase
+ {
+ private readonly bool _renderAsText;
+
+ public LevelColumnWriter(bool renderAsText = false, NpgsqlDbType dbType = NpgsqlDbType.Smallint) : base(dbType)
+ {
+ _renderAsText = renderAsText;
+ }
+
+ public override object GetValue(LogEvent logEvent, IFormatProvider formatProvider = null)
+ {
+ if (_renderAsText)
+ {
+ return logEvent.Level.ToString();
+ }
+
+ return (short)logEvent.Level;
+ }
+ }
+
+ ///
+ /// Writes exception (just it ToString())
+ ///
+ public class ExceptionColumnWriter : ColumnWriterBase
+ {
+ public ExceptionColumnWriter(NpgsqlDbType dbType = NpgsqlDbType.Text) : base(dbType)
+ {
+ }
+
+ public override object GetValue(LogEvent logEvent, IFormatProvider formatProvider = null)
+ {
+ return logEvent.Exception == null ? (object)DBNull.Value : logEvent.Exception.ToString();
+ }
+ }
+
+ ///
+ /// Writes all event properties as json
+ ///
+ public class PropertiesColumnWriter : ColumnWriterBase
+ {
+ public PropertiesColumnWriter(NpgsqlDbType dbType = NpgsqlDbType.Jsonb) : base(dbType)
+ {
+ }
+
+ public override object GetValue(LogEvent logEvent, IFormatProvider formatProvider = null)
+ {
+ return PropertiesToJson(logEvent);
+ }
+
+ private object PropertiesToJson(LogEvent logEvent)
+ {
+ if (logEvent.Properties.Count == 0)
+ return "{}";
+
+ var valuesFormatter = new JsonValueFormatter();
+
+ var sb = new StringBuilder();
+
+ sb.Append("{");
+
+ using (var writer = new System.IO.StringWriter(sb))
+ {
+ foreach (var logEventProperty in logEvent.Properties)
+ {
+ sb.Append($"\"{logEventProperty.Key}\":");
+
+ valuesFormatter.Format(logEventProperty.Value, writer);
+
+ sb.Append(", ");
+ }
+ }
+
+ sb.Remove(sb.Length - 2, 2);
+ sb.Append("}");
+
+ return sb.ToString();
+ }
+ }
+
+ ///
+ /// Writes log event as json
+ ///
+ public class LogEventSerializedColumnWriter : ColumnWriterBase
+ {
+ public LogEventSerializedColumnWriter(NpgsqlDbType dbType = NpgsqlDbType.Jsonb) : base(dbType)
+ {
+ }
+
+ public override object GetValue(LogEvent logEvent, IFormatProvider formatProvider = null)
+ {
+ return LogEventToJson(logEvent, formatProvider);
+ }
+
+ private object LogEventToJson(LogEvent logEvent, IFormatProvider formatProvider)
+ {
+ var jsonFormatter = new JsonFormatter(formatProvider: formatProvider);
+
+ var sb = new StringBuilder();
+ using (var writer = new System.IO.StringWriter(sb))
+ jsonFormatter.Format(logEvent, writer);
+ return sb.ToString();
+ }
+ }
+
+ ///
+ /// Write single event property
+ ///
+ public class SinglePropertyColumnWriter : ColumnWriterBase
+ {
+ public string Name { get; }
+ public PropertyWriteMethod WriteMethod { get; }
+ public string Format { get; }
+
+ public SinglePropertyColumnWriter(string propertyName, PropertyWriteMethod writeMethod = PropertyWriteMethod.ToString,
+ NpgsqlDbType dbType = NpgsqlDbType.Text, string format = null) : base(dbType)
+ {
+ Name = propertyName;
+ WriteMethod = writeMethod;
+ Format = format;
+ }
+
+ public override object GetValue(LogEvent logEvent, IFormatProvider formatProvider = null)
+ {
+ if (!logEvent.Properties.ContainsKey(Name))
+ {
+ return DBNull.Value;
+ }
+
+ switch (WriteMethod)
+ {
+ case PropertyWriteMethod.Raw:
+ return GetPropertyValue(logEvent.Properties[Name]);
+ case PropertyWriteMethod.Json:
+ var valuesFormatter = new JsonValueFormatter();
+
+ var sb = new StringBuilder();
+
+ using (var writer = new System.IO.StringWriter(sb))
+ {
+ valuesFormatter.Format(logEvent.Properties[Name], writer);
+ }
+
+ return sb.ToString();
+
+ default:
+ return logEvent.Properties[Name].ToString(Format, formatProvider);
+ }
+
+ }
+
+ private object GetPropertyValue(LogEventPropertyValue logEventProperty)
+ {
+ //TODO: Add support for arrays
+ if (logEventProperty is ScalarValue scalarValue)
+ {
+ return scalarValue.Value;
+ }
+
+ return logEventProperty;
+ }
+ }
+
+ public enum PropertyWriteMethod
+ {
+ Raw,
+ ToString,
+ Json
+ }
+}
\ No newline at end of file
diff --git a/Utils/SerilogPostgreSqlSink/LoggerConfigurationPostgreSqlExtensions.cs b/Utils/SerilogPostgreSqlSink/LoggerConfigurationPostgreSqlExtensions.cs
new file mode 100644
index 0000000..8746b5e
--- /dev/null
+++ b/Utils/SerilogPostgreSqlSink/LoggerConfigurationPostgreSqlExtensions.cs
@@ -0,0 +1,69 @@
+using System;
+using System.Collections.Generic;
+using Serilog;
+using Serilog.Configuration;
+using Serilog.Core;
+using Serilog.Events;
+using Serilog.Sinks.PostgreSQL;
+
+namespace Serilog.Sinks.PostgreSql
+{
+ public static class LoggerConfigurationPostgreSqlExtensions
+ {
+ ///
+ /// Default time to wait between checking for event batches.
+ ///
+ public static readonly TimeSpan DefaultPeriod = TimeSpan.FromSeconds(5);
+
+ ///
+ /// Adds a sink which writes to PostgreSQL table
+ ///
+ /// The logger configuration.
+ /// The connection string to the database where to store the events.
+ /// Name of the table to store the events in.
+ /// Table columns writers
+ /// The minimum log event level required in order to write an event to the sink.
+ /// The time to wait between checking for event batches.
+ /// Supplies culture-specific formatting information, or null.
+ /// The maximum number of events to include to single batch.
+ /// A switch allowing the pass-through minimum level to be changed at runtime.
+ /// If true inserts data via COPY command, otherwise uses INSERT INTO satement
+ /// Schema name
+ /// Set if sink should create table
+ /// Set if sink should auto quotate identifiers
+ /// Logger configuration, allowing configuration to continue.
+ public static LoggerConfiguration PostgreSql(this LoggerSinkConfiguration sinkConfiguration,
+ string connectionString,
+ string tableName,
+ IDictionary columnOptions = null,
+ LogEventLevel restrictedToMinimumLevel = LevelAlias.Minimum,
+ TimeSpan? period = null,
+ IFormatProvider formatProvider = null,
+ int batchSizeLimit = PostgreSqlSink.DefaultBatchSizeLimit,
+ LoggingLevelSwitch levelSwitch = null,
+ bool useCopy = true,
+ string schemaName = "",
+ bool needAutoCreateTable = false,
+ bool respectCase = false)
+ {
+ if (sinkConfiguration == null)
+ {
+ throw new ArgumentNullException(nameof(sinkConfiguration));
+ }
+
+
+ period = period ?? DefaultPeriod;
+
+ return sinkConfiguration.Sink(new PostgreSqlSink(connectionString,
+ tableName,
+ period.Value,
+ formatProvider,
+ columnOptions,
+ batchSizeLimit,
+ useCopy,
+ schemaName,
+ needAutoCreateTable,
+ respectCase), restrictedToMinimumLevel, levelSwitch);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Utils/SerilogPostgreSqlSink/PostgreSqlSink.cs b/Utils/SerilogPostgreSqlSink/PostgreSqlSink.cs
new file mode 100644
index 0000000..59c0c26
--- /dev/null
+++ b/Utils/SerilogPostgreSqlSink/PostgreSqlSink.cs
@@ -0,0 +1,225 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using NodaTime;
+using NodaTime.Extensions;
+using Npgsql;
+using Serilog.Events;
+using Serilog.Sinks.PeriodicBatching;
+using Serilog.Sinks.PostgreSQL;
+
+namespace Serilog.Sinks.PostgreSql
+{
+ public class PostgreSqlSink : PeriodicBatchingSink
+ {
+ private readonly string _connectionString;
+
+ private readonly string _fullTableName;
+ private readonly IDictionary _columnOptions;
+ private readonly IFormatProvider _formatProvider;
+ private readonly bool _useCopy;
+
+ public const int DefaultBatchSizeLimit = 30;
+ public const int DefaultQueueLimit = Int32.MaxValue;
+
+ private bool _isTableCreated;
+
+
+ public PostgreSqlSink(string connectionString,
+ string tableName,
+ TimeSpan period,
+ IFormatProvider formatProvider = null,
+ IDictionary columnOptions = null,
+ int batchSizeLimit = DefaultBatchSizeLimit,
+ bool useCopy = true,
+ string schemaName = "",
+ bool needAutoCreateTable = false,
+ bool respectCase = false) : base(batchSizeLimit, period)
+ {
+ _connectionString = connectionString;
+
+ if (respectCase)
+ {
+ schemaName = QuoteIdentifier(schemaName);
+ tableName = QuoteIdentifier(tableName);
+ }
+
+ _fullTableName = GetFullTableName(tableName, schemaName);
+
+ _formatProvider = formatProvider;
+ _useCopy = useCopy;
+
+ _columnOptions = columnOptions ?? ColumnOptions.Default;
+ if (respectCase)
+ {
+ _columnOptions = CreateQuotedColumnsDict(_columnOptions);
+ }
+
+
+ _isTableCreated = !needAutoCreateTable;
+ }
+
+ public PostgreSqlSink(string connectionString,
+ string tableName,
+ TimeSpan period,
+ IFormatProvider formatProvider = null,
+ IDictionary columnOptions = null,
+ int batchSizeLimit = DefaultBatchSizeLimit,
+ int queueLimit = DefaultQueueLimit,
+ bool useCopy = true,
+ string schemaName = "",
+ bool needAutoCreateTable = false,
+ bool respectCase = false) : base(batchSizeLimit, period, queueLimit)
+ {
+ _connectionString = connectionString;
+
+ if (respectCase)
+ {
+ schemaName = QuoteIdentifier(schemaName);
+ tableName = QuoteIdentifier(tableName);
+ }
+
+ _fullTableName = GetFullTableName(tableName, schemaName);
+
+ _formatProvider = formatProvider;
+ _useCopy = useCopy;
+
+ _columnOptions = columnOptions ?? ColumnOptions.Default;
+ if (respectCase)
+ {
+ _columnOptions = CreateQuotedColumnsDict(_columnOptions);
+ }
+
+
+ _isTableCreated = !needAutoCreateTable;
+ }
+
+ private static string QuoteIdentifier(string identifier)
+ {
+ if (String.IsNullOrEmpty(identifier) || identifier.StartsWith("\""))
+ {
+ return identifier;
+ }
+
+ return $"\"{identifier}\"";
+ }
+
+ private IDictionary CreateQuotedColumnsDict(
+ IDictionary originalColumnsDict)
+ {
+ var result = new Dictionary(originalColumnsDict.Count);
+
+ foreach (var kvp in originalColumnsDict)
+ {
+ result[QuoteIdentifier(kvp.Key)] = kvp.Value;
+ }
+
+ return result;
+ }
+
+ private string GetFullTableName(string tableName, string schemaName)
+ {
+ var schemaPrefix = String.Empty;
+ if (!String.IsNullOrEmpty(schemaName))
+ {
+ schemaPrefix = schemaName + ".";
+ }
+
+ return schemaPrefix + tableName;
+ }
+
+
+ protected override async Task EmitBatchAsync(IEnumerable events)
+ {
+ using (var connection = new NpgsqlConnection(_connectionString))
+ {
+ await connection.OpenAsync();
+
+ if (!_isTableCreated)
+ {
+ TableCreator.CreateTable(connection, _fullTableName, _columnOptions);
+ _isTableCreated = true;
+ }
+
+ if (_useCopy)
+ {
+ ProcessEventsByCopyCommand(events, connection);
+ }
+ else
+ {
+ await ProcessEventsByInsertStatements(events, connection);
+ }
+ }
+ }
+
+ private async Task ProcessEventsByInsertStatements(IEnumerable events, NpgsqlConnection connection)
+ {
+ using (var command = connection.CreateCommand())
+ {
+ command.CommandText = GetInsertQuery();
+
+ foreach (var col in _columnOptions)
+ {
+ command.Parameters.Add(ClearColumnNameForParameterName(col.Key), col.Value.DbType);
+ }
+ foreach (var logEvent in events)
+ {
+ foreach (var col in _columnOptions)
+ {
+ command.Parameters[col.Key].Value = col.Value.GetValue(logEvent, _formatProvider);
+ }
+ await command.ExecuteNonQueryAsync();
+ }
+ }
+ }
+
+ private static string ClearColumnNameForParameterName(string columnName)
+ {
+ return columnName?.Replace("\"", "");
+ }
+
+ private void ProcessEventsByCopyCommand(IEnumerable events, NpgsqlConnection connection)
+ {
+ using (var writer = connection.BeginBinaryImport(GetCopyCommand()))
+ {
+
+ foreach (var e in events)
+ {
+ writer.StartRow();
+
+ foreach (var columnKey in _columnOptions.Keys)
+ {
+ writer.Write(_columnOptions[columnKey].GetValue(e, _formatProvider),
+ _columnOptions[columnKey].DbType);
+ }
+ }
+
+ writer.Complete();
+ }
+ }
+
+ private string GetCopyCommand()
+ {
+ var columns = String.Join(", ", _columnOptions.Keys);
+
+ return $"COPY {_fullTableName}({columns}) FROM STDIN BINARY;";
+
+ }
+
+ private string GetInsertQuery()
+ {
+ var columns = String.Join(", ", _columnOptions.Keys);
+
+ var parameters = String.Join(", ",
+ _columnOptions.Keys.Select(cn => ":" + ClearColumnNameForParameterName(cn)));
+
+ return $@"INSERT INTO {_fullTableName} ({columns})
+ VALUES ({parameters})";
+ }
+
+ private void WriteToStream(NpgsqlBinaryImporter writer, IEnumerable entities)
+ {
+ }
+ }
+}
\ No newline at end of file
diff --git a/Utils/SerilogPostgreSqlSink/TableCreator.cs b/Utils/SerilogPostgreSqlSink/TableCreator.cs
new file mode 100644
index 0000000..b4a0a72
--- /dev/null
+++ b/Utils/SerilogPostgreSqlSink/TableCreator.cs
@@ -0,0 +1,101 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Npgsql;
+using NpgsqlTypes;
+
+namespace Serilog.Sinks.PostgreSql
+{
+ public class TableCreator
+ {
+ public static int DefaultCharColumnsLength = 50;
+ public static int DefaultVarcharColumnsLength = 50;
+ public static int DefaultBitColumnsLength = 8;
+
+ public static void CreateTable(NpgsqlConnection connection, string tableName, IDictionary columnsInfo)
+ {
+ using (var command = connection.CreateCommand())
+ {
+ command.CommandText = GetCreateTableQuery(tableName, columnsInfo);
+ command.ExecuteNonQuery();
+ }
+ }
+
+ private static string GetCreateTableQuery(string tableName, IDictionary columnsInfo)
+ {
+ var builder = new StringBuilder("CREATE TABLE IF NOT EXISTS ");
+ builder.Append(tableName);
+ builder.AppendLine(" (");
+
+ builder.AppendLine(String.Join(",\n", columnsInfo.Select(r => $" {r.Key} {GetSqlTypeStr(r.Value.DbType)} ")));
+
+ builder.AppendLine(")");
+
+ return builder.ToString();
+ }
+
+ private static string GetSqlTypeStr(NpgsqlDbType dbType)
+ {
+ switch (dbType)
+ {
+ case NpgsqlDbType.Bigint:
+ return "bigint";
+ case NpgsqlDbType.Double:
+ return "double precision";
+ case NpgsqlDbType.Integer:
+ return "integer";
+ case NpgsqlDbType.Numeric:
+ return "numeric";
+ case NpgsqlDbType.Real:
+ return "real";
+ case NpgsqlDbType.Smallint:
+ return "smallint";
+ case NpgsqlDbType.Boolean:
+ return "boolean";
+ case NpgsqlDbType.Money:
+ return "money";
+ case NpgsqlDbType.Char:
+ return $"character({DefaultCharColumnsLength})";
+ case NpgsqlDbType.Text:
+ return "text";
+ case NpgsqlDbType.Varchar:
+ return $"character varying({DefaultVarcharColumnsLength})";
+ case NpgsqlDbType.Bytea:
+ return "bytea";
+ case NpgsqlDbType.Date:
+ return "date";
+ case NpgsqlDbType.Time:
+ return "time";
+ case NpgsqlDbType.Timestamp:
+ return "timestamp";
+ case NpgsqlDbType.TimestampTz:
+ return "timestamp with time zone";
+ case NpgsqlDbType.Interval:
+ return "interval";
+ case NpgsqlDbType.TimeTz:
+ return "time with time zone";
+ case NpgsqlDbType.Inet:
+ return "inet";
+ case NpgsqlDbType.Cidr:
+ return "cidr";
+ case NpgsqlDbType.MacAddr:
+ return "macaddr";
+ case NpgsqlDbType.Bit:
+ return $"bit({DefaultBitColumnsLength})";
+ case NpgsqlDbType.Varbit:
+ return $"bit varying({DefaultBitColumnsLength})";
+ case NpgsqlDbType.Uuid:
+ return "uuid";
+ case NpgsqlDbType.Xml:
+ return "xml";
+ case NpgsqlDbType.Json:
+ return "json";
+ case NpgsqlDbType.Jsonb:
+ return "jsonb";
+ default:
+ throw new ArgumentOutOfRangeException(nameof(dbType), dbType, "Cannot atomatically create column of type " + dbType);
+ }
+ }
+ }
+}
\ No newline at end of file