Skip to content

Commit 5891f6c

Browse files
committed
Moving logging to defined methods in DiagnosticEventsTableStorageRepository. Disabling service when the client is not authorized to use the table storage service.
1 parent deb8616 commit 5891f6c

File tree

4 files changed

+137
-18
lines changed

4 files changed

+137
-18
lines changed
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the MIT License. See License.txt in the project root for license information.
3+
4+
using System;
5+
using Microsoft.Extensions.Logging;
6+
7+
namespace Microsoft.Azure.WebJobs.Script.WebHost.Diagnostics
8+
{
9+
public partial class DiagnosticEventTableStorageRepository
10+
{
11+
private static class Logger
12+
{
13+
private static readonly Action<ILogger, Exception> _serviceDisabledFailedToCreateClient =
14+
LoggerMessage.Define(LogLevel.Warning, new EventId(1, nameof(ServiceDisabledFailedToCreateClient)), "An error occurred initializing the Table Storage Client. We are unable to record diagnostic events, so the diagnostic logging service is being stopped.");
15+
16+
private static readonly Action<ILogger, Exception> _serviceDisabledUnauthorizedClient =
17+
LoggerMessage.Define(LogLevel.Warning, new EventId(2, nameof(ServiceDisabledUnauthorizedClient)), "The Table Storage Client is not authorized to access the table storage account. We are unable to record diagnostic events, so the diagnostic logging service is being stopped.");
18+
19+
private static readonly Action<ILogger, string, Exception> _purgingDiagnosticEvents =
20+
LoggerMessage.Define<string>(LogLevel.Debug, new EventId(3, nameof(PurgingDiagnosticEvents)), "Purging diagnostic events with versions older than '{currentEventVersion}'.");
21+
22+
private static readonly Action<ILogger, string, Exception> _deletingTableWithoutEventVersion =
23+
LoggerMessage.Define<string>(LogLevel.Debug, new EventId(4, nameof(DeletingTableWithoutEventVersion)), "Deleting table '{tableName}' as it contains records without an EventVersion.");
24+
25+
private static readonly Action<ILogger, string, Exception> _deletingTableWithOutdatedEventVersion =
26+
LoggerMessage.Define<string>(LogLevel.Debug, new EventId(5, nameof(DeletingTableWithOutdatedEventVersion)), "Deleting table '{tableName}' as it contains records with an outdated EventVersion.");
27+
28+
private static readonly Action<ILogger, Exception> _errorPurgingDiagnosticEventVersions =
29+
LoggerMessage.Define(LogLevel.Error, new EventId(6, nameof(ErrorPurgingDiagnosticEventVersions)), "Error occurred when attempting to purge previous diagnostic event versions.");
30+
31+
private static readonly Action<ILogger, Exception> _unableToGetTableReference =
32+
LoggerMessage.Define(LogLevel.Error, new EventId(7, nameof(UnableToGetTableReference)), "Unable to get table reference. Aborting write operation.");
33+
34+
private static readonly Action<ILogger, Exception> _unableToGetTableReferenceOrCreateTable =
35+
LoggerMessage.Define(LogLevel.Error, new EventId(8, nameof(UnableToGetTableReferenceOrCreateTable)), "Unable to get table reference or create table. Aborting write operation.");
36+
37+
private static readonly Action<ILogger, Exception> _unableToWriteDiagnosticEvents =
38+
LoggerMessage.Define(LogLevel.Error, new EventId(9, nameof(UnableToWriteDiagnosticEvents)), "Unable to write diagnostic events to table storage.");
39+
40+
private static readonly Action<ILogger, Exception> _primaryHostStateProviderNotAvailable =
41+
LoggerMessage.Define(LogLevel.Debug, new EventId(10, nameof(PrimaryHostStateProviderNotAvailable)), "PrimaryHostStateProvider is not available. Skipping the check for primary host.");
42+
43+
private static readonly Action<ILogger, Exception> _stoppingFlushLogsTimer =
44+
LoggerMessage.Define(LogLevel.Information, new EventId(11, nameof(StoppingFlushLogsTimer)), "Stopping the flush logs timer.");
45+
46+
private static readonly Action<ILogger, Exception> _queueingBackgroundTablePurge =
47+
LoggerMessage.Define(LogLevel.Debug, new EventId(12, nameof(QueueingBackgroundTablePurge)), "Queueing background table purge.");
48+
49+
public static void ServiceDisabledFailedToCreateClient(ILogger logger) => _serviceDisabledFailedToCreateClient(logger, null);
50+
51+
public static void ServiceDisabledUnauthorizedClient(ILogger logger, Exception exception) => _serviceDisabledUnauthorizedClient(logger, exception);
52+
53+
public static void PurgingDiagnosticEvents(ILogger logger, string currentEventVersion) => _purgingDiagnosticEvents(logger, currentEventVersion, null);
54+
55+
public static void DeletingTableWithoutEventVersion(ILogger logger, string tableName) => _deletingTableWithoutEventVersion(logger, tableName, null);
56+
57+
public static void DeletingTableWithOutdatedEventVersion(ILogger logger, string tableName) => _deletingTableWithOutdatedEventVersion(logger, tableName, null);
58+
59+
public static void ErrorPurgingDiagnosticEventVersions(ILogger<DiagnosticEventTableStorageRepository> logger, Exception exception) => _errorPurgingDiagnosticEventVersions(logger, exception);
60+
61+
public static void UnableToGetTableReference(ILogger logger) => _unableToGetTableReference(logger, null);
62+
63+
public static void UnableToGetTableReferenceOrCreateTable(ILogger logger, Exception exception) => _unableToGetTableReferenceOrCreateTable(logger, exception);
64+
65+
public static void UnableToWriteDiagnosticEvents(ILogger logger, Exception exception) => _unableToWriteDiagnosticEvents(logger, exception);
66+
67+
public static void PrimaryHostStateProviderNotAvailable(ILogger logger) => _primaryHostStateProviderNotAvailable(logger, null);
68+
69+
public static void StoppingFlushLogsTimer(ILogger logger) => _stoppingFlushLogsTimer(logger, null);
70+
71+
public static void QueueingBackgroundTablePurge(ILogger logger) => _queueingBackgroundTablePurge(logger, null);
72+
}
73+
}
74+
}

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

