-
Notifications
You must be signed in to change notification settings - Fork 246
A logger for Azure WebApps #468
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
// Copyright (c) .NET Foundation. All rights reserved. | ||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. | ||
|
||
using System; | ||
|
||
namespace Microsoft.Extensions.Logging.Abstractions.Internal | ||
{ | ||
public class NullLogger : ILogger | ||
{ | ||
public static NullLogger Instance { get; } = new NullLogger(); | ||
|
||
private NullLogger() | ||
{ | ||
} | ||
|
||
public IDisposable BeginScope<TState>(TState state) | ||
{ | ||
return NullScope.Instance; | ||
} | ||
|
||
public bool IsEnabled(LogLevel logLevel) | ||
{ | ||
return false; | ||
} | ||
|
||
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter) | ||
{ | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
// Copyright (c) .NET Foundation. All rights reserved. | ||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. | ||
|
||
namespace Microsoft.Extensions.Logging.Abstractions.Internal | ||
{ | ||
public class NullLoggerProvider : ILoggerProvider | ||
{ | ||
public static NullLoggerProvider Instance { get; } = new NullLoggerProvider(); | ||
|
||
private NullLoggerProvider() | ||
{ | ||
} | ||
|
||
public ILogger CreateLogger(string categoryName) | ||
{ | ||
return NullLogger.Instance; | ||
} | ||
|
||
public void Dispose() | ||
{ | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
// Copyright (c) .NET Foundation. All rights reserved. | ||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. | ||
|
||
using System; | ||
|
||
namespace Microsoft.Extensions.Logging.Abstractions.Internal | ||
{ | ||
/// <summary> | ||
/// An empty scope without any logic | ||
/// </summary> | ||
public class NullScope : IDisposable | ||
{ | ||
public static NullScope Instance { get; } = new NullScope(); | ||
|
||
private NullScope() | ||
{ | ||
} | ||
|
||
/// <inheritdoc /> | ||
public void Dispose() | ||
{ | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
// Copyright (c) .NET Foundation. All rights reserved. | ||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. | ||
|
||
using Microsoft.Extensions.Logging.AzureWebAppDiagnostics; | ||
using Microsoft.Extensions.Logging.AzureWebAppDiagnostics.Internal; | ||
|
||
namespace Microsoft.Extensions.Logging | ||
{ | ||
/// <summary> | ||
/// Extension methods for <see cref="AzureWebAppDiagnosticsLoggerProvider"/>. | ||
/// </summary> | ||
public static class AzureWebAppDiagnosticsFactoryExtensions | ||
{ | ||
/// <summary> | ||
/// Adds an Azure Web Apps diagnostics logger. | ||
/// </summary> | ||
/// <param name="factory">The extension method argument</param> | ||
/// <param name="fileSizeLimitMb">A strictly positive value representing the maximum log size in megabytes. Once the log is full, no more message will be appended</param> | ||
public static ILoggerFactory AddAzureWebAppDiagnostics(this ILoggerFactory factory, int fileSizeLimitMb = FileLoggerProvider.DefaultFileSizeLimitMb) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The code configuration will be addressed in #470 |
||
{ | ||
if (WebAppContext.Default.IsRunningInAzureWebApp) | ||
{ | ||
// Only add the provider if we're in Azure WebApp. That cannot change once the apps started | ||
factory.AddProvider(new AzureWebAppDiagnosticsLoggerProvider(WebAppContext.Default, fileSizeLimitMb)); | ||
} | ||
return factory; | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
// Copyright (c) .NET Foundation. All rights reserved. | ||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. | ||
|
||
using Microsoft.Extensions.Logging.Abstractions.Internal; | ||
using Microsoft.Extensions.Logging.AzureWebAppDiagnostics.Internal; | ||
|
||
namespace Microsoft.Extensions.Logging.AzureWebAppDiagnostics | ||
{ | ||
/// <summary> | ||
/// Logger provider for Azure WebApp. | ||
/// </summary> | ||
public class AzureWebAppDiagnosticsLoggerProvider : ILoggerProvider | ||
{ | ||
private readonly IWebAppLogConfigurationReader _configurationReader; | ||
|
||
private readonly ILoggerProvider _innerLoggerProvider; | ||
private readonly bool _runningInWebApp; | ||
|
||
/// <summary> | ||
/// Creates a new instance of the <see cref="AzureWebAppDiagnosticsLoggerProvider"/> class. | ||
/// </summary> | ||
public AzureWebAppDiagnosticsLoggerProvider(WebAppContext context, int fileSizeLimitMb) | ||
{ | ||
_configurationReader = new WebAppLogConfigurationReader(context); | ||
|
||
var config = _configurationReader.Current; | ||
_runningInWebApp = config.IsRunningInWebApp; | ||
|
||
if (!_runningInWebApp) | ||
{ | ||
_innerLoggerProvider = NullLoggerProvider.Instance; | ||
} | ||
else | ||
{ | ||
_innerLoggerProvider = new FileLoggerProvider(_configurationReader, fileSizeLimitMb); | ||
|
||
if (!string.IsNullOrEmpty(config.BlobContainerUrl)) | ||
{ | ||
// TODO: Add the blob logger by creating a composite inner logger which calls | ||
// both loggers | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Might be good to reference another ticket somewhere for the TODO. |
||
} | ||
} | ||
} | ||
|
||
/// <inheritdoc /> | ||
public ILogger CreateLogger(string categoryName) | ||
{ | ||
return _innerLoggerProvider.CreateLogger(categoryName); | ||
} | ||
|
||
/// <inheritdoc /> | ||
public void Dispose() | ||
{ | ||
_innerLoggerProvider.Dispose(); | ||
_configurationReader.Dispose(); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
// Copyright (c) .NET Foundation. All rights reserved. | ||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. | ||
|
||
using System; | ||
using System.Collections.Concurrent; | ||
using System.Threading; | ||
using Serilog.Core; | ||
using Serilog.Events; | ||
|
||
// TODO: Might want to consider using https://github.com/jezzsantos/Serilog.Sinks.Async | ||
// instead of this, once that supports netstandard | ||
namespace Microsoft.Extensions.Logging.AzureWebAppDiagnostics.Internal | ||
{ | ||
/// <summary> | ||
/// A background sink for Serilog. | ||
/// </summary> | ||
public class BackgroundSink : ILogEventSink, IDisposable | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I just spiked this up a few days ago as well: https://github.com/jezzsantos/Serilog.Sinks.Async/blob/master/src/Serilog.Sinks.Async/AsyncWorkerSink.cs - given there's a high probability of either Serilog.Sinks.Async, or eventually Serilog itself, shipping a type equivalent to this, it may be better There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We have this convention that anything in the |
||
{ | ||
/// <summary> | ||
/// The default queue size. | ||
/// </summary> | ||
public const int DefaultLogMessagesQueueSize = 1024; | ||
|
||
private readonly CancellationTokenSource _disposedTokenSource = new CancellationTokenSource(); | ||
private readonly CancellationToken _disposedToken; | ||
|
||
private readonly BlockingCollection<LogEvent> _messages; | ||
private readonly Thread _workerThread; | ||
|
||
private ILogEventSink _innerSink; | ||
|
||
/// <summary> | ||
/// Creates a new instance of the <see cref="BackgroundSink"/> class. | ||
/// </summary> | ||
/// <param name="innerSink">The inner sink which does the actual logging</param> | ||
/// <param name="maxQueueSize">The maximum size of the background queue</param> | ||
public BackgroundSink(ILogEventSink innerSink, int? maxQueueSize) | ||
{ | ||
if (innerSink == null) | ||
{ | ||
throw new ArgumentNullException(nameof(innerSink)); | ||
} | ||
|
||
_disposedToken = _disposedTokenSource.Token; | ||
|
||
if (maxQueueSize == null || maxQueueSize <= 0) | ||
{ | ||
_messages = new BlockingCollection<LogEvent>(new ConcurrentQueue<LogEvent>()); | ||
} | ||
else | ||
{ | ||
_messages = new BlockingCollection<LogEvent>(new ConcurrentQueue<LogEvent>(), maxQueueSize.Value); | ||
} | ||
|
||
_innerSink = innerSink; | ||
|
||
_workerThread = new Thread(Worker); | ||
_workerThread.Name = GetType().Name; | ||
_workerThread.IsBackground = true; | ||
_workerThread.Start(); | ||
} | ||
|
||
/// <inheritdoc /> | ||
public void Emit(LogEvent logEvent) | ||
{ | ||
if (!_disposedToken.IsCancellationRequested) | ||
{ | ||
_messages.Add(logEvent); | ||
} | ||
} | ||
|
||
/// <summary> | ||
/// Disposes this object instance. | ||
/// </summary> | ||
public virtual void Dispose() | ||
{ | ||
lock (_disposedTokenSource) | ||
{ | ||
if (!_disposedTokenSource.IsCancellationRequested) | ||
{ | ||
_disposedTokenSource.Cancel(); | ||
} | ||
|
||
// Wait for the thread to complete before disposing the resources | ||
_workerThread.Join(5 /*seconds */ * 1000); | ||
_messages.Dispose(); | ||
} | ||
} | ||
|
||
private void Worker() | ||
{ | ||
try | ||
{ | ||
foreach (var logEvent in _messages.GetConsumingEnumerable(_disposedToken)) | ||
{ | ||
PassLogEventToInnerSink(logEvent); | ||
} | ||
} | ||
catch (OperationCanceledException) | ||
{ | ||
// Do nothing, we just cancelled the task | ||
} | ||
} | ||
|
||
private void PassLogEventToInnerSink(LogEvent logEvent) | ||
{ | ||
_innerSink.Emit(logEvent); | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can cache
NullLoggerProvider
instance insideNullLoggerProvider
, it's enough to have one singleton.