Skip to content

Commit 1abb9bd

Browse files
committed
Refactor
1 parent a8c7d98 commit 1abb9bd

File tree

10 files changed

+145
-99
lines changed

10 files changed

+145
-99
lines changed

src/api/Elastic.Documentation.Api.Core/Elastic.Documentation.Api.Core.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" />
1313
<PackageReference Include="Microsoft.Extensions.Logging" />
1414
<PackageReference Include="Microsoft.Extensions.Telemetry.Abstractions" />
15+
<PackageReference Include="NetEscapades.EnumGenerators" />
1516
</ItemGroup>
1617

1718
</Project>

src/api/Elastic.Documentation.Api.Core/Telemetry/IOtlpGateway.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,13 @@ public interface IOtlpGateway
1212
/// <summary>
1313
/// Forwards OTLP telemetry data to the collector.
1414
/// </summary>
15-
/// <param name="signalType">The OTLP signal type: traces, logs, or metrics</param>
15+
/// <param name="signalType">The OTLP signal type (traces, logs, or metrics)</param>
1616
/// <param name="requestBody">The raw OTLP payload stream</param>
1717
/// <param name="contentType">Content-Type of the payload</param>
1818
/// <param name="ctx">Cancellation token</param>
1919
/// <returns>HTTP status code and response content</returns>
2020
Task<(int StatusCode, string? Content)> ForwardOtlp(
21-
string signalType,
21+
OtlpSignalType signalType,
2222
Stream requestBody,
2323
string contentType,
2424
Cancel ctx = default);

src/api/Elastic.Documentation.Api.Core/Telemetry/OtlpProxyRequest.cs

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,37 @@
22
// Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
33
// See the LICENSE file in the project root for more information
44

5+
using System.ComponentModel.DataAnnotations;
6+
using NetEscapades.EnumGenerators;
7+
58
namespace Elastic.Documentation.Api.Core.Telemetry;
69

10+
/// <summary>
11+
/// OTLP signal types supported by the proxy.
12+
/// The Display names match the OTLP path segments (lowercase).
13+
/// </summary>
14+
[EnumExtensions]
15+
public enum OtlpSignalType
16+
{
17+
/// <summary>
18+
/// Distributed traces - maps to /v1/traces
19+
/// </summary>
20+
[Display(Name = "traces")]
21+
Traces,
22+
23+
/// <summary>
24+
/// Log records - maps to /v1/logs
25+
/// </summary>
26+
[Display(Name = "logs")]
27+
Logs,
28+
29+
/// <summary>
30+
/// Metrics data - maps to /v1/metrics
31+
/// </summary>
32+
[Display(Name = "metrics")]
33+
Metrics
34+
}
35+
736
/// <summary>
837
/// Request model for OTLP proxy endpoint.
938
/// Accepts raw OTLP payload from frontend and forwards to configured OTLP endpoint.
@@ -15,4 +44,3 @@ public class OtlpProxyRequest
1544
/// </summary>
1645
public required string SignalType { get; init; }
1746
}
18-

src/api/Elastic.Documentation.Api.Core/Telemetry/OtlpProxyUsecase.cs

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,43 +3,33 @@
33
// See the LICENSE file in the project root for more information
44

55
using System.Diagnostics;
6-
using Microsoft.Extensions.Logging;
76

87
namespace Elastic.Documentation.Api.Core.Telemetry;
98

