Skip to content

Commit 9cc6004

Browse files
committed
adding FileLogger E2E tests; caching in FunctionTraceWriterFactory
1 parent f9b16eb commit 9cc6004

30 files changed

+438
-331
lines changed

src/WebJobs.Script/Description/DotNet/DotNetFunctionInvoker.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,8 @@ internal DotNetFunctionInvoker(ScriptHost host,
4848
IFunctionEntryPointResolver functionEntryPointResolver,
4949
FunctionAssemblyLoader assemblyLoader,
5050
ICompilationServiceFactory<ICompilationService<IDotNetCompilation>, IFunctionMetadataResolver> compilationServiceFactory,
51-
ITraceWriterFactory traceWriterFactory = null,
5251
IFunctionMetadataResolver metadataResolver = null)
53-
: base(host, functionMetadata, traceWriterFactory)
52+
: base(host, functionMetadata)
5453
{
5554
_metricsLogger = Host.ScriptConfig.HostConfig.GetService<IMetricsLogger>();
5655
_functionEntryPointResolver = functionEntryPointResolver;

src/WebJobs.Script/Description/FunctionInvokerBase.cs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,15 +27,14 @@ public abstract class FunctionInvokerBase : IFunctionInvoker, IDisposable
2727
private IMetricsLogger _metrics;
2828
private IDisposable _fileChangeSubscription;
2929

30-
internal FunctionInvokerBase(ScriptHost host, FunctionMetadata functionMetadata, ITraceWriterFactory traceWriterFactory = null)
30+
internal FunctionInvokerBase(ScriptHost host, FunctionMetadata functionMetadata)
3131
{
3232
Host = host;
3333
Metadata = functionMetadata;
3434
_metrics = host.ScriptConfig.HostConfig.GetService<IMetricsLogger>();
3535

3636
// Function file logging is only done conditionally
37-
traceWriterFactory = traceWriterFactory ?? new FunctionTraceWriterFactory(functionMetadata.Name, Host.ScriptConfig);
38-
TraceWriter traceWriter = traceWriterFactory.Create();
37+
TraceWriter traceWriter = host.FunctionTraceWriterFactory.Create(functionMetadata.Name);
3938
FileTraceWriter = traceWriter.Conditional(t => Host.FileLoggingEnabled && (!(t.Properties?.ContainsKey(ScriptConstants.TracePropertyPrimaryHostKey) ?? false) || Host.IsPrimary));
4039

4140
// The global trace writer used by the invoker will write all traces to both
@@ -67,7 +66,7 @@ internal FunctionInvokerBase(ScriptHost host, FunctionMetadata functionMetadata,
6766

6867
public FunctionMetadata Metadata { get; }
6968

70-
private TraceWriter FileTraceWriter { get; set; }
69+
internal TraceWriter FileTraceWriter { get; set; }
7170

7271
public TraceWriter TraceWriter { get; }
7372

src/WebJobs.Script/Description/Node/NodeFunctionInvoker.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,8 @@ static NodeFunctionInvoker()
5757

5858
internal NodeFunctionInvoker(ScriptHost host, BindingMetadata trigger, FunctionMetadata functionMetadata,
5959
Collection<FunctionBinding> inputBindings, Collection<FunctionBinding> outputBindings,
60-
ICompilationService<IJavaScriptCompilation> compilationService, ITraceWriterFactory traceWriterFactory = null)
61-
: base(host, functionMetadata, traceWriterFactory)
60+
ICompilationService<IJavaScriptCompilation> compilationService)
61+
: base(host, functionMetadata)
6262
{
6363
_trigger = trigger;
6464
_inputBindings = inputBindings;

src/WebJobs.Script/Description/PowerShell/PowerShellFunctionInvoker.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,8 @@ public class PowerShellFunctionInvoker : ScriptFunctionInvokerBase
3131
private List<string> _moduleFiles;
3232

3333
internal PowerShellFunctionInvoker(ScriptHost host, FunctionMetadata functionMetadata,
34-
Collection<FunctionBinding> inputBindings, Collection<FunctionBinding> outputBindings, ITraceWriterFactory traceWriterFactory = null)
35-
: base(host, functionMetadata, traceWriterFactory)
34+
Collection<FunctionBinding> inputBindings, Collection<FunctionBinding> outputBindings)
35+
: base(host, functionMetadata)
3636
{
3737
_host = host;
3838
_scriptFilePath = functionMetadata.ScriptFile;

src/WebJobs.Script/Description/Script/ScriptFunctionInvoker.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@ public class ScriptFunctionInvoker : ScriptFunctionInvokerBase
2626
private readonly Collection<FunctionBinding> _outputBindings;
2727

2828
internal ScriptFunctionInvoker(string scriptFilePath, ScriptHost host, FunctionMetadata functionMetadata,
29-
Collection<FunctionBinding> inputBindings, Collection<FunctionBinding> outputBindings, ITraceWriterFactory traceWriterFactory = null)
30-
: base(host, functionMetadata, traceWriterFactory)
29+
Collection<FunctionBinding> inputBindings, Collection<FunctionBinding> outputBindings)
30+
: base(host, functionMetadata)
3131
{
3232
_scriptFilePath = scriptFilePath;
3333
_host = host;

src/WebJobs.Script/Description/ScriptFunctionInvokerBase.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ namespace Microsoft.Azure.WebJobs.Script.Description
1717
{
1818
public class ScriptFunctionInvokerBase : FunctionInvokerBase
1919
{
20-
public ScriptFunctionInvokerBase(ScriptHost host, FunctionMetadata functionMetadata, ITraceWriterFactory traceWriterFactory)
21-
: base(host, functionMetadata, traceWriterFactory)
20+
public ScriptFunctionInvokerBase(ScriptHost host, FunctionMetadata functionMetadata)
21+
: base(host, functionMetadata)
2222
{
2323
}
2424

src/WebJobs.Script/Diagnostics/FileLogger.cs

Lines changed: 21 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
// Licensed under the MIT License. See License.txt in the project root for license information.
33

44
using System;
5-
using System.Collections.Concurrent;
65
using System.Collections.Generic;
76
using System.Diagnostics;
87
using System.Linq;
@@ -17,16 +16,15 @@ namespace Microsoft.Azure.WebJobs.Script.Diagnostics
1716
/// </summary>
1817
internal class FileLogger : ILogger
1918
{
20-
private static readonly ConcurrentDictionary<string, TraceWriter> _writerCache = new ConcurrentDictionary<string, TraceWriter>();
2119
private readonly Func<string, LogLevel, bool> _filter;
2220
private readonly string _categoryName;
23-
private readonly ScriptHostConfiguration _config;
21+
private readonly IFunctionTraceWriterFactory _traceWriterFactory;
2422

25-
public FileLogger(string categoryName, ScriptHostConfiguration config, Func<string, LogLevel, bool> filter)
23+
public FileLogger(string categoryName, IFunctionTraceWriterFactory traceWriterFactory, Func<string, LogLevel, bool> filter)
2624
{
2725
_categoryName = categoryName;
28-
_config = config;
2926
_filter = filter;
27+
_traceWriterFactory = traceWriterFactory;
3028
}
3129

3230
public IDisposable BeginScope<TState>(TState state) => DictionaryLoggerScope.Push(state);
@@ -53,12 +51,27 @@ public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Except
5351
return;
5452
}
5553

54+
TraceLevel traceLevel = GetTraceLevel(logLevel);
55+
TraceEvent traceEvent = new TraceEvent(traceLevel, formattedMessage, _categoryName, exception);
56+
string functionName = GetFunctionName();
57+
58+
// If we don't have a function name, we have no way to create a TraceWriter
59+
if (functionName == null)
60+
{
61+
return;
62+
}
63+
64+
TraceWriter traceWriter = _traceWriterFactory.Create(functionName);
65+
traceWriter.Trace(traceEvent);
66+
}
67+
68+
private static string GetFunctionName()
69+
{
5670
IDictionary<string, object> scopeProperties = DictionaryLoggerScope.GetMergedStateDictionary();
5771

5872
if (!scopeProperties.TryGetValue(ScriptConstants.LoggerFunctionNameKey, out string functionName))
5973
{
60-
// We have nowhere to write the file if we don't know the function name
61-
return;
74+
return null;
6275
}
6376

6477
// this function name starts with "Functions.", but file paths do not include this
@@ -68,20 +81,7 @@ public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Except
6881
functionName = functionName.Substring(functionsPrefix.Length);
6982
}
7083

71-
TraceWriter traceWriter = _writerCache.GetOrAdd(functionName, (n) => CreateFileTraceWriter(n, _config));
72-
TraceLevel traceLevel = GetTraceLevel(logLevel);
73-
TraceEvent traceEvent = new TraceEvent(traceLevel, formattedMessage, _categoryName, exception);
74-
75-
traceWriter.Trace(traceEvent);
76-
}
77-
78-
// For testing
79-
internal static void FlushAllTraceWriters()
80-
{
81-
foreach (TraceWriter writer in _writerCache.Values)
82-
{
83-
writer.Flush();
84-
}
84+
return functionName;
8585
}
8686

8787
private static TraceLevel GetTraceLevel(LogLevel logLevel)
@@ -109,11 +109,5 @@ private static bool IsFromTraceWriter(IEnumerable<KeyValuePair<string, object>>
109109
{
110110
return properties.Any(kvp => kvp.Key == ScriptConstants.TracePropertyIsUserTraceKey);
111111
}
112-
113-
private static TraceWriter CreateFileTraceWriter(string functionName, ScriptHostConfiguration scriptHostConfig)
114-
{
115-
ITraceWriterFactory factory = new FunctionTraceWriterFactory(functionName, scriptHostConfig);
116-
return factory.Create();
117-
}
118112
}
119113
}

src/WebJobs.Script/Diagnostics/FileLoggerProvider.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,16 @@ namespace Microsoft.Azure.WebJobs.Script.Diagnostics
88
{
99
internal class FileLoggerProvider : ILoggerProvider
1010
{
11-
private ScriptHostConfiguration _config;
11+
private IFunctionTraceWriterFactory _traceWriterFactory;
1212
private Func<string, LogLevel, bool> _filter;
1313

14-
public FileLoggerProvider(ScriptHostConfiguration config, Func<string, LogLevel, bool> filter)
14+
public FileLoggerProvider(IFunctionTraceWriterFactory traceWriterFactory, Func<string, LogLevel, bool> filter)
1515
{
16-
_config = config;
16+
_traceWriterFactory = traceWriterFactory;
1717
_filter = filter;
1818
}
1919

20-
public ILogger CreateLogger(string categoryName) => new FileLogger(categoryName, _config, _filter);
20+
public ILogger CreateLogger(string categoryName) => new FileLogger(categoryName, _traceWriterFactory, _filter);
2121

2222
public void Dispose()
2323
{

src/WebJobs.Script/Diagnostics/FileTraceWriter.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ public override void Trace(TraceEvent traceEvent)
129129
{
130130
if (traceEvent == null)
131131
{
132-
throw new ArgumentNullException("traceEvent");
132+
throw new ArgumentNullException(nameof(traceEvent));
133133
}
134134

135135
object value;
Lines changed: 54 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,78 @@
11
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the MIT License. See License.txt in the project root for license information.
33

4+
using System;
5+
using System.Collections.Concurrent;
46
using System.Diagnostics;
57
using System.IO;
68
using Microsoft.Azure.WebJobs.Host;
79

810
namespace Microsoft.Azure.WebJobs.Script
911
{
10-
public sealed class FunctionTraceWriterFactory : ITraceWriterFactory
12+
public sealed class FunctionTraceWriterFactory : IFunctionTraceWriterFactory
1113
{
12-
private readonly string _functionName;
14+
private readonly ConcurrentDictionary<string, TraceWriter> _writerCache = new ConcurrentDictionary<string, TraceWriter>(StringComparer.OrdinalIgnoreCase);
1315
private readonly ScriptHostConfiguration _scriptHostConfig;
1416

15-
public FunctionTraceWriterFactory(string functionName, ScriptHostConfiguration scriptHostConfig)
17+
public FunctionTraceWriterFactory(ScriptHostConfiguration scriptHostConfig)
1618
{
17-
_functionName = functionName;
1819
_scriptHostConfig = scriptHostConfig;
1920
}
2021

21-
public TraceWriter Create()
22+
public TraceWriter Create(string functionName)
2223
{
2324
if (_scriptHostConfig.FileLoggingMode != FileLoggingMode.Never)
2425
{
25-
TraceLevel functionTraceLevel = _scriptHostConfig.HostConfig.Tracing.ConsoleLevel;
26-
string logFilePath = Path.Combine(_scriptHostConfig.RootLogPath, "Function", _functionName);
27-
return new FileTraceWriter(logFilePath, functionTraceLevel);
26+
return _writerCache.GetOrAdd(functionName, f => CreateTraceWriter(_scriptHostConfig, f));
2827
}
2928

3029
return NullTraceWriter.Instance;
3130
}
31+
32+
private TraceWriter CreateTraceWriter(ScriptHostConfiguration config, string functionName)
33+
{
34+
TraceLevel functionTraceLevel = config.HostConfig.Tracing.ConsoleLevel;
35+
string logFilePath = Path.Combine(config.RootLogPath, "Function", functionName);
36+
37+
// Wrap the FileTraceWriter in a RemovableTraceWriter so we can remove it from the cache when it is disposed
38+
var innerTraceWriter = new FileTraceWriter(logFilePath, functionTraceLevel);
39+
return new RemovableTraceWriter(this, functionName, innerTraceWriter);
40+
}
41+
42+
private void RemoveTraceWriter(string functionName)
43+
{
44+
_writerCache.TryRemove(functionName, out TraceWriter unused);
45+
}
46+
47+
internal class RemovableTraceWriter : TraceWriter, IDisposable
48+
{
49+
private readonly TraceWriter _innerWriter;
50+
private readonly FunctionTraceWriterFactory _parentFactory;
51+
private readonly string _functionName;
52+
private bool _disposed = false;
53+
54+
public RemovableTraceWriter(FunctionTraceWriterFactory parentFactory, string functionName, TraceWriter innerWriter)
55+
: base(innerWriter.Level)
56+
{
57+
_parentFactory = parentFactory;
58+
_innerWriter = innerWriter;
59+
_functionName = functionName;
60+
}
61+
62+
public override void Trace(TraceEvent traceEvent)
63+
{
64+
_innerWriter.Trace(traceEvent);
65+
}
66+
67+
public void Dispose()
68+
{
69+
if (!_disposed)
70+
{
71+
_parentFactory.RemoveTraceWriter(_functionName);
72+
(_innerWriter as IDisposable)?.Dispose();
73+
_disposed = true;
74+
}
75+
}
76+
}
3277
}
33-
}
78+
}

0 commit comments

Comments
 (0)