Skip to content

Commit 98a1944

Browse files
authored
Handle an uninitialised appender (#900)
1 parent ecddb51 commit 98a1944

File tree

2 files changed

+72
-5
lines changed

2 files changed

+72
-5
lines changed

apis/Google.Cloud.Logging.V2/Google.Cloud.Logging.Log4Net.Tests/Log4NetTest.cs

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,6 @@ private void LogInfo(IEnumerable<string> messages)
5959
private class LoggingErrorException : Exception
6060
{
6161
public LoggingErrorException(string message) : base(message) { }
62-
public LoggingErrorException(string message, Exception e) : base(message, e) { }
6362
}
6463

6564
private class ThrowingErrorHandler : IErrorHandler
@@ -71,12 +70,12 @@ public void Error(string message)
7170

7271
public void Error(string message, Exception e)
7372
{
74-
throw new LoggingErrorException(message, e);
73+
throw e;
7574
}
7675

7776
public void Error(string message, Exception e, ErrorCode errorCode)
7877
{
79-
throw new LoggingErrorException($"{message} [errorCode={errorCode}]", e);
78+
throw e;
8079
}
8180
}
8281

@@ -178,6 +177,44 @@ private string TimeOfs(int secondsOfs) =>
178177
(s_time0 + TimeSpan.FromSeconds(secondsOfs))
179178
.ToString(GoogleStackdriverAppender.s_lostDateTimeFmt);
180179

180+
[Fact]
181+
public async Task UninitialisedBehaviour()
182+
{
183+
var fakeClient = new Mock<LoggingServiceV2Client>(MockBehavior.Strict);
184+
var appender = new GoogleStackdriverAppender(fakeClient.Object, new NoDelayScheduler(), new FakeClock())
185+
{
186+
ErrorHandler = new ThrowingErrorHandler(),
187+
Layout = new PatternLayout { ConversionPattern = "%message" },
188+
ProjectId = s_projectId,
189+
LogId = s_logId,
190+
};
191+
// ActivateOptions() has not been called, so appending throws.
192+
Assert.Throws<InvalidOperationException>(() => appender.DoAppend(CreateLog(Level.Info, "Message0")));
193+
Assert.Throws<InvalidOperationException>(() => appender.Flush(TimeSpan.FromSeconds(1)));
194+
await Assert.ThrowsAsync<InvalidOperationException>(() => appender.FlushAsync(TimeSpan.FromSeconds(1)));
195+
// But close and dispose can be called fine.
196+
// They silently do nothing if the appender is not activated.
197+
appender.Close();
198+
appender.Dispose();
199+
}
200+
201+
[Fact]
202+
public void UninitialisedGced()
203+
{
204+
var fakeClient = new Mock<LoggingServiceV2Client>(MockBehavior.Strict);
205+
var appender = new GoogleStackdriverAppender(fakeClient.Object, new NoDelayScheduler(), new FakeClock())
206+
{
207+
ErrorHandler = new ThrowingErrorHandler(),
208+
Layout = new PatternLayout { ConversionPattern = "%message" },
209+
ProjectId = s_projectId,
210+
LogId = s_logId,
211+
};
212+
appender = null;
213+
// This should not cause an error on AppenderSkeleton finalization.
214+
GC.Collect();
215+
GC.WaitForPendingFinalizers();
216+
}
217+
181218
[Fact]
182219
public async Task SingleLogEntry()
183220
{

apis/Google.Cloud.Logging.V2/Google.Cloud.Logging.Log4Net/GoogleStackdriverAppender.cs

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ internal GoogleStackdriverAppender(LoggingServiceV2Client client,
115115
private ILogQueue _logQ;
116116
private LogUploader _logUploader;
117117
private Task<long?> _initIdTask;
118+
private bool _isActivated;
118119
private long _currentId = -1; // Initialised here, in case Flush() is called before any log entries written.
119120

120121
private readonly List<Label> _resourceLabels = new List<Label>();
@@ -192,6 +193,7 @@ public override void ActivateOptions()
192193
_client, _scheduler, _clock,
193194
_logQ, logsLostWarningEntry, MaxUploadBatchSize,
194195
serverErrorBackoffSettings);
196+
_isActivated = true;
195197
}
196198

197199
private void ActivateLogIdAndResource()
@@ -383,13 +385,21 @@ private void Write(IEnumerable<LogEntry> logEntries)
383385
/// <inheritdoc/>
384386
protected override void Append(LoggingEvent loggingEvent)
385387
{
388+
if (!_isActivated)
389+
{
390+
throw new InvalidOperationException($"{nameof(ActivateOptions)}() must be called before using this appender.");
391+
}
386392
var entries = new[] { BuildLogEntry(loggingEvent) };
387393
Write(entries);
388394
}
389395

390396
/// <inheritdoc/>
391397
protected override void Append(LoggingEvent[] loggingEvents)
392398
{
399+
if (!_isActivated)
400+
{
401+
throw new InvalidOperationException($"{nameof(ActivateOptions)}() must be called before using this appender.");
402+
}
393403
var entries = loggingEvents.Select(x => BuildLogEntry(x)).ToList();
394404
Write(entries);
395405
}
@@ -403,6 +413,10 @@ protected override void Append(LoggingEvent[] loggingEvents)
403413
/// <returns>A task representing whether the flush completed within the timeout.</returns>
404414
public Task<bool> FlushAsync(TimeSpan timeout, CancellationToken cancellationToken = default(CancellationToken))
405415
{
416+
if (!_isActivated)
417+
{
418+
throw new InvalidOperationException($"{nameof(ActivateOptions)}() must be called before using this appender.");
419+
}
406420
long untilId;
407421
lock (_lock)
408422
{
@@ -418,8 +432,14 @@ protected override void Append(LoggingEvent[] loggingEvents)
418432
/// <param name="cancellationToken">The token to monitor for cancellation requests.
419433
/// The default value is <see cref="CancellationToken.None"/>.</param>
420434
/// <returns>Whether the flush completed within the timeout.</returns>
421-
public bool Flush(TimeSpan timeout, CancellationToken cancellationToken = default(CancellationToken)) =>
422-
Task.Run(async () => await FlushAsync(timeout, cancellationToken).ConfigureAwait(false)).Result;
435+
public bool Flush(TimeSpan timeout, CancellationToken cancellationToken = default(CancellationToken))
436+
{
437+
if (!_isActivated)
438+
{
439+
throw new InvalidOperationException($"{nameof(ActivateOptions)}() must be called before using this appender.");
440+
}
441+
return Task.Run(async () => await FlushAsync(timeout, cancellationToken).ConfigureAwait(false)).Result;
442+
}
423443

424444
/// <summary>
425445
/// Dispose of this appender, by flushing locally buffer entries then closing the appender.
@@ -430,6 +450,11 @@ protected override void Append(LoggingEvent[] loggingEvents)
430450
/// </remarks>
431451
public void Dispose()
432452
{
453+
if (!_isActivated)
454+
{
455+
// Appender not activated, so Dispose is a nop.
456+
return;
457+
}
433458
bool flushSucceeded = Flush(TimeSpan.FromSeconds(DisposeTimeoutSeconds));
434459
if (!flushSucceeded)
435460
{
@@ -442,6 +467,11 @@ public void Dispose()
442467
protected override void OnClose()
443468
{
444469
base.OnClose();
470+
if (!_isActivated)
471+
{
472+
// Appender not activated, so Close is a nop.
473+
return;
474+
}
445475
_logUploader.Close();
446476
}
447477
}

0 commit comments

Comments
 (0)