Skip to content

Commit

Permalink
Added request tracing traces API.
Browse files Browse the repository at this point in the history
  • Loading branch information
jimmyca15 committed Feb 22, 2017
1 parent fb4dae9 commit 4083f96
Show file tree
Hide file tree
Showing 6 changed files with 338 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.


namespace Microsoft.IIS.Administration.WebServer.HttpRequestTracing
{
using AspNetCore.Mvc;
using Core;
using Core.Http;
using Files;
using Sites;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Web.Administration;

public class RequestTracesController : ApiBaseController
{
private IFileProvider _provider;

public RequestTracesController(IFileProvider provider)
{
_provider = provider;
}

[HttpGet]
[ResourceInfo(Name = Defines.TracesName)]
public async Task<object> Get()
{
string hrtUuid = Context.Request.Query[Defines.IDENTIFIER];

if (string.IsNullOrEmpty(hrtUuid)) {
return NotFound();
}

var id = new HttpRequestTracingId(hrtUuid);
Site site = id?.SiteId == null ? null : SiteHelper.GetSite(id.SiteId.Value);

if (site == null) {
return NotFound();
}

var helper = new TracesHelper(_provider, site);

IEnumerable<TraceInfo> traces = await helper.GetTraces();

Context.Response.SetItemsCount(traces.Count());

return new {
traces = traces.Select(t => helper.ToJsonModel(t, Context.Request.GetFields()))
};
}

[HttpGet]
[ResourceInfo(Name = Defines.TraceName)]
public async Task<object> Get(string id)
{
TraceId traceId = new TraceId(id);
Site site = SiteHelper.GetSite(traceId.SiteId);

if (site == null) {
return NotFound();
}

var helper = new TracesHelper(_provider, site);

TraceInfo trace = await helper.GetTrace(traceId.Name);

if (trace == null) {
return NotFound();
}

return helper.ToJsonModel(trace, Context.Request.GetFields());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ public class Defines
private const string ENDPOINT = "http-request-tracing";
private const string PROVIDERS_ENDPOINT = "providers";
private const string RULES_ENDPOINT = "rules";
private const string TRACES_ENDPOINT = "traces";

public const string HttpRequestTracingName = "Microsoft.WebServer.HttpRequestTracing";
public static readonly string PATH = $"{WebServer.Defines.PATH}/{ENDPOINT}";
Expand All @@ -29,5 +30,11 @@ public class Defines
public static readonly string RULES_PATH = $"{PATH}/{RULES_ENDPOINT}";
public static readonly ResDef RulesResource = new ResDef("rules", new Guid("FE6059CD-DF4D-47E9-AAB3-5C380874E323"), RULES_ENDPOINT);
public const string RULES_IDENTIFIER = "rule.id";

public const string TracesName = "Microsoft.WebServer.HttpRequestTracing.Traces";
public const string TraceName = "Microsoft.WebServer.HttpRequestTracing.Trace";
public static readonly string TRACES_PATH = $"{PATH}/{TRACES_ENDPOINT}";
public static readonly ResDef TracesResource = new ResDef("traces", new Guid("3A103124-E4AF-49D9-A379-949F314A89F1"), TRACES_ENDPOINT);
public const string TRACES_IDENTIFIER = "trace.id";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
namespace Microsoft.IIS.Administration.WebServer.HttpRequestTracing
{
using Core;
using Core.Utils;
using Files;
using System;
using System.Collections.Generic;
using System.Dynamic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using System.Xml;
using Web.Administration;

sealed class TracesHelper
{
private static readonly Fields RefFields = new Fields("url", "id", "http_status", "method", "time_taken");

private static readonly XmlReaderSettings _xmlReaderSettings = new XmlReaderSettings() {
Async = true
};

private IFileProvider _provider;
private Site _site;

public TracesHelper(IFileProvider provider, Site site)
{
_provider = provider;
_site = site;
}

public async Task<IEnumerable<TraceInfo>> GetTraces()
{
IEnumerable<IFileInfo> files = null;
string dir = _site.TraceFailedRequestsLogging.Directory;
string path = string.IsNullOrEmpty(dir) ? null : Path.Combine(PathUtil.GetFullPath(dir), "W3SVC" + _site.Id);

if (path != null) {
files = _provider.GetFiles(path, "*.xml");
}

return await Task.WhenAll(files.Select(f => GetTraceInternal(f)));
}

public async Task<TraceInfo> GetTrace(string id)
{
return (await GetTraces()).FirstOrDefault(t => t.File.Name.Equals(id, StringComparison.OrdinalIgnoreCase));
}

public object ToJsonModel(TraceInfo trace, Fields fields = null, bool full = true)
{
TraceId traceId = new TraceId(_site.Id, trace.File.Name);

if (fields == null) {
fields = Fields.All;
}

dynamic obj = new ExpandoObject();

//
// id
obj.id = traceId.Uuid;

//
// url
if (fields.Exists("url") && !string.IsNullOrEmpty(trace.Url)) {
obj.url = trace.Url;
}

//
// method
if (fields.Exists("method") && !string.IsNullOrEmpty(trace.Method)) {
obj.method = trace.Method;
}

//
// status_code
if (fields.Exists("status_code") && trace.StatusCode > 0) {
obj.status_code = trace.StatusCode;
}

//
// date
if (fields.Exists("date")) {
obj.date = trace.Date;
}

//
// time_taken
if (fields.Exists("time_taken")) {
obj.time_taken = trace.TimeTaken;
}

//
// process_id
if (fields.Exists("process_id") && !string.IsNullOrEmpty(trace.ProcessId)) {
obj.process_id = trace.ProcessId;
}

//
// activity_id
if (fields.Exists("activity_id") && !string.IsNullOrEmpty(trace.ActivityId)) {
obj.activity_id = trace.ActivityId;
}

//
// file_info
if (fields.Exists("file_info")) {
obj.file_info = new FilesHelper(_provider).ToJsonModelRef(trace.File, fields.Filter("file_info"));
}

//
// request_tracing
if (fields.Exists("request_tracing")) {
obj.request_tracing = Helper.ToJsonModelRef(_site, "/");
}

return Core.Environment.Hal.Apply(Defines.TracesResource.Guid, obj, full); ;
}

public object ToJsonModelRef(TraceInfo trace, Fields fields = null)
{
if (fields == null || !fields.HasFields) {
return ToJsonModel(trace, RefFields, false);
}
else {
return ToJsonModel(trace, fields, false);
}
}

public static string GetLocation(string id)
{
return $"/{Defines.TRACES_PATH}/{id}";
}

private Task<TraceInfo> GetTraceInternal(IFileInfo trace)
{
return ParseTrace(trace);
}

private async Task<TraceInfo> ParseTrace(IFileInfo trace)
{
TraceInfo info = null;


using (var stream = _provider.GetFileStream(trace.Path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
using (var reader = XmlReader.Create(stream, _xmlReaderSettings)) {
try {

while (await reader.ReadAsync()) {
if (reader.NodeType == XmlNodeType.Element && reader.Name.Equals("failedRequest")) {

info = new TraceInfo() {
File = trace,
Url = reader.GetAttribute("url"),
Method = reader.GetAttribute("verb"),
Date = trace.Created,
ProcessId = reader.GetAttribute("processId"),
ActivityId = reader.GetAttribute("activityId")
};

float.TryParse(reader.GetAttribute("triggerStatusCode"), out info.StatusCode);
int.TryParse(reader.GetAttribute("timeTaken"), out info.TimeTaken);

break;
}
}
}
catch(XmlException) {
// Ignore malformatted XML
}
}

if (info == null) {
info = new TraceInfo() {
File = trace
};
}

return info;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ public override void Start()
ConfigureHttpRequestTracing();
ConfigureProviders();
ConfigureRules();
ConfigureTraces();
}

private void ConfigureXsl()
Expand Down Expand Up @@ -84,5 +85,14 @@ private void ConfigureRules()

Environment.Hal.ProvideLink(Defines.Resource.Guid, Defines.RulesResource.Name, hrt => new { href = $"/{Defines.RULES_PATH}?{Defines.IDENTIFIER}={hrt.id}" });
}

private void ConfigureTraces()
{
Environment.Host.RouteBuilder.MapWebApiRoute(Defines.TracesResource.Guid, $"{ Defines.TRACES_PATH}/{{id?}}", new { controller = "RequestTraces" });

Environment.Hal.ProvideLink(Defines.TracesResource.Guid, "self", r => new { href = TracesHelper.GetLocation(r.id) });

Environment.Hal.ProvideLink(Defines.Resource.Guid, Defines.TracesResource.Name, hrt => new { href = $"/{Defines.TRACES_PATH}?{Defines.IDENTIFIER}={hrt.id}" });
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.


namespace Microsoft.IIS.Administration.WebServer.HttpRequestTracing
{
using System;

public class TraceId
{
private const string PURPOSE = "WebServer.HttpRequestTracing.Trace";
private const char DELIMITER = '\n';

private const uint SITE_ID_INDEX = 0;
private const uint NAME_INDEX = 1;

public long SiteId { get; private set; }
public string Name { get; private set; }
public string Uuid { get; private set; }

public TraceId(string uuid)
{
if (string.IsNullOrEmpty(uuid)) {
throw new ArgumentNullException("uuid");
}

var info = Core.Utils.Uuid.Decode(uuid, PURPOSE).Split(DELIMITER);

this.Name = info[NAME_INDEX];
this.SiteId = long.Parse(info[SITE_ID_INDEX]);
this.Uuid = uuid;
}

public TraceId(long siteId, string name)
{
this.SiteId = siteId;
this.Name = name;
this.Uuid = Core.Utils.Uuid.Encode(this.Name + DELIMITER + this.SiteId.ToString(), PURPOSE);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.


namespace Microsoft.IIS.Administration.WebServer.HttpRequestTracing
{
using Files;
using System;

class TraceInfo
{
public IFileInfo File;
public string Url;
public string Method;
public float StatusCode;
public DateTime Date;
public int TimeTaken;
public string ProcessId;
public string ActivityId;
}
}

0 comments on commit 4083f96

Please sign in to comment.