Skip to content

Commit 9323241

Browse files
committed
Ensure Default TraceWriter (#2186)
1 parent cb712cc commit 9323241

File tree

5 files changed

+108
-3
lines changed

5 files changed

+108
-3
lines changed

src/WebJobs.Script.WebHost/App_Start/AutofacBootstrap.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ internal static void Initialize(ScriptSettingsManager settingsManager, Container
2323

2424
// these services are externally owned by the WebHostResolver, and will be disposed
2525
// when the resolver is disposed
26-
builder.Register<TraceWriter>(ct => ct.ResolveOptional<WebScriptHostManager>()?.Instance?.TraceWriter ?? NullTraceWriter.Instance).ExternallyOwned();
26+
builder.Register<TraceWriter>(ct => ct.Resolve<WebHostResolver>().GetTraceWriter(settings)).ExternallyOwned();
2727
builder.Register<ISecretManager>(ct => ct.Resolve<WebHostResolver>().GetSecretManager(settings)).ExternallyOwned();
2828
builder.Register<ISwaggerDocumentManager>(ct => ct.Resolve<WebHostResolver>().GetSwaggerDocumentManager(settings)).ExternallyOwned();
2929
builder.Register<WebScriptHostManager>(ct => ct.Resolve<WebHostResolver>().GetWebScriptHostManager(settings)).ExternallyOwned();

src/WebJobs.Script.WebHost/App_Start/WebHostResolver.cs

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,14 @@
33

44
using System;
55
using System.Collections.Generic;
6+
using System.Diagnostics;
67
using System.IO;
78
using System.Threading;
89
using System.Threading.Tasks;
10+
using Microsoft.Azure.WebJobs.Host;
911
using Microsoft.Azure.WebJobs.Script.Config;
1012
using Microsoft.Azure.WebJobs.Script.Eventing;
13+
using Microsoft.Azure.WebJobs.Script.WebHost.Diagnostics;
1114
using Microsoft.Azure.WebJobs.Script.WebHost.Properties;
1215
using Microsoft.Azure.WebJobs.Script.WebHost.WebHooks;
1316
using Microsoft.Extensions.Logging;
@@ -28,6 +31,7 @@ public sealed class WebHostResolver : IDisposable
2831
private ScriptHostConfiguration _activeScriptHostConfig;
2932
private WebScriptHostManager _activeHostManager;
3033
private WebHookReceiverManager _activeReceiverManager;
34+
private TraceWriter _defaultTraceWriter;
3135
private Timer _specializationTimer;
3236

3337
public WebHostResolver(ScriptSettingsManager settingsManager, ISecretManagerFactory secretManagerFactory, IScriptEventManager eventManager)
@@ -53,6 +57,33 @@ public ILoggerFactory GetLoggerFactory(WebHostSettings settings)
5357
return GetScriptHostConfiguration(settings).HostConfig.LoggerFactory;
5458
}
5559

60+
public TraceWriter GetTraceWriter(WebHostSettings settings)
61+
{
62+
// if we have an active host, return it's fully configured
63+
// trace writer
64+
var hostManager = GetWebScriptHostManager(settings);
65+
var traceWriter = hostManager.Instance?.TraceWriter;
66+
67+
if (traceWriter != null)
68+
{
69+
return traceWriter;
70+
}
71+
72+
// if there is no active host, return the default trace writer
73+
if (_defaultTraceWriter == null)
74+
{
75+
lock (_syncLock)
76+
{
77+
if (_defaultTraceWriter == null)
78+
{
79+
_defaultTraceWriter = CreateDefaultTraceWriter(settings);
80+
}
81+
}
82+
}
83+
84+
return _defaultTraceWriter;
85+
}
86+
5687
public ISwaggerDocumentManager GetSwaggerDocumentManager(WebHostSettings settings)
5788
{
5889
return GetWebScriptHostManager(settings).SwaggerDocumentManager;
@@ -191,6 +222,23 @@ internal static ScriptHostConfiguration CreateScriptHostConfiguration(WebHostSet
191222
return scriptHostConfig;
192223
}
193224

225+
private TraceWriter CreateDefaultTraceWriter(WebHostSettings settings)
226+
{
227+
// need to set up a default trace writer that logs both host logs and system logs
228+
var config = GetScriptHostConfiguration(settings);
229+
var systemEventGenerator = config.HostConfig.GetService<IEventGenerator>() ?? new EventGenerator();
230+
TraceWriter systemTraceWriter = new SystemTraceWriter(systemEventGenerator, _settingsManager, TraceLevel.Verbose);
231+
232+
// Note that we're creating a logger here that is independent of log configuration settings
233+
// since we haven't read config yet. This logger is independent on the host having been started.
234+
// That does mean that even if file logging is disabled, some logs might get written to the file system
235+
// but that's ok.
236+
string hostLogFilePath = Path.Combine(config.RootLogPath, "Host");
237+
TraceWriter fileTraceWriter = new FileTraceWriter(hostLogFilePath, config.HostConfig.Tracing.ConsoleLevel);
238+
239+
return new CompositeTraceWriter(new[] { systemTraceWriter, fileTraceWriter });
240+
}
241+
194242
/// <summary>
195243
/// Helper function used to manage active/standby transitions for objects managed
196244
/// by this class.
@@ -269,6 +317,7 @@ public void Dispose()
269317
_activeHostManager?.Dispose();
270318
_activeReceiverManager?.Dispose();
271319
_specializationTimer?.Dispose();
320+
((IDisposable)_defaultTraceWriter)?.Dispose();
272321
}
273322
}
274323
}

