diff --git a/src/Microsoft.Extensions.Logging.AzureWebAppDiagnostics/AzureWebAppDiagnosticsFactoryExtensions.cs b/src/Microsoft.Extensions.Logging.AzureWebAppDiagnostics/AzureWebAppDiagnosticsFactoryExtensions.cs
index d1a09a85..0124877d 100644
--- a/src/Microsoft.Extensions.Logging.AzureWebAppDiagnostics/AzureWebAppDiagnosticsFactoryExtensions.cs
+++ b/src/Microsoft.Extensions.Logging.AzureWebAppDiagnostics/AzureWebAppDiagnosticsFactoryExtensions.cs
@@ -15,13 +15,22 @@ public static class AzureWebAppDiagnosticsFactoryExtensions
/// Adds an Azure Web Apps diagnostics logger.
///
/// The extension method argument
- /// A strictly positive value representing the maximum log size in megabytes. Once the log is full, no more message will be appended
- public static ILoggerFactory AddAzureWebAppDiagnostics(this ILoggerFactory factory, int fileSizeLimitMb = FileLoggerProvider.DefaultFileSizeLimitMb)
+ public static ILoggerFactory AddAzureWebAppDiagnostics(this ILoggerFactory factory)
+ {
+ return AddAzureWebAppDiagnostics(factory, new AzureWebAppDiagnosticsSettings());
+ }
+
+ ///
+ /// Adds an Azure Web Apps diagnostics logger.
+ ///
+ /// The extension method argument
+ /// The setting object to configure loggers.
+ public static ILoggerFactory AddAzureWebAppDiagnostics(this ILoggerFactory factory, AzureWebAppDiagnosticsSettings settings)
{
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));
+ factory.AddProvider(new AzureWebAppDiagnosticsLoggerProvider(WebAppContext.Default, settings));
}
return factory;
}
diff --git a/src/Microsoft.Extensions.Logging.AzureWebAppDiagnostics/AzureWebAppDiagnosticsLoggerProvider.cs b/src/Microsoft.Extensions.Logging.AzureWebAppDiagnostics/AzureWebAppDiagnosticsLoggerProvider.cs
index 8029af32..4ee51c27 100644
--- a/src/Microsoft.Extensions.Logging.AzureWebAppDiagnostics/AzureWebAppDiagnosticsLoggerProvider.cs
+++ b/src/Microsoft.Extensions.Logging.AzureWebAppDiagnostics/AzureWebAppDiagnosticsLoggerProvider.cs
@@ -1,8 +1,11 @@
// 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 Microsoft.Extensions.Logging.Abstractions;
+using Microsoft.Extensions.Logging.Abstractions.Internal;
using Microsoft.Extensions.Logging.AzureWebAppDiagnostics.Internal;
+using Serilog;
namespace Microsoft.Extensions.Logging.AzureWebAppDiagnostics
{
@@ -13,31 +16,44 @@ public class AzureWebAppDiagnosticsLoggerProvider : ILoggerProvider
{
private readonly IWebAppLogConfigurationReader _configurationReader;
- private readonly ILoggerProvider _innerLoggerProvider;
- private readonly bool _runningInWebApp;
+ private readonly LoggerFactory _loggerFactory;
///
/// Creates a new instance of the class.
///
- public AzureWebAppDiagnosticsLoggerProvider(WebAppContext context, int fileSizeLimitMb)
+ public AzureWebAppDiagnosticsLoggerProvider(WebAppContext context, AzureWebAppDiagnosticsSettings settings)
{
+ if (settings == null)
+ {
+ throw new ArgumentNullException(nameof(settings));
+ }
+
_configurationReader = new WebAppLogConfigurationReader(context);
var config = _configurationReader.Current;
- _runningInWebApp = config.IsRunningInWebApp;
+ var runningInWebApp = config.IsRunningInWebApp;
- if (!_runningInWebApp)
- {
- _innerLoggerProvider = NullLoggerProvider.Instance;
- }
- else
+ if (runningInWebApp)
{
- _innerLoggerProvider = new FileLoggerProvider(_configurationReader, fileSizeLimitMb);
+ _loggerFactory = new LoggerFactory();
+ var fileLoggerProvider = new FileLoggerProvider(
+ settings.FileSizeLimit,
+ settings.RetainedFileCountLimit,
+ settings.BackgroundQueueSize,
+ settings.OutputTemplate);
+ _loggerFactory.AddSerilog(fileLoggerProvider.ConfigureLogger(_configurationReader));
if (!string.IsNullOrEmpty(config.BlobContainerUrl))
{
- // TODO: Add the blob logger by creating a composite inner logger which calls
- // both loggers
+ var blobLoggerProvider = new AzureBlobLoggerProvider(
+ settings.OutputTemplate,
+ context.SiteName,
+ context.SiteInstanceId,
+ settings.BlobName,
+ settings.BlobBatchSize,
+ settings.BackgroundQueueSize,
+ settings.BlobCommitPeriod);
+ _loggerFactory.AddSerilog(blobLoggerProvider.ConfigureLogger(_configurationReader));
}
}
}
@@ -45,13 +61,13 @@ public AzureWebAppDiagnosticsLoggerProvider(WebAppContext context, int fileSizeL
///
public ILogger CreateLogger(string categoryName)
{
- return _innerLoggerProvider.CreateLogger(categoryName);
+ return _loggerFactory?.CreateLogger(categoryName) ?? NullLogger.Instance;
}
///
public void Dispose()
{
- _innerLoggerProvider.Dispose();
+ _loggerFactory?.Dispose();
_configurationReader.Dispose();
}
}
diff --git a/src/Microsoft.Extensions.Logging.AzureWebAppDiagnostics/AzureWebAppDiagnosticsSettings.cs b/src/Microsoft.Extensions.Logging.AzureWebAppDiagnostics/AzureWebAppDiagnosticsSettings.cs
new file mode 100644
index 00000000..3ee7e71f
--- /dev/null
+++ b/src/Microsoft.Extensions.Logging.AzureWebAppDiagnostics/AzureWebAppDiagnosticsSettings.cs
@@ -0,0 +1,46 @@
+// 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.AzureWebAppDiagnostics
+{
+ ///
+ /// Settings for .
+ ///
+ public class AzureWebAppDiagnosticsSettings
+ {
+ ///
+ /// Gets or sets a strictly positive value representing the maximum log size in bytes. Once the log is full, no more message will be appended.
+ ///
+ public int FileSizeLimit { get; set; } = 10 * 1024 * 1024;
+
+ ///
+ /// Gets or sets a strictly positive value representing the maximum retained file count.
+ ///
+ public int RetainedFileCountLimit { get; set; } = 2;
+
+ ///
+ /// Gets or sets a message template describing the output messages.
+ ///
+ public string OutputTemplate { get; set; } = "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level}] {Message}{NewLine}{Exception}";
+
+ ///
+ /// Gets or sets a maximum number of events to include in a single blob append batch.
+ ///
+ public int BlobBatchSize { get; set; } = 32;
+
+ ///
+ /// Gets or sets a time to wait between checking for blob log batches.
+ ///
+ public TimeSpan BlobCommitPeriod { get; set; } = TimeSpan.FromSeconds(5);
+
+ ///
+ /// Gets or sets the last section of log blob name.
+ ///
+ public string BlobName { get; set; } = "applicationLog.txt";
+
+ /// Gets of sets the maximum size of the background log message queue.
+ public int BackgroundQueueSize { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.Extensions.Logging.AzureWebAppDiagnostics/Internal/AzureBlobLoggerProvider.cs b/src/Microsoft.Extensions.Logging.AzureWebAppDiagnostics/Internal/AzureBlobLoggerProvider.cs
new file mode 100644
index 00000000..07ac3eea
--- /dev/null
+++ b/src/Microsoft.Extensions.Logging.AzureWebAppDiagnostics/Internal/AzureBlobLoggerProvider.cs
@@ -0,0 +1,99 @@
+// 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 Serilog;
+using Serilog.Core;
+using Serilog.Formatting.Display;
+using Microsoft.WindowsAzure.Storage.Blob;
+
+namespace Microsoft.Extensions.Logging.AzureWebAppDiagnostics.Internal
+{
+ ///
+ /// The implemenation of logger provider that creates instances of .
+ ///
+ public class AzureBlobLoggerProvider
+ {
+ private readonly string _outputTemplate;
+ private readonly string _appName;
+ private readonly string _instanceId;
+ private readonly string _fileName;
+ private readonly int _batchSize;
+ private readonly int _backgroundQueueSize;
+ private readonly TimeSpan _period;
+
+ ///
+ /// Creates a new instance of the class.
+ ///
+ /// A message template describing the output messages
+ /// The application name to use in blob name
+ /// The application instance id to use in blob name
+ /// The last section in log blob name
+ /// A maximum number of events to include in a single blob append batch
+ /// The maximum size of the background queue
+ /// A time to wait between checking for blob log batches
+ public AzureBlobLoggerProvider(string outputTemplate, string appName, string instanceId, string fileName, int batchSize, int backgroundQueueSize, TimeSpan period)
+ {
+ if (outputTemplate == null)
+ {
+ throw new ArgumentNullException(nameof(outputTemplate));
+ }
+ if (appName == null)
+ {
+ throw new ArgumentNullException(nameof(appName));
+ }
+ if (instanceId == null)
+ {
+ throw new ArgumentNullException(nameof(instanceId));
+ }
+ if (fileName == null)
+ {
+ throw new ArgumentNullException(nameof(fileName));
+ }
+ if (batchSize <= 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(batchSize), $"{nameof(batchSize)} should be a positive number.");
+ }
+ if (period <= TimeSpan.Zero)
+ {
+ throw new ArgumentOutOfRangeException(nameof(period), $"{nameof(period)} should be longer than zero.");
+ }
+
+ _outputTemplate = outputTemplate;
+ _appName = appName;
+ _instanceId = instanceId;
+ _fileName = fileName;
+ _batchSize = batchSize;
+ _backgroundQueueSize = backgroundQueueSize;
+ _period = period;
+ }
+
+ ///
+ public Logger ConfigureLogger(IWebAppLogConfigurationReader reader)
+ {
+ var messageFormatter = new MessageTemplateTextFormatter(_outputTemplate, null);
+ var container = new CloudBlobContainer(new Uri(reader.Current.BlobContainerUrl));
+ var fileName = _instanceId + "-" + _fileName;
+ var azureBlobSink = new AzureBlobSink(
+ name => new BlobAppendReferenceWrapper(container.GetAppendBlobReference(name)),
+ _appName,
+ fileName,
+ messageFormatter,
+ _batchSize,
+ _period);
+
+ var backgroundSink = new BackgroundSink(azureBlobSink, _backgroundQueueSize);
+ var loggerConfiguration = new LoggerConfiguration();
+
+ loggerConfiguration.WriteTo.Sink(backgroundSink);
+ loggerConfiguration.MinimumLevel.ControlledBy(new WebConfigurationReaderLevelSwitch(reader,
+ configuration =>
+ {
+ return configuration.BlobLoggingEnabled ? configuration.BlobLoggingLevel : LogLevel.None;
+ }));
+
+ return loggerConfiguration.CreateLogger();
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.Extensions.Logging.AzureWebAppDiagnostics/Internal/AzureBlobSink.cs b/src/Microsoft.Extensions.Logging.AzureWebAppDiagnostics/Internal/AzureBlobSink.cs
new file mode 100644
index 00000000..cb137ba8
--- /dev/null
+++ b/src/Microsoft.Extensions.Logging.AzureWebAppDiagnostics/Internal/AzureBlobSink.cs
@@ -0,0 +1,111 @@
+// 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.Generic;
+using System.IO;
+using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.WindowsAzure.Storage;
+using Serilog.Core;
+using Serilog.Events;
+using Serilog.Formatting;
+using Serilog.Sinks.PeriodicBatching;
+
+namespace Microsoft.Extensions.Logging.AzureWebAppDiagnostics.Internal
+{
+ ///
+ /// The implemenation that stores messages by appending them to Azure Blob in batches.
+ ///
+ public class AzureBlobSink : PeriodicBatchingSink
+ {
+ private readonly string _appName;
+ private readonly string _fileName;
+ private readonly ITextFormatter _formatter;
+ private readonly Func _blobReferenceFactory;
+
+ ///
+ /// Creates a new instance of
+ ///
+ /// The container to store logs to.
+ /// The application name to use in blob path generation.
+ /// The last segment of blob name.
+ /// The for log messages.
+ /// The maximum number of events to include in a single batch.
+ /// The time to wait between checking for event batches.
+ public AzureBlobSink(Func blobReferenceFactory,
+ string appName,
+ string fileName,
+ ITextFormatter formatter,
+ int batchSizeLimit,
+ TimeSpan period) : base(batchSizeLimit, period)
+ {
+ if (appName == null)
+ {
+ throw new ArgumentNullException(nameof(appName));
+ }
+ if (fileName == null)
+ {
+ throw new ArgumentNullException(nameof(fileName));
+ }
+ if (formatter == null)
+ {
+ throw new ArgumentNullException(nameof(formatter));
+ }
+ if (batchSizeLimit <= 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(batchSizeLimit), $"{nameof(batchSizeLimit)} should be a positive number.");
+ }
+ if (period <= TimeSpan.Zero)
+ {
+ throw new ArgumentOutOfRangeException(nameof(period), $"{nameof(period)} should be longer than zero.");
+ }
+
+ _appName = appName;
+ _fileName = fileName;
+ _formatter = formatter;
+ _blobReferenceFactory = blobReferenceFactory;
+ }
+
+ ///
+ protected override async Task EmitBatchAsync(IEnumerable events)
+ {
+ var eventGroups = events.GroupBy(GetBlobKey);
+ foreach (var eventGroup in eventGroups)
+ {
+ var key = eventGroup.Key;
+ var blobName = $"{_appName}/{key.Item1}/{key.Item2:00}/{key.Item3:00}/{key.Item4:00}/{_fileName}";
+
+ var blob = _blobReferenceFactory(blobName);
+
+ Stream stream;
+ try
+ {
+ stream = await blob.OpenWriteAsync();
+ }
+ // Blob does not exist
+ catch (StorageException ex) when (ex.RequestInformation.HttpStatusCode == 404)
+ {
+ await blob.CreateAsync();
+ stream = await blob.OpenWriteAsync();
+ }
+
+ using (var writer = new StreamWriter(stream))
+ {
+ foreach (var logEvent in eventGroup)
+ {
+ _formatter.Format(logEvent, writer);
+ }
+ }
+ }
+ }
+
+ private Tuple GetBlobKey(LogEvent e)
+ {
+ return Tuple.Create(e.Timestamp.Year,
+ e.Timestamp.Month,
+ e.Timestamp.Day,
+ e.Timestamp.Hour);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.Extensions.Logging.AzureWebAppDiagnostics/Internal/BlobAppendReferenceWrapper.cs b/src/Microsoft.Extensions.Logging.AzureWebAppDiagnostics/Internal/BlobAppendReferenceWrapper.cs
new file mode 100644
index 00000000..8c3bf1d1
--- /dev/null
+++ b/src/Microsoft.Extensions.Logging.AzureWebAppDiagnostics/Internal/BlobAppendReferenceWrapper.cs
@@ -0,0 +1,36 @@
+// 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.IO;
+using System.Threading.Tasks;
+using Microsoft.WindowsAzure.Storage;
+using Microsoft.WindowsAzure.Storage.Blob;
+
+namespace Microsoft.Extensions.Logging.AzureWebAppDiagnostics.Internal
+{
+ ///
+ public class BlobAppendReferenceWrapper : ICloudAppendBlob
+ {
+ private readonly CloudAppendBlob _cloudAppendBlob;
+
+ ///
+ /// Creates new instance of .
+ ///
+ /// The instance to wrap.
+ public BlobAppendReferenceWrapper(CloudAppendBlob cloudAppendBlob)
+ {
+ _cloudAppendBlob = cloudAppendBlob;
+ }
+ ///
+ public async Task OpenWriteAsync()
+ {
+ return await _cloudAppendBlob.OpenWriteAsync(createNew: false);
+ }
+
+ ///
+ public async Task CreateAsync()
+ {
+ await _cloudAppendBlob.CreateOrReplaceAsync(AccessCondition.GenerateIfNotExistsCondition(), options: null, operationContext: null);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.Extensions.Logging.AzureWebAppDiagnostics/Internal/FileLoggerProvider.cs b/src/Microsoft.Extensions.Logging.AzureWebAppDiagnostics/Internal/FileLoggerProvider.cs
index f1a12ff6..10c7241e 100644
--- a/src/Microsoft.Extensions.Logging.AzureWebAppDiagnostics/Internal/FileLoggerProvider.cs
+++ b/src/Microsoft.Extensions.Logging.AzureWebAppDiagnostics/Internal/FileLoggerProvider.cs
@@ -4,69 +4,81 @@
using System;
using System.IO;
using Serilog;
+using Serilog.Core;
using Serilog.Formatting.Display;
using Serilog.Sinks.RollingFile;
namespace Microsoft.Extensions.Logging.AzureWebAppDiagnostics.Internal
{
///
- /// A file logger for Azure WebApp.
+ /// The logger provider that creates instances of .
///
- public class FileLoggerProvider : SerilogLoggerProvider
+ public class FileLoggerProvider
{
- ///
- /// The default file size limit in megabytes
- ///
- public const int DefaultFileSizeLimitMb = 10;
-
- // Two days retention limit is okay because the file logger turns itself off after 12 hours (portal feature)
- private const int RetainedFileCountLimit = 2; // Days (also number of files because we have 1 file/day)
+ private readonly int _fileSizeLimit;
+ private readonly int _retainedFileCountLimit;
+ private readonly int _backgroundQueueSize;
+ private readonly string _outputTemplate;
- private const string OutputTemplate = "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level}] {Message}{NewLine}{Exception}";
private const string FileNamePattern = "diagnostics-{Date}.txt";
///
/// Creates a new instance of the class.
///
- /// A configuration reader
/// A strictly positive value representing the maximum log size in megabytes. Once the log is full, no more message will be appended
- public FileLoggerProvider(IWebAppLogConfigurationReader configReader, int fileSizeLimit)
- : base(configReader, (loggerConfiguration, webAppConfiguration) =>
+ /// A strictly positive value representing the maximum retained file count
+ /// The maximum size of the background queue
+ /// A message template describing the output messages
+ public FileLoggerProvider(int fileSizeLimit, int retainedFileCountLimit, int backgroundQueueSize, string outputTemplate)
+ {
+ if (outputTemplate == null)
{
- if (string.IsNullOrEmpty(webAppConfiguration.FileLoggingFolder))
- {
- throw new ArgumentNullException(nameof(webAppConfiguration.FileLoggingFolder), "The file logger path cannot be null or empty.");
- }
-
- var logsFolder = webAppConfiguration.FileLoggingFolder;
- if (!Directory.Exists(logsFolder))
- {
- Directory.CreateDirectory(logsFolder);
- }
- var logsFilePattern = Path.Combine(logsFolder, FileNamePattern);
-
- var fileSizeLimitBytes = fileSizeLimit * 1024 * 1024;
-
- var messageFormatter = new MessageTemplateTextFormatter(OutputTemplate, null);
- var rollingFileSink = new RollingFileSink(logsFilePattern, messageFormatter, fileSizeLimitBytes, RetainedFileCountLimit);
- var backgroundSink = new BackgroundSink(rollingFileSink, BackgroundSink.DefaultLogMessagesQueueSize);
+ throw new ArgumentNullException(nameof(outputTemplate));
+ }
+ if (fileSizeLimit <= 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(fileSizeLimit), $"{nameof(fileSizeLimit)} should be positive.");
+ }
+ if (retainedFileCountLimit <= 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(retainedFileCountLimit), $"{nameof(retainedFileCountLimit)} should be positive.");
+ }
- loggerConfiguration.WriteTo.Sink(backgroundSink);
- })
- {
+ _fileSizeLimit = fileSizeLimit;
+ _retainedFileCountLimit = retainedFileCountLimit;
+ _backgroundQueueSize = backgroundQueueSize;
+ _outputTemplate = outputTemplate;
}
///
- protected override void OnConfigurationChanged(WebAppLogConfiguration newConfiguration)
+ public Logger ConfigureLogger(IWebAppLogConfigurationReader reader)
{
- if (!newConfiguration.FileLoggingEnabled)
+ var webAppConfiguration = reader.Current;
+ if (string.IsNullOrEmpty(webAppConfiguration.FileLoggingFolder))
{
- LevelSwitcher.MinimumLevel = LogLevelDisabled;
+ throw new ArgumentNullException(nameof(webAppConfiguration.FileLoggingFolder),
+ "The file logger path cannot be null or empty.");
}
- else
+
+ var logsFolder = webAppConfiguration.FileLoggingFolder;
+ if (!Directory.Exists(logsFolder))
{
- LevelSwitcher.MinimumLevel = LogLevelToLogEventLevel(newConfiguration.FileLoggingLevel);
+ Directory.CreateDirectory(logsFolder);
}
+ var logsFilePattern = Path.Combine(logsFolder, FileNamePattern);
+
+ var messageFormatter = new MessageTemplateTextFormatter(_outputTemplate, null);
+ var rollingFileSink = new RollingFileSink(logsFilePattern, messageFormatter, _fileSizeLimit, _retainedFileCountLimit);
+ var backgroundSink = new BackgroundSink(rollingFileSink, _backgroundQueueSize);
+
+ var loggerConfiguration = new LoggerConfiguration();
+ loggerConfiguration.WriteTo.Sink(backgroundSink);
+ loggerConfiguration.MinimumLevel.ControlledBy(new WebConfigurationReaderLevelSwitch(reader,
+ configuration =>
+ {
+ return configuration.FileLoggingEnabled ? configuration.FileLoggingLevel : LogLevel.None;
+ }));
+ return loggerConfiguration.CreateLogger();
}
}
}
diff --git a/src/Microsoft.Extensions.Logging.AzureWebAppDiagnostics/Internal/ICloudAppendBlob.cs b/src/Microsoft.Extensions.Logging.AzureWebAppDiagnostics/Internal/ICloudAppendBlob.cs
new file mode 100644
index 00000000..b03f9920
--- /dev/null
+++ b/src/Microsoft.Extensions.Logging.AzureWebAppDiagnostics/Internal/ICloudAppendBlob.cs
@@ -0,0 +1,26 @@
+// 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.IO;
+using System.Threading.Tasks;
+
+namespace Microsoft.Extensions.Logging.AzureWebAppDiagnostics.Internal
+{
+ ///
+ /// Represents an append blob, a type of blob where blocks of data are always committed to the end of the blob.
+ ///
+ public interface ICloudAppendBlob
+ {
+ ///
+ /// Initiates an asynchronous operation to open a stream for writing to the blob.
+ ///
+ /// A object of type that represents the asynchronous operation.
+ Task OpenWriteAsync();
+
+ ///
+ /// Initiates an asynchronous operation to create an empty append blob.
+ ///
+ /// A object that represents the asynchronous operation.
+ Task CreateAsync();
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.Extensions.Logging.AzureWebAppDiagnostics/Internal/IWebAppContext.cs b/src/Microsoft.Extensions.Logging.AzureWebAppDiagnostics/Internal/IWebAppContext.cs
index 7af58cf3..ee64358a 100644
--- a/src/Microsoft.Extensions.Logging.AzureWebAppDiagnostics/Internal/IWebAppContext.cs
+++ b/src/Microsoft.Extensions.Logging.AzureWebAppDiagnostics/Internal/IWebAppContext.cs
@@ -13,6 +13,16 @@ public interface IWebAppContext
///
string HomeFolder { get; }
+ ///
+ /// Gets the name of site if running in Azure WebApp
+ ///
+ string SiteName { get; }
+
+ ///
+ /// Gets the id of site if running in Azure WebApp
+ ///
+ string SiteInstanceId { get; }
+
///
/// Gets a value indicating whether or new we're in an Azure WebApp
///
diff --git a/src/Microsoft.Extensions.Logging.AzureWebAppDiagnostics/Internal/SerilogLoggerProvider.cs b/src/Microsoft.Extensions.Logging.AzureWebAppDiagnostics/Internal/SerilogLoggerProvider.cs
deleted file mode 100644
index 6316742b..00000000
--- a/src/Microsoft.Extensions.Logging.AzureWebAppDiagnostics/Internal/SerilogLoggerProvider.cs
+++ /dev/null
@@ -1,125 +0,0 @@
-// 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 Serilog;
-using Serilog.Core;
-using Serilog.Events;
-
-namespace Microsoft.Extensions.Logging.AzureWebAppDiagnostics.Internal
-{
- ///
- /// Represents a Serilog logger provider use for Azure WebApp.
- ///
- public abstract class SerilogLoggerProvider : ILoggerProvider
- {
- // Solution suggested by the Serilog creator http://stackoverflow.com/questions/30849166/how-to-turn-off-serilog
- ///
- /// The log level at which the logger is disabled.
- ///
- protected static LogEventLevel LogLevelDisabled = ((LogEventLevel)1 + (int)LogEventLevel.Fatal);
-
- private readonly LoggingLevelSwitch _levelSwitch = new LoggingLevelSwitch();
-
- private readonly IWebAppLogConfigurationReader _configReader;
- private readonly ILoggerFactory _loggerFactory;
-
- ///
- /// Creates a new instance of the class.
- ///
- /// The configuration reader
- /// The actions required to configure the logger
- public SerilogLoggerProvider(IWebAppLogConfigurationReader configReader, Action configureLogger)
- {
- if (configReader == null)
- {
- throw new ArgumentNullException(nameof(configReader));
- }
- if (configureLogger == null)
- {
- throw new ArgumentNullException(nameof(configureLogger));
- }
-
- _configReader = configReader;
- var webAppsConfiguration = configReader.Current;
-
- configReader.OnConfigurationChanged += OnConfigurationChanged;
-
- var loggerConfiguration = new LoggerConfiguration()
- .MinimumLevel.ControlledBy(_levelSwitch);
- configureLogger(loggerConfiguration, webAppsConfiguration);
- var serilogLogger = loggerConfiguration.CreateLogger();
-
- OnConfigurationChanged(webAppsConfiguration);
-
- _loggerFactory = new LoggerFactory();
- _loggerFactory.AddSerilog(serilogLogger);
- }
-
- ///
- /// The switch used the modify the logging level.
- ///
- protected LoggingLevelSwitch LevelSwitcher => _levelSwitch;
-
- ///
- /// Called when the configuration changes
- ///
- /// The new configuration values
- protected abstract void OnConfigurationChanged(WebAppLogConfiguration newConfiguration);
-
- ///
- public ILogger CreateLogger(string categoryName)
- {
- return _loggerFactory.CreateLogger(categoryName);
- }
-
- ///
- /// Disposes this object.
- ///
- public void Dispose()
- {
- _configReader.OnConfigurationChanged -= OnConfigurationChanged;
- _loggerFactory.Dispose();
- }
-
- private void OnConfigurationChanged(object sender, WebAppLogConfiguration newConfiguration)
- {
- OnConfigurationChanged(newConfiguration);
- }
-
- ///
- /// Converts a object to .
- ///
- /// The log level to convert
- /// A instance
- protected static LogEventLevel LogLevelToLogEventLevel(LogLevel logLevel)
- {
- switch (logLevel)
- {
- case LogLevel.Trace:
- return LogEventLevel.Verbose;
-
- case LogLevel.Debug:
- return LogEventLevel.Debug;
-
- case LogLevel.Information:
- return LogEventLevel.Information;
-
- case LogLevel.Warning:
- return LogEventLevel.Warning;
-
- case LogLevel.Error:
- return LogEventLevel.Error;
-
- case LogLevel.Critical:
- return LogEventLevel.Fatal;
-
- case LogLevel.None:
- return LogLevelDisabled;
-
- default:
- throw new ArgumentOutOfRangeException($"Unknown log level: {logLevel}");
- }
- }
- }
-}
diff --git a/src/Microsoft.Extensions.Logging.AzureWebAppDiagnostics/Internal/WebAppContext.cs b/src/Microsoft.Extensions.Logging.AzureWebAppDiagnostics/Internal/WebAppContext.cs
index 4d473dbf..cd4e58c3 100644
--- a/src/Microsoft.Extensions.Logging.AzureWebAppDiagnostics/Internal/WebAppContext.cs
+++ b/src/Microsoft.Extensions.Logging.AzureWebAppDiagnostics/Internal/WebAppContext.cs
@@ -21,14 +21,13 @@ private WebAppContext() { }
public string HomeFolder { get; } = Environment.GetEnvironmentVariable("HOME");
///
- public bool IsRunningInAzureWebApp
- {
- get
- {
- return
- !string.IsNullOrEmpty(HomeFolder) &&
- !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("WEBSITE_SITE_NAME"));
- }
- }
+ public string SiteName { get; } = Environment.GetEnvironmentVariable("WEBSITE_SITE_NAME");
+
+ ///
+ public string SiteInstanceId { get; } = Environment.GetEnvironmentVariable("WEBSITE_INSTANCE_ID");
+
+ ///
+ public bool IsRunningInAzureWebApp => !string.IsNullOrEmpty(HomeFolder) &&
+ !string.IsNullOrEmpty(SiteName);
}
}
diff --git a/src/Microsoft.Extensions.Logging.AzureWebAppDiagnostics/Internal/WebAppLogConfiguration.cs b/src/Microsoft.Extensions.Logging.AzureWebAppDiagnostics/Internal/WebAppLogConfiguration.cs
index 12b7545b..48cf489f 100644
--- a/src/Microsoft.Extensions.Logging.AzureWebAppDiagnostics/Internal/WebAppLogConfiguration.cs
+++ b/src/Microsoft.Extensions.Logging.AzureWebAppDiagnostics/Internal/WebAppLogConfiguration.cs
@@ -14,12 +14,12 @@ public class WebAppLogConfiguration
public static WebAppLogConfiguration Disabled { get; } = new WebAppLogConfigurationBuilder().Build();
internal WebAppLogConfiguration(
- bool isRunningInWebApp,
+ bool isRunningInWebApp,
bool fileLoggingEnabled,
- LogLevel fileLoggingLevel,
+ LogLevel fileLoggingLevel,
string fileLoggingFolder,
- bool blobLoggingEnabled,
- LogLevel blobLoggingLevel,
+ bool blobLoggingEnabled,
+ LogLevel blobLoggingLevel,
string blobContainerUrl)
{
IsRunningInWebApp = isRunningInWebApp;
diff --git a/src/Microsoft.Extensions.Logging.AzureWebAppDiagnostics/Internal/WebAppLogConfigurationReader.cs b/src/Microsoft.Extensions.Logging.AzureWebAppDiagnostics/Internal/WebAppLogConfigurationReader.cs
index db519f9e..95a31205 100644
--- a/src/Microsoft.Extensions.Logging.AzureWebAppDiagnostics/Internal/WebAppLogConfigurationReader.cs
+++ b/src/Microsoft.Extensions.Logging.AzureWebAppDiagnostics/Internal/WebAppLogConfigurationReader.cs
@@ -79,10 +79,7 @@ private void OnConfigurationTokenChange(object state)
ReloadConfiguration();
SubscribeToConfigurationChangeEvent();
- if (OnConfigurationChanged != null)
- {
- OnConfigurationChanged(this, _latestConfiguration);
- }
+ OnConfigurationChanged?.Invoke(this, _latestConfiguration);
}
private void SubscribeToConfigurationChangeEvent()
diff --git a/src/Microsoft.Extensions.Logging.AzureWebAppDiagnostics/Internal/WebConfigurationReaderLevelSwitch.cs b/src/Microsoft.Extensions.Logging.AzureWebAppDiagnostics/Internal/WebConfigurationReaderLevelSwitch.cs
new file mode 100644
index 00000000..f4710d4d
--- /dev/null
+++ b/src/Microsoft.Extensions.Logging.AzureWebAppDiagnostics/Internal/WebConfigurationReaderLevelSwitch.cs
@@ -0,0 +1,69 @@
+// 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 Serilog.Core;
+using Serilog.Events;
+
+namespace Microsoft.Extensions.Logging.AzureWebAppDiagnostics.Internal
+{
+ ///
+ /// The implementation that runs callback
+ /// when event fires.
+ ///
+ public class WebConfigurationReaderLevelSwitch : LoggingLevelSwitch
+ {
+ ///
+ /// The log level at which the logger is disabled.
+ ///
+ private static readonly LogEventLevel LogLevelDisabled = LogEventLevel.Fatal + 1;
+
+ ///
+ /// Creates a new instance of the class.
+ ///
+ ///
+ ///
+ public WebConfigurationReaderLevelSwitch(IWebAppLogConfigurationReader reader, Func convert )
+ {
+ reader.OnConfigurationChanged += (sender, configuration) =>
+ {
+ MinimumLevel = LogLevelToLogEventLevel(convert(configuration));
+ };
+ }
+
+ ///
+ /// Converts a object to .
+ ///
+ /// The log level to convert
+ /// A instance
+ private static LogEventLevel LogLevelToLogEventLevel(LogLevel logLevel)
+ {
+ switch (logLevel)
+ {
+ case LogLevel.Trace:
+ return LogEventLevel.Verbose;
+
+ case LogLevel.Debug:
+ return LogEventLevel.Debug;
+
+ case LogLevel.Information:
+ return LogEventLevel.Information;
+
+ case LogLevel.Warning:
+ return LogEventLevel.Warning;
+
+ case LogLevel.Error:
+ return LogEventLevel.Error;
+
+ case LogLevel.Critical:
+ return LogEventLevel.Fatal;
+
+ case LogLevel.None:
+ return LogLevelDisabled;
+
+ default:
+ throw new ArgumentOutOfRangeException($"Unknown log level: {logLevel}");
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.Extensions.Logging.AzureWebAppDiagnostics/project.json b/src/Microsoft.Extensions.Logging.AzureWebAppDiagnostics/project.json
index af5a6079..3e9b7ce8 100644
--- a/src/Microsoft.Extensions.Logging.AzureWebAppDiagnostics/project.json
+++ b/src/Microsoft.Extensions.Logging.AzureWebAppDiagnostics/project.json
@@ -17,7 +17,9 @@
"Microsoft.Extensions.Logging": "1.1.0-*",
"Microsoft.Extensions.Logging.Abstractions": "1.1.0-*",
"Serilog.Extensions.Logging": "1.0.0",
- "Serilog.Sinks.RollingFile": "2.1.0"
+ "Serilog.Sinks.RollingFile": "2.1.0",
+ "Serilog.Sinks.PeriodicBatching": "2.0.0",
+ "WindowsAzure.Storage": "7.2.0"
},
"frameworks": {
"net451": {
@@ -31,7 +33,8 @@
"dependencies": {
"System.Console": "4.0.0-*",
"System.Threading.Thread": "4.0.0-*"
- }
+ },
+ "imports": [ "portable-net40+sl5+win8+wp8+wpa81", "portable-net45+win8+wp8+wpa81" ]
}
}
}
\ No newline at end of file
diff --git a/test/Microsoft.Extensions.Logging.AzureWebAppDiagnostics.Test/AzureBlobSinkTests.cs b/test/Microsoft.Extensions.Logging.AzureWebAppDiagnostics.Test/AzureBlobSinkTests.cs
new file mode 100644
index 00000000..8431c205
--- /dev/null
+++ b/test/Microsoft.Extensions.Logging.AzureWebAppDiagnostics.Test/AzureBlobSinkTests.cs
@@ -0,0 +1,181 @@
+// 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.Generic;
+using System.Globalization;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Logging.AzureWebAppDiagnostics.Internal;
+using Microsoft.WindowsAzure.Storage;
+using Moq;
+using Serilog;
+using Serilog.Core;
+using Serilog.Events;
+using Serilog.Formatting.Display;
+using Serilog.Parsing;
+using Xunit;
+
+namespace Microsoft.Extensions.Logging.AzureWebApps.Test
+{
+ public class AzureBlobSinkTests
+ {
+ private static readonly TimeSpan DefaultTimeout = TimeSpan.FromSeconds(10);
+
+ [Fact]
+ public void WritesMessagesInBatches()
+ {
+ var blob = new Mock();
+ var buffers = new List();
+ blob.Setup(b => b.OpenWriteAsync()).Returns(() => Task.FromResult((Stream)new TestMemoryStream(buffers)));
+
+ var sink = new TestAzureBlobSink(name => blob.Object, 5);
+ var logger = CreateLogger(sink);
+
+ for (int i = 0; i < 5; i++)
+ {
+ logger.Information("Text " + i);
+ }
+
+ Assert.True(sink.CountdownEvent.Wait(DefaultTimeout));
+
+#if NET451
+ Assert.Equal(1, buffers.Count);
+ Assert.Equal(Encoding.UTF8.GetString(buffers[0]), @"Information Text 0
+Information Text 1
+Information Text 2
+Information Text 3
+Information Text 4
+");
+#else
+ // PeriodicBatchingSink always writes first message as seperate batch on coreclr
+ // https://github.com/serilog/serilog-sinks-periodicbatching/issues/7
+ Assert.Equal(2, buffers.Count);
+ Assert.Equal(Encoding.UTF8.GetString(buffers[0]), @"Information Text 0" + Environment.NewLine);
+ Assert.Equal(Encoding.UTF8.GetString(buffers[1]), @"Information Text 1
+Information Text 2
+Information Text 3
+Information Text 4
+");
+#endif
+ }
+
+ [Fact]
+ public void GroupsByHour()
+ {
+ var blob = new Mock();
+ var buffers = new List();
+ var names = new List();
+
+ blob.Setup(b => b.OpenWriteAsync()).Returns(() => Task.FromResult((Stream)new TestMemoryStream(buffers)));
+
+ var sink = new TestAzureBlobSink(name =>
+ {
+ names.Add(name);
+ return blob.Object;
+ }, 3);
+ var logger = CreateLogger(sink);
+
+ var startDate = new DateTime(2016, 8, 29, 22, 0, 0);
+ for (int i = 0; i < 3; i++)
+ {
+ var addHours = startDate.AddHours(i);
+ logger.Write(new LogEvent(
+ new DateTimeOffset(addHours),
+ LogEventLevel.Information,
+ null,
+ new MessageTemplate("Text", Enumerable.Empty()),
+ Enumerable.Empty()));
+ }
+
+ Assert.True(sink.CountdownEvent.Wait(DefaultTimeout));
+
+ Assert.Equal(3, buffers.Count);
+
+ Assert.Equal("appname/2016/08/29/22/filename", names[0]);
+ Assert.Equal("appname/2016/08/29/23/filename", names[1]);
+ Assert.Equal("appname/2016/08/30/00/filename", names[2]);
+ }
+
+ [Fact]
+ public void CreatesBlobIfNotExists()
+ {
+ var blob = new Mock();
+ var buffers = new List();
+ bool created = false;
+
+ blob.Setup(b => b.OpenWriteAsync()).Returns(() =>
+ {
+ if (!created)
+ {
+ throw new StorageException(new RequestResult() { HttpStatusCode = 404 }, string.Empty, null);
+ }
+ return Task.FromResult((Stream) new TestMemoryStream(buffers));
+ });
+
+ blob.Setup(b => b.CreateAsync()).Returns(() =>
+ {
+ created = true;
+ return Task.FromResult(0);
+ });
+
+ var sink = new TestAzureBlobSink((name) => blob.Object, 1);
+ var logger = CreateLogger(sink);
+ logger.Information("Text");
+
+ Assert.True(sink.CountdownEvent.Wait(DefaultTimeout));
+
+ Assert.Equal(1, buffers.Count);
+ Assert.Equal(true, created);
+ }
+
+ private static Logger CreateLogger(AzureBlobSink sink)
+ {
+ var loggerConfiguration = new LoggerConfiguration();
+ loggerConfiguration.WriteTo.Sink(sink);
+ var logger = loggerConfiguration.CreateLogger();
+ return logger;
+ }
+
+ private class TestAzureBlobSink: AzureBlobSink
+ {
+ public CountdownEvent CountdownEvent { get; }
+
+ public TestAzureBlobSink(Func blob, int count):base(
+ blob,
+ "appname",
+ "filename",
+ new MessageTemplateTextFormatter("{Level} {Message}{NewLine}", CultureInfo.InvariantCulture),
+ 10,
+ TimeSpan.FromSeconds(0.1))
+ {
+ CountdownEvent = new CountdownEvent(count);
+ }
+
+ protected override async Task EmitBatchAsync(IEnumerable events)
+ {
+ await base.EmitBatchAsync(events);
+ CountdownEvent.Signal(events.Count());
+ }
+ }
+
+ private class TestMemoryStream : MemoryStream
+ {
+ public List Buffers { get; }
+
+ public TestMemoryStream(List buffers)
+ {
+ Buffers = buffers;
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ Buffers.Add(ToArray());
+ base.Dispose(disposing);
+ }
+ }
+ }
+}
diff --git a/test/Microsoft.Extensions.Logging.AzureWebAppDiagnostics.Test/SerilogLoggerProviderTests.cs b/test/Microsoft.Extensions.Logging.AzureWebAppDiagnostics.Test/SerilogLoggerProviderTests.cs
index e7b2bf10..4954f303 100644
--- a/test/Microsoft.Extensions.Logging.AzureWebAppDiagnostics.Test/SerilogLoggerProviderTests.cs
+++ b/test/Microsoft.Extensions.Logging.AzureWebAppDiagnostics.Test/SerilogLoggerProviderTests.cs
@@ -22,11 +22,9 @@ public void OnStartDisable()
// Nothing should be called on this object
var testSink = new Mock(MockBehavior.Strict);
- using (var provider = new TestWebAppSerilogLoggerProvider(testSink.Object, configReader.Object))
- {
- var logger = provider.CreateLogger("TestLogger");
- logger.LogInformation("Test");
- }
+ var provider = new TestWebAppSerilogLoggerProvider(testSink.Object);
+ var logger = provider.ConfigureLogger(configReader.Object);
+ logger.Information("Test");
}
[Fact]
@@ -40,15 +38,13 @@ public void OnStartLoggingLevel()
var configReader = new Mock();
configReader.SetupGet(m => m.Current).Returns(config);
-
+
var testSink = new Mock(MockBehavior.Strict);
testSink.Setup(m => m.Emit(It.IsAny()));
- using (var provider = new TestWebAppSerilogLoggerProvider(testSink.Object, configReader.Object))
- {
- var logger = provider.CreateLogger("TestLogger");
- logger.LogInformation("Test");
- }
+ var provider = new TestWebAppSerilogLoggerProvider(testSink.Object);
+ var logger = provider.ConfigureLogger(configReader.Object);
+ logger.Information("Test");
testSink.Verify(m => m.Emit(It.IsAny()), Times.Once);
}
@@ -70,11 +66,11 @@ public void DynamicDisable()
var testSink = new Mock(MockBehavior.Strict);
testSink.Setup(m => m.Emit(It.IsAny()));
- using (var provider = new TestWebAppSerilogLoggerProvider(testSink.Object, configReader.Object))
+ var provider = new TestWebAppSerilogLoggerProvider(testSink.Object);
{
- var logger = provider.CreateLogger("TestLogger");
+ var logger = provider.ConfigureLogger(configReader.Object);
- logger.LogInformation("Test1");
+ logger.Information("Test1");
testSink.Verify(m => m.Emit(It.IsAny()), Times.Once);
configBuilder.SetFileLoggingEnabled(false);
@@ -83,7 +79,7 @@ public void DynamicDisable()
configReader.Raise(m => m.OnConfigurationChanged += (sender, e) => { }, null, currentConfig);
// Logging should be disabled now
- logger.LogInformation("Test1");
+ logger.Information("Test1");
testSink.Verify(m => m.Emit(It.IsAny()), Times.Once);
}
}
@@ -92,11 +88,11 @@ public void DynamicDisable()
public void DynamicLoggingLevel()
{
var configBuilder = new WebAppLogConfigurationBuilder()
- .SetIsRunningInAzureWebApps(true)
- .SetFileLoggingEnabled(true)
- .SetFileLoggingLevel(LogLevel.Critical);
+ .SetIsRunningInAzureWebApps(true)
+ .SetFileLoggingEnabled(true)
+ .SetFileLoggingLevel(LogLevel.Critical);
- var currentConfig = configBuilder.Build();
+ var currentConfig = configBuilder.Build();
var configReader = new Mock();
configReader.SetupGet(m => m.Current)
@@ -105,22 +101,20 @@ public void DynamicLoggingLevel()
var testSink = new Mock(MockBehavior.Strict);
testSink.Setup(m => m.Emit(It.IsAny()));
- using (var provider = new TestWebAppSerilogLoggerProvider(testSink.Object, configReader.Object))
- {
- var logger = provider.CreateLogger("TestLogger");
+ var provider = new TestWebAppSerilogLoggerProvider(testSink.Object);
+ var logger = provider.ConfigureLogger(configReader.Object);
- logger.LogDebug("Test1");
- testSink.Verify(m => m.Emit(It.IsAny()), Times.Never);
+ logger.Debug("Test1");
+ testSink.Verify(m => m.Emit(It.IsAny()), Times.Never);
- configBuilder.SetFileLoggingLevel(LogLevel.Debug);
- currentConfig = configBuilder.Build();
+ configBuilder.SetFileLoggingLevel(LogLevel.Debug);
+ currentConfig = configBuilder.Build();
- configReader.Raise(m => m.OnConfigurationChanged += (sender, e) => { }, null, currentConfig);
+ configReader.Raise(m => m.OnConfigurationChanged += (sender, e) => { }, null, currentConfig);
- // Logging for this level should be enabled now
- logger.LogDebug("Test1");
- testSink.Verify(m => m.Emit(It.IsAny()), Times.Once);
- }
+ // Logging for this level should be enabled now
+ logger.Debug("Test1");
+ testSink.Verify(m => m.Emit(It.IsAny()), Times.Once);
}
// Checks that the .net log level to serilog level mappings are doing what we expect
@@ -128,8 +122,8 @@ public void DynamicLoggingLevel()
public void LevelMapping()
{
var configBuilder = new WebAppLogConfigurationBuilder()
- .SetIsRunningInAzureWebApps(true)
- .SetFileLoggingEnabled(true);
+ .SetIsRunningInAzureWebApps(true)
+ .SetFileLoggingEnabled(true);
var currentConfig = configBuilder.Build();
@@ -140,68 +134,69 @@ public void LevelMapping()
var testSink = new Mock(MockBehavior.Strict);
testSink.Setup(m => m.Emit(It.IsAny()));
- using (var provider = new TestWebAppSerilogLoggerProvider(testSink.Object, configReader.Object))
+ var provider = new TestWebAppSerilogLoggerProvider(testSink.Object);
+ var levelsToCheck = new []
{
- var levelsToCheck = new LogLevel[] {
- LogLevel.None,
- LogLevel.Critical,
- LogLevel.Error,
- LogLevel.Warning,
- LogLevel.Information,
- LogLevel.Debug,
- LogLevel.Trace
- };
-
- var logger = provider.CreateLogger("TestLogger");
-
- for (int i = 0; i < levelsToCheck.Length; i++)
- {
- var enabledLevel = levelsToCheck[i];
+ LogLevel.None,
+ LogLevel.Critical,
+ LogLevel.Error,
+ LogLevel.Warning,
+ LogLevel.Information,
+ LogLevel.Debug,
+ LogLevel.Trace
+ };
+
+ var seriloglogger = provider.ConfigureLogger(configReader.Object);
+ var loggerFactory = new LoggerFactory();
+ loggerFactory.AddSerilog(seriloglogger);
+ var logger = loggerFactory.CreateLogger("TestLogger");
+
+ for (var i = 0; i < levelsToCheck.Length; i++)
+ {
+ var enabledLevel = levelsToCheck[i];
- // Change the logging level
- configBuilder.SetFileLoggingLevel(enabledLevel);
- currentConfig = configBuilder.Build();
- configReader.Raise(m => m.OnConfigurationChanged += (sender, e) => { }, null, currentConfig);
+ // Change the logging level
+ configBuilder.SetFileLoggingLevel(enabledLevel);
+ currentConfig = configBuilder.Build();
+ configReader.Raise(m => m.OnConfigurationChanged += (sender, e) => { }, null, currentConfig);
- // Don't try to log "None" (start at 1)
- for (int j = 1; j < levelsToCheck.Length; j++)
- {
- logger.Log(levelsToCheck[j], 1, new object(), null, (state, ex) => string.Empty);
- }
+ // Don't try to log "None" (start at 1)
+ for (var j = 1; j < levelsToCheck.Length; j++)
+ {
+ logger.Log(levelsToCheck[j], 1, new object(), null, (state, ex) => string.Empty);
+ }
- // On each level we expect an extra message from the previous
- testSink.Verify(
- m => m.Emit(It.IsAny()),
- Times.Exactly(i),
- $"Enabled level: {enabledLevel}");
+ // On each level we expect an extra message from the previous
+ testSink.Verify(
+ m => m.Emit(It.IsAny()),
+ Times.Exactly(i),
+ $"Enabled level: {enabledLevel}");
- testSink.ResetCalls();
- }
+ testSink.ResetCalls();
}
}
- private class TestWebAppSerilogLoggerProvider : SerilogLoggerProvider
+ private class TestWebAppSerilogLoggerProvider
{
- public TestWebAppSerilogLoggerProvider(ILogEventSink sink, IWebAppLogConfigurationReader configReader) :
- base(configReader,
- (logger, config) =>
- {
- logger.WriteTo.Sink(sink);
- })
+ private readonly ILogEventSink _sink;
+
+ public TestWebAppSerilogLoggerProvider(ILogEventSink sink)
{
+ _sink = sink;
}
- protected override void OnConfigurationChanged(WebAppLogConfiguration newConfiguration)
+ public Logger ConfigureLogger(IWebAppLogConfigurationReader reader)
{
- if (!newConfiguration.FileLoggingEnabled)
- {
- LevelSwitcher.MinimumLevel = LogLevelDisabled;
- }
- else
- {
- LevelSwitcher.MinimumLevel = LogLevelToLogEventLevel(newConfiguration.FileLoggingLevel);
- }
+ var loggerConfiguration = new LoggerConfiguration();
+ loggerConfiguration.WriteTo.Sink(_sink);
+ loggerConfiguration.MinimumLevel.ControlledBy(new WebConfigurationReaderLevelSwitch(reader,
+ configuration =>
+ {
+ return configuration.FileLoggingEnabled ? configuration.FileLoggingLevel : LogLevel.None;
+ }));
+
+ return loggerConfiguration.CreateLogger();
}
}
}
diff --git a/test/Microsoft.Extensions.Logging.AzureWebAppDiagnostics.Test/project.json b/test/Microsoft.Extensions.Logging.AzureWebAppDiagnostics.Test/project.json
index 7e3ab744..ac5eff87 100644
--- a/test/Microsoft.Extensions.Logging.AzureWebAppDiagnostics.Test/project.json
+++ b/test/Microsoft.Extensions.Logging.AzureWebAppDiagnostics.Test/project.json
@@ -17,7 +17,8 @@
"version": "1.0.0-*",
"type": "platform"
}
- }
+ },
+ "imports": [ "portable-net40+sl5+win8+wp8+wpa81", "portable-net45+win8+wp8+wpa81" ]
},
"net451": {}
}