Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Trace Actuator Updates #1356

Merged
merged 11 commits into from
Sep 25, 2024
14 changes: 14 additions & 0 deletions src/Common/src/Common/Json/JsonIgnoreEmptyCollectionAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
// See the LICENSE file in the project root for more information.

using System.Text.Json;

namespace Steeltoe.Common.Json;

/// <summary>
/// Indicates that a collection property should not be serialized if the collection is empty. Use
/// <see cref="JsonSerializerOptionsExtensions.AddJsonIgnoreEmptyCollection" /> to register in <see cref="JsonSerializerOptions" />.
/// </summary>
[AttributeUsage(AttributeTargets.Property)]
public sealed class JsonIgnoreEmptyCollectionAttribute : Attribute;
48 changes: 48 additions & 0 deletions src/Common/src/Common/Json/JsonSerializerOptionsExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
// See the LICENSE file in the project root for more information.

using System.Collections;
using System.Text.Json;
using System.Text.Json.Serialization.Metadata;

namespace Steeltoe.Common.Json;

public static class JsonSerializerOptionsExtensions
{
/// <summary>
/// Registers a contract modifier in the specified <see cref="JsonSerializerOptions" /> that skips serialization of empty collections marked with
/// <see cref="JsonIgnoreEmptyCollectionAttribute" />.
/// </summary>
/// <param name="options">
/// The JSON serializer options to configure.
/// </param>
/// <returns>
/// The incoming <paramref name="options" /> so that additional calls can be chained.
/// </returns>
public static JsonSerializerOptions AddJsonIgnoreEmptyCollection(this JsonSerializerOptions options)
{
ArgumentNullException.ThrowIfNull(options);

options.TypeInfoResolverChain.Insert(0, new DefaultJsonTypeInfoResolver
{
Modifiers =
{
SkipEmptyCollection
}
});

return options;
}

private static void SkipEmptyCollection(JsonTypeInfo info)
{
foreach (JsonPropertyInfo property in info.Properties)
{
if (property.AttributeProvider != null && property.AttributeProvider.IsDefined(typeof(JsonIgnoreEmptyCollectionAttribute), true))
{
property.ShouldSerialize = (_, value) => value is not IEnumerable enumerable || enumerable.Cast<object>().Any();
}
}
}
}
4 changes: 4 additions & 0 deletions src/Common/src/Common/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ override Steeltoe.Common.CasingConventions.SnakeCaseAllCapsEnumMemberJsonConvert
override Steeltoe.Common.CasingConventions.SnakeCaseAllCapsEnumMemberJsonConverter.CreateConverter(System.Type! typeToConvert, System.Text.Json.JsonSerializerOptions! options) -> System.Text.Json.Serialization.JsonConverter!
static Steeltoe.Common.CasingConventions.EnumExtensions.ToSnakeCaseString<TEnum>(this TEnum value, Steeltoe.Common.CasingConventions.SnakeCaseStyle style) -> string!
static Steeltoe.Common.Extensions.ServiceCollectionExtensions.AddApplicationInstanceInfo(this Microsoft.Extensions.DependencyInjection.IServiceCollection! services) -> Microsoft.Extensions.DependencyInjection.IServiceCollection!
static Steeltoe.Common.Json.JsonSerializerOptionsExtensions.AddJsonIgnoreEmptyCollection(this System.Text.Json.JsonSerializerOptions! options) -> System.Text.Json.JsonSerializerOptions!
static Steeltoe.Common.Platform.IsCloudFoundry.get -> bool
static Steeltoe.Common.Platform.IsCloudHosted.get -> bool
static Steeltoe.Common.Platform.IsContainerized.get -> bool
Expand Down Expand Up @@ -54,6 +55,9 @@ Steeltoe.Common.IApplicationInstanceInfo
Steeltoe.Common.IApplicationInstanceInfo.ApplicationName.get -> string?
Steeltoe.Common.IApplicationTask
Steeltoe.Common.IApplicationTask.RunAsync(System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task!
Steeltoe.Common.Json.JsonIgnoreEmptyCollectionAttribute
Steeltoe.Common.Json.JsonIgnoreEmptyCollectionAttribute.JsonIgnoreEmptyCollectionAttribute() -> void
Steeltoe.Common.Json.JsonSerializerOptionsExtensions
Steeltoe.Common.Net.InetOptions
Steeltoe.Common.Net.InetOptions.DefaultHostname.get -> string?
Steeltoe.Common.Net.InetOptions.DefaultHostname.set -> void
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
using Steeltoe.Management.Endpoint.Actuators.Environment;
using Steeltoe.Management.Endpoint.Actuators.Health;
using Steeltoe.Management.Endpoint.Actuators.HeapDump;
using Steeltoe.Management.Endpoint.Actuators.HttpExchanges;
using Steeltoe.Management.Endpoint.Actuators.Hypermedia;
using Steeltoe.Management.Endpoint.Actuators.Info;
using Steeltoe.Management.Endpoint.Actuators.Loggers;
Expand All @@ -23,7 +24,6 @@
using Steeltoe.Management.Endpoint.Actuators.RouteMappings;
using Steeltoe.Management.Endpoint.Actuators.Services;
using Steeltoe.Management.Endpoint.Actuators.ThreadDump;
using Steeltoe.Management.Endpoint.Actuators.Trace;
using Steeltoe.Management.Endpoint.Configuration;
using Steeltoe.Management.Endpoint.ManagementPort;

Expand Down Expand Up @@ -98,7 +98,7 @@ public static IServiceCollection AddAllActuators(this IServiceCollection service
services.AddInfoActuator();
services.AddHealthActuator();
services.AddLoggersActuator();
services.AddTraceActuator(version);
services.AddHttpExchangesActuator();
services.AddMappingsActuator();
services.AddMetricsActuator();
services.AddRefreshActuator();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
// See the LICENSE file in the project root for more information.

using Microsoft.Extensions.Configuration;
using Microsoft.Net.Http.Headers;
using Steeltoe.Management.Endpoint.Configuration;

namespace Steeltoe.Management.Endpoint.Actuators.HttpExchanges;

internal sealed class ConfigureHttpExchangesEndpointOptions(IConfiguration configuration)
: ConfigureEndpointOptions<HttpExchangesEndpointOptions>(configuration, ManagementInfoPrefix, "httpexchanges")
{
private const string ManagementInfoPrefix = "management:endpoints:httpexchanges";
private const int DefaultCapacity = 100;

private static readonly string[] DefaultAllowedRequestHeaders =
[
HeaderNames.Accept,
HeaderNames.AcceptCharset,
HeaderNames.AcceptEncoding,
HeaderNames.AcceptLanguage,
HeaderNames.Allow,
HeaderNames.CacheControl,
HeaderNames.Connection,
HeaderNames.ContentEncoding,
HeaderNames.ContentLength,
HeaderNames.ContentType,
HeaderNames.Date,
HeaderNames.DNT,
HeaderNames.Expect,
HeaderNames.Host,
HeaderNames.MaxForwards,
HeaderNames.Range,
HeaderNames.SecWebSocketExtensions,
HeaderNames.SecWebSocketVersion,
HeaderNames.TE,
HeaderNames.Trailer,
HeaderNames.TransferEncoding,
HeaderNames.Upgrade,
HeaderNames.UserAgent,
HeaderNames.Warning,
HeaderNames.XRequestedWith,
HeaderNames.XUACompatible
];

private static readonly string[] DefaultAllowedResponseHeaders =
[
HeaderNames.AcceptRanges,
HeaderNames.Age,
HeaderNames.Allow,
HeaderNames.AltSvc,
HeaderNames.Connection,
HeaderNames.ContentDisposition,
HeaderNames.ContentLanguage,
HeaderNames.ContentLength,
HeaderNames.ContentLocation,
HeaderNames.ContentRange,
HeaderNames.ContentType,
HeaderNames.Date,
HeaderNames.Expires,
HeaderNames.LastModified,
HeaderNames.Location,
HeaderNames.Server,
HeaderNames.TransferEncoding,
HeaderNames.Upgrade,
HeaderNames.XPoweredBy
];

public override void Configure(HttpExchangesEndpointOptions options)
{
ArgumentNullException.ThrowIfNull(options);

base.Configure(options);

if (options.Capacity < 1)
{
options.Capacity = DefaultCapacity;
}

foreach (string defaultKey in DefaultAllowedRequestHeaders)
{
options.RequestHeaders.Add(defaultKey);
}

foreach (string defaultKey in DefaultAllowedResponseHeaders)
{
options.ResponseHeaders.Add(defaultKey);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
// See the LICENSE file in the project root for more information.

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Steeltoe.Management.Diagnostics;

namespace Steeltoe.Management.Endpoint.Actuators.HttpExchanges;

public static class EndpointServiceCollectionExtensions
{
/// <summary>
/// Adds the HttpExchanges actuator to the service container.
/// </summary>
/// <param name="services">
/// The <see cref="IServiceCollection" /> to add services to.
/// </param>
/// <returns>
/// The incoming <paramref name="services" /> so that additional calls can be chained.
/// </returns>
public static IServiceCollection AddHttpExchangesActuator(this IServiceCollection services)
{
ArgumentNullException.ThrowIfNull(services);
bart-vmware marked this conversation as resolved.
Show resolved Hide resolved

services.TryAddSingleton<IDiagnosticsManager, DiagnosticsManager>();
services.AddHostedService<DiagnosticsService>();
services.AddCommonActuatorServices();
services.AddHttpExchangesActuatorServices();
services.TryAddEnumerable(ServiceDescriptor.Singleton<IDiagnosticObserver, HttpExchangesDiagnosticObserver>());

services.TryAddSingleton<IHttpExchangesRepository>(provider =>
provider.GetServices<IDiagnosticObserver>().OfType<HttpExchangesDiagnosticObserver>().Single());

return services;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
// See the LICENSE file in the project root for more information.

using System.Text.Json.Serialization;
using System.Xml;

namespace Steeltoe.Management.Endpoint.Actuators.HttpExchanges;

public sealed class HttpExchange
{
[JsonPropertyName("timeTaken")]
public string SerializedTimeTaken => XmlConvert.ToString(TimeTaken);

public DateTime Timestamp { get; }
bart-vmware marked this conversation as resolved.
Show resolved Hide resolved
public HttpExchangePrincipal? Principal { get; }
public HttpExchangeSession? Session { get; }
public HttpExchangeRequest Request { get; }
public HttpExchangeResponse Response { get; }

[JsonIgnore]
public TimeSpan TimeTaken { get; }

public HttpExchange(HttpExchangeRequest request, HttpExchangeResponse response, DateTime timestamp, HttpExchangePrincipal? principal,
HttpExchangeSession? session, TimeSpan timeTaken)
{
ArgumentNullException.ThrowIfNull(request);
ArgumentNullException.ThrowIfNull(response);

Request = request;
Response = response;
Timestamp = timestamp;
Principal = principal;
Session = session;
TimeTaken = timeTaken;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
// See the LICENSE file in the project root for more information.

namespace Steeltoe.Management.Endpoint.Actuators.Trace;
namespace Steeltoe.Management.Endpoint.Actuators.HttpExchanges;

public sealed class TracePrincipal
public sealed class HttpExchangePrincipal
{
public string Name { get; }

public TracePrincipal(string name)
public HttpExchangePrincipal(string name)
{
ArgumentException.ThrowIfNullOrEmpty(name);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,25 @@
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
// See the LICENSE file in the project root for more information.

namespace Steeltoe.Management.Endpoint.Actuators.Trace;
using Microsoft.Extensions.Primitives;
using Steeltoe.Common.Json;

public sealed class TraceRequest
namespace Steeltoe.Management.Endpoint.Actuators.HttpExchanges;

public sealed class HttpExchangeRequest
{
public string Method { get; }
public string Uri { get; }
public IDictionary<string, IList<string?>> Headers { get; }
public Uri Uri { get; }

[JsonIgnoreEmptyCollection]
public IDictionary<string, StringValues> Headers { get; }

public string? RemoteAddress { get; }

public TraceRequest(string method, string uri, IDictionary<string, IList<string?>> headers, string? remoteAddress)
public HttpExchangeRequest(string method, Uri uri, IDictionary<string, StringValues> headers, string? remoteAddress)
{
ArgumentException.ThrowIfNullOrWhiteSpace(method);
ArgumentException.ThrowIfNullOrWhiteSpace(uri);
ArgumentNullException.ThrowIfNull(uri);
ArgumentNullException.ThrowIfNull(headers);

Method = method;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,19 @@
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
// See the LICENSE file in the project root for more information.

namespace Steeltoe.Management.Endpoint.Actuators.Trace;
using Microsoft.Extensions.Primitives;
using Steeltoe.Common.Json;

public sealed class TraceResponse
namespace Steeltoe.Management.Endpoint.Actuators.HttpExchanges;

public sealed class HttpExchangeResponse
{
public int Status { get; }
public IDictionary<string, IList<string?>> Headers { get; }

public TraceResponse(int status, IDictionary<string, IList<string?>> headers)
[JsonIgnoreEmptyCollection]
public IDictionary<string, StringValues> Headers { get; }

public HttpExchangeResponse(int status, IDictionary<string, StringValues> headers)
{
ArgumentNullException.ThrowIfNull(headers);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
// See the LICENSE file in the project root for more information.

namespace Steeltoe.Management.Endpoint.Actuators.Trace;
namespace Steeltoe.Management.Endpoint.Actuators.HttpExchanges;

public sealed class TraceSession
public sealed class HttpExchangeSession
{
public string Id { get; }

public TraceSession(string id)
public HttpExchangeSession(string id)
{
ArgumentException.ThrowIfNullOrWhiteSpace(id);

Expand Down
Loading
Loading