Lines changed: 47 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using System.Linq;
88
using System.Threading;
99
using System.Threading.Tasks;
10+
using Azure;
1011
using Azure.Data.Tables;
1112
using Microsoft.Azure.WebJobs.Host.Executors;
1213
using Microsoft.Azure.WebJobs.Hosting;
@@ -17,7 +18,7 @@
1718

1819
namespace Microsoft.Azure.WebJobs.Script.WebHost.Diagnostics
1920
{
20-
public class DiagnosticEventTableStorageRepository : IDiagnosticEventRepository, IDisposable
21+
public partial class DiagnosticEventTableStorageRepository : IDiagnosticEventRepository, IDisposable
2122
{
2223
internal const string TableNamePrefix = "AzureFunctionsDiagnosticEvents";
2324
private const int LogFlushInterval = 1000 * 60 * 10; // 10 minutes
@@ -59,11 +60,33 @@ internal TableServiceClient TableClient
5960
{
6061
get
6162
{
62-
if (!_environment.IsPlaceholderModeEnabled() && _tableClient == null && !_azureTableStorageProvider.TryCreateHostingTableServiceClient(out _tableClient))
63+
if (!_environment.IsPlaceholderModeEnabled() && _tableClient == null)
6364
{
64-
_logger.LogWarning("An error occurred initializing the Table Storage Client. We are unable to record diagnostic events, so the diagnostic logging service is being stopped.");
65-
_isEnabled = false;
66-
StopTimer();
65+
if (!_azureTableStorageProvider.TryCreateHostingTableServiceClient(out _tableClient))
66+
{
67+
DisableService();
68+
Logger.ServiceDisabledFailedToCreateClient(_logger);
69+
return _tableClient;
70+
}
71+
72+
try
73+
{
74+
// When using RBAC, we need "Storage Table Data Contributor" as we require to list, create and delete tables and query/insert/delete entities.
75+
// Testing permissions by listing tables and creating a test table.
76+
var testTable = _tableClient.GetTableClient($"{TableNamePrefix}Check");
77+
_ = TableStorageHelpers.TableExist(testTable, _tableClient);
78+
_ = testTable.CreateIfNotExists();
79+
}
80+
catch (RequestFailedException ex) when (ex.Status == 403)
81+
{
82+
DisableService();
83+
Logger.ServiceDisabledUnauthorizedClient(_logger, ex);
84+
}
85+
catch (Exception)
86+
{
87+
// Other exceptions might be due to conflict issues or other transient errors
88+
// which don't necessarily indicate a permissions problem
89+
}
6790
}
6891

6992
return _tableClient;
@@ -84,6 +107,13 @@ internal string HostId
84107

85108
internal ConcurrentDictionary<string, DiagnosticEvent> Events => _events;
86109

110+
private void DisableService()
111+
{
112+
_isEnabled = false;
113+
StopTimer();
114+
_events.Clear();
115+
}
116+
87117
internal TableClient GetDiagnosticEventsTable(DateTime? now = null)
88118
{
89119
if (TableClient != null)
@@ -114,7 +144,7 @@ protected internal virtual async void OnFlushLogs(object state)
114144

115145
private async Task PurgePreviousEventVersions()
116146
{
117-
_logger.LogDebug("Purging diagnostic events with versions older than '{currentEventVersion}'.", DiagnosticEvent.CurrentEventVersion);
147+
Logger.PurgingDiagnosticEvents(_logger, DiagnosticEvent.CurrentEventVersion);
118148

119149
bool tableDeleted = false;
120150

@@ -133,7 +163,7 @@ await Utility.InvokeWithRetriesAsync(async () =>
133163
// Delete table if it doesn't have records with EventVersion
134164
if (string.IsNullOrEmpty(record.EventVersion) == true)
135165
{
136-
_logger.LogDebug("Deleting table '{tableName}' as it contains records without an EventVersion.", table.Name);
166+
Logger.DeletingTableWithoutEventVersion(_logger, table.Name);
137167
await table.DeleteAsync();
138168
tableDeleted = true;
139169
break;
@@ -142,7 +172,7 @@ await Utility.InvokeWithRetriesAsync(async () =>
142172
// If the table does have EventVersion, query if it is an outdated version
143173
if (string.Compare(DiagnosticEvent.CurrentEventVersion, record.EventVersion, StringComparison.Ordinal) > 0)
144174
{
145-
_logger.LogDebug("Deleting table '{tableName}' as it contains records with an outdated EventVersion.", table.Name);
175+
Logger.DeletingTableWithOutdatedEventVersion(_logger, table.Name);
146176
await table.DeleteAsync();
147177
tableDeleted = true;
148178
break;
@@ -154,7 +184,7 @@ await Utility.InvokeWithRetriesAsync(async () =>
154184
}
155185
catch (Exception ex)
156186
{
157-
_logger.LogError(ex, "Error occurred when attempting to purge previous diagnostic event versions.");
187+
Logger.ErrorPurgingDiagnosticEventVersions(_logger, ex);
158188
}
159189
}, maxRetries: 5, retryInterval: TimeSpan.FromSeconds(5));
160190

@@ -170,7 +200,7 @@ internal virtual async Task FlushLogs(TableClient table = null)
170200
// TableClient is initialized lazily and it will stop the timer that schedules flush logs whenever it fails to initialize.
171201
// We need to check if the TableClient is null before proceeding. This helps when the first time the property is accessed is as part of the FlushLogs method.
172202
// We should not have any events stored pending to be written since WriteDiagnosticEvent will check for an initialized TableClient.
173-
if (_environment.IsPlaceholderModeEnabled() || TableClient is null)
203+
if (_environment.IsPlaceholderModeEnabled() || TableClient is null || !IsEnabled())
174204
{
175205
return;
176206
}
@@ -186,21 +216,21 @@ internal virtual async Task FlushLogs(TableClient table = null)
186216

187217
if (table == null)
188218
{
189-
_logger.LogError("Unable to get table reference. Aborting write operation.");
190-
StopTimer();
219+
Logger.UnableToGetTableReference(_logger);
220+
DisableService();
191221
return;
192222
}
193223

194224
bool tableCreated = await TableStorageHelpers.CreateIfNotExistsAsync(table, TableClient, TableCreationMaxRetryCount);
195225
if (tableCreated)
196226
{
197-
_logger.LogDebug("Queueing background table purge.");
227+
Logger.QueueingBackgroundTablePurge(_logger);
198228
TableStorageHelpers.QueueBackgroundTablePurge(table, TableClient, TableNamePrefix, _logger);
199229
}
200230
}
201231
catch (Exception ex)
202232
{
203-
_logger.LogError(ex, "Unable to get table reference or create table. Aborting write operation.");
233+
Logger.UnableToGetTableReferenceOrCreateTable(_logger, ex);
204234
// Clearing the memory cache to avoid memory build up.
205235
_events.Clear();
206236
return;
@@ -234,7 +264,7 @@ internal async Task ExecuteBatchAsync(ConcurrentDictionary<string, DiagnosticEve
234264
}
235265
catch (Exception ex)
236266
{
237-
_logger.LogError(ex, "Unable to write diagnostic events to table storage.");
267+
Logger.UnableToWriteDiagnosticEvents(_logger, ex);
238268
}
239269
}
240270

@@ -275,7 +305,7 @@ private bool IsPrimaryHost()
275305
var primaryHostStateProvider = _serviceProvider?.GetService<IPrimaryHostStateProvider>();
276306
if (primaryHostStateProvider is null)
277307
{
278-
_logger.LogDebug("PrimaryHostStateProvider is not available. Skipping the check for primary host.");
308+
Logger.PrimaryHostStateProviderNotAvailable(_logger);
279309
return false;
280310
}
281311

@@ -284,7 +314,7 @@ private bool IsPrimaryHost()
284314

285315
private void StopTimer()
286316
{
287-
_logger.LogInformation("Stopping the flush logs timer.");
317+
Logger.StoppingFlushLogsTimer(_logger);
288318
_flushLogsTimer?.Change(Timeout.Infinite, Timeout.Infinite);
289319
}
290320

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,8 @@ internal static async Task<IEnumerable<TableClient>> ListOldTablesAsync(TableCli
9696

9797
internal static async Task<IEnumerable<TableClient>> ListTablesAsync(TableServiceClient tableClient, string tableNamePrefix)
9898
{
99+
ArgumentNullException.ThrowIfNull(tableClient, nameof(tableClient));
100+
99101
// Azure.Data.Tables doesn't have a direct way to list tables with a prefix so we need to do it manually
100102
var givenValue = tableNamePrefix + "{";
101103
AsyncPageable<TableItem> tablesQuery = tableClient.QueryAsync(p => p.Name.CompareTo(tableNamePrefix) >= 0 && p.Name.CompareTo(givenValue) <= 0);
@@ -120,5 +122,17 @@ internal static async Task<bool> TableExistAsync(TableClient table, TableService
120122

121123
return false;
122124
}
125+
126+
internal static bool TableExist(TableClient table, TableServiceClient tableClient)
127+
{
128+
var query = tableClient.Query(p => p.Name == table.Name);
129+
130+
foreach (var item in query)
131+
{
132+
return true;
133+
}
134+
135+
return false;
136+
}
123137
}
124138
}

test/WebJobs.Script.Tests.Integration/Diagnostics/DiagnosticEventTableStorageRepositoryTests.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,8 @@ public void GetDiagnosticEventsTable_LogsError_StorageConnectionStringIsNotPrese
164164
var cloudTable = repository.GetDiagnosticEventsTable(dateTime);
165165
Assert.Null(cloudTable);
166166
var messages = _loggerProvider.GetAllLogMessages();
167-
Assert.Equal(messages[0].FormattedMessage, "An error occurred initializing the Table Storage Client. We are unable to record diagnostic events, so the diagnostic logging service is being stopped.");
167+
var errorIntializingPresent = messages.Any(m => m.FormattedMessage.Contains( "An error occurred initializing the Table Storage Client. We are unable to record diagnostic events, so the diagnostic logging service is being stopped."));
168+
Assert.True(errorIntializingPresent);
168169
Assert.False(repository.IsEnabled());
169170
}
170171

0 commit comments

Comments
 (0)