Skip to content

Commit c812b41

Browse files
committed
Fixing null reference exception when AzureWebJobsStorage is not set. (#9641)
1 parent 6760b11 commit c812b41

File tree

5 files changed

+84
-27
lines changed

5 files changed

+84
-27
lines changed

build/common.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<LangVersion>latest</LangVersion>
66
<MajorVersion>4</MajorVersion>
77
<MinorVersion>27</MinorVersion>
8-
<PatchVersion>5</PatchVersion>
8+
<PatchVersion>7</PatchVersion>
99
<BuildNumber Condition="'$(BuildNumber)' == '' ">0</BuildNumber>
1010
<PreviewVersion></PreviewVersion>
1111

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
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 System.Collections.Concurrent;
6+
using System.Threading;
7+
using System.Threading.Tasks;
8+
using Microsoft.Azure.Cosmos.Table;
9+
using Microsoft.Azure.WebJobs.Host.Executors;
10+
using Microsoft.Azure.WebJobs.Script.WebHost.Helpers;
11+
using Microsoft.Extensions.Configuration;
12+
using Microsoft.Extensions.Logging;
13+
14+
namespace Microsoft.Azure.WebJobs.Script.WebHost.Diagnostics
15+
{
16+
public class DiagnosticEventNullRepository : IDiagnosticEventRepository
17+
{
18+
public void WriteDiagnosticEvent(DateTime timestamp, string errorCode, LogLevel level, string message, string helpLink, Exception exception) { }
19+
}
20+
}

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

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

44
using System;
5+
using Microsoft.Extensions.Configuration;
56
using Microsoft.Extensions.DependencyInjection;
7+
using Microsoft.Extensions.Logging;
68

79
namespace Microsoft.Azure.WebJobs.Script.WebHost.Diagnostics
810
{
911
public class DiagnosticEventRepositoryFactory : IDiagnosticEventRepositoryFactory
1012
{
1113
private readonly IServiceProvider _serviceProvider;
14+
private readonly IConfiguration _configuration;
1215

13-
public DiagnosticEventRepositoryFactory(IServiceProvider serviceProvider)
16+
public DiagnosticEventRepositoryFactory(IServiceProvider serviceProvider, IConfiguration configuration)
1417
{
1518
_serviceProvider = serviceProvider;
19+
_configuration = configuration;
1620
}
1721

1822
public IDiagnosticEventRepository Create()
1923
{
2024
// Using this to break ciruclar dependency for ILoggers. Typically you cannot log errors within the logging pipeline because it creates infinte loop.
2125
// However in this case that loop is broken because of the filtering in the DiagnosticEventLogger
22-
return _serviceProvider.GetRequiredService<IDiagnosticEventRepository>();
26+
27+
string storageConnectionString = _configuration.GetWebJobsConnectionString(ConnectionStringNames.Storage);
28+
if (string.IsNullOrEmpty(storageConnectionString))
29+
{
30+
return new DiagnosticEventNullRepository();
31+
}
32+
else
33+
{
34+
return _serviceProvider.GetRequiredService<IDiagnosticEventRepository>();
35+
}
2336
}
2437
}
2538
}

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

Lines changed: 27 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -53,16 +53,16 @@ internal CloudTableClient TableClient
5353
if (!_environment.IsPlaceholderModeEnabled() && _tableClient == null)
5454
{
5555
string storageConnectionString = _configuration.GetWebJobsConnectionString(ConnectionStringNames.Storage);
56-
if (string.IsNullOrEmpty(storageConnectionString))
57-
{
58-
_logger.LogError("Azure Storage connection string is empty or invalid. Unable to write diagnostic events.");
59-
}
60-
61-
if (CloudStorageAccount.TryParse(storageConnectionString, out CloudStorageAccount account))
56+
if (!string.IsNullOrEmpty(storageConnectionString)
57+
&& CloudStorageAccount.TryParse(storageConnectionString, out CloudStorageAccount account))
6258
{
6359
var tableClientConfig = new TableClientConfiguration();
6460
_tableClient = new CloudTableClient(account.TableStorageUri, account.Credentials, tableClientConfig);
6561
}
62+
else
63+
{
64+
_logger.LogError("Azure Storage connection string is empty or invalid. Unable to write diagnostic events.");
65+
}
6666
}
6767

6868
return _tableClient;
@@ -124,10 +124,17 @@ internal virtual async Task FlushLogs(CloudTable table = null)
124124
return;
125125
}
126126

