Skip to content

Commit 870e7e9

Browse files
committed
Adding HTTP pipeline logging for all controllers
1 parent d8ea4c6 commit 870e7e9

File tree

18 files changed

+264
-45
lines changed

18 files changed

+264
-45
lines changed

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

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
using Autofac.Integration.WebApi;
1010
using Microsoft.Azure.WebJobs.Script.Config;
1111
using Microsoft.Azure.WebJobs.Script.WebHost.Controllers;
12+
using Microsoft.Azure.WebJobs.Script.WebHost.Filters;
1213
using Microsoft.Azure.WebJobs.Script.WebHost.Handlers;
1314

1415
namespace Microsoft.Azure.WebJobs.Script.WebHost
@@ -48,7 +49,7 @@ public static void Register(HttpConfiguration config, ScriptSettingsManager sett
4849
var container = builder.Build();
4950
config.DependencyResolver = new AutofacWebApiDependencyResolver(container);
5051
config.Formatters.Add(new PlaintextMediaTypeFormatter());
51-
config.MessageHandlers.Add(new WebScriptHostHandler(config));
52+
AddMessageHandlers(config);
5253

5354
// Web API configuration and services
5455

@@ -81,5 +82,11 @@ public static void Register(HttpConfiguration config, ScriptSettingsManager sett
8182
config.InitializeReceiveGitHubWebHooks();
8283
config.InitializeReceiveSalesforceWebHooks();
8384
}
85+
86+
private static void AddMessageHandlers(HttpConfiguration config)
87+
{
88+
config.MessageHandlers.Add(new WebScriptHostHandler(config));
89+
config.MessageHandlers.Add(new SystemTraceHandler(config));
90+
}
8491
}
8592
}

src/WebJobs.Script.WebHost/Controllers/AdminController.cs

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,11 @@
1111
using System.Threading.Tasks;
1212
using System.Web.Http;
1313
using System.Web.Http.Controllers;
14-
using Microsoft.Azure.WebJobs.Script.Config;
14+
using Microsoft.Azure.WebJobs.Host;
1515
using Microsoft.Azure.WebJobs.Script.Description;
1616
using Microsoft.Azure.WebJobs.Script.WebHost.Filters;
1717
using Microsoft.Azure.WebJobs.Script.WebHost.Models;
18+
using Newtonsoft.Json;
1819

1920
namespace Microsoft.Azure.WebJobs.Script.WebHost.Controllers
2021
{
@@ -27,11 +28,13 @@ public class AdminController : ApiController
2728
{
2829
private readonly WebScriptHostManager _scriptHostManager;
2930
private readonly WebHostSettings _webHostSettings;
31+
private readonly TraceWriter _traceWriter;
3032

31-
public AdminController(WebScriptHostManager scriptHostManager, WebHostSettings webHostSettings)
33+
public AdminController(WebScriptHostManager scriptHostManager, WebHostSettings webHostSettings, TraceWriter traceWriter)
3234
{
3335
_scriptHostManager = scriptHostManager;
3436
_webHostSettings = webHostSettings;
37+
_traceWriter = traceWriter.WithSource($"{ScriptConstants.TraceSourceHostAdmin}.Api");
3538
}
3639

3740
[HttpPost]
@@ -90,8 +93,6 @@ public FunctionStatus GetFunctionStatus(string name)
9093
[AllowAnonymous]
9194
public IHttpActionResult GetHostStatus()
9295
{
93-
// based on the authorization level we determine
94-
// the level of detail to return
9596
var authorizationLevel = Request.GetAuthorizationLevel();
9697
if (authorizationLevel == AuthorizationLevel.Admin ||
9798
Request.IsAntaresInternalRequest())
@@ -120,15 +121,24 @@ public IHttpActionResult GetHostStatus()
120121
};
121122
}
122123

124+
_traceWriter.Info($"Host Status: {JsonConvert.SerializeObject(status, Formatting.Indented)}");
125+
123126
return Ok(status);
124127
}
125128
else
126129
{
127-
// for Anonymous requests, we don't return any info
128-
return Ok();
130+
return Unauthorized();
129131
}
130132
}
131133

134+
[HttpPost]
135+
[Route("admin/host/ping")]
136+
[AllowAnonymous]
137+
public IHttpActionResult Ping()
138+
{
139+
return Ok();
140+
}
141+
132142
[HttpPost]
133143
[Route("admin/host/debug")]
134144
public HttpResponseMessage LaunchDebugger()