src/WebJobs.Script.WebHost/GlobalSuppressions.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,4 +109,5 @@
109109
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors", Scope = "member", Target = "Microsoft.Azure.WebJobs.Script.WebHost.SecretManager.#.ctor(Microsoft.Azure.WebJobs.Script.WebHost.ISecretsRepository,Microsoft.Azure.WebJobs.Script.WebHost.IKeyValueConverterFactory,Microsoft.Azure.WebJobs.Host.TraceWriter,Microsoft.Extensions.Logging.ILoggerFactory,System.Boolean)")]
110110
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "host", Scope = "member", Target = "Microsoft.Azure.WebJobs.Script.WebHost.StandbyManager.#WarmUp(Microsoft.Azure.WebJobs.Script.ScriptHost)")]
111111
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "settings", Scope = "member", Target = "Microsoft.Azure.WebJobs.Script.WebHost.WebHostResolver.#GetPerformanceManager(Microsoft.Azure.WebJobs.Script.WebHost.WebHostSettings)")]
112-
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2213:DisposableFieldsShouldBeDisposed", MessageId = "_specializationTimer", Scope = "member", Target = "Microsoft.Azure.WebJobs.Script.WebHost.WebHostResolver.#Dispose()")]
112+
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2213:DisposableFieldsShouldBeDisposed", MessageId = "_specializationTimer", Scope = "member", Target = "Microsoft.Azure.WebJobs.Script.WebHost.WebHostResolver.#Dispose()")]
113+
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Scope = "member", Target = "Microsoft.Azure.WebJobs.Script.WebHost.WebHostResolver.#CreateDefaultTraceWriter(Microsoft.Azure.WebJobs.Script.WebHost.WebHostSettings)")]

src/WebJobs.Script.WebHost/Handlers/WebScriptHostHandler.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage
3131
var scriptHostManager = resolver.GetService<WebScriptHostManager>();
3232
if (!scriptHostManager.Initialized)
3333
{
34-
// need to ensure the host manager is initilized early in the pipeline
34+
// need to ensure the host manager is initialized early in the pipeline
3535
// before any other request code runs
3636
scriptHostManager.Initialize();
3737
}

test/WebJobs.Script.Tests/WebHostResolverTests.cs

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,14 @@
44
using System;
55
using System.Collections.Generic;
66
using System.IO;
7+
using System.Linq;
8+
using System.Reflection;
9+
using System.Threading.Tasks;
10+
using Microsoft.Azure.WebJobs.Host;
711
using Microsoft.Azure.WebJobs.Script.Config;
812
using Microsoft.Azure.WebJobs.Script.Eventing;
913
using Microsoft.Azure.WebJobs.Script.WebHost;
14+
using Microsoft.Azure.WebJobs.Script.WebHost.Diagnostics;
1015
using Moq;
1116
using Xunit;
1217

@@ -58,5 +63,55 @@ public void CreateScriptHostConfiguration_StandbyMode_ReturnsExpectedConfigurati
5863
Assert.Null(config.HostConfig.StorageConnectionString);
5964
Assert.Null(config.HostConfig.DashboardConnectionString);
6065
}
66+
67+
[Fact]
68+
public async Task GetTraceWriter_ReturnsExpectedValue()
69+
{
70+
var settingsManager = new ScriptSettingsManager();
71+
var secretManagerFactoryMock = new Mock<ISecretManagerFactory>();
72+
var eventManagerMock = new Mock<IScriptEventManager>();
73+
var resolver = new WebHostResolver(settingsManager, secretManagerFactoryMock.Object, eventManagerMock.Object);
74+
75+
using (resolver)
76+
{
77+
string tempRoot = Path.GetTempPath();
78+
var settings = new WebHostSettings
79+
{
80+
LogPath = Path.Combine(tempRoot, @"Functions"),
81+
ScriptPath = Path.Combine(tempRoot, @"Functions"),
82+
SecretsPath = Path.Combine(tempRoot, @"Functions"),
83+
};
84+
85+
// ensure that the returned trace writer isn't null even though the
86+
// host hasn't been initialized yet
87+
var traceWriter = resolver.GetTraceWriter(settings);
88+
Assert.NotNull(traceWriter);
89+
var hostManager = resolver.GetWebScriptHostManager(settings);
90+
Assert.Null(hostManager.Instance?.TraceWriter);
91+
92+
// verify the internals of the composite writer
93+
var fieldInfo = typeof(CompositeTraceWriter).GetField("_innerTraceWriters", BindingFlags.Instance | BindingFlags.NonPublic);
94+
TraceWriter[] innerWriters = ((IEnumerable<TraceWriter>)fieldInfo.GetValue(traceWriter)).ToArray();
95+
Assert.Equal(2, innerWriters.Length);
96+
Assert.Equal(typeof(SystemTraceWriter), innerWriters[0].GetType());
97+
Assert.Equal(typeof(FileTraceWriter), innerWriters[1].GetType());
98+
99+
// write a log and verify
100+
TestHelpers.ClearHostLogs();
101+
var id = Guid.NewGuid().ToString();
102+
traceWriter.Info(id);
103+
traceWriter.Flush();
104+
var logs = await TestHelpers.GetHostLogsAsync();
105+
Assert.True(logs.Single().Contains(id));
106+
107+
// now wait for the host to be initialized and verify the trace
108+
// writer returned is the host trace writer
109+
Task ignored = Task.Run(() => hostManager.RunAndBlock());
110+
await TestHelpers.Await(() => hostManager.Instance != null);
111+
112+
traceWriter = resolver.GetTraceWriter(settings);
113+
Assert.Same(hostManager.Instance.TraceWriter, traceWriter);
114+
}
115+
}
61116
}
62117
}

0 commit comments

Comments
 (0)