Skip to content

Commit 33db2a1

Browse files
committed
Add OTLP proxy endpoint
1 parent e5d1be7 commit 33db2a1

File tree

16 files changed

+1002
-48
lines changed

16 files changed

+1002
-48
lines changed

Directory.Packages.props

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
<PackageVersion Include="AWSSDK.S3" Version="4.0.7.14" />
2828
<PackageVersion Include="Elastic.OpenTelemetry" Version="1.1.0" />
2929
<PackageVersion Include="Microsoft.Extensions.Configuration.UserSecrets" Version="10.0.0" />
30+
<PackageVersion Include="Microsoft.Extensions.Configuration.Abstractions" Version="10.0.0" />
3031
<PackageVersion Include="Microsoft.Extensions.Telemetry.Abstractions" Version="10.0.0" />
3132
<PackageVersion Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.13.0" />
3233
<PackageVersion Include="Generator.Equals" Version="3.2.1" PrivateAssets="all" IncludeAssets="runtime; build; native; contentfiles; analyzers; buildtransitive" />
@@ -41,6 +42,7 @@
4142
<PackageVersion Include="Microsoft.OpenApi" Version="3.0.1" />
4243
<PackageVersion Include="TUnit" Version="0.25.21" />
4344
<PackageVersion Include="xunit.v3.extensibility.core" Version="2.0.2" />
45+
<PackageVersion Include="WireMock.Net" Version="1.6.11" />
4446
</ItemGroup>
4547
<!-- Build -->
4648
<ItemGroup>
@@ -106,4 +108,4 @@
106108
</PackageVersion>
107109
<PackageVersion Include="xunit.v3" Version="2.0.2" />
108110
</ItemGroup>
109-
</Project>
111+
</Project>

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
@@ -9,6 +9,7 @@
99
</PropertyGroup>
1010

