Skip to content

Commit 8d4e9df

Browse files
cjaliagajviau
andauthored
[1ES] Migrate Diagnostic Events to Azure.Data.Tables (#10167)
* Migrate DiagnosticEvents from Azure.Cosmos.Table to Azure.Data.Tables --------- Co-authored-by: Jacob Viau <[email protected]>
1 parent e5a04b7 commit 8d4e9df

File tree

8 files changed

+179
-106
lines changed

8 files changed

+179
-106
lines changed

src/WebJobs.Script.WebHost/Diagnostics/DiagnosticEvent.cs

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,15 @@
22
// Licensed under the MIT License. See License.txt in the project root for license information.
33

44
using System;
5-
using System.Threading;
6-
using Microsoft.Azure.Cosmos.Table;
5+
using System.Runtime.Serialization;
6+
using Azure;
7+
using Azure.Data.Tables;
78
using Microsoft.Azure.WebJobs.Script.WebHost.Helpers;
89
using Microsoft.Extensions.Logging;
910

1011
namespace Microsoft.Azure.WebJobs.Script.WebHost.Diagnostics
1112
{
12-
public class DiagnosticEvent : TableEntity
13+
public class DiagnosticEvent : ITableEntity
1314
{
1415
internal const string CurrentEventVersion = "2024-05-01";
1516

@@ -23,6 +24,14 @@ public DiagnosticEvent(string hostId, DateTime timestamp)
2324
EventVersion = CurrentEventVersion;
2425
}
2526

27+
public string PartitionKey { get; set; }
28+
29+
public string RowKey { get; set; }
30+
31+
public DateTimeOffset? Timestamp { get; set; }
32+
33+
public ETag ETag { get; set; }
34+
2635
public string EventVersion { get; set; }
2736

2837
public int HitCount { get; set; }
@@ -35,7 +44,7 @@ public DiagnosticEvent(string hostId, DateTime timestamp)
3544

3645
public int Level { get; set; }
3746

38-
[IgnoreProperty]
47+
[IgnoreDataMember]
3948
public LogLevel LogLevel
4049
{
4150
get { return (LogLevel)Level; }

src/WebJobs.Script.WebHost/Diagnostics/DiagnosticEventTableStorageRepository.cs

Lines changed: 37 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
using System.Linq;
88
using System.Threading;
99
using System.Threading.Tasks;
10-
using Microsoft.Azure.Cosmos.Table;
10+
using Azure.Data.Tables;
1111
using Microsoft.Azure.WebJobs.Host.Executors;
1212
using Microsoft.Azure.WebJobs.Hosting;
1313
using Microsoft.Azure.WebJobs.Logging;
@@ -33,8 +33,8 @@ public class DiagnosticEventTableStorageRepository : IDiagnosticEventRepository,
3333
private readonly object _syncLock = new object();
3434

3535
private ConcurrentDictionary<string, DiagnosticEvent> _events = new ConcurrentDictionary<string, DiagnosticEvent>();
36-
private CloudTableClient _tableClient;
37-
private CloudTable _diagnosticEventsTable;
36+
private TableServiceClient _tableClient;
37+
private TableClient _diagnosticEventsTable;
3838
private string _hostId;
3939
private bool _disposed = false;
4040
private bool _purged = false;
@@ -55,22 +55,21 @@ public DiagnosticEventTableStorageRepository(IConfiguration configuration, IHost
5555
ILogger<DiagnosticEventTableStorageRepository> logger)
5656
: this(configuration, hostIdProvider, environment, scriptHost, logger, LogFlushInterval) { }
5757

58-
internal CloudTableClient TableClient
58+
internal TableServiceClient TableClient
5959
{
6060
get
6161
{
6262
if (!_environment.IsPlaceholderModeEnabled() && _tableClient == null)
6363
{
6464
string storageConnectionString = _configuration.GetWebJobsConnectionString(ConnectionStringNames.Storage);
65-
if (!string.IsNullOrEmpty(storageConnectionString)
66-
&& CloudStorageAccount.TryParse(storageConnectionString, out CloudStorageAccount account))
65+
66+
try
6767
{
68-
var tableClientConfig = new TableClientConfiguration();
69-
_tableClient = new CloudTableClient(account.TableStorageUri, account.Credentials, tableClientConfig);
68+
_tableClient = new TableServiceClient(storageConnectionString);
7069
}
71-
else
70+
catch (Exception ex)
7271
{
73-
_logger.LogError("Azure Storage connection string is empty or invalid. Unable to write diagnostic events.");
72+
_logger.LogError(ex, "Azure Storage connection string is empty or invalid. Unable to write diagnostic events.");
7473
}
7574
}
7675

@@ -92,7 +91,7 @@ internal string HostId
9291

9392
internal ConcurrentDictionary<string, DiagnosticEvent> Events => _events;
9493

95-
internal CloudTable GetDiagnosticEventsTable(DateTime? now = null)
94+
internal TableClient GetDiagnosticEventsTable(DateTime? now = null)
9695
{
9796
if (TableClient != null)
9897
{
@@ -103,7 +102,7 @@ internal CloudTable GetDiagnosticEventsTable(DateTime? now = null)
103102
if (_diagnosticEventsTable == null || currentTableName != _tableName)
104103
{
105104
_tableName = currentTableName;
106-
_diagnosticEventsTable = TableClient.GetTableReference(_tableName);
105+
_diagnosticEventsTable = TableClient.GetTableClient(_tableName);
107106
}
108107
}
109108

@@ -134,31 +133,27 @@ await Utility.InvokeWithRetriesAsync(async () =>
134133

135134
foreach (var table in tables)
136135
{
137-
var tableRecords = await table.ExecuteQuerySegmentedAsync(new TableQuery<DiagnosticEvent>(), null);
138-
139-
// Skip tables that have 0 records
140-
if (tableRecords.Results.Count == 0)
141-
{
142-
continue;
143-
}
144-
145-
// Delete table if it doesn't have records with EventVersion
146-
var eventVersionDoesNotExists = tableRecords.Results.Any(record => string.IsNullOrEmpty(record.EventVersion) == true);
147-
if (eventVersionDoesNotExists)
148-
{
149-
_logger.LogDebug("Deleting table '{tableName}' as it contains records without an EventVersion.", table.Name);
150-
await table.DeleteIfExistsAsync();
151-
tableDeleted = true;
152-
continue;
153-
}
136+
var tableQuery = table.QueryAsync<DiagnosticEvent>(cancellationToken: default);
154137

155-
// If the table does have EventVersion, query if it is an outdated version
156-
var eventVersionOutdated = tableRecords.Results.Any(record => string.Compare(DiagnosticEvent.CurrentEventVersion, record.EventVersion, StringComparison.Ordinal) > 0);
157-
if (eventVersionOutdated)
138+
await foreach (var record in tableQuery)
158139
{
159-
_logger.LogDebug("Deleting table '{tableName}' as it contains records with an outdated EventVersion.", table.Name);
160-
await table.DeleteIfExistsAsync();
161-
tableDeleted = true;
140+
// Delete table if it doesn't have records with EventVersion
141+
if (string.IsNullOrEmpty(record.EventVersion) == true)
142+
{
143+
_logger.LogDebug("Deleting table '{tableName}' as it contains records without an EventVersion.", table.Name);
144+
await table.DeleteAsync();
145+
tableDeleted = true;
146+
break;
147+
}
148+
149+
// If the table does have EventVersion, query if it is an outdated version
150+
if (string.Compare(DiagnosticEvent.CurrentEventVersion, record.EventVersion, StringComparison.Ordinal) > 0)
151+
{
152+
_logger.LogDebug("Deleting table '{tableName}' as it contains records with an outdated EventVersion.", table.Name);
153+
await table.DeleteAsync();
154+
tableDeleted = true;
155+
break;
156+
}
162157
}
163158
}
164159

@@ -177,7 +172,7 @@ await Utility.InvokeWithRetriesAsync(async () =>
177172
}
178173
}
179174

180-
internal virtual async Task FlushLogs(CloudTable table = null)
175+
internal virtual async Task FlushLogs(TableClient table = null)
181176
{
182177
if (_environment.IsPlaceholderModeEnabled())
183178
{
@@ -200,7 +195,7 @@ internal virtual async Task FlushLogs(CloudTable table = null)
200195
return;
201196
}
202197

203-
bool tableCreated = await TableStorageHelpers.CreateIfNotExistsAsync(table, TableCreationMaxRetryCount);
198+
bool tableCreated = await TableStorageHelpers.CreateIfNotExistsAsync(table, TableClient, TableCreationMaxRetryCount);
204199
if (tableCreated)
205200
{
206201
_logger.LogDebug("Queueing background table purge.");
@@ -225,20 +220,20 @@ internal virtual async Task FlushLogs(CloudTable table = null)
225220
}
226221
}
227222

228-
internal async Task ExecuteBatchAsync(ConcurrentDictionary<string, DiagnosticEvent> events, CloudTable table)
223+
internal async Task ExecuteBatchAsync(ConcurrentDictionary<string, DiagnosticEvent> events, TableClient table)
229224
{
230225
try
231226
{
232-
var batch = new TableBatchOperation();
227+
var batch = new List<TableTransactionAction>();
233228
foreach (string errorCode in events.Keys)
234229
{
235230
var diagnosticEvent = events[errorCode];
236231
diagnosticEvent.Message = Sanitizer.Sanitize(diagnosticEvent.Message);
237232
diagnosticEvent.Details = Sanitizer.Sanitize(diagnosticEvent.Details);
238-
TableOperation insertOperation = TableOperation.Insert(diagnosticEvent);
239-
batch.Add(insertOperation);
233+
TableTransactionAction insertAction = new TableTransactionAction(TableTransactionActionType.Add, diagnosticEvent);
234+
batch.Add(insertAction);
240235
}
241-
await table.ExecuteBatchAsync(batch);
236+
await table.SubmitTransactionAsync(batch);
242237
events.Clear();
243238
}
244239
catch (Exception ex)

src/WebJobs.Script.WebHost/Helpers/TableStorageHelpers.cs

Lines changed: 31 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@
66
using System.Linq;
77
using System.Net;
88
using System.Threading.Tasks;
9-
using Microsoft.Azure.Cosmos.Table;
9+
using Azure;
10+
using Azure.Data.Tables;
11+
using Azure.Data.Tables.Models;
1012
using Microsoft.Extensions.Logging;
1113

1214
namespace Microsoft.Azure.WebJobs.Script.WebHost.Helpers
@@ -18,27 +20,27 @@ internal static string GetRowKey(DateTime now)
1820
return string.Format("{0:D19}-{1}", DateTime.MaxValue.Ticks - now.Ticks, Guid.NewGuid());
1921
}
2022

21-
internal static async Task<bool> CreateIfNotExistsAsync(CloudTable table, int tableCreationRetries, int retryDelayMS = 1000)
23+
internal static async Task<bool> CreateIfNotExistsAsync(TableClient table, TableServiceClient tableClient, int tableCreationRetries, int retryDelayMS = 1000)
2224
{
2325
int attempt = 0;
2426
do
2527
{
2628
try
2729
{
28-
if (!table.Exists())
30+
if (!await TableExistAsync(table, tableClient))
2931
{
30-
return await table.CreateIfNotExistsAsync();
32+
return (await table.CreateIfNotExistsAsync())?.Value is not null;
3133
}
3234
}
33-
catch (StorageException e)
35+
catch (RequestFailedException rfe)
3436
{
3537
// Can get conflicts with multiple instances attempting to create
3638
// the same table.
3739
// Also, if a table queued up for deletion, we can get a conflict on create,
3840
// though these should only happen in tests not production, because we only ever
3941
// delete OLD tables and we'll never be attempting to recreate a table we just
4042
// deleted outside of tests.
41-
if (e.RequestInformation.HttpStatusCode == (int)HttpStatusCode.Conflict &&
43+
if ((rfe.Status != (int)HttpStatusCode.Conflict || rfe.ErrorCode == TableErrorCode.TableBeingDeleted) &&
4244
attempt < tableCreationRetries)
4345
{
4446
// wait a bit and try again
@@ -53,7 +55,7 @@ internal static async Task<bool> CreateIfNotExistsAsync(CloudTable table, int ta
5355
return false;
5456
}
5557

56-
internal static void QueueBackgroundTablePurge(CloudTable currentTable, CloudTableClient tableClient, string tableNamePrefix, ILogger logger, int delaySeconds = 30)
58+
internal static void QueueBackgroundTablePurge(TableClient currentTable, TableServiceClient tableClient, string tableNamePrefix, ILogger logger, int delaySeconds = 30)
5759
{
5860
var tIgnore = Task.Run(async () =>
5961
{
@@ -74,38 +76,49 @@ internal static void QueueBackgroundTablePurge(CloudTable currentTable, CloudTab
7476
});
7577
}
7678

77-
internal static async Task DeleteOldTablesAsync(CloudTable currentTable, CloudTableClient tableClient, string tableNamePrefix, ILogger logger)
79+
internal static async Task DeleteOldTablesAsync(TableClient currentTable, TableServiceClient tableClient, string tableNamePrefix, ILogger logger)
7880
{
7981
var tablesToDelete = await ListOldTablesAsync(currentTable, tableClient, tableNamePrefix);
8082
logger.LogDebug($"Deleting {tablesToDelete.Count()} old tables.");
8183
foreach (var table in tablesToDelete)
8284
{
8385
logger.LogDebug($"Deleting table '{table.Name}'");
84-
await table.DeleteIfExistsAsync();
86+
await tableClient.DeleteTableAsync(table.Name);
8587
logger.LogDebug($"{table.Name} deleted.");
8688
}
8789
}
8890

89-
internal static async Task<IEnumerable<CloudTable>> ListOldTablesAsync(CloudTable currentTable, CloudTableClient tableClient, string tableNamePrefix)
91+
internal static async Task<IEnumerable<TableClient>> ListOldTablesAsync(TableClient currentTable, TableServiceClient tableClient, string tableNamePrefix)
9092
{
9193
var tables = await ListTablesAsync(tableClient, tableNamePrefix);
9294
return tables.Where(p => !string.Equals(currentTable.Name, p.Name, StringComparison.OrdinalIgnoreCase));
9395
}
9496

95-
internal static async Task<IEnumerable<CloudTable>> ListTablesAsync(CloudTableClient tableClient, string tableNamePrefix)
97+
internal static async Task<IEnumerable<TableClient>> ListTablesAsync(TableServiceClient tableClient, string tableNamePrefix)
9698
{
97-
List<CloudTable> tables = new List<CloudTable>();
98-
TableContinuationToken continuationToken = null;
99+
// Azure.Data.Tables doesn't have a direct way to list tables with a prefix so we need to do it manually
100+
var givenValue = tableNamePrefix + "{";
101+
AsyncPageable<TableItem> tablesQuery = tableClient.QueryAsync(p => p.Name.CompareTo(tableNamePrefix) >= 0 && p.Name.CompareTo(givenValue) <= 0);
102+
var tables = new List<TableClient>();
99103

100-
do
104+
await foreach (var table in tablesQuery)
101105
{
102-
var results = await tableClient.ListTablesSegmentedAsync(tableNamePrefix, continuationToken);
103-
continuationToken = results.ContinuationToken;
104-
tables.AddRange(results.Results);
106+
tables.Add(tableClient.GetTableClient(table.Name));
105107
}
106-
while (continuationToken != null);
107108

108109
return tables;
109110
}
111+
112+
internal static async Task<bool> TableExistAsync(TableClient table, TableServiceClient tableClient)
113+
{
114+
var query = tableClient.QueryAsync(p => p.Name == table.Name);
115+
116+
await foreach (var item in query)
117+
{
118+
return true;
119+
}
120+
121+
return false;
122+
}
110123
}
111124
}

src/WebJobs.Script.WebHost/WebJobs.Script.WebHost.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@
6262
</ItemGroup>
6363

6464
<ItemGroup>
65+
<PackageReference Include="Azure.Data.Tables" Version="12.8.3" />
6566
<PackageReference Include="Azure.Identity" Version="1.11.4" />
6667
<PackageReference Include="Azure.Security.KeyVault.Secrets" Version="4.2.0" />
6768
<PackageReference Include="Microsoft.ApplicationInsights" Version="2.22.0" />

src/WebJobs.Script/runtimeassemblies.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@
1111
"name": "Azure.Core",
1212
"resolutionPolicy": "private"
1313
},
14+
{
15+
"name": "Azure.Data.Tables",
16+
"resolutionPolicy": "private"
17+
},
1418
{
1519
"name": "Azure.Identity",
1620
"resolutionPolicy": "private"

0 commit comments

Comments
 (0)