Skip to content

Commit

Permalink
feat: add dependency id to http dep tracking (#373)
Browse files Browse the repository at this point in the history
* feat: add dependency id to http dep tracking

* pr-style: remove unn blank space
  • Loading branch information
stijnmoreels committed May 18, 2022
1 parent 17333b1 commit 5f153bb
Show file tree
Hide file tree
Showing 6 changed files with 597 additions and 85 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using Arcus.Observability.Telemetry.Core;
using Arcus.Observability.Telemetry.Core.Logging;
using GuardNet;
using Microsoft.AspNetCore.Http;

// ReSharper disable once CheckNamespace
namespace Microsoft.Extensions.Logging
{
/// <summary>
/// Telemetry extensions on the <see cref="ILogger"/> instance to write Application Insights compatible log messages.
/// </summary>
// ReSharper disable once InconsistentNaming
public static class ILoggerHttpDependencyExtensions
{
/// <summary>
/// Logs an HTTP dependency.
/// </summary>
/// <param name="logger">The logger to track the telemetry.</param>
/// <param name="request">The request that started the HTTP communication.</param>
/// <param name="statusCode">The status code that was returned by the service for this HTTP communication.</param>
/// <param name="measurement">The measuring the latency of the HTTP dependency.</param>
/// <param name="context">The context that provides more insights on the dependency that was measured.</param>
/// <exception cref="ArgumentNullException">Thrown when the <paramref name="logger"/>, <paramref name="request"/>, or <paramref name="measurement"/> is <c>null</c>.</exception>
/// <exception cref="ArgumentException">
/// Thrown when the <paramref name="request"/> doesn't have a request URI or HTTP method, the <paramref name="statusCode"/> is outside the bounds of the enumeration.
/// </exception>
public static void LogHttpDependency(
this ILogger logger,
HttpRequest request,
HttpStatusCode statusCode,
DurationMeasurement measurement,
Dictionary<string, object> context = null)
{
Guard.NotNull(logger, nameof(logger), "Requires a logger instance to track telemetry");
Guard.NotNull(request, nameof(request), "Requires a HTTP request message to track a HTTP dependency");
Guard.NotNull(measurement, nameof(measurement), "Requires a dependency measurement instance to track the latency of the HTTP communication when tracking a HTTP dependency");
Guard.For(() => !Enum.IsDefined(typeof(HttpStatusCode), statusCode),
new ArgumentException("Requires a response HTTP status code that's within the bound of the enumeration to track a HTTP dependency"));

LogHttpDependency(logger, request, statusCode, measurement.StartTime, measurement.Elapsed, context);
}

/// <summary>
/// Logs an HTTP dependency.
/// </summary>
/// <param name="logger">The logger to track the telemetry.</param>
/// <param name="request">The request that started the HTTP communication.</param>
/// <param name="statusCode">The status code that was returned by the service for this HTTP communication.</param>
/// <param name="measurement">The measuring the latency of the HTTP dependency.</param>
/// <param name="dependencyId">The ID of the dependency to link as parent ID.</param>
/// <param name="context">The context that provides more insights on the dependency that was measured.</param>
/// <exception cref="ArgumentNullException">Thrown when the <paramref name="logger"/>, <paramref name="request"/>, or <paramref name="measurement"/> is <c>null</c>.</exception>
/// <exception cref="ArgumentException">
/// Thrown when the <paramref name="request"/> doesn't have a request URI or HTTP method, the <paramref name="statusCode"/> is outside the bounds of the enumeration.
/// </exception>
public static void LogHttpDependency(
this ILogger logger,
HttpRequest request,
HttpStatusCode statusCode,
DurationMeasurement measurement,
string dependencyId,
Dictionary<string, object> context = null)
{
Guard.NotNull(logger, nameof(logger), "Requires a logger instance to track telemetry");
Guard.NotNull(request, nameof(request), "Requires a HTTP request message to track a HTTP dependency");
Guard.NotNull(measurement, nameof(measurement), "Requires a dependency measurement instance to track the latency of the HTTP communication when tracking a HTTP dependency");
Guard.For(() => !Enum.IsDefined(typeof(HttpStatusCode), statusCode),
new ArgumentException("Requires a response HTTP status code that's within the bound of the enumeration to track a HTTP dependency"));

LogHttpDependency(logger, request, statusCode, measurement.StartTime, measurement.Elapsed, dependencyId, context);
}

/// <summary>
/// Logs an HTTP dependency
/// </summary>
/// <param name="logger">The logger to track the telemetry.</param>
/// <param name="request">Request that started the HTTP communication</param>
/// <param name="statusCode">Status code that was returned by the service for this HTTP communication</param>
/// <param name="startTime">Point in time when the interaction with the HTTP dependency was started</param>
/// <param name="duration">Duration of the operation</param>
/// <param name="context">Context that provides more insights on the dependency that was measured</param>
/// <exception cref="ArgumentNullException">Thrown when the <paramref name="logger"/> or <paramref name="request"/> is <c>null</c>.</exception>
/// <exception cref="ArgumentOutOfRangeException">Thrown when the <paramref name="duration"/> is a negative time range.</exception>
/// <exception cref="ArgumentException">
/// Thrown when the <paramref name="request"/> doesn't have a request URI or HTTP method, the <paramref name="statusCode"/> is outside the bounds of the enumeration.
/// </exception>
public static void LogHttpDependency(
this ILogger logger,
HttpRequest request,
HttpStatusCode statusCode,
DateTimeOffset startTime,
TimeSpan duration,
Dictionary<string, object> context = null)
{
Guard.NotNull(logger, nameof(logger), "Requires a logger instance to track telemetry");
Guard.NotNull(request, nameof(request), "Requires a HTTP request message to track a HTTP dependency");
Guard.NotLessThan(duration, TimeSpan.Zero, nameof(duration), "Requires a positive time duration of the HTTP dependency operation");
Guard.For(() => !Enum.IsDefined(typeof(HttpStatusCode), statusCode),
new ArgumentException("Requires a response HTTP status code that's within the bound of the enumeration to track a HTTP dependency"));

context = context ?? new Dictionary<string, object>();

LogHttpDependency(logger, request, statusCode, startTime, duration, dependencyId: null, context);
}

/// <summary>
/// Logs an HTTP dependency
/// </summary>
/// <param name="logger">The logger to track the telemetry.</param>
/// <param name="request">Request that started the HTTP communication</param>
/// <param name="statusCode">Status code that was returned by the service for this HTTP communication</param>
/// <param name="startTime">Point in time when the interaction with the HTTP dependency was started</param>
/// <param name="duration">Duration of the operation</param>
/// <param name="dependencyId">The ID of the dependency to link as parent ID.</param>
/// <param name="context">Context that provides more insights on the dependency that was measured</param>
/// <exception cref="ArgumentNullException">Thrown when the <paramref name="logger"/> or <paramref name="request"/> is <c>null</c>.</exception>
/// <exception cref="ArgumentOutOfRangeException">Thrown when the <paramref name="duration"/> is a negative time range.</exception>
/// <exception cref="ArgumentException">
/// Thrown when the <paramref name="request"/> doesn't have a request URI or HTTP method, the <paramref name="statusCode"/> is outside the bounds of the enumeration.
/// </exception>
public static void LogHttpDependency(
this ILogger logger,
HttpRequest request,
HttpStatusCode statusCode,
DateTimeOffset startTime,
TimeSpan duration,
string dependencyId,
Dictionary<string, object> context = null)
{
Guard.NotNull(logger, nameof(logger), "Requires a logger instance to track telemetry");
Guard.NotNull(request, nameof(request), "Requires a HTTP request message to track a HTTP dependency");
Guard.NotLessThan(duration, TimeSpan.Zero, nameof(duration), "Requires a positive time duration of the HTTP dependency operation");
Guard.For(() => !Enum.IsDefined(typeof(HttpStatusCode), statusCode),
new ArgumentException("Requires a response HTTP status code that's within the bound of the enumeration to track a HTTP dependency"));

context = context ?? new Dictionary<string, object>();

string requestUri = request.Path;
string targetName = request.Host.Host;
string requestMethod = request.Method;
string dependencyName = $"{requestMethod} {requestUri}";
bool isSuccessful = (int)statusCode >= 200 && (int)statusCode < 300;

logger.LogWarning(MessageFormats.HttpDependencyFormat, new DependencyLogEntry(
dependencyType: "Http",
dependencyName: dependencyName,
dependencyData: null,
targetName: targetName,
duration: duration,
startTime: startTime,
dependencyId: dependencyId,
resultCode: (int)statusCode,
isSuccessful: isSuccessful,
context: context));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ namespace Microsoft.Extensions.Logging
/// Telemetry extensions on the <see cref="ILogger"/> instance to write Application Insights compatible log messages.
/// </summary>
// ReSharper disable once InconsistentNaming
public static class ILoggerExtensions
public static class ILoggerRequestExtensions
{
/// <summary>
/// Logs an HTTP request
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,71 @@ public static partial class ILoggerExtensions
LogHttpDependency(logger, request, statusCode, measurement.StartTime, measurement.Elapsed, context);
}

/// <summary>
/// Logs an HTTP dependency.
/// </summary>
/// <param name="logger">The logger to track the telemetry.</param>
/// <param name="request">The request that started the HTTP communication.</param>
/// <param name="statusCode">The status code that was returned by the service for this HTTP communication.</param>
/// <param name="measurement">The measuring the latency of the HTTP dependency.</param>
/// <param name="dependencyId">The ID of the dependency to link as parent ID.</param>
/// <param name="context">The context that provides more insights on the dependency that was measured.</param>
/// <exception cref="ArgumentNullException">Thrown when the <paramref name="logger"/>, <paramref name="request"/>, or <paramref name="measurement"/> is <c>null</c>.</exception>
/// <exception cref="ArgumentException">
/// Thrown when the <paramref name="request"/> doesn't have a request URI or HTTP method, the <paramref name="statusCode"/> is outside the bounds of the enumeration.
/// </exception>
public static void LogHttpDependency(
this ILogger logger,
HttpRequestMessage request,
HttpStatusCode statusCode,
DurationMeasurement measurement,
string dependencyId,
Dictionary<string, object> context = null)
{
Guard.NotNull(logger, nameof(logger), "Requires a logger instance to track telemetry");
Guard.NotNull(request, nameof(request), "Requires a HTTP request message to track a HTTP dependency");
Guard.NotNull(measurement, nameof(measurement), "Requires a dependency measurement instance to track the latency of the HTTP communication when tracking a HTTP dependency");
Guard.For(() => !Enum.IsDefined(typeof(HttpStatusCode), statusCode),
new ArgumentException("Requires a response HTTP status code that's within the bound of the enumeration to track a HTTP dependency"));

LogHttpDependency(logger, request, statusCode, measurement.StartTime, measurement.Elapsed, dependencyId, context);
}

/// <summary>
/// Logs an HTTP dependency
/// </summary>
/// <param name="logger">The logger to track the telemetry.</param>
/// <param name="request">Request that started the HTTP communication</param>
/// <param name="statusCode">Status code that was returned by the service for this HTTP communication</param>
/// <param name="startTime">Point in time when the interaction with the HTTP dependency was started</param>
/// <param name="duration">Duration of the operation</param>
/// <param name="context">Context that provides more insights on the dependency that was measured</param>
/// <exception cref="ArgumentNullException">Thrown when the <paramref name="logger"/> or <paramref name="request"/> is <c>null</c>.</exception>
/// <exception cref="ArgumentOutOfRangeException">Thrown when the <paramref name="duration"/> is a negative time range.</exception>
/// <exception cref="ArgumentException">
/// Thrown when the <paramref name="request"/> doesn't have a request URI or HTTP method, the <paramref name="statusCode"/> is outside the bounds of the enumeration.
/// </exception>
public static void LogHttpDependency(
this ILogger logger,
HttpRequestMessage request,
HttpStatusCode statusCode,
DateTimeOffset startTime,
TimeSpan duration,
Dictionary<string, object> context = null)
{
Guard.NotNull(logger, nameof(logger), "Requires a logger instance to track telemetry");
Guard.NotNull(request, nameof(request), "Requires a HTTP request message to track a HTTP dependency");
Guard.NotLessThan(duration, TimeSpan.Zero, nameof(duration), "Requires a positive time duration of the HTTP dependency operation");
Guard.For(() => request.RequestUri is null, new ArgumentException("Requires a HTTP request URI to track a HTTP dependency", nameof(request)));
Guard.For(() => request.Method is null, new ArgumentException("Requires a HTTP request method to track a HTTP dependency", nameof(request)));
Guard.For(() => !Enum.IsDefined(typeof(HttpStatusCode), statusCode),
new ArgumentException("Requires a response HTTP status code that's within the bound of the enumeration to track a HTTP dependency"));

context = context ?? new Dictionary<string, object>();

LogHttpDependency(logger, request, statusCode, startTime, duration, dependencyId: null, context);
}

/// <summary>
/// Logs an HTTP dependency
/// </summary>
Expand All @@ -80,6 +145,7 @@ public static partial class ILoggerExtensions
/// <param name="statusCode">Status code that was returned by the service for this HTTP communication</param>
/// <param name="startTime">Point in time when the interaction with the HTTP dependency was started</param>
/// <param name="duration">Duration of the operation</param>
/// <param name="dependencyId">The ID of the dependency to link as parent ID.</param>
/// <param name="context">Context that provides more insights on the dependency that was measured</param>
/// <exception cref="ArgumentNullException">Thrown when the <paramref name="logger"/> or <paramref name="request"/> is <c>null</c>.</exception>
/// <exception cref="ArgumentOutOfRangeException">Thrown when the <paramref name="duration"/> is a negative time range.</exception>
Expand All @@ -92,6 +158,7 @@ public static partial class ILoggerExtensions
HttpStatusCode statusCode,
DateTimeOffset startTime,
TimeSpan duration,
string dependencyId,
Dictionary<string, object> context = null)
{
Guard.NotNull(logger, nameof(logger), "Requires a logger instance to track telemetry");
Expand All @@ -117,7 +184,8 @@ public static partial class ILoggerExtensions
targetName: targetName,
duration: duration,
startTime: startTime,
resultCode: (int)statusCode,
dependencyId: dependencyId,
resultCode: (int) statusCode,
isSuccessful: isSuccessful,
context: context));
}
Expand Down
Loading

0 comments on commit 5f153bb

Please sign in to comment.