Skip to content
This repository was archived by the owner on Dec 13, 2018. It is now read-only.

Commit b7bcb77

Browse files
author
Victor Hurdugaci
committed
A logger for Azure WebApps
1 parent 62265c0 commit b7bcb77

26 files changed

+1603
-11
lines changed

Logging.sln

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
Microsoft Visual Studio Solution File, Format Version 12.00
22
# Visual Studio 14
3-
VisualStudioVersion = 14.0.25123.0
3+
VisualStudioVersion = 14.0.25420.1
44
MinimumVisualStudioVersion = 10.0.40219.1
55
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.Extensions.Logging", "src\Microsoft.Extensions.Logging\Microsoft.Extensions.Logging.xproj", "{19D1B6C5-8A62-4387-8816-C54874D1DF5F}"
66
EndProject
@@ -34,6 +34,10 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.Extensions.Loggin
3434
EndProject
3535
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.Extensions.Logging.EventSource.Test", "test\Microsoft.Extensions.Logging.EventSource.Test\Microsoft.Extensions.Logging.EventSource.Test.xproj", "{F3B898C3-D441-4207-A92B-420D6E73CA5D}"
3636
EndProject
37+
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.Extensions.Logging.AzureWebAppDiagnostics", "src\Microsoft.Extensions.Logging.AzureWebAppDiagnostics\Microsoft.Extensions.Logging.AzureWebAppDiagnostics.xproj", "{854133D5-6252-4A0A-B682-BDBB83B62AE6}"
38+
EndProject
39+
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.Extensions.Logging.AzureWebAppDiagnostics.Test", "test\Microsoft.Extensions.Logging.AzureWebAppDiagnostics.Test\Microsoft.Extensions.Logging.AzureWebAppDiagnostics.Test.xproj", "{B4A43221-DE95-47BB-A2D4-2DC761FC9419}"
40+
EndProject
3741
Global
3842
GlobalSection(SolutionConfigurationPlatforms) = preSolution
3943
Debug|Any CPU = Debug|Any CPU
@@ -190,6 +194,30 @@ Global
190194
{F3B898C3-D441-4207-A92B-420D6E73CA5D}.Release|Mixed Platforms.Build.0 = Release|Any CPU
191195
{F3B898C3-D441-4207-A92B-420D6E73CA5D}.Release|x86.ActiveCfg = Release|Any CPU
192196
{F3B898C3-D441-4207-A92B-420D6E73CA5D}.Release|x86.Build.0 = Release|Any CPU
197+
{854133D5-6252-4A0A-B682-BDBB83B62AE6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
198+
{854133D5-6252-4A0A-B682-BDBB83B62AE6}.Debug|Any CPU.Build.0 = Debug|Any CPU
199+
{854133D5-6252-4A0A-B682-BDBB83B62AE6}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
200+
{854133D5-6252-4A0A-B682-BDBB83B62AE6}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
201+
{854133D5-6252-4A0A-B682-BDBB83B62AE6}.Debug|x86.ActiveCfg = Debug|Any CPU
202+
{854133D5-6252-4A0A-B682-BDBB83B62AE6}.Debug|x86.Build.0 = Debug|Any CPU
203+
{854133D5-6252-4A0A-B682-BDBB83B62AE6}.Release|Any CPU.ActiveCfg = Release|Any CPU
204+
{854133D5-6252-4A0A-B682-BDBB83B62AE6}.Release|Any CPU.Build.0 = Release|Any CPU
205+
{854133D5-6252-4A0A-B682-BDBB83B62AE6}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
206+
{854133D5-6252-4A0A-B682-BDBB83B62AE6}.Release|Mixed Platforms.Build.0 = Release|Any CPU
207+
{854133D5-6252-4A0A-B682-BDBB83B62AE6}.Release|x86.ActiveCfg = Release|Any CPU
208+
{854133D5-6252-4A0A-B682-BDBB83B62AE6}.Release|x86.Build.0 = Release|Any CPU
209+
{B4A43221-DE95-47BB-A2D4-2DC761FC9419}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
210+
{B4A43221-DE95-47BB-A2D4-2DC761FC9419}.Debug|Any CPU.Build.0 = Debug|Any CPU
211+
{B4A43221-DE95-47BB-A2D4-2DC761FC9419}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
212+
{B4A43221-DE95-47BB-A2D4-2DC761FC9419}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
213+
{B4A43221-DE95-47BB-A2D4-2DC761FC9419}.Debug|x86.ActiveCfg = Debug|Any CPU
214+
{B4A43221-DE95-47BB-A2D4-2DC761FC9419}.Debug|x86.Build.0 = Debug|Any CPU
215+
{B4A43221-DE95-47BB-A2D4-2DC761FC9419}.Release|Any CPU.ActiveCfg = Release|Any CPU
216+
{B4A43221-DE95-47BB-A2D4-2DC761FC9419}.Release|Any CPU.Build.0 = Release|Any CPU
217+
{B4A43221-DE95-47BB-A2D4-2DC761FC9419}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
218+
{B4A43221-DE95-47BB-A2D4-2DC761FC9419}.Release|Mixed Platforms.Build.0 = Release|Any CPU
219+
{B4A43221-DE95-47BB-A2D4-2DC761FC9419}.Release|x86.ActiveCfg = Release|Any CPU
220+
{B4A43221-DE95-47BB-A2D4-2DC761FC9419}.Release|x86.Build.0 = Release|Any CPU
193221
EndGlobalSection
194222
GlobalSection(SolutionProperties) = preSolution
195223
HideSolutionNode = FALSE
@@ -208,5 +236,7 @@ Global
208236
{0D190EE0-E305-403D-AC01-DEE71D8DBDB5} = {699DB330-0095-4266-B7B0-3EAB3710CA49}
209237
{84073E58-1802-4525-A9E5-1E6A70DAF0B2} = {699DB330-0095-4266-B7B0-3EAB3710CA49}
210238
{F3B898C3-D441-4207-A92B-420D6E73CA5D} = {09920C51-6220-4D8D-94DC-E70C13446187}
239+
{854133D5-6252-4A0A-B682-BDBB83B62AE6} = {699DB330-0095-4266-B7B0-3EAB3710CA49}
240+
{B4A43221-DE95-47BB-A2D4-2DC761FC9419} = {09920C51-6220-4D8D-94DC-E70C13446187}
211241
EndGlobalSection
212242
EndGlobal
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
6+
namespace Microsoft.Extensions.Logging.Abstractions.Internal
7+
{
8+
public class NullLogger : ILogger
9+
{
10+
public static NullLogger Instance { get; } = new NullLogger();
11+
12+
private NullLogger()
13+
{
14+
}
15+
16+
public IDisposable BeginScope<TState>(TState state)
17+
{
18+
return NullScope.Instance;
19+
}
20+
21+
public bool IsEnabled(LogLevel logLevel)
22+
{
23+
return false;
24+
}
25+
26+
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
27+
{
28+
}
29+
}
30+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
namespace Microsoft.Extensions.Logging.Abstractions.Internal
5+
{
6+
public class NullLoggerProvider : ILoggerProvider
7+
{
8+
public static NullLoggerProvider Instance { get; } = new NullLoggerProvider();
9+
10+
private NullLoggerProvider()
11+
{
12+
}
13+
14+
public ILogger CreateLogger(string categoryName)
15+
{
16+
return NullLogger.Instance;
17+
}
18+
19+
public void Dispose()
20+
{
21+
}
22+
}
23+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
6+
namespace Microsoft.Extensions.Logging.Abstractions.Internal
7+
{
8+
/// <summary>
9+
/// An empty scope without any logic
10+
/// </summary>
11+
public class NullScope : IDisposable
12+
{
13+
public static NullScope Instance { get; } = new NullScope();
14+
15+
private NullScope()
16+
{
17+
}
18+
19+
/// <inheritdoc />
20+
public void Dispose()
21+
{
22+
}
23+
}
24+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using Microsoft.Extensions.Logging.AzureWebAppDiagnostics;
5+
using Microsoft.Extensions.Logging.AzureWebAppDiagnostics.Internal;
6+
7+
namespace Microsoft.Extensions.Logging
8+
{
9+
/// <summary>
10+
/// Extension methods for <see cref="AzureWebAppDiagnosticsLoggerProvider"/>.
11+
/// </summary>
12+
public static class AzureWebAppDiagnosticsFactoryExtensions
13+
{
14+
/// <summary>
15+
/// Adds an Azure Web Apps diagnostics logger.
16+
/// </summary>
17+
/// <param name="factory">The extension method argument</param>
18+
/// <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>
19+
public static ILoggerFactory AddAzureWebAppDiagnostics(this ILoggerFactory factory, int fileSizeLimitMb = FileLoggerProvider.DefaultFileSizeLimitMb)
20+
{
21+
if (WebAppContext.Default.IsRunningInAzureWebApp)
22+
{
23+
// Only add the provider if we're in Azure WebApp. That cannot change once the apps started
24+
factory.AddProvider(new AzureWebAppDiagnosticsLoggerProvider(WebAppContext.Default, fileSizeLimitMb));
25+
}
26+
return factory;
27+
}
28+
}
29+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using Microsoft.Extensions.Logging.Abstractions.Internal;
5+
using Microsoft.Extensions.Logging.AzureWebAppDiagnostics.Internal;
6+
7+
namespace Microsoft.Extensions.Logging.AzureWebAppDiagnostics
8+
{
9+
/// <summary>
10+
/// Logger provider for Azure WebApp.
11+
/// </summary>
12+
public class AzureWebAppDiagnosticsLoggerProvider : ILoggerProvider
13+
{
14+
private readonly IWebAppLogConfigurationReader _configurationReader;
15+
16+
private readonly ILoggerProvider _innerLoggerProvider;
17+
private readonly bool _runningInWebApp;
18+
19+
/// <summary>
20+
/// Creates a new instance of the <see cref="AzureWebAppDiagnosticsLoggerProvider"/> class.
21+
/// </summary>
22+
public AzureWebAppDiagnosticsLoggerProvider(WebAppContext context, int fileSizeLimitMb)
23+
{
24+
_configurationReader = new WebAppLogConfigurationReader(context);
25+
26+
var config = _configurationReader.Current;
27+
_runningInWebApp = config.IsRunningInWebApp;
28+
29+
if (!_runningInWebApp)
30+
{
31+
_innerLoggerProvider = NullLoggerProvider.Instance;
32+
}
33+
else
34+
{
35+
_innerLoggerProvider = new FileLoggerProvider(_configurationReader, fileSizeLimitMb);
36+
37+
if (!string.IsNullOrEmpty(config.BlobContainerUrl))
38+
{
39+
// TODO: Add the blob logger by creating a composite inner logger which calls
40+
// both loggers
41+
}
42+
}
43+
}
44+
45+
/// <inheritdoc />
46+
public ILogger CreateLogger(string categoryName)
47+
{
48+
return _innerLoggerProvider.CreateLogger(categoryName);
49+
}
50+
51+
/// <inheritdoc />
52+
public void Dispose()
53+
{
54+
_innerLoggerProvider.Dispose();
55+
_configurationReader.Dispose();
56+
}
57+
}
58+
}
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. 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 Serilog.Core;
8+
using Serilog.Events;
9+
10+
// TODO: Might want to consider using https://github.com/jezzsantos/Serilog.Sinks.Async
11+
// instead of this, once that supports netstandard
12+
namespace Microsoft.Extensions.Logging.AzureWebAppDiagnostics.Internal
13+
{
14+
/// <summary>
15+
/// A background sink for Serilog.
16+
/// </summary>
17+
public class BackgroundSink : ILogEventSink, IDisposable
18+
{
19+
/// <summary>
20+
/// The default queue size.
21+
/// </summary>
22+
public const int DefaultLogMessagesQueueSize = 1024;
23+
24+
private readonly CancellationTokenSource _disposedTokenSource = new CancellationTokenSource();
25+
private readonly CancellationToken _disposedToken;
26+
27+
private readonly BlockingCollection<LogEvent> _messages;
28+
private readonly Thread _workerThread;
29+
30+
private ILogEventSink _innerSink;
31+
32+
/// <summary>
33+
/// Creates a new instance of the <see cref="BackgroundSink"/> class.
34+
/// </summary>
35+
/// <param name="innerSink">The inner sink which does the actual logging</param>
36+
/// <param name="maxQueueSize">The maximum size of the background queue</param>
37+
public BackgroundSink(ILogEventSink innerSink, int? maxQueueSize)
38+
{
39+
if (innerSink == null)
40+
{
41+
throw new ArgumentNullException(nameof(innerSink));
42+
}
43+
44+
_disposedToken = _disposedTokenSource.Token;
45+
46+
if (maxQueueSize == null || maxQueueSize <= 0)
47+
{
48+
_messages = new BlockingCollection<LogEvent>(new ConcurrentQueue<LogEvent>());
49+
}
50+
else
51+
{
52+
_messages = new BlockingCollection<LogEvent>(new ConcurrentQueue<LogEvent>(), maxQueueSize.Value);
53+
}
54+
55+
_innerSink = innerSink;
56+
57+
_workerThread = new Thread(Worker);
58+
_workerThread.Name = GetType().Name;
59+
_workerThread.IsBackground = true;
60+
_workerThread.Start();
61+
}
62+
63+
/// <inheritdoc />
64+
public void Emit(LogEvent logEvent)
65+
{
66+
if (!_disposedToken.IsCancellationRequested)
67+
{
68+
_messages.Add(logEvent);
69+
}
70+
}
71+
72+
/// <summary>
73+
/// Disposes this object instance.
74+
/// </summary>
75+
public virtual void Dispose()
76+
{
77+
lock (_disposedTokenSource)
78+
{
79+
if (!_disposedTokenSource.IsCancellationRequested)
80+
{
81+
_disposedTokenSource.Cancel();
82+
}
83+
84+
// Wait for the thread to complete before disposing the resources
85+
_workerThread.Join(5 /*seconds */ * 1000);
86+
_messages.Dispose();
87+
}
88+
}
89+
90+
private void Worker()
91+
{
92+
try
93+
{
94+
foreach (var logEvent in _messages.GetConsumingEnumerable(_disposedToken))
95+
{
96+
PassLogEventToInnerSink(logEvent);
97+
}
98+
}
99+
catch (OperationCanceledException)
100+
{
101+
// Do nothing, we just cancelled the task
102+
}
103+
}
104+
105+
private void PassLogEventToInnerSink(LogEvent logEvent)
106+
{
107+
_innerSink.Emit(logEvent);
108+
}
109+
}
110+
}

0 commit comments

Comments
 (0)