Skip to content

Commit

Permalink
Merge pull request #490 from nhart12/dev
Browse files Browse the repository at this point in the history
Truncate additional columns, allow varchar on standard columns
  • Loading branch information
ckadluba authored Nov 20, 2023
2 parents 3fdce96 + b9e9a15 commit 480d36b
Show file tree
Hide file tree
Showing 12 changed files with 67 additions and 24 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -512,7 +512,7 @@ The content of this column is rendered as JSON by default or with a custom IText

## Custom Property Columns

By default, any log event properties you include in your log statements will be saved to the XML `Properties` column or the JSON `LogEvent` column. But they can also be stored in their own individual columns via the `AdditionalColumns` collection. This adds overhead to write operations but is very useful for frequently-queried properties. Only `ColumnName` is required; the default configuration is `varchar(max)`.
By default, any log event properties you include in your log statements will be saved to the XML `Properties` column or the JSON `LogEvent` column. But they can also be stored in their own individual columns via the `AdditionalColumns` collection. This adds overhead to write operations but is very useful for frequently-queried properties. Only `ColumnName` is required; the default configuration is `varchar(max)`. If you specify a DataLength on a column of character data types (NVarChar, VarChar, Char, NChar) the string will be automatically truncated to the datalength to fit in the column.

```csharp
var columnOptions = new ColumnOptions
Expand Down
5 changes: 5 additions & 0 deletions src/Serilog.Sinks.MSSqlServer/Extensions/StringExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ namespace Serilog.Sinks.MSSqlServer.Extensions
{
internal static class StringExtensions
{
public static string TruncateOutput(this string value, int dataLength) =>
dataLength < 0
? value // No need to truncate if length set to maximum
: value.Truncate(dataLength, "...");

public static string Truncate(this string value, int maxLength, string suffix)
{
if (value == null) return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ public ExceptionColumnOptions() : base()
get => base.DataType;
set
{
if (value != SqlDbType.NVarChar)
throw new ArgumentException("The Standard Column \"Exception\" must be NVarChar.");
if (!SqlDataTypes.VariableCharacterColumnTypes.Contains(value))
throw new ArgumentException("The Standard Column \"Exception\" must be NVarChar or VarChar.");
base.DataType = value;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ public LevelColumnOptions() : base()
get => base.DataType;
set
{
if (value != SqlDbType.NVarChar && value != SqlDbType.TinyInt)
throw new ArgumentException("The Standard Column \"Level\" must be of data type NVarChar or TinyInt.");
if (!SqlDataTypes.VariableCharacterColumnTypes.Contains(value) && value != SqlDbType.TinyInt)
throw new ArgumentException("The Standard Column \"Level\" must be of data type NVarChar, VarChar or TinyInt.");
base.DataType = value;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ public LogEventColumnOptions() : base()
get => base.DataType;
set
{
if (value != SqlDbType.NVarChar)
throw new ArgumentException("The Standard Column \"LogEvent\" must be NVarChar.");
if (!SqlDataTypes.VariableCharacterColumnTypes.Contains(value))
throw new ArgumentException("The Standard Column \"LogEvent\" must be NVarChar or VarChar.");
base.DataType = value;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ public MessageColumnOptions() : base()
get => base.DataType;
set
{
if (value != SqlDbType.NVarChar)
throw new ArgumentException("The Standard Column \"Message\" must be NVarChar.");
if (!SqlDataTypes.VariableCharacterColumnTypes.Contains(value))
throw new ArgumentException("The Standard Column \"Message\" must be NVarChar or VarChar.");
base.DataType = value;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ public MessageTemplateColumnOptions() : base()
get => base.DataType;
set
{
if (value != SqlDbType.NVarChar)
throw new ArgumentException("The Standard Column \"MessageTemplate\" must be NVarChar.");
if (!SqlDataTypes.VariableCharacterColumnTypes.Contains(value))
throw new ArgumentException("The Standard Column \"MessageTemplate\" must be NVarChar or VarChar.");
base.DataType = value;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.ComponentModel;
using Serilog.Events;
using Serilog.Sinks.MSSqlServer.Extensions;

namespace Serilog.Sinks.MSSqlServer.Output
{
Expand Down Expand Up @@ -45,6 +46,11 @@ public KeyValuePair<string, object> GetAdditionalColumnNameAndValue(SqlColumn ad
var columnType = additionalColumn.AsDataColumn().DataType;
if (columnType.IsAssignableFrom(scalarValue.Value.GetType()))
{
if (SqlDataTypes.DataLengthRequired.Contains(additionalColumn.DataType))
{
return new KeyValuePair<string, object>(columnName, scalarValue.Value.ToString().TruncateOutput(additionalColumn.DataLength));

}
return new KeyValuePair<string, object>(columnName, scalarValue.Value);
}

Expand All @@ -54,6 +60,9 @@ public KeyValuePair<string, object> GetAdditionalColumnNameAndValue(SqlColumn ad
}
else
{
if (additionalColumn.AllowNull) {
return new KeyValuePair<string, object>(columnName, DBNull.Value);
}
return new KeyValuePair<string, object>(columnName, property.Value.ToString());
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,15 +45,15 @@ public KeyValuePair<string, object> GetStandardColumnNameAndValue(StandardColumn
switch (column)
{
case StandardColumn.Message:
return new KeyValuePair<string, object>(_columnOptions.Message.ColumnName, TruncateOutput(logEvent.RenderMessage(_formatProvider), _columnOptions.Message.DataLength));
return new KeyValuePair<string, object>(_columnOptions.Message.ColumnName, logEvent.RenderMessage(_formatProvider).TruncateOutput(_columnOptions.Message.DataLength));
case StandardColumn.MessageTemplate:
return new KeyValuePair<string, object>(_columnOptions.MessageTemplate.ColumnName, TruncateOutput(logEvent.MessageTemplate.Text, _columnOptions.MessageTemplate.DataLength));
return new KeyValuePair<string, object>(_columnOptions.MessageTemplate.ColumnName, logEvent.MessageTemplate.Text.TruncateOutput(_columnOptions.MessageTemplate.DataLength));
case StandardColumn.Level:
return new KeyValuePair<string, object>(_columnOptions.Level.ColumnName, _columnOptions.Level.StoreAsEnum ? (object)logEvent.Level : logEvent.Level.ToString());
case StandardColumn.TimeStamp:
return GetTimeStampStandardColumnNameAndValue(logEvent);
case StandardColumn.Exception:
return new KeyValuePair<string, object>(_columnOptions.Exception.ColumnName, TruncateOutput(logEvent.Exception?.ToString(), _columnOptions.Exception.DataLength));
return new KeyValuePair<string, object>(_columnOptions.Exception.ColumnName, logEvent.Exception?.ToString().TruncateOutput(_columnOptions.Exception.DataLength));
case StandardColumn.Properties:
return new KeyValuePair<string, object>(_columnOptions.Properties.ColumnName, ConvertPropertiesToXmlStructure(logEvent.Properties));
case StandardColumn.LogEvent:
Expand All @@ -63,11 +63,6 @@ public KeyValuePair<string, object> GetStandardColumnNameAndValue(StandardColumn
}
}

private static string TruncateOutput(string value, int dataLength) =>
dataLength < 0
? value // No need to truncate if length set to maximum
: value.Truncate(dataLength, "...");

private KeyValuePair<string, object> GetTimeStampStandardColumnNameAndValue(LogEvent logEvent)
{
var dateTimeOffset = _columnOptions.TimeStamp.ConvertToUtc ? logEvent.Timestamp.ToUniversalTime() : logEvent.Timestamp;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Data;

namespace Serilog.Sinks.MSSqlServer
Expand Down Expand Up @@ -51,6 +52,14 @@ public static class SqlDataTypes
// not supported by enum: numeric, FILESTREAM, rowversion
};

/// <summary>
/// SQL column types for supported strings
/// </summary>
public static readonly ReadOnlyCollection<SqlDbType> VariableCharacterColumnTypes = new ReadOnlyCollection<SqlDbType>(new List<SqlDbType> {
SqlDbType.NVarChar,
SqlDbType.VarChar
});

/// <summary>
/// The SQL column types which require a non-zero DataLength property.
/// </summary>
Expand Down
7 changes: 5 additions & 2 deletions test/Serilog.Sinks.MSSqlServer.Tests/Misc/SqlTypesTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -108,9 +108,12 @@ public void AuditLogCharacterDataSqlTypes()
Log.Information("NVarChar {NVarChar}", twentyChars);
Log.Information("VarChar {VarChar}", twentyChars);

// should throw truncation exception
// should truncate but not throw

Assert.Throws<AggregateException>(() => Log.Information("Char {Char}", thirtyChars));
Log.Information("Char {Char}", thirtyChars);
Log.Information("NChar {NChar}", thirtyChars);
Log.Information("NVarChar {NVarChar}", thirtyChars);
Log.Information("VarChar {VarChar}", thirtyChars);

Log.CloseAndFlush();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ public void GetAdditionalColumnNameAndValueReturnsNullForNotFoundHierachicalProp
}

[Fact]
public void GetAdditionalColumnNameAndValueConvertsValueTypeToStringIfConversionToColumnTypeFails()
public void GetAdditionalColumnNameAndValueUsesNullIfConversionToColumnTypeFails()
{
// Arrange
const string columnName = "AdditionalProperty1";
Expand All @@ -131,8 +131,8 @@ public void GetAdditionalColumnNameAndValueConvertsValueTypeToStringIfConversion
// Assert
_columnSimplePropertyValueResolver.Verify(r => r.GetPropertyValueForColumn(additionalColumn, properties), Times.Once);
Assert.Equal(columnName, result.Key);
Assert.IsType<string>(result.Value);
Assert.Equal("1", result.Value); // Cannot convert int to SqlDbType.DateTimeOffset so returns string
Assert.IsType<DBNull>(result.Value);
Assert.Equal(DBNull.Value, result.Value); // Cannot convert int to SqlDbType.DateTimeOffset so returns null
}

[Fact]
Expand Down Expand Up @@ -180,5 +180,27 @@ public void GetAdditionalColumnNameAndValueConvertsNullValueForNullable()
Assert.IsType<DBNull>(result.Value);
Assert.Equal(DBNull.Value, result.Value);
}

[Fact]
public void GetAdditionalColumnNameAndValueReturnsTruncatedForCharacterTypesWithDataLength()
{
// Arrange
const string columnName = "AdditionalProperty1";
const string propertyValue = "Additional Property Value";
var additionalColumn = new SqlColumn(columnName, SqlDbType.NVarChar);
additionalColumn.DataLength = 10;
var properties = new Dictionary<string, LogEventPropertyValue>();
_columnSimplePropertyValueResolver.Setup(r => r.GetPropertyValueForColumn(
It.IsAny<SqlColumn>(), It.IsAny<IReadOnlyDictionary<string, LogEventPropertyValue>>()))
.Returns(new KeyValuePair<string, LogEventPropertyValue>(columnName, new ScalarValue(propertyValue)));

// Act
var result = _sut.GetAdditionalColumnNameAndValue(additionalColumn, properties);

// Assert
_columnSimplePropertyValueResolver.Verify(r => r.GetPropertyValueForColumn(additionalColumn, properties), Times.Once);
Assert.Equal(columnName, result.Key);
Assert.Equal("Additio...", result.Value);
}
}
}

0 comments on commit 480d36b

Please sign in to comment.