109
/// <summary>
1110
/// Proxies OTLP telemetry from the frontend to the local ADOT Lambda Layer collector.
1211
/// The ADOT layer handles authentication and forwarding to the backend.
1312
/// </summary>
14-
public class OtlpProxyUsecase(
15-
IOtlpGateway gateway,
16-
ILogger<OtlpProxyUsecase> logger)
13+
public class OtlpProxyUsecase(IOtlpGateway gateway)
1714
{
1815
private static readonly ActivitySource ActivitySource = new(TelemetryConstants.OtlpProxySourceName);
1916

2017
/// <summary>
2118
/// Proxies OTLP data from the frontend to the local ADOT collector.
2219
/// </summary>
23-
/// <param name="signalType">The OTLP signal type: traces, logs, or metrics</param>
20+
/// <param name="signalType">The OTLP signal type (traces, logs, or metrics)</param>
2421
/// <param name="requestBody">The raw OTLP payload (JSON or protobuf)</param>
2522
/// <param name="contentType">Content-Type header from the original request</param>
2623
/// <param name="ctx">Cancellation token</param>
2724
/// <returns>HTTP status code and response content</returns>
2825
public async Task<(int StatusCode, string? Content)> ProxyOtlp(
29-
string signalType,
26+
OtlpSignalType signalType,
3027
Stream requestBody,
3128
string contentType,
3229
Cancel ctx = default)
3330
{
3431
using var activity = ActivitySource.StartActivity("ProxyOtlp", ActivityKind.Client);
3532

36-
// Validate signal type
37-
if (signalType is not ("traces" or "logs" or "metrics"))
38-
{
39-
logger.LogWarning("Invalid OTLP signal type: {SignalType}", signalType);
40-
return (400, $"Invalid signal type: {signalType}. Must be traces, logs, or metrics");
41-
}
42-
4333
// Forward to gateway
4434
return await gateway.ForwardOtlp(signalType, requestBody, contentType, ctx);
4535
}

src/api/Elastic.Documentation.Api.Infrastructure/Adapters/Telemetry/AdotOtlpGateway.cs

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,39 +11,39 @@ namespace Elastic.Documentation.Api.Infrastructure.Adapters.Telemetry;
1111
/// Gateway that forwards OTLP telemetry to the ADOT Lambda Layer collector.
1212
/// </summary>
1313
public class AdotOtlpGateway(
14+
IHttpClientFactory httpClientFactory,
1415
OtlpProxyOptions options,
1516
ILogger<AdotOtlpGateway> logger) : IOtlpGateway
1617
{
17-
private static readonly HttpClient HttpClient = new()
18-
{
19-
Timeout = TimeSpan.FromSeconds(30)
20-
};
18+
public const string HttpClientName = "OtlpProxy";
19+
private readonly HttpClient _httpClient = httpClientFactory.CreateClient(HttpClientName);
2120

2221
/// <inheritdoc />
2322
public async Task<(int StatusCode, string? Content)> ForwardOtlp(
24-
string signalType,
23+
OtlpSignalType signalType,
2524
Stream requestBody,
2625
string contentType,
2726
Cancel ctx = default)
2827
{
2928
try
3029
{
3130
// Build the target URL: http://localhost:4318/v1/{signalType}
32-
var targetUrl = $"{options.Endpoint.TrimEnd('/')}/v1/{signalType}";
31+
// Use ToStringFast(true) from generated enum extensions (returns Display name: "traces", "logs", "metrics")
32+
var targetUrl = $"{options.Endpoint.TrimEnd('/')}/v1/{signalType.ToStringFast(true)}";
3333

3434
logger.LogDebug("Forwarding OTLP {SignalType} to ADOT collector at {TargetUrl}", signalType, targetUrl);
3535

3636
using var request = new HttpRequestMessage(HttpMethod.Post, targetUrl);
3737

38-
// Forward the content
38+
// Forward the content with the original content type
3939
request.Content = new StreamContent(requestBody);
40-
request.Content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue(contentType);
40+
_ = request.Content.Headers.TryAddWithoutValidation("Content-Type", contentType);
4141

4242
// No need to add authentication headers - ADOT layer handles auth to backend
4343
// Just forward the telemetry to the local collector
4444

4545
// Forward to ADOT collector
46-
using var response = await HttpClient.SendAsync(request, HttpCompletionOption.ResponseContentRead, ctx);
46+
using var response = await _httpClient.SendAsync(request, HttpCompletionOption.ResponseContentRead, ctx);
4747
var responseContent = response.Content.Headers.ContentLength > 0
4848
? await response.Content.ReadAsStringAsync(ctx)
4949
: string.Empty;

src/api/Elastic.Documentation.Api.Infrastructure/MappingsExtensions.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ private static void MapOtlpProxyEndpoint(IEndpointRouteBuilder group)
6969
async (HttpContext context, OtlpProxyUsecase proxyUsecase, Cancel ctx) =>
7070
{
7171
var contentType = context.Request.ContentType ?? "application/json";
72-
var (statusCode, content) = await proxyUsecase.ProxyOtlp("traces", context.Request.Body, contentType, ctx);
72+
var (statusCode, content) = await proxyUsecase.ProxyOtlp(OtlpSignalType.Traces, context.Request.Body, contentType, ctx);
7373
return Results.Content(content ?? string.Empty, contentType, statusCode: statusCode);
7474
})
7575
.DisableAntiforgery(); // Frontend requests won't have antiforgery tokens
@@ -80,7 +80,7 @@ private static void MapOtlpProxyEndpoint(IEndpointRouteBuilder group)
8080
async (HttpContext context, OtlpProxyUsecase proxyUsecase, Cancel ctx) =>
8181
{
8282
var contentType = context.Request.ContentType ?? "application/json";
83-
var (statusCode, content) = await proxyUsecase.ProxyOtlp("logs", context.Request.Body, contentType, ctx);
83+
var (statusCode, content) = await proxyUsecase.ProxyOtlp(OtlpSignalType.Logs, context.Request.Body, contentType, ctx);
8484
return Results.Content(content ?? string.Empty, contentType, statusCode: statusCode);
8585
})
8686
.DisableAntiforgery();
@@ -91,7 +91,7 @@ private static void MapOtlpProxyEndpoint(IEndpointRouteBuilder group)
9191
async (HttpContext context, OtlpProxyUsecase proxyUsecase, Cancel ctx) =>
9292
{
9393
var contentType = context.Request.ContentType ?? "application/json";
94-
var (statusCode, content) = await proxyUsecase.ProxyOtlp("metrics", context.Request.Body, contentType, ctx);
94+
var (statusCode, content) = await proxyUsecase.ProxyOtlp(OtlpSignalType.Metrics, context.Request.Body, contentType, ctx);
9595
return Results.Content(content ?? string.Empty, contentType, statusCode: statusCode);
9696
})
9797
.DisableAntiforgery();

src/api/Elastic.Documentation.Api.Infrastructure/ServicesExtension.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,14 @@ private static void AddOtlpProxyUsecase(IServiceCollection services, AppEnv appE
186186
var config = sp.GetRequiredService<IConfiguration>();
187187
return new OtlpProxyOptions(config);
188188
});
189+
190+
// Register named HttpClient for OTLP proxy
191+
_ = services.AddHttpClient(AdotOtlpGateway.HttpClientName)
192+
.ConfigureHttpClient(client =>
193+
{
194+
client.Timeout = TimeSpan.FromSeconds(30);
195+
});
196+
189197
_ = services.AddScoped<IOtlpGateway, AdotOtlpGateway>();
190198
_ = services.AddScoped<OtlpProxyUsecase>();
191199
logger?.LogInformation("OTLP proxy configured to forward to ADOT Lambda Layer collector");

