-
Notifications
You must be signed in to change notification settings - Fork 454
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Showing
10 changed files
with
390 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
24 changes: 24 additions & 0 deletions
24
src/WebJobs.Script.WebHost/Diagnostics/DeferredLogEntry.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
// Copyright (c) .NET Foundation. All rights reserved. | ||
// Licensed under the MIT License. See License.txt in the project root for license information. | ||
|
||
using System; | ||
using System.Collections.Generic; | ||
using Microsoft.Extensions.Logging; | ||
|
||
namespace Microsoft.Azure.WebJobs.Script.WebHost.Diagnostics | ||
{ | ||
public struct DeferredLogEntry | ||
{ | ||
public LogLevel LogLevel { get; set; } | ||
|
||
public string Category { get; set; } | ||
|
||
public string Message { get; set; } | ||
|
||
public Exception Exception { get; set; } | ||
|
||
public EventId EventId { get; set; } | ||
|
||
public List<object> ScopeStorage { get; set; } | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
// Copyright (c) .NET Foundation. All rights reserved. | ||
// Licensed under the MIT License. See License.txt in the project root for license information. | ||
|
||
using System; | ||
using System.Collections.Generic; | ||
using System.Threading.Channels; | ||
using Microsoft.Extensions.Logging; | ||
|
||
namespace Microsoft.Azure.WebJobs.Script.WebHost.Diagnostics | ||
{ | ||
/// <summary> | ||
/// A logger that defers log entries to a channel. | ||
/// </summary> | ||
public class DeferredLogger : ILogger | ||
{ | ||
private readonly Channel<DeferredLogEntry> _channel; | ||
private readonly string _categoryName; | ||
private readonly IExternalScopeProvider _scopeProvider; | ||
private readonly IEnvironment _environment; | ||
|
||
// Cached placeholder mode flag | ||
private bool _isPlaceholderModeDisabled = false; | ||
|
||
public DeferredLogger(Channel<DeferredLogEntry> channel, string categoryName, IExternalScopeProvider scopeProvider, IEnvironment environment) | ||
{ | ||
_channel = channel; | ||
_categoryName = categoryName; | ||
_scopeProvider = scopeProvider; | ||
_environment = environment; | ||
} | ||
|
||
public IDisposable BeginScope<TState>(TState state) => _scopeProvider.Push(state); | ||
|
||
// Restrict logging to errors only for now, as we are seeing a lot of unnecessary logs. | ||
// https://github.com/Azure/azure-functions-host/issues/10556 | ||
public bool IsEnabled(LogLevel logLevel) => logLevel >= LogLevel.Error; | ||
|
||
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter) | ||
{ | ||
// Skip logging if it's not enabled or placeholder mode is enabled | ||
if (!IsEnabled(logLevel)) | ||
{ | ||
return; | ||
} | ||
|
||
// Only check IsPlaceholderModeEnabled if it hasn't been disabled | ||
if (!_isPlaceholderModeDisabled && _environment.IsPlaceholderModeEnabled()) | ||
{ | ||
return; | ||
} | ||
|
||
// Cache the fact that placeholder mode is disabled | ||
_isPlaceholderModeDisabled = true; | ||
|
||
string formattedMessage = formatter?.Invoke(state, exception); | ||
if (string.IsNullOrEmpty(formattedMessage)) | ||
{ | ||
return; | ||
} | ||
|
||
var log = new DeferredLogEntry | ||
{ | ||
LogLevel = logLevel, | ||
Category = _categoryName, | ||
Message = formattedMessage, | ||
Exception = exception, | ||
EventId = eventId | ||
}; | ||
|
||
// Persist the scope state so it can be reapplied in the original order when forwarding logs to the logging provider. | ||
_scopeProvider.ForEachScope((scope, state) => | ||
{ | ||
state.ScopeStorage ??= new List<object>(); | ||
state.ScopeStorage.Add(scope); | ||
}, log); | ||
|
||
_channel.Writer.TryWrite(log); | ||
} | ||
} | ||
} |
124 changes: 124 additions & 0 deletions
124
src/WebJobs.Script.WebHost/Diagnostics/DeferredLoggerProvider.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
// Copyright (c) .NET Foundation. All rights reserved. | ||
// Licensed under the MIT License. See License.txt in the project root for license information. | ||
|
||
using System; | ||
using System.Collections.Generic; | ||
using System.Threading.Channels; | ||
using System.Threading.Tasks; | ||
using Microsoft.Extensions.Logging; | ||
using Microsoft.Extensions.Logging.Abstractions; | ||
|
||
namespace Microsoft.Azure.WebJobs.Script.WebHost.Diagnostics | ||
{ | ||
public sealed class DeferredLoggerProvider : ILoggerProvider, ISupportExternalScope | ||
{ | ||
private readonly Channel<DeferredLogEntry> _channel = Channel.CreateBounded<DeferredLogEntry>(new BoundedChannelOptions(150) | ||
{ | ||
FullMode = BoundedChannelFullMode.DropOldest, | ||
// Avoids locks and interlocked operations when reading from the channel. | ||
SingleReader = true, | ||
SingleWriter = false | ||
}); | ||
|
||
private readonly TimeSpan _deferredLogDelay = TimeSpan.FromSeconds(10); | ||
private readonly IEnvironment _environment; | ||
private IExternalScopeProvider _scopeProvider; | ||
private bool _isEnabled = true; | ||
|
||
public DeferredLoggerProvider(IEnvironment environment) | ||
{ | ||
_environment = environment; | ||
} | ||
|
||
public int Count => _channel.Reader.Count; | ||
|
||
public ILogger CreateLogger(string categoryName) | ||
{ | ||
return _isEnabled ? new DeferredLogger(_channel, categoryName, _scopeProvider, _environment) : NullLogger.Instance; | ||
} | ||
|
||
public void ProcessBufferedLogs(IReadOnlyCollection<ILoggerProvider> forwardingProviders, bool runImmediately = false) | ||
{ | ||
// Forward all buffered logs to the new provider | ||
Task.Run(async () => | ||
{ | ||
if (!runImmediately) | ||
{ | ||
// Wait for 10 seconds, this will increase the probability of these logs appearing in live metrics. | ||
await Task.Delay(_deferredLogDelay); | ||
} | ||
|
||
// Disable the channel and let the consumer know that there won't be any more logs. | ||
_isEnabled = false; | ||
_channel.Writer.TryComplete(); | ||
|
||
try | ||
{ | ||
if (forwardingProviders is null || forwardingProviders.Count == 0) | ||
{ | ||
// No providers, just drain the messages without logging | ||
while (_channel.Reader.TryRead(out _)) | ||
{ | ||
// Drain the channel | ||
} | ||
} | ||
|
||
await foreach (var log in _channel.Reader.ReadAllAsync()) | ||
{ | ||
foreach (var forwardingProvider in forwardingProviders) | ||
{ | ||
var logger = forwardingProvider.CreateLogger(log.Category); | ||
List<IDisposable> scopes = null; | ||
|
||
try | ||
{ | ||
// Create a scope for each object in ScopeObject | ||
if (log.ScopeStorage?.Count > 0) | ||
{ | ||
scopes ??= new List<IDisposable>(); | ||
foreach (var scope in log.ScopeStorage) | ||
{ | ||
// Create and store each scope | ||
scopes.Add(logger.BeginScope(scope)); | ||
} | ||
} | ||
|
||
// Log the message | ||
logger.Log(log.LogLevel, log.EventId, log.Exception, log.Message); | ||
} | ||
finally | ||
{ | ||
if (scopes is not null) | ||
{ | ||
// Dispose all scopes in reverse order to properly unwind them | ||
for (int i = scopes.Count - 1; i >= 0; i--) | ||
{ | ||
scopes[i].Dispose(); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} | ||
catch (Exception ex) | ||
{ | ||
if (ex.IsFatal()) | ||
{ | ||
throw; | ||
} | ||
} | ||
}); | ||
} | ||
|
||
public void SetScopeProvider(IExternalScopeProvider scopeProvider) | ||
{ | ||
_scopeProvider = scopeProvider; | ||
} | ||
|
||
public void Dispose() | ||
{ | ||
_isEnabled = false; | ||
_channel.Writer.TryComplete(); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -61,4 +61,4 @@ public void SetScopeProvider(IExternalScopeProvider scopeProvider) | |
_scopeProvider = scopeProvider; | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.