Feature/fix tracing (#297)

* hacked together tracing fix by wrapping middleware delegate in another delegate

* #227 have re-implemented tracing, cleaned up trace names, probably still need some refactoring and tests as this was a bit of a hack job

* #227 bit of checking for when we dont want to use tracing, also removed a unit test for websockets that wasnt a unit test, i stuck it there because i wanted the code coverage and now im paying the price, will have to work out a better way to do it

* #227 a bit of refactoring to make this work better, still a bit hacky...would like to revisit the whole thing one day

* #227 dont need this

* #227 or this

* #227 small refactor
This commit is contained in:
Tom Pallister
2018-04-02 18:12:35 +01:00
committed by GitHub
parent 463a7bdab4
commit ec545fadae
14 changed files with 228 additions and 283 deletions

View File

@ -1,13 +1,24 @@
using Ocelot.Configuration.File;
using Butterfly.Client.Tracing;
using Ocelot.Configuration.File;
using Ocelot.Requester;
namespace Ocelot.Configuration.Creator
{
public class HttpHandlerOptionsCreator : IHttpHandlerOptionsCreator
{
private IServiceTracer _tracer;
public HttpHandlerOptionsCreator(IServiceTracer tracer)
{
_tracer = tracer;
}
public HttpHandlerOptions Create(FileReRoute fileReRoute)
{
var useTracing = _tracer.GetType() != typeof(FakeServiceTracer) ? fileReRoute.HttpHandlerOptions.UseTracing : false;
return new HttpHandlerOptions(fileReRoute.HttpHandlerOptions.AllowAutoRedirect,
fileReRoute.HttpHandlerOptions.UseCookieContainer, fileReRoute.HttpHandlerOptions.UseTracing);
fileReRoute.HttpHandlerOptions.UseCookieContainer, useTracing);
}
}
}

View File

@ -0,0 +1,17 @@
using Microsoft.Extensions.Primitives;
using System.Linq;
namespace Ocelot.Infrastructure.Extensions
{
internal static class StringValueExtensions
{
public static string GetValue(this StringValues stringValues)
{
if (stringValues.Count == 1)
{
return stringValues;
}
return stringValues.ToArray().LastOrDefault();
}
}
}

View File

@ -0,0 +1,22 @@
using System;
namespace Ocelot.Logging
{
/// <summary>
/// Thin wrapper around the DotNet core logging framework, used to allow the scopedDataRepository to be injected giving access to the Ocelot RequestId
/// </summary>
public interface IOcelotLogger
{
void LogTrace(string message, params object[] args);
void LogDebug(string message, params object[] args);
void LogInformation(string message, params object[] args);
void LogError(string message, Exception exception);
void LogError(string message, params object[] args);
void LogCritical(string message, Exception exception);
/// <summary>
/// The name of the type the logger has been built for.
/// </summary>
string Name { get; }
}
}

View File

@ -1,27 +1,7 @@
using System;
namespace Ocelot.Logging
namespace Ocelot.Logging
{
public interface IOcelotLoggerFactory
{
IOcelotLogger CreateLogger<T>();
}
/// <summary>
/// Thin wrapper around the DotNet core logging framework, used to allow the scopedDataRepository to be injected giving access to the Ocelot RequestId
/// </summary>
public interface IOcelotLogger
{
void LogTrace(string message, params object[] args);
void LogDebug(string message, params object[] args);
void LogInformation(string message, params object[] args);
void LogError(string message, Exception exception);
void LogError(string message, params object[] args);
void LogCritical(string message, Exception exception);
/// <summary>
/// The name of the type the logger has been built for.
/// </summary>
string Name { get; }
}
}

View File