tests-integration/Elastic.Documentation.Api.IntegrationTests/Examples/ServiceMockingExampleTests.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ public async Task ExampleWithMultipleServiceMocks()
2727

2828
// Configure mock behaviors
2929
A.CallTo(() => mockOtlpGateway.ForwardOtlp(
30-
A<string>._,
30+
A<OtlpSignalType>._,
3131
A<Stream>._,
3232
A<string>._,
3333
A<Cancel>._))
@@ -80,7 +80,7 @@ public async Task ExampleWithSingletonMock()
8080
{
8181
// Arrange
8282
var mockGateway = A.Fake<IOtlpGateway>();
83-
A.CallTo(() => mockGateway.ForwardOtlp(A<string>._, A<Stream>._, A<string>._, A<Cancel>._))
83+
A.CallTo(() => mockGateway.ForwardOtlp(A<OtlpSignalType>._, A<Stream>._, A<string>._, A<Cancel>._))
8484
.Returns(Task.FromResult<(int, string?)>((503, "Unavailable")));
8585

8686
// Use ReplaceSingleton for services registered as singletons

tests-integration/Elastic.Documentation.Api.IntegrationTests/Fixtures/ApiWebApplicationFactory.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,14 @@ public static ApiWebApplicationFactory WithMockedServices(Action<ServiceReplacem
5151
return new ApiWebApplicationFactory(builder.Build());
5252
}
5353

54+
/// <summary>
55+
/// Creates a factory with custom service configuration.
56+
/// </summary>
57+
/// <param name="configureServices">Action to configure services directly</param>
58+
/// <returns>New factory instance with custom service configuration</returns>
59+
public static ApiWebApplicationFactory WithMockedServices(Action<IServiceCollection> configureServices)
60+
=> new(configureServices);
61+
5462
protected override void ConfigureWebHost(IWebHostBuilder builder) => builder.ConfigureServices(services =>
5563
{
5664
var otelBuilder = services.AddOpenTelemetry();

0 commit comments

Comments
 (0)