src/WebJobs.Script.WebHost/Controllers/FunctionsController.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ public override async Task<HttpResponseMessage> ExecuteAsync(HttpControllerConte
4747
var authorizationLevel = settings.IsAuthDisabled
4848
? AuthorizationLevel.Admin
4949
: await AuthorizationLevelAttribute.GetAuthorizationLevelAsync(request, secretManager, functionName: function.Name);
50+
request.SetAuthorizationLevel(authorizationLevel);
5051

5152
if (function.Metadata.IsExcluded ||
5253
(function.Metadata.IsDisabled && authorizationLevel != AuthorizationLevel.Admin))

src/WebJobs.Script.WebHost/Filters/AuthorizationLevelAttribute.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ public async override Task OnAuthorizationAsync(HttpActionContext actionContext,
3838
var secretManager = actionContext.ControllerContext.Configuration.DependencyResolver.GetService<ISecretManager>();
3939
var settings = actionContext.ControllerContext.Configuration.DependencyResolver.GetService<WebHostSettings>();
4040
var requestAuthorizationLevel = await GetAuthorizationLevelAsync(actionContext.Request, secretManager, EvaluateKeyMatch);
41-
actionContext.Request.Properties[ScriptConstants.AzureFunctionsHttpRequestAuthorizationLevel] = requestAuthorizationLevel;
41+
actionContext.Request.SetAuthorizationLevel(requestAuthorizationLevel);
4242

4343
if (settings.IsAuthDisabled ||
4444
SkipAuthorization(actionContext) ||
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the MIT License. See License.txt in the project root for license information.
3+
4+
using System.Net.Http;
5+
using System.Threading;
6+
using System.Threading.Tasks;
7+
using System.Web.Http;
8+
using Microsoft.Azure.WebJobs.Host;
9+
using Newtonsoft.Json.Linq;
10+
11+
namespace Microsoft.Azure.WebJobs.Script.WebHost.Handlers
12+
{
13+
public class SystemTraceHandler : DelegatingHandler
14+
{
15+
private readonly HttpConfiguration _config;
16+
17+
public SystemTraceHandler(HttpConfiguration config)
18+
{
19+
_config = config;
20+
}
21+
22+
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
23+
{
24+
var traceWriter = _config.DependencyResolver.GetService<TraceWriter>();
25+
var details = new JObject
26+
{
27+
{ "id", request.GetRequestId() },
28+
{ "method", request.Method.ToString() },
29+
{ "uri", request.RequestUri.LocalPath.ToString() }
30+
};
31+
traceWriter.Info($"Executing HTTP request: {details}");
32+
33+
var response = await base.SendAsync(request, cancellationToken);
34+
35+
details = new JObject
36+
{
37+
{ "id", request.GetRequestId() },
38+
{ "method", request.Method.ToString() },
39+
{ "uri", request.RequestUri.LocalPath.ToString() },
40+
{ "authorizationLevel", request.GetAuthorizationLevel().ToString() }
41+
};
42+
traceWriter.Info($"Executed HTTP request: {details}");
43+
44+
details = new JObject
45+
{
46+
{ "id", request.GetRequestId() },
47+
{ "status", response.StatusCode.ToString() }
48+
};
49+
traceWriter.Info($"Response details: {details}");
50+
51+
return response;
52+
}
53+
}
54+
}

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage
3333
{
3434
var scriptHostManager = _config.DependencyResolver.GetService<WebScriptHostManager>();
3535

36+
SetRequestId(request);
37+
3638
// some routes do not require the host to be running (most do)
3739
// in standby mode, we don't want to wait for host start
3840
bool bypassHostCheck = request.RequestUri.LocalPath.Trim('/').ToLowerInvariant().EndsWith("admin/host/status") ||
@@ -69,5 +71,11 @@ protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage
6971

7072
return await base.SendAsync(request, cancellationToken);
7173
}
74+
75+
internal static void SetRequestId(HttpRequestMessage request)
76+
{
77+
string requestID = request.GetHeaderValueOrDefault(ScriptConstants.AntaresLogIdHeaderName) ?? Guid.NewGuid().ToString();
78+
request.Properties[ScriptConstants.AzureFunctionsRequestIdKey] = requestID;
79+
}
7280
}
7381
}

src/WebJobs.Script.WebHost/WebJobs.Script.WebHost.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -423,6 +423,7 @@
423423
<DependentUpon>Global.asax</DependentUpon>
424424
</Compile>
425425
<Compile Include="GlobalSuppressions.cs" />
426+
<Compile Include="Handlers\SystemTraceHandler.cs" />
426427
<Compile Include="Handlers\WebScriptHostHandler.cs" />
427428
<Compile Include="Models\ApiModel.cs" />
428429
<Compile Include="Models\ApiModelUtility.cs" />

src/WebJobs.Script/Extensions/HttpRequestMessageExtensions.cs

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,17 @@ public static class HttpRequestMessageExtensions
1313
{
1414
public static AuthorizationLevel GetAuthorizationLevel(this HttpRequestMessage request)
1515
{
16-
return request.GetRequestPropertyOrDefault<AuthorizationLevel>(ScriptConstants.AzureFunctionsHttpRequestAuthorizationLevel);
16+
return request.GetRequestPropertyOrDefault<AuthorizationLevel>(ScriptConstants.AzureFunctionsHttpRequestAuthorizationLevelKey);
17+
}
18+
19+
public static string GetRequestId(this HttpRequestMessage request)
20+
{
21+
return request.GetRequestPropertyOrDefault<string>(ScriptConstants.AzureFunctionsRequestIdKey);
22+
}
23+
24+
public static void SetAuthorizationLevel(this HttpRequestMessage request, AuthorizationLevel authorizationLevel)
25+
{
26+
request.Properties[ScriptConstants.AzureFunctionsHttpRequestAuthorizationLevelKey] = authorizationLevel;
1727
}
1828

1929
public static bool IsAntaresInternalRequest(this HttpRequestMessage request)
@@ -26,7 +36,17 @@ public static bool IsAntaresInternalRequest(this HttpRequestMessage request)
2636
// this header will *always* be present on requests originating externally (i.e. going
2737
// through the Anatares front end). For requests originating internally it will NOT be
2838
// present.
29-
return !request.Headers.Contains(ScriptConstants.AntaresExternalRequestHeaderName);
39+
return !request.Headers.Contains(ScriptConstants.AntaresLogIdHeaderName);
40+
}
41+
42+
public static string GetHeaderValueOrDefault(this HttpRequestMessage request, string headerName)
43+
{
44+
IEnumerable<string> values = null;
45+
if (request.Headers.TryGetValues(headerName, out values))
46+
{
47+
return values.First();
48+
}
49+
return null;
3050
}
3151

3252
public static TValue GetRequestPropertyOrDefault<TValue>(this HttpRequestMessage request, string key)

src/WebJobs.Script/ScriptConstants.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ public static class ScriptConstants
1111
public const string AzureFunctionsWebHookDataKey = "MS_AzureFunctionsWebHookData";
1212
public const string AzureFunctionsHttpResponseKey = "MS_AzureFunctionsHttpResponse";
1313
public const string AzureFunctionsHttpRouteDataKey = "MS_AzureFunctionsHttpRouteData";
14-
public const string AzureFunctionsHttpRequestAuthorizationLevel = "MS_AzureFunctionsAuthorizationLevel";
14+
public const string AzureFunctionsHttpRequestAuthorizationLevelKey = "MS_AzureFunctionsAuthorizationLevel";
15+
public const string AzureFunctionsRequestIdKey = "MS_AzureFunctionsRequestID";
1516

1617
public const string TracePropertyPrimaryHostKey = "MS_PrimaryHost";
1718
public const string TracePropertyFunctionNameKey = "MS_FunctionName";
@@ -21,6 +22,7 @@ public static class ScriptConstants
2122
public const string TracePropertyIsSystemTraceKey = "MS_IsSystemTrace";
2223

2324
public const string TraceSourceSecretManagement = "SecretManagement";
25+
public const string TraceSourceHostAdmin = "HostAdmin";
2426
public const string TraceSourceFileWatcher = "FileWatcher";
2527

2628
// Define all system parameters we inject with a prefix to avoid collisions
@@ -39,7 +41,7 @@ public static class ScriptConstants
3941
public const string DefaultMasterKeyName = "master";
4042
public const string DefaultFunctionKeyName = "default";
4143

42-
public const string AntaresExternalRequestHeaderName = "X-ARR-LOG-ID";
44+
public const string AntaresLogIdHeaderName = "X-ARR-LOG-ID";
4345
public const string CheckLoadQueryParameterName = "checkLoad";
4446
public const string DynamicSku = "Dynamic";
4547

test/WebJobs.Script.Tests.Integration/Host/StandbyModeTests.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,12 @@ public void GetWebHookReceiverManager_ReturnsExpectedValue()
6363
TestGetter(_webHostResolver.GetWebHookReceiverManager);
6464
}
6565

66+
[Fact]
67+
public void GetPerformanceManager_ReturnsExpectedValue()
68+
{
69+
TestGetter(_webHostResolver.GetPerformanceManager);
70+
}
71+
6672
[Fact]
6773
public void GetWebScriptHostManager_ReturnsExpectedValue()
6874
{

0 commit comments

Comments
 (0)