Skip to content

Commit 9267d19

Browse files
authored
Merge pull request #32 from serilog/dev
4.0.0 Release
2 parents eeaa870 + b5cd8cc commit 9267d19

17 files changed

+575
-164
lines changed

src/Serilog.Sinks.AzureTableStorage/LoggerConfigurationAzureTableStorageExtensions.cs

Lines changed: 36 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
using Serilog.Core;
1919
using Serilog.Events;
2020
using Serilog.Sinks.AzureTableStorage;
21+
using Serilog.Sinks.AzureTableStorage.KeyGenerator;
22+
using Serilog.Sinks.AzureTableStorage.Sinks;
2123

2224
namespace Serilog
2325
{
@@ -49,6 +51,7 @@ public static class LoggerConfigurationAzureTableStorageExtensions
4951
/// key used for the events so is not enabled by default.</param>
5052
/// <param name="batchPostingLimit">The maximum number of events to post in a single batch.</param>
5153
/// <param name="period">The time to wait between checking for event batches.</param>
54+
/// <param name="keyGenerator">The key generator used to create the PartitionKey and the RowKey for each log entry</param>
5255
/// <returns>Logger configuration, allowing configuration to continue.</returns>
5356
/// <exception cref="ArgumentNullException">A required parameter is null.</exception>
5457
public static LoggerConfiguration AzureTableStorage(
@@ -59,14 +62,25 @@ public static LoggerConfiguration AzureTableStorage(
5962
string storageTableName = null,
6063
bool writeInBatches = false,
6164
TimeSpan? period = null,
62-
int? batchPostingLimit = null)
65+
int? batchPostingLimit = null,
66+
IKeyGenerator keyGenerator = null)
6367
{
6468
if (loggerConfiguration == null) throw new ArgumentNullException("loggerConfiguration");
6569
if (storageAccount == null) throw new ArgumentNullException("storageAccount");
6670

67-
var sink = writeInBatches ?
68-
(ILogEventSink)new AzureBatchingTableStorageSink(storageAccount, formatProvider, batchPostingLimit ?? DefaultBatchPostingLimit, period ?? DefaultPeriod, storageTableName) :
69-
new AzureTableStorageSink(storageAccount, formatProvider, storageTableName);
71+
ILogEventSink sink;
72+
73+
try
74+
{
75+
sink = writeInBatches ?
76+
(ILogEventSink)new AzureBatchingTableStorageSink(storageAccount, formatProvider, batchPostingLimit ?? DefaultBatchPostingLimit, period ?? DefaultPeriod, storageTableName, keyGenerator) :
77+
new AzureTableStorageSink(storageAccount, formatProvider, storageTableName, keyGenerator);
78+
}
79+
catch (Exception ex)
80+
{
81+
Debugging.SelfLog.WriteLine("Error configuring AzureTableStorage: {0}", ex);
82+
sink = new LoggerConfiguration().CreateLogger();
83+
}
7084

7185
return loggerConfiguration.Sink(sink, restrictedToMinimumLevel);
7286
}
@@ -83,6 +97,8 @@ public static LoggerConfiguration AzureTableStorage(
8397
/// key used for the events so is not enabled by default.</param>
8498
/// <param name="batchPostingLimit">The maximum number of events to post in a single batch.</param>
8599
/// <param name="period">The time to wait between checking for event batches.</param>
100+
/// <param name="keyGenerator">The key generator used to create the PartitionKey and the RowKey for each log entry</param>
101+
/// <param name="propagateExceptions">Whether connection string issues should throw an exception; disabled by default.</param>
86102
/// <returns>Logger configuration, allowing configuration to continue.</returns>
87103
/// <exception cref="ArgumentNullException">A required parameter is null.</exception>
88104
public static LoggerConfiguration AzureTableStorage(
@@ -93,12 +109,24 @@ public static LoggerConfiguration AzureTableStorage(
93109
string storageTableName = null,
94110
bool writeInBatches = false,
95111
TimeSpan? period = null,
96-
int? batchPostingLimit = null)
112+
int? batchPostingLimit = null,
113+
IKeyGenerator keyGenerator = null)
97114
{
98115
if (loggerConfiguration == null) throw new ArgumentNullException("loggerConfiguration");
99116
if (String.IsNullOrEmpty(connectionString)) throw new ArgumentNullException("connectionString");
100-
var storageAccount = CloudStorageAccount.Parse(connectionString);
101-
return AzureTableStorage(loggerConfiguration, storageAccount, restrictedToMinimumLevel, formatProvider, storageTableName, writeInBatches, period, batchPostingLimit);
117+
118+
try
119+
{
120+
var storageAccount = CloudStorageAccount.Parse(connectionString);
121+
return AzureTableStorage(loggerConfiguration, storageAccount, restrictedToMinimumLevel, formatProvider, storageTableName, writeInBatches, period, batchPostingLimit, keyGenerator);
122+
}
123+
catch (Exception ex)
124+
{
125+
Debugging.SelfLog.WriteLine("Error configuring AzureTableStorage: {0}", ex);
126+
127+
ILogEventSink sink = new LoggerConfiguration().CreateLogger();
128+
return loggerConfiguration.Sink(sink, restrictedToMinimumLevel);
129+
}
102130
}
103131
}
104-
}
132+
}

