Skip to content

Commit eefaea2

Browse files
committed
unit tests writing all SQL column data types
1 parent a9ef42d commit eefaea2

File tree

3 files changed

+175
-2
lines changed

3 files changed

+175
-2
lines changed

README.md

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ All sink configuration methods accept the following parameters, though not neces
3333
* `restrictedToMinimumLevel`
3434
* `batchPostingLimit`
3535
* `period`
36+
* `formatProvider`
37+
3638

3739
### Basic Parameters
3840

@@ -48,6 +50,8 @@ This is a "periodic batching sink." The sink will queue a certain number of log
4850

4951
Consider increasing the batch size in high-volume logging environments. In one test of a loop writing a single log entry, the default batch size averaged about 14,000 rows per second. Increasing the batch size to 1000 rows increased average write speed to nearly 43,000 rows per second. However, you should also consider the risk-factor. If the client or server crashes, or if the connection goes down, you may lose an entire batch of log entries. You can mitigate this by reducing the timeout. Run performance tests to find the optimal batch size for your production log table definition and log event content, network setup, and server configuration.
5052

53+
Refer to the Serilog Wiki's explanation of [Format Providers](https://github.com/serilog/serilog/wiki/Formatting-Output#format-providers) for details about the `formatProvider` parameter.
54+
5155
### Code-Only (any .NET target)
5256

5357
All sink features are configurable from code. Here is a typical example that works the same way for any .NET target:
@@ -113,15 +117,16 @@ _NOTE:_ Although the configuration package can support many configuration source
113117

114118
## Audit Sink Configuration
115119

116-
A Serilog audit sink is any sink which writes log events which are of such importance that they must succeed, and that verification of a successful write is more important than write performance. Unlike the regular sink, an audit sink _does not_ fail silently -- it can throw exceptions. You should wrap audit logging output in a `try/catch` block. The usual example is bank account withdrawal events -- a bank would certainly not want to allow a failure to record those transactions to fail silently.
120+
A Serilog audit sink writes log events which are of such importance that they must succeed, and that verification of a successful write is more important than write performance. Unlike the regular sink, an audit sink _does not_ fail silently -- it can throw exceptions. You should wrap audit logging output in a `try/catch` block. The usual example is bank account withdrawal events -- a bank would certainly not want to allow a failure to record those transactions to fail silently.
117121

118-
The `MSSqlServerAuditSink` constructor accepts most of the same parameters:
122+
The constructor accepts most of the same parameters, and like other Serilog audit sinks, you configure one by using `AuditTo` instead of `WriteTo`.
119123

120124
* `connectionString`
121125
* `schemaName`
122126
* `tableName`
123127
* `autoCreateSqlTable`
124128
* `columnOptions`
129+
* `formatProvider`
125130

126131
The `restrictedToMinimumLevel` parameter is not available because all events written to an audit sink are required to succeed.
127132

@@ -261,6 +266,8 @@ This property can be set to nearly any value in the `System.Data.SqlDbType` enum
261266
* `varchar`
262267
* `xml`
263268

269+
Numeric types use the default precision and scale. For numeric types, you are responsible for ensuring the values you write do not exceed the min/max values of the underlying SQL column data types. For example, the SQL `decimal` type defaults to 18-digit precision (and scale 0) meaning the maximum value is 10<sup>18</sup>-1, or 999,999,999,999,999,999, whereas the .NET `decimal` type has a much higher maximum value of 79,228,162,514,264,337,593,543,950,335.
270+
264271
### AllowNull
265272

266273
Determines whether or not the column can store SQL `NULL` values. Some of the other features like `PrimaryKey` have related restrictions, and some of the Standard Columns impose restrictions (for example, the `Id` column never allows nulls).

src/Serilog.Sinks.MSSqlServer/Sinks/MSSqlServer/SqlColumn.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,17 @@ public class SqlColumn
1717
public SqlColumn()
1818
{ }
1919

20+
/// <summary>
21+
/// Constructor with property initialization.
22+
/// </summary>
23+
public SqlColumn(string columnName, SqlDbType dataType, bool allowNull = true, int dataLength = -1)
24+
{
25+
ColumnName = columnName;
26+
DataType = dataType;
27+
AllowNull = allowNull;
28+
DataLength = dataLength;
29+
}
30+
2031
/// <summary>
2132
/// A constructor that initializes the object from a DataColumn object.
2233
/// </summary>
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Collections.ObjectModel;
4+
using System.Data;
5+
using System.Data.SqlClient;
6+
using System.Linq;
7+
using Dapper;
8+
using FluentAssertions;
9+
using Xunit;
10+
11+
namespace Serilog.Sinks.MSSqlServer.Tests
12+
{
13+
[Collection("LogTest")]
14+
public class TestSqlTypes : IDisposable
15+
{
16+
// Since the point of these tests are to validate we can write to
17+
// specific underlying SQL Server column data types, we use audit
18+
// logging so exceptions from logevent writes do not fail silently.
19+
20+
[Fact]
21+
public void AuditLogNumericSqlTypes()
22+
{
23+
Arrange(new Collection<SqlColumn>
24+
{
25+
new SqlColumn("BigInt", SqlDbType.BigInt),
26+
new SqlColumn("Decimal", SqlDbType.Decimal),
27+
new SqlColumn("Float", SqlDbType.Float),
28+
new SqlColumn("Int", SqlDbType.Int),
29+
new SqlColumn("Money", SqlDbType.Money),
30+
new SqlColumn("Real", SqlDbType.Real),
31+
new SqlColumn("SmallInt", SqlDbType.SmallInt),
32+
new SqlColumn("SmallMoney", SqlDbType.SmallMoney),
33+
new SqlColumn("TinyInt", SqlDbType.TinyInt),
34+
});
35+
36+
// Some underlying .NET equivalents have a higher MaxValue than the SQL types' defaults.
37+
decimal maxDecimal = 999999999999999999M;
38+
decimal maxMoney = 922337203685477.5807M;
39+
decimal maxSmallMoney = 214748.3647M;
40+
41+
// success: should not throw
42+
43+
Log.Information("BigInt {BigInt}", long.MaxValue);
44+
Log.Information("Decimal {Decimal}", maxDecimal);
45+
Log.Information("Float {Float}", double.MaxValue);
46+
Log.Information("Int {Int}", int.MaxValue);
47+
Log.Information("Money {Money}", maxMoney);
48+
Log.Information("Real {Real}", float.MaxValue);
49+
Log.Information("SmallInt {SmallInt}", short.MaxValue);
50+
Log.Information("SmallMoney {SmallMoney}", maxSmallMoney);
51+
Log.Information("TinyInt {TinyInt}", byte.MaxValue);
52+
Log.CloseAndFlush();
53+
}
54+
55+
[Fact]
56+
public void AuditLogDateAndTimeSqlTypes()
57+
{
58+
Arrange(new Collection<SqlColumn>
59+
{
60+
new SqlColumn("Date", SqlDbType.Date),
61+
new SqlColumn("DateTime", SqlDbType.DateTime),
62+
new SqlColumn("DateTime2", SqlDbType.DateTime2),
63+
new SqlColumn("DateTimeOffset", SqlDbType.DateTimeOffset),
64+
new SqlColumn("SmallDateTime", SqlDbType.SmallDateTime),
65+
new SqlColumn("Time", SqlDbType.Time),
66+
});
67+
68+
// .NET DateTime is limited to 999ms, some of the SQL types have higher precision as noted
69+
DateTime maxDate = new DateTime(9999, 12, 31);
70+
DateTime maxDateTime = new DateTime(9999,12,31,23,59,59,997);
71+
DateTime maxDateTime2 = new DateTime(9999, 12, 31, 23, 59, 59, 999); // SQL max 9999999ms
72+
DateTimeOffset maxDateTimeOffset = new DateTimeOffset(9999, 12, 31, 23, 59, 59, 999, TimeSpan.FromMinutes(0)); // SQL max 9999999ms
73+
DateTime maxSmallDateTime = new DateTime(2079, 6, 6, 23, 59, 0); // seconds round up or down, 59 seconds will overflow here
74+
TimeSpan maxTime = new TimeSpan(0, 23, 59, 59, 999); // SQL max 9999999ms
75+
76+
// sucess: should not throw
77+
78+
Log.Information("Date {Date}", maxDate);
79+
Log.Information("DateTime {DateTime}", maxDateTime);
80+
Log.Information("DateTime2 {DateTime2}", maxDateTime2);
81+
Log.Information("DateTimeOffset {datDateTimeOffsete}", maxDateTimeOffset);
82+
Log.Information("SmallDateTime {SmallDateTime}", maxSmallDateTime);
83+
Log.Information("Time {Time}", maxTime);
84+
Log.CloseAndFlush();
85+
}
86+
87+
[Fact]
88+
public void AuditLogCharacterDataSqlTypes()
89+
{
90+
Arrange(new Collection<SqlColumn>
91+
{
92+
new SqlColumn("Char", SqlDbType.Char, dataLength: 20),
93+
new SqlColumn("NChar", SqlDbType.NChar, dataLength: 20),
94+
new SqlColumn("NVarChar", SqlDbType.NVarChar, dataLength: 20),
95+
new SqlColumn("VarChar", SqlDbType.VarChar, dataLength: 20),
96+
});
97+
98+
string twentyChars = new string('x', 20);
99+
string thirtyChars = new string('x', 30);
100+
101+
// sucess: should not throw
102+
103+
Log.Information("Char {Char}", twentyChars);
104+
Log.Information("NChar {NChar}", twentyChars);
105+
Log.Information("NVarChar {NVarChar}", twentyChars);
106+
Log.Information("VarChar {VarChar}", twentyChars);
107+
108+
// should throw truncation exception
109+
110+
Assert.Throws<AggregateException>(() => Log.Information("Char {Char}", thirtyChars));
111+
112+
Log.CloseAndFlush();
113+
}
114+
115+
[Fact]
116+
public void AuditLogMiscellaneousSqlTypes()
117+
{
118+
Arrange(new Collection<SqlColumn>
119+
{
120+
new SqlColumn("UniqueIdentifier", SqlDbType.UniqueIdentifier),
121+
new SqlColumn("Xml", SqlDbType.Xml)
122+
});
123+
124+
// SQL Server will convert strings internally automatically
125+
string xmlAsStringData = "<?xml version=\"1.0\"?><test><node attrib = \"value1\">value2</node></test>";
126+
127+
// sucess: should not throw
128+
129+
Log.Information("UniqueIdentifier {UniqueIdentifier}", Guid.NewGuid());
130+
Log.Information("Xml {Xml}", xmlAsStringData);
131+
Log.CloseAndFlush();
132+
}
133+
134+
private void Arrange(ICollection<SqlColumn> customColumns)
135+
{
136+
var columnOptions = new ColumnOptions();
137+
columnOptions.AdditionalColumns = customColumns;
138+
139+
Log.Logger = new LoggerConfiguration()
140+
.AuditTo.MSSqlServer
141+
(
142+
connectionString: DatabaseFixture.LogEventsConnectionString,
143+
tableName: DatabaseFixture.LogTableName,
144+
columnOptions: columnOptions,
145+
autoCreateSqlTable: true
146+
)
147+
.CreateLogger();
148+
}
149+
150+
public void Dispose()
151+
{
152+
DatabaseFixture.DropTable();
153+
}
154+
}
155+
}

0 commit comments

Comments
 (0)