Skip to content

Commit 3463107

Browse files
committed
cleanup
1 parent 64dd953 commit 3463107

21 files changed

+509
-39
lines changed

README.md

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,121 @@ The sink includes the following standard column mappings:
206206
| `Properties` | `string` | Additional properties as JSON | Yes | MAX |
207207
| `SourceContext` | `string` | Source context (typically class name) | Yes | 1000 |
208208

209+
### JSON Structure for Exception and Properties
210+
211+
#### Exception Column
212+
213+
The `Exception` column stores exception details as a JSON object with the following structure:
214+
215+
```json
216+
{
217+
"Message": "The error message",
218+
"BaseMessage": "Inner exception message (if present)",
219+
"Type": "System.InvalidOperationException",
220+
"Text": "Full exception text including stack trace",
221+
"HResult": -2146233079,
222+
"ErrorCode": -2147467259,
223+
"Source": "MyApplication",
224+
"MethodName": "MyMethod",
225+
"ModuleName": "MyAssembly",
226+
"ModuleVersion": "1.0.0.0"
227+
}
228+
```
229+
230+
**Key fields:**
231+
232+
- `Message` - The exception's primary error message
233+
- `BaseMessage` - Message from the innermost exception (if there's an inner exception chain)
234+
- `Type` - Fully qualified type name of the exception
235+
- `Text` - Complete exception text including stack trace (from `ToString()`)
236+
- `HResult` - The HRESULT error code
237+
- `ErrorCode` - Error code for `ExternalException` types
238+
- `Source` - The application or object that caused the error
239+
- `MethodName` - Name of the method that threw the exception
240+
- `ModuleName` - Name of the assembly containing the throwing method
241+
- `ModuleVersion` - Version of the assembly
242+
243+
> **Note:** Aggregate exceptions with a single inner exception are automatically flattened to the inner exception.
244+
245+
#### Properties Column
246+
247+
The `Properties` column stores log event properties as a JSON object. Property values are serialized according to their type:
248+
249+
**Scalar values:**
250+
251+
```json
252+
{
253+
"UserId": 123,
254+
"UserName": "John Doe",
255+
"IsActive": true,
256+
"Amount": 99.99,
257+
"RequestId": "550e8400-e29b-41d4-a716-446655440000",
258+
"Timestamp": "2024-01-15T10:30:45Z"
259+
}
260+
```
261+
262+
**Structured values:**
263+
264+
```json
265+
{
266+
"User": {
267+
"Id": 123,
268+
"Name": "John Doe",
269+
"Email": "[email protected]"
270+
}
271+
}
272+
```
273+
274+
**Arrays/Sequences:**
275+
276+
```json
277+
{
278+
"Roles": ["Admin", "User", "Manager"],
279+
"Numbers": [1, 2, 3, 4, 5]
280+
}
281+
```
282+
283+
**Dictionaries:**
284+
285+
```json
286+
{
287+
"Headers": {
288+
"Content-Type": "application/json",
289+
"Authorization": "Bearer token"
290+
}
291+
}
292+
```
293+
294+
**Complex nested structures:**
295+
296+
```json
297+
{
298+
"Request": {
299+
"Method": "POST",
300+
"Path": "/api/users",
301+
"Headers": {
302+
"Content-Type": "application/json",
303+
"User-Agent": "MyApp/1.0"
304+
},
305+
"Body": {
306+
"Users": [
307+
{ "Id": 1, "Name": "Alice" },
308+
{ "Id": 2, "Name": "Bob" }
309+
]
310+
}
311+
}
312+
}
313+
```
314+
315+
**Supported scalar types:**
316+
317+
- Primitive types: `string`, `bool`, `int`, `long`, `double`, `float`, `decimal`, `byte`, `short`, etc.
318+
- Date/time types: `DateTime`, `DateTimeOffset`, `TimeSpan`, `DateOnly`, `TimeOnly` (as ISO strings)
319+
- Other types: `Guid` (as string), `Enum` (as string), `BigInteger` (as string), `char` (as string)
320+
- `null` values are preserved
321+
322+
> **Note:** By default, properties enriched via `FromLogContext()` or `WithProperty()` are included in this column. You can extract specific properties to dedicated columns using custom mappings (see below).
323+
209324
### Custom Property Mappings
210325

211326
Add custom property mappings to log additional data:

src/Directory.Build.props

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
11
<Project>
22

33
<PropertyGroup Label="Package">
4-
<Description>A high-performance Serilog sink that writes log events to MongoDB</Description>
4+
<Description>A high-performance Serilog sink that writes log events to SQL Server</Description>
55
<Copyright>Copyright © $([System.DateTime]::Now.ToString(yyyy)) LoreSoft</Copyright>
66
<Authors>LoreSoft</Authors>
77
<NeutralLanguage>en-US</NeutralLanguage>
88
<GenerateDocumentationFile>true</GenerateDocumentationFile>
9-
<PackageTags>serilog;logging;mongodb</PackageTags>
10-
<PackageProjectUrl>https://github.com/loresoft/serilog-sinks-mongo</PackageProjectUrl>
9+
<PackageTags>serilog;logging;sqlserver</PackageTags>
10+
<PackageProjectUrl>https://github.com/loresoft/serilog-sinks-sqlserver</PackageProjectUrl>
1111
<PackageLicenseExpression>MIT</PackageLicenseExpression>
1212
<PackageIcon>logo.png</PackageIcon>
1313
<PackageReadmeFile>README.md</PackageReadmeFile>
1414
<RepositoryType>git</RepositoryType>
15-
<RepositoryUrl>https://github.com/loresoft/serilog-sinks-mongo</RepositoryUrl>
15+
<RepositoryUrl>https://github.com/loresoft/serilog-sinks-sqlserver</RepositoryUrl>
1616
<PublishRepositoryUrl>true</PublishRepositoryUrl>
1717
</PropertyGroup>
1818

src/Serilog.Sinks.SqlServer/ColumnMapping.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,14 @@
11
namespace Serilog.Sinks.SqlServer;
22

3+
/// <summary>
4+
/// Represents the mapping configuration for a database column.
5+
/// </summary>
6+
/// <typeparam name="T">The type of the source object to map from.</typeparam>
7+
/// <param name="ColumnName">The name of the database column.</param>
8+
/// <param name="ColumnType">The data type of the database column.</param>
9+
/// <param name="GetValue">A function that extracts the value from the source object.</param>
10+
/// <param name="Nullable">Indicates whether the column allows null values. Default is <c>true</c>.</param>
11+
/// <param name="Size">The optional size constraint for the column (e.g., varchar length).</param>
312
public record ColumnMapping<T>
413
(
514
string ColumnName,

src/Serilog.Sinks.SqlServer/JsonWriter.cs

Lines changed: 59 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,20 @@
88

99
namespace Serilog.Sinks.SqlServer;
1010

11+
/// <summary>
12+
/// Provides utility methods for writing Serilog log event data to JSON format.
13+
/// </summary>
1114
public static class JsonWriter
1215
{
16+
/// <summary>
17+
/// Writes an exception to a JSON string representation.
18+
/// </summary>
19+
/// <param name="exception">The exception to serialize. Can be null.</param>
20+
/// <returns>A JSON string containing exception details, or null if the exception is null.</returns>
21+
/// <remarks>
22+
/// The JSON output includes the exception message, type, full text, error codes, source, and method information.
23+
/// Aggregate exceptions with a single inner exception are automatically flattened.
24+
/// </remarks>
1325
public static string? WriteException(Exception? exception)
1426
{
1527
if (exception == null)
@@ -77,6 +89,12 @@ public static class JsonWriter
7789

7890
}
7991

92+
/// <summary>
93+
/// Writes a collection of log event properties to a JSON string representation.
94+
/// </summary>
95+
/// <param name="properties">The dictionary of log event properties to serialize. Can be null or empty.</param>
96+
/// <param name="ignored">An optional set of property names to exclude from the output.</param>
97+
/// <returns>A JSON string containing the properties, or null if there are no properties to write or all properties are ignored.</returns>
8098
public static string? WriteProperties(IReadOnlyDictionary<string, LogEventPropertyValue>? properties, HashSet<string>? ignored = null)
8199
{
82100
// no properties to write
@@ -117,6 +135,11 @@ public static class JsonWriter
117135
#endif
118136
}
119137

138+
/// <summary>
139+
/// Writes a single log event property value to a JSON string representation.
140+
/// </summary>
141+
/// <param name="value">The log event property value to serialize. Can be null.</param>
142+
/// <returns>A JSON string containing the property value, or null if the value is null.</returns>
120143
public static string? WritePropertyValue(LogEventPropertyValue value)
121144
{
122145
if (value == null)
@@ -141,8 +164,15 @@ public static class JsonWriter
141164
#endif
142165
}
143166

144-
145-
public static void WritePropertyValue(Utf8JsonWriter writer, LogEventPropertyValue value)
167+
/// <summary>
168+
/// Writes a log event property value to the specified JSON writer.
169+
/// </summary>
170+
/// <param name="writer">The UTF-8 JSON writer to write to.</param>
171+
/// <param name="value">The log event property value to write.</param>
172+
/// <remarks>
173+
/// Handles scalar values, sequences, structures, and dictionaries appropriately.
174+
/// </remarks>
175+
private static void WritePropertyValue(Utf8JsonWriter writer, LogEventPropertyValue value)
146176
{
147177
if (value is ScalarValue scalarValue)
148178
WriteScalarValue(writer, scalarValue);
@@ -156,7 +186,12 @@ public static void WritePropertyValue(Utf8JsonWriter writer, LogEventPropertyVal
156186
writer.WriteStringValue(value.ToString());
157187
}
158188

159-
public static void WriteDictionaryValue(Utf8JsonWriter writer, DictionaryValue dictionaryValue)
189+
/// <summary>
190+
/// Writes a dictionary value to the specified JSON writer as a JSON object.
191+
/// </summary>
192+
/// <param name="writer">The UTF-8 JSON writer to write to.</param>
193+
/// <param name="dictionaryValue">The dictionary value to write.</param>
194+
private static void WriteDictionaryValue(Utf8JsonWriter writer, DictionaryValue dictionaryValue)
160195
{
161196
writer.WriteStartObject();
162197
foreach (var kvp in dictionaryValue.Elements)
@@ -170,7 +205,12 @@ public static void WriteDictionaryValue(Utf8JsonWriter writer, DictionaryValue d
170205
writer.WriteEndObject();
171206
}
172207

173-
public static void WriteStructureValue(Utf8JsonWriter writer, StructureValue structureValue)
208+
/// <summary>
209+
/// Writes a structure value to the specified JSON writer as a JSON object.
210+
/// </summary>
211+
/// <param name="writer">The UTF-8 JSON writer to write to.</param>
212+
/// <param name="structureValue">The structure value to write.</param>
213+
private static void WriteStructureValue(Utf8JsonWriter writer, StructureValue structureValue)
174214
{
175215
writer.WriteStartObject();
176216
foreach (var prop in structureValue.Properties)
@@ -181,7 +221,12 @@ public static void WriteStructureValue(Utf8JsonWriter writer, StructureValue str
181221
writer.WriteEndObject();
182222
}
183223

184-
public static void WriteSequenceValue(Utf8JsonWriter writer, SequenceValue sequenceValue)
224+
/// <summary>
225+
/// Writes a sequence value to the specified JSON writer as a JSON array.
226+
/// </summary>
227+
/// <param name="writer">The UTF-8 JSON writer to write to.</param>
228+
/// <param name="sequenceValue">The sequence value to write.</param>
229+
private static void WriteSequenceValue(Utf8JsonWriter writer, SequenceValue sequenceValue)
185230
{
186231
writer.WriteStartArray();
187232
foreach (var item in sequenceValue.Elements)
@@ -191,7 +236,15 @@ public static void WriteSequenceValue(Utf8JsonWriter writer, SequenceValue seque
191236
writer.WriteEndArray();
192237
}
193238

194-
public static void WriteScalarValue(Utf8JsonWriter writer, ScalarValue scalarValue)
239+
/// <summary>
240+
/// Writes a scalar value to the specified JSON writer with appropriate JSON typing.
241+
/// </summary>
242+
/// <param name="writer">The UTF-8 JSON writer to write to.</param>
243+
/// <param name="scalarValue">The scalar value to write.</param>
244+
/// <remarks>
245+
/// Handles all primitive types, date/time types, numeric types, and falls back to JSON serialization for unknown types.
246+
/// </remarks>
247+
private static void WriteScalarValue(Utf8JsonWriter writer, ScalarValue scalarValue)
195248
{
196249
if (scalarValue.Value == null)
197250
{

src/Serilog.Sinks.SqlServer/ListDataReader.cs

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,27 @@
22

33
namespace Serilog.Sinks.SqlServer;
44

5+
/// <summary>
6+
/// Provides an <see cref="IDataReader"/> implementation for reading a list of items with configurable column mappings.
7+
/// </summary>
8+
/// <typeparam name="T">The type of items to read.</typeparam>
9+
/// <remarks>
10+
/// This class enables bulk insert operations by wrapping an enumerable collection and exposing it through the IDataReader interface.
11+
/// Column mappings are defined through the <see cref="MappingContext{T}"/> which specifies how to extract values from each item.
12+
/// </remarks>
513
public class ListDataReader<T> : IDataReader
614
{
715
private readonly IEnumerator<T> _iterator;
816
private readonly MappingContext<T> _mappingContext;
917

1018
private bool _disposed;
1119

20+
/// <summary>
21+
/// Initializes a new instance of the <see cref="ListDataReader{T}"/> class.
22+
/// </summary>
23+
/// <param name="logEvents">The collection of items to read.</param>
24+
/// <param name="mappingContext">The mapping context that defines how to map items to columns.</param>
25+
/// <exception cref="ArgumentNullException">Thrown when <paramref name="logEvents"/> or <paramref name="mappingContext"/> is null.</exception>
1226
public ListDataReader(
1327
IEnumerable<T> logEvents,
1428
MappingContext<T> mappingContext)
@@ -54,6 +68,10 @@ public void Dispose()
5468
GC.SuppressFinalize(this);
5569
}
5670

71+
/// <summary>
72+
/// Releases the resources used by the <see cref="ListDataReader{T}"/>.
73+
/// </summary>
74+
/// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
5775
protected virtual void Dispose(bool disposing)
5876
{
5977
if (_disposed)
@@ -98,7 +116,15 @@ public Type GetFieldType(int i)
98116
return columnMapping.ColumnType;
99117
}
100118

101-
/// <inheritdoc/>
119+
/// <summary>
120+
/// Gets the value of the specified column in its native format.
121+
/// </summary>
122+
/// <param name="i">The zero-based column ordinal.</param>
123+
/// <returns>The value of the specified column, or <see cref="DBNull.Value"/> if the value is null.</returns>
124+
/// <exception cref="IndexOutOfRangeException">Thrown when no column mapping is found for the specified ordinal.</exception>
125+
/// <remarks>
126+
/// String values that exceed the column's defined size are automatically truncated to fit the maximum length.
127+
/// </remarks>
102128
public object GetValue(int i)
103129
{
104130
var columnMapping = _mappingContext.GetMapping(i);

src/Serilog.Sinks.SqlServer/LogEventExtensions.cs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,25 @@
22

33
namespace Serilog.Sinks.SqlServer;
44

5+
/// <summary>
6+
/// Provides extension methods for <see cref="LogEvent"/> to simplify property value extraction.
7+
/// </summary>
58
public static class LogEventExtensions
69
{
10+
/// <summary>
11+
/// Gets the value of a specified property from the log event.
12+
/// </summary>
13+
/// <param name="logEvent">The log event to extract the property from.</param>
14+
/// <param name="propertyName">The name of the property to retrieve.</param>
15+
/// <returns>
16+
/// The underlying value if the property is a <see cref="ScalarValue"/>,
17+
/// a JSON string representation for complex types (sequences, structures, dictionaries),
18+
/// or <c>null</c> if the log event is null, the property name is empty, or the property does not exist.
19+
/// </returns>
20+
/// <remarks>
21+
/// Scalar values are returned directly as their underlying type.
22+
/// Complex property types (sequences, structures, dictionaries) are serialized to JSON strings.
23+
/// </remarks>
724
public static object? GetPropertyValue(this LogEvent logEvent, string propertyName)
825
{
926
if (logEvent == null || string.IsNullOrEmpty(propertyName))

0 commit comments

Comments
 (0)