src/Serilog.Sinks.AzureTableStorage/LoggerConfigurationAzureTableStorageWithPropertiesExtensions.cs

Lines changed: 38 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
using Serilog.Events;
1919
using Serilog.Sinks.AzureTableStorage;
2020
using System;
21+
using Serilog.Sinks.AzureTableStorage.KeyGenerator;
2122

2223
namespace Serilog
2324
{
@@ -50,6 +51,8 @@ public static class LoggerConfigurationAzureTableStorageWithPropertiesExtensions
5051
/// <param name="batchPostingLimit">The maximum number of events to post in a single batch.</param>
5152
/// <param name="period">The time to wait between checking for event batches.</param>
5253
/// <param name="additionalRowKeyPostfix">Additional postfix string that will be appended to row keys</param>
54+
/// <param name="keyGenerator">Generates the PartitionKey and the RowKey</param>
55+
/// <param name="propertyColumns">Specific properties to be written to columns. By default, all properties will be written to columns.</param>
5356
/// <returns>Logger configuration, allowing configuration to continue.</returns>
5457
/// <exception cref="ArgumentNullException">A required parameter is null.</exception>
5558
public static LoggerConfiguration AzureTableStorageWithProperties(
@@ -61,14 +64,27 @@ public static LoggerConfiguration AzureTableStorageWithProperties(
6164
bool writeInBatches = false,
6265
TimeSpan? period = null,
6366
int? batchPostingLimit = null,
64-
string additionalRowKeyPostfix = null)
67+
string additionalRowKeyPostfix = null,
68+
IKeyGenerator keyGenerator = null,
69+
string[] propertyColumns = null)
6570
{
6671
if (loggerConfiguration == null) throw new ArgumentNullException("loggerConfiguration");
6772
if (storageAccount == null) throw new ArgumentNullException("storageAccount");
6873

69-
var sink = writeInBatches ?
70-
(ILogEventSink)new AzureBatchingTableStorageWithPropertiesSink(storageAccount, formatProvider, batchPostingLimit ?? DefaultBatchPostingLimit, period ?? DefaultPeriod, storageTableName, additionalRowKeyPostfix) :
71-
new AzureTableStorageWithPropertiesSink(storageAccount, formatProvider, storageTableName, additionalRowKeyPostfix);
74+
ILogEventSink sink;
75+
76+
try
77+
{
78+
sink = writeInBatches
79+
? (ILogEventSink)
80+
new AzureBatchingTableStorageWithPropertiesSink(storageAccount, formatProvider, batchPostingLimit ?? DefaultBatchPostingLimit, period ?? DefaultPeriod, storageTableName, additionalRowKeyPostfix, keyGenerator, propertyColumns)
81+
: new AzureTableStorageWithPropertiesSink(storageAccount, formatProvider, storageTableName, additionalRowKeyPostfix, keyGenerator, propertyColumns);
82+
}
83+
catch (Exception ex)
84+
{
85+
Debugging.SelfLog.WriteLine("Error configuring AzureTableStorageWithProperties: {0}", ex);
86+
sink = new LoggerConfiguration().CreateLogger();
87+
}
7288

7389
return loggerConfiguration.Sink(sink, restrictedToMinimumLevel);
7490
}
@@ -86,6 +102,8 @@ public static LoggerConfiguration AzureTableStorageWithProperties(
86102
/// <param name="batchPostingLimit">The maximum number of events to post in a single batch.</param>
87103
/// <param name="period">The time to wait between checking for event batches.</param>
88104
/// <param name="additionalRowKeyPostfix">Additional postfix string that will be appended to row keys</param>
105+
/// <param name="keyGenerator">Generates the PartitionKey and the RowKey</param>
106+
/// <param name="propertyColumns">Specific properties to be written to columns. By default, all properties will be written to columns.</param>
89107
/// <returns>Logger configuration, allowing configuration to continue.</returns>
90108
/// <exception cref="ArgumentNullException">A required parameter is null.</exception>
91109
public static LoggerConfiguration AzureTableStorageWithProperties(
@@ -97,12 +115,25 @@ public static LoggerConfiguration AzureTableStorageWithProperties(
97115
bool writeInBatches = false,
98116
TimeSpan? period = null,
99117
int? batchPostingLimit = null,
100-
string additionalRowKeyPostfix = null)
118+
string additionalRowKeyPostfix = null,
119+
IKeyGenerator keyGenerator = null,
120+
string[] propertyColumns = null)
101121
{
102122
if (loggerConfiguration == null) throw new ArgumentNullException("loggerConfiguration");
103123
if (String.IsNullOrEmpty(connectionString)) throw new ArgumentNullException("connectionString");
104-
var storageAccount = CloudStorageAccount.Parse(connectionString);
105-
return AzureTableStorageWithProperties(loggerConfiguration, storageAccount, restrictedToMinimumLevel, formatProvider, storageTableName, writeInBatches, period, batchPostingLimit, additionalRowKeyPostfix);
124+
125+
try
126+
{
127+
var storageAccount = CloudStorageAccount.Parse(connectionString);
128+
return AzureTableStorageWithProperties(loggerConfiguration, storageAccount, restrictedToMinimumLevel, formatProvider, storageTableName, writeInBatches, period, batchPostingLimit, additionalRowKeyPostfix, keyGenerator, propertyColumns);
129+
}
130+
catch (Exception ex)
131+
{
132+
Debugging.SelfLog.WriteLine("Error configuring AzureTableStorageWithProperties: {0}", ex);
133+
134+
ILogEventSink sink = new LoggerConfiguration().CreateLogger();
135+
return loggerConfiguration.Sink(sink, restrictedToMinimumLevel);
136+
}
106137
}
107138
}
108139
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
using System.Text.RegularExpressions;
2+
3+
namespace Serilog.Sinks.AzureTableStorage
4+
{
5+
/// <summary>
6+
/// Defines naming restrictions for Azure Table Storage objects
7+
/// </summary>
8+
public static class ObjectNaming
9+
{
10+
/// <summary>
11+
/// The regex defining characters which are disallowed for key field values.
12+
/// </summary>
13+
/// <see href="https://msdn.microsoft.com/en-us/library/azure/dd179338.aspx"/>
14+
public static readonly Regex KeyFieldValueCharactersNotAllowedMatch =
15+
new Regex(@"(\\|/|#|\?|[\x00-\x1f]|[\x7f-\x9f])");
16+
17+
/// <summary>
18+
/// Given a <param name="keyValue">key value</param>, returns a value
19+
/// which has been 'cleaned' of any disallowed characters and trimmed
20+
/// to the allowed length.
21+
/// </summary>
22+
public static string GetValidKeyValue(string keyValue)
23+
{
24+
keyValue = KeyFieldValueCharactersNotAllowedMatch.Replace(keyValue, "");
25+
return keyValue.Length > 1024 ? keyValue.Substring(0, 1024) : keyValue;
26+
}
27+
}
28+
}

src/Serilog.Sinks.AzureTableStorage/Serilog.Sinks.AzureTableStorage.nuspec

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,9 @@
1111
<iconUrl>http://serilog.net/images/serilog-sink-nuget.png</iconUrl>
1212
<tags>serilog logging azure</tags>
1313
<dependencies>
14-
<dependency id="Serilog" version="2.0" />
15-
<dependency id="WindowsAzure.Storage" version="2.0" />
14+
<dependency id="Serilog" version="2.3.0" />
15+
<dependency id="Serilog.Sinks.PeriodicBatching" version="2.1.0" />
16+
<dependency id="WindowsAzure.Storage" version="7.2.0" />
1617
</dependencies>
1718
</metadata>
1819
</package>

src/Serilog.Sinks.AzureTableStorage/Sinks/AzureTableStorage/AzureBatchingTableStorageSink.cs

Lines changed: 51 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,10 @@
1818
using Microsoft.WindowsAzure.Storage;
1919
using Microsoft.WindowsAzure.Storage.Table;
2020
using Serilog.Events;
21+
2122
using Serilog.Sinks.PeriodicBatching;
2223
using System.Threading.Tasks;
24+
using Serilog.Sinks.AzureTableStorage.KeyGenerator;
2325

2426
namespace Serilog.Sinks.AzureTableStorage
2527
{
@@ -30,10 +32,27 @@ public class AzureBatchingTableStorageSink : PeriodicBatchingSink
3032
{
3133
readonly int _waitTimeoutMilliseconds = Timeout.Infinite;
3234
readonly IFormatProvider _formatProvider;
35+
private readonly IKeyGenerator _keyGenerator;
3336
readonly CloudTable _table;
3437

35-
long _partitionKey;
36-
int _batchRowId;
38+
/// <summary>
39+
/// Construct a sink that saves logs to the specified storage account.
40+
/// </summary>
41+
/// <param name="storageAccount">The Cloud Storage Account to use to insert the log entries to.</param>
42+
/// <param name="formatProvider">Supplies culture-specific formatting information, or null.</param>
43+
/// <param name="batchSizeLimit"></param>
44+
/// <param name="period"></param>
45+
/// <param name="storageTableName">Table name that log entries will be written to. Note: Optional, setting this may impact performance</param>
46+
public AzureBatchingTableStorageSink(
47+
CloudStorageAccount storageAccount,
48+
IFormatProvider formatProvider,
49+
int batchSizeLimit,
50+
TimeSpan period,
51+
string storageTableName = null)
52+
: this(storageAccount, formatProvider, batchSizeLimit, period, storageTableName, new DefaultKeyGenerator())
53+
54+
{
55+
}
3756

3857
/// <summary>
3958
/// Construct a sink that saves logs to the specified storage account.
@@ -43,12 +62,21 @@ public class AzureBatchingTableStorageSink : PeriodicBatchingSink
4362
/// <param name="batchSizeLimit"></param>
4463
/// <param name="period"></param>
4564
/// <param name="storageTableName">Table name that log entries will be written to. Note: Optional, setting this may impact performance</param>
46-
public AzureBatchingTableStorageSink(CloudStorageAccount storageAccount, IFormatProvider formatProvider, int batchSizeLimit, TimeSpan period, string storageTableName = null)
47-
:base(batchSizeLimit, period)
65+
/// <param name="keyGenerator">generator used for partition keys and row keys</param>
66+
public AzureBatchingTableStorageSink(
67+
CloudStorageAccount storageAccount,
68+
IFormatProvider formatProvider,
69+
int batchSizeLimit,
70+
TimeSpan period,
71+
string storageTableName = null,
72+
IKeyGenerator keyGenerator = null)
73+
: base(batchSizeLimit, period)
4874
{
4975
if (batchSizeLimit < 1 || batchSizeLimit > 100)
5076
throw new ArgumentException("batchSizeLimit must be between 1 and 100 for Azure Table Storage");
77+
5178
_formatProvider = formatProvider;
79+
_keyGenerator = keyGenerator ?? new DefaultKeyGenerator();
5280
var tableClient = storageAccount.CreateCloudTableClient();
5381

5482
if (string.IsNullOrEmpty(storageTableName)) storageTableName = typeof(LogEventEntity).Name;
@@ -59,33 +87,35 @@ public AzureBatchingTableStorageSink(CloudStorageAccount storageAccount, IFormat
5987

6088
protected override async Task EmitBatchAsync(IEnumerable<LogEvent> events)
6189
{
62-
var operation = new TableBatchOperation();
90+
TableBatchOperation operation = new TableBatchOperation();
6391

64-
var first = true;
92+
string lastPartitionKey = null;
6593

6694
foreach (var logEvent in events)
6795
{
68-
if (first)
96+
var partitionKey = _keyGenerator.GeneratePartitionKey(logEvent);
97+
98+
if (partitionKey != lastPartitionKey)
6999
{
70-
//check to make sure the partition key is not the same as the previous batch
71-
var ticks = logEvent.Timestamp.ToUniversalTime().Ticks;
72-
if (_partitionKey != ticks)
100+
lastPartitionKey = partitionKey;
101+
if (operation.Count > 0)
73102
{
74-
_batchRowId = 0; //the partitionkey has been reset
75-
_partitionKey = ticks; //store the new partition key
103+
await _table.ExecuteBatchAsync(operation);
104+
operation = new TableBatchOperation();
76105
}
77-
first = false;
78106
}
79-
80-
var logEventEntity = new LogEventEntity(logEvent, _formatProvider, _partitionKey);
81-
logEventEntity.RowKey += "|" + _batchRowId;
107+
var logEventEntity = new LogEventEntity(
108+
logEvent,
109+
_formatProvider,
110+
partitionKey,
111+
_keyGenerator.GenerateRowKey(logEvent)
112+
);
82113
operation.Add(TableOperation.Insert(logEventEntity));
83-
84-
_batchRowId++;
85114
}
86-
87-
await _table.ExecuteBatchAsync(operation);
115+
if (operation.Count > 0)
116+
{
117+
await _table.ExecuteBatchAsync(operation);
118+
}
88119
}
89-
90120
}
91121
}

src/Serilog.Sinks.AzureTableStorage/Sinks/AzureTableStorage/AzureTableStorageSink.cs

Lines changed: 12 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
using Microsoft.WindowsAzure.Storage.Table;
1919
using Serilog.Core;
2020
using Serilog.Events;
21+
using Serilog.Sinks.AzureTableStorage.KeyGenerator;
2122

2223
namespace Serilog.Sinks.AzureTableStorage
2324
{
@@ -28,18 +29,24 @@ public class AzureTableStorageSink : ILogEventSink
2829
{
2930
readonly int _waitTimeoutMilliseconds = Timeout.Infinite;
3031
readonly IFormatProvider _formatProvider;
32+
readonly IKeyGenerator _keyGenerator;
3133
readonly CloudTable _table;
32-
long _rowKeyIndex;
3334

3435
/// <summary>
3536
/// Construct a sink that saves logs to the specified storage account.
3637
/// </summary>
3738
/// <param name="storageAccount">The Cloud Storage Account to use to insert the log entries to.</param>
3839
/// <param name="formatProvider">Supplies culture-specific formatting information, or null.</param>
3940
/// <param name="storageTableName">Table name that log entries will be written to. Note: Optional, setting this may impact performance</param>
40-
public AzureTableStorageSink(CloudStorageAccount storageAccount, IFormatProvider formatProvider, string storageTableName = null)
41+
/// <param name="keyGenerator">generator used to generate partition keys and row keys</param>
42+
public AzureTableStorageSink(
43+
CloudStorageAccount storageAccount,
44+
IFormatProvider formatProvider,
45+
string storageTableName = null,
46+
IKeyGenerator keyGenerator = null)
4147
{
4248
_formatProvider = formatProvider;
49+
_keyGenerator = keyGenerator ?? new DefaultKeyGenerator();
4350
var tableClient = storageAccount.CreateCloudTableClient();
4451

4552
if (string.IsNullOrEmpty(storageTableName))
@@ -60,22 +67,12 @@ public void Emit(LogEvent logEvent)
6067
var logEventEntity = new LogEventEntity(
6168
logEvent,
6269
_formatProvider,
63-
logEvent.Timestamp.ToUniversalTime().Ticks);
64-
EnsureUniqueRowKey(logEventEntity);
70+
_keyGenerator.GeneratePartitionKey(logEvent),
71+
_keyGenerator.GenerateRowKey(logEvent)
72+
);
6573

6674
_table.ExecuteAsync(TableOperation.Insert(logEventEntity))
6775
.SyncContextSafeWait(_waitTimeoutMilliseconds);
6876
}
69-
70-
/// <summary>
71-
/// Appends an incrementing index to the row key to ensure that it will
72-
/// not conflict with existing rows created at the same time / with the
73-
/// same partition key.
74-
/// </summary>
75-
/// <param name="logEventEntity"></param>
76-
void EnsureUniqueRowKey(ITableEntity logEventEntity)
77-
{
78-
logEventEntity.RowKey += "|" + Interlocked.Increment(ref _rowKeyIndex);
79-
}
8077
}
8178
}

0 commit comments

Comments
 (0)