127-
table = table ?? GetDiagnosticEventsTable();
128-
129127
try
130128
{
129+
table = table ?? GetDiagnosticEventsTable();
130+
131+
if (table == null)
132+
{
133+
_logger.LogError("Unable to get table reference. Aborting write operation");
134+
StopTimer();
135+
return;
136+
}
137+
131138
bool tableCreated = await TableStorageHelpers.CreateIfNotExistsAsync(table, _tableCreationRetries);
132139
if (tableCreated)
133140
{
@@ -136,11 +143,13 @@ internal virtual async Task FlushLogs(CloudTable table = null)
136143
}
137144
catch (Exception ex)
138145
{
139-
_logger.LogError(ex, $"Unable to create table '{table.Name}' after {_tableCreationRetries} retries. Aborting write operation {ex}");
140-
146+
_logger.LogError(ex, $"Unable to get table reference or create table. Aborting write operation.");
147+
return;
148+
}
149+
finally
150+
{
141151
// Clearing the memory cache to avoid memory build up.
142152
_events.Clear();
143-
return;
144153
}
145154

146155
// Assigning a new empty directory to reset the event count in the new duration window.
@@ -174,9 +183,8 @@ internal async Task ExecuteBatchAsync(ConcurrentDictionary<string, DiagnosticEve
174183

175184
public void WriteDiagnosticEvent(DateTime timestamp, string errorCode, LogLevel level, string message, string helpLink, Exception exception)
176185
{
177-
if (string.IsNullOrEmpty(HostId))
186+
if (TableClient == null || string.IsNullOrEmpty(HostId))
178187
{
179-
_logger.LogError("Unable to write diagnostic events. Host id is set to null.");
180188
return;
181189
}
182190

@@ -200,6 +208,12 @@ public void WriteDiagnosticEvent(DateTime timestamp, string errorCode, LogLevel
200208
}
201209
}
202210

211+
internal void StopTimer()
212+
{
213+
_logger.LogInformation("Stopping the flush logs timer");
214+
_flushLogsTimer?.Change(Timeout.Infinite, Timeout.Infinite);
215+
}
216+
203217
protected virtual void Dispose(bool disposing)
204218
{
205219
if (!_disposed)

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

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ public void WriteDiagnostic_LogsError_whenHostIdNotSet()
7373
repository.WriteDiagnosticEvent(DateTime.UtcNow, "eh1", LogLevel.Information, "This is the message", "https://fwlink/", new Exception("exception message"));
7474

7575
var messages = _loggerProvider.GetAllLogMessages();
76-
Assert.Equal(messages[0].FormattedMessage, "Unable to write diagnostic events. Host id is set to null.");
76+
Assert.Equal(0, repository.Events.Values.Count());
7777
}
7878

7979
[Fact]
@@ -195,25 +195,35 @@ await TestHelpers.Await(async () =>
195195
[Fact]
196196
public async Task FlushLogs_LogsErrorAndClearsEvents_WhenTableCreatingFails()
197197
{
198-
// Clear events by flush logs if table creation attempts fail
198+
// Arrange
199199
IEnvironment testEnvironment = new TestEnvironment();
200200
testEnvironment.SetEnvironmentVariable(EnvironmentSettingNames.AzureWebsitePlaceholderMode, "0");
201201

202-
DiagnosticEventTableStorageRepository repository =
203-
new DiagnosticEventTableStorageRepository(_configuration, _hostIdProvider, testEnvironment, _logger);
202+
var testData = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
203+
{
204+
{ "AzureWebJobsStorage", null }
205+
};
204206

205-
var tableClient = repository.TableClient;
206-
var cloudTable = tableClient.GetTableReference("aa");
207+
var configuration = new ConfigurationBuilder()
208+
.AddEnvironmentVariables()
209+
.AddInMemoryCollection(testData)
210+
.Build();
207211

208-
repository.WriteDiagnosticEvent(DateTime.UtcNow, "eh1", LogLevel.Information, "This is the message", "https://fwlink/", new Exception("exception message"));
212+
DiagnosticEventTableStorageRepository repository =
213+
new DiagnosticEventTableStorageRepository(configuration, _hostIdProvider, testEnvironment, _logger);
209214

210-
Assert.Equal(1, repository.Events.Values.Count());
215+
// Act
216+
repository.WriteDiagnosticEvent(DateTime.UtcNow, "eh1", LogLevel.Information, "This is the message", "https://fwlink/", new Exception("exception message"));
217+
await repository.FlushLogs();
211218

212-
await repository.FlushLogs(cloudTable);
219+
// Assert
220+
var logMessage = _loggerProvider.GetAllLogMessages().SingleOrDefault(m => m.FormattedMessage.Contains("Unable to get table reference"));
221+
Assert.NotNull(logMessage);
213222

223+
var messagePresent = _loggerProvider.GetAllLogMessages().Any(m => m.FormattedMessage.Contains("Azure Storage connection string is empty or invalid. Unable to write diagnostic events."));
224+
Assert.True(messagePresent);
225+
214226
Assert.Equal(0, repository.Events.Values.Count());
215-
var logMessage = _loggerProvider.GetAllLogMessages()[0];
216-
Assert.True(logMessage.FormattedMessage.StartsWith("Unable to create table 'aa'"));
217227
}
218228

219229
[Fact]

0 commit comments

Comments
 (0)