1111
<ItemGroup>
12+
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" />
1213
<PackageReference Include="Microsoft.Extensions.Logging" />
1314
<PackageReference Include="Microsoft.Extensions.Telemetry.Abstractions" />
1415
</ItemGroup>
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// Licensed to Elasticsearch B.V under one or more agreements.
2+
// Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
3+
// See the LICENSE file in the project root for more information
4+
5+
namespace Elastic.Documentation.Api.Core.Telemetry;
6+
7+
/// <summary>
8+
/// Gateway for forwarding OTLP telemetry to a collector.
9+
/// </summary>
10+
public interface IOtlpGateway
11+
{
12+
/// <summary>
13+
/// Forwards OTLP telemetry data to the collector.
14+
/// </summary>
15+
/// <param name="signalType">The OTLP signal type: traces, logs, or metrics</param>
16+
/// <param name="requestBody">The raw OTLP payload stream</param>
17+
/// <param name="contentType">Content-Type of the payload</param>
18+
/// <param name="ctx">Cancellation token</param>
19+
/// <returns>HTTP status code and response content</returns>
20+
Task<(int StatusCode, string? Content)> ForwardOtlp(
21+
string signalType,
22+
Stream requestBody,
23+
string contentType,
24+
Cancel ctx = default);
25+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
// Licensed to Elasticsearch B.V under one or more agreements.
2+
// Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
3+
// See the LICENSE file in the project root for more information
4+
5+
using Microsoft.Extensions.Configuration;
6+
7+
namespace Elastic.Documentation.Api.Core.Telemetry;
8+
9+
/// <summary>
10+
/// Configuration options for the OTLP proxy.
11+
/// When using ADOT Lambda Layer, the proxy forwards to the local collector at localhost:4318.
12+
/// The ADOT layer handles authentication and forwarding to the backend (Elastic APM, etc).
13+
/// </summary>
14+
/// <remarks>
15+
/// ADOT Lambda Layer runs a local OpenTelemetry Collector that accepts OTLP/HTTP on:
16+
/// - localhost:4318 (HTTP/JSON and HTTP/protobuf)
17+
/// - localhost:4317 (gRPC)
18+
///
19+
/// The ADOT layer is configured via environment variables:
20+
/// - OTEL_EXPORTER_OTLP_ENDPOINT: Where ADOT forwards telemetry
21+
/// - OTEL_EXPORTER_OTLP_HEADERS: Authentication headers ADOT uses
22+
/// - AWS_LAMBDA_EXEC_WRAPPER: /opt/otel-instrument (enables ADOT)
23+
/// </remarks>
24+
public class OtlpProxyOptions
25+
{
26+
/// <summary>
27+
/// OTLP endpoint URL for the local ADOT collector.
28+
/// Defaults to localhost:4318 when running in Lambda with ADOT layer.
29+
/// </summary>
30+
public string Endpoint { get; }
31+
32+
public OtlpProxyOptions(IConfiguration configuration)
33+
{
34+
// Check for test override first (for integration tests with WireMock)
35+
var configEndpoint = configuration["OtlpProxy:Endpoint"];
36+
if (!string.IsNullOrEmpty(configEndpoint))
37+
{
38+
Endpoint = configEndpoint;
39+
return;
40+
}
41+
42+
// Check if we're in Lambda with ADOT layer
43+
var execWrapper = Environment.GetEnvironmentVariable("AWS_LAMBDA_EXEC_WRAPPER");
44+
var isAdotEnabled = execWrapper?.Contains("otel-instrument") == true;
45+
46+
if (isAdotEnabled)
47+
{
48+
// ADOT Lambda Layer runs collector on localhost:4318
49+
Endpoint = "http://localhost:4318";
50+
}
51+
else
52+
{
53+
// Fallback to configured endpoint for local development
54+
Endpoint = Environment.GetEnvironmentVariable("OTEL_EXPORTER_OTLP_ENDPOINT")
55+
?? "http://localhost:4318";
56+
}
57+
}
58+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// Licensed to Elasticsearch B.V under one or more agreements.
2+
// Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
3+
// See the LICENSE file in the project root for more information
4+
5+
namespace Elastic.Documentation.Api.Core.Telemetry;
6+
7+
/// <summary>
8+
/// Request model for OTLP proxy endpoint.
9+
/// Accepts raw OTLP payload from frontend and forwards to configured OTLP endpoint.
10+
/// </summary>
11+
public class OtlpProxyRequest
12+
{
13+
/// <summary>
14+
/// The OTLP signal type: traces, logs, or metrics
15+
/// </summary>
16+
public required string SignalType { get; init; }
17+
}
18+
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// Licensed to Elasticsearch B.V under one or more agreements.
2+
// Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
3+
// See the LICENSE file in the project root for more information
4+
5+
using System.Diagnostics;
6+
using Microsoft.Extensions.Logging;
7+
8+
namespace Elastic.Documentation.Api.Core.Telemetry;
9+
10+
/// <summary>
11+
/// Proxies OTLP telemetry from the frontend to the local ADOT Lambda Layer collector.
12+
/// The ADOT layer handles authentication and forwarding to the backend.
13+
/// </summary>
14+
public class OtlpProxyUsecase(
15+
IOtlpGateway gateway,
16+
ILogger<OtlpProxyUsecase> logger)
17+
{
18+
private static readonly ActivitySource ActivitySource = new(TelemetryConstants.OtlpProxySourceName);
19+
20+
/// <summary>
21+
/// Proxies OTLP data from the frontend to the local ADOT collector.
22+
/// </summary>
23+
/// <param name="signalType">The OTLP signal type: traces, logs, or metrics</param>
24+
/// <param name="requestBody">The raw OTLP payload (JSON or protobuf)</param>
25+
/// <param name="contentType">Content-Type header from the original request</param>
26+
/// <param name="ctx">Cancellation token</param>
27+
/// <returns>HTTP status code and response content</returns>
28+
public async Task<(int StatusCode, string? Content)> ProxyOtlp(
29+
string signalType,
30+
Stream requestBody,
31+
string contentType,
32+
Cancel ctx = default)
33+
{
34+
using var activity = ActivitySource.StartActivity("ProxyOtlp", ActivityKind.Client);
35+
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+
43+
// Forward to gateway
44+
return await gateway.ForwardOtlp(signalType, requestBody, contentType, ctx);
45+
}
46+
}

0 commit comments

Comments
 (0)