@ -3,18 +3,48 @@ using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DiagnosticAdapter;
using Butterfly.Client.AspNetCore;
using Butterfly.OpenTracing;
using Ocelot.Middleware;
using Butterfly.Client.Tracing;
using System.Linq;
using System.Collections.Generic;
using Ocelot.Infrastructure.Extensions;
using Microsoft.Extensions.Logging;
using Ocelot.Requester;
namespace Ocelot.Logging
{
public class OcelotDiagnosticListener
{
private IServiceTracer _tracer;
private IOcelotLogger _logger;
public OcelotDiagnosticListener(IOcelotLoggerFactory factory)
public OcelotDiagnosticListener(IOcelotLoggerFactory factory, IServiceTracer tracer)
{
_tracer = tracer;
_logger = factory.CreateLogger<OcelotDiagnosticListener>();
}
[DiagnosticName("Ocelot.MiddlewareException")]
public virtual void OcelotMiddlewareException(Exception exception, DownstreamContext context, string name)
{
_logger.LogTrace($"Ocelot.MiddlewareException: {name}; {exception.Message}");
Event(context.HttpContext, $"Ocelot.MiddlewareStarted: {name}; {context.HttpContext.Request.Path}");
}
[DiagnosticName("Ocelot.MiddlewareStarted")]
public virtual void OcelotMiddlewareStarted(DownstreamContext context, string name)
{
_logger.LogTrace($"Ocelot.MiddlewareStarted: {name}; {context.HttpContext.Request.Path}");
Event(context.HttpContext, $"Ocelot.MiddlewareStarted: {name}; {context.HttpContext.Request.Path}");
}
[DiagnosticName("Ocelot.MiddlewareFinished")]
public virtual void OcelotMiddlewareFinished(DownstreamContext context, string name)
{
_logger.LogTrace($"OcelotMiddlewareFinished: {name}; {context.HttpContext.Request.Path}");
Event(context.HttpContext, $"OcelotMiddlewareFinished: {name}; {context.HttpContext.Request.Path}");
}
[DiagnosticName("Microsoft.AspNetCore.MiddlewareAnalysis.MiddlewareStarting")]
public virtual void OnMiddlewareStarting(HttpContext httpContext, string name)
{
@ -36,8 +66,28 @@ namespace Ocelot.Logging
}
private void Event(HttpContext httpContext, string @event)
{
{
// Hack - if the user isnt using tracing the code gets here and will blow up on
// _tracer.Tracer.TryExtract. We already use the fake tracer for another scenario
// so sticking it here as well..I guess we need a factory for this but no idea
// how to hook that into the diagnostic framework at the moment.
if(_tracer.GetType() == typeof(FakeServiceTracer))
{
return;
}
var span = httpContext.GetSpan();
if(span == null)
{
var spanBuilder = new SpanBuilder($"server {httpContext.Request.Method} {httpContext.Request.Path}");
if (_tracer.Tracer.TryExtract(out var spanContext, httpContext.Request.Headers, (c, k) => c[k].GetValue(),
c => c.Select(x => new KeyValuePair<string, string>(x.Key, x.Value.GetValue())).GetEnumerator()))
{
spanBuilder.AsChildOf(spanContext);
};
span = _tracer.Start(spanBuilder);
httpContext.SetSpan(span);
}
span?.Log(LogField.CreateNew().Event(@event));
}
}

View File

@ -65,6 +65,8 @@
rest of asp.net..
*/
builder.Properties["analysis.NextMiddlewareName"] = "TransitionToOcelotMiddleware";
builder.Use(async (context, task) =>
{
var downstreamContext = new DownstreamContext(context);

View File

@ -3,6 +3,7 @@
// Removed code and changed RequestDelete to OcelotRequestDelete, HttpContext to DownstreamContext, removed some exception handling messages
using System;
using System.Diagnostics;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
@ -75,7 +76,28 @@ namespace Ocelot.Middleware.Pipeline
var instance = ActivatorUtilities.CreateInstance(app.ApplicationServices, middleware, ctorArgs);
if (parameters.Length == 1)
{
return (OcelotRequestDelegate)methodinfo.CreateDelegate(typeof(OcelotRequestDelegate), instance);
var ocelotDelegate = (OcelotRequestDelegate)methodinfo.CreateDelegate(typeof(OcelotRequestDelegate), instance);
var diagnosticListener = (DiagnosticListener)app.ApplicationServices.GetService(typeof(DiagnosticListener));
var middlewareName = ocelotDelegate.Target.GetType().Name;
OcelotRequestDelegate wrapped = context => {
try
{
Write(diagnosticListener, "Ocelot.MiddlewareStarted", middlewareName, context);
return ocelotDelegate(context);
}
catch(Exception ex)
{
Write(diagnosticListener, "Ocelot.MiddlewareException", middlewareName, context);
throw ex;
}
finally
{
Write(diagnosticListener, "Ocelot.MiddlewareFinished", middlewareName, context);
}
};
return wrapped;
}
var factory = Compile<object>(methodinfo, parameters);
@ -93,6 +115,14 @@ namespace Ocelot.Middleware.Pipeline
});
}
private static void Write(DiagnosticListener diagnosticListener, string message, string middlewareName, DownstreamContext context)
{
if(diagnosticListener != null)
{
diagnosticListener.Write(message, new { name = middlewareName, context = context });
}
}
public static IOcelotPipelineBuilder MapWhen(this IOcelotPipelineBuilder app, Predicate predicate, Action<IOcelotPipelineBuilder> configuration)
{
if (app == null)

View File

@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using Butterfly.Client.Tracing;
using Microsoft.Extensions.DependencyInjection;
using Ocelot.Configuration;
using Ocelot.Logging;

View File

@ -0,0 +1,17 @@
using Butterfly.Client.Tracing;
using Butterfly.OpenTracing;
namespace Ocelot.Requester
{
public class FakeServiceTracer : IServiceTracer
{
public ITracer Tracer { get; }
public string ServiceName { get; }
public string Environment { get; }
public string Identity { get; }
public ISpan Start(ISpanBuilder spanBuilder)
{
return null;
}
}
}

View File

@ -1,5 +1,4 @@
using Butterfly.Client.Tracing;
using Butterfly.OpenTracing;
using Ocelot.Infrastructure.RequestData;
namespace Ocelot.Requester
@ -22,16 +21,4 @@ namespace Ocelot.Requester
return new OcelotHttpTracingHandler(_tracer, _repo);
}
}
public class FakeServiceTracer : IServiceTracer
{
public ITracer Tracer { get; }
public string ServiceName { get; }
public string Environment { get; }
public string Identity { get; }
public ISpan Start(ISpanBuilder spanBuilder)
{
throw new System.NotImplementedException();
}
}
}