-
Notifications
You must be signed in to change notification settings - Fork 461
(feat-351) add oltp headers #969
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -197,6 +197,45 @@ public Builder reconnectAttempts(int reconnectAttempts) { | |
| return this; | ||
| } | ||
|
|
||
| /** | ||
| * Adds a single OTLP header for authentication. | ||
| * | ||
| * <p>This header will be included in OTLP HTTP requests to the tracing endpoint. | ||
| * Useful for systems like Langfuse that require authentication headers. | ||
| * | ||
| * <p>Example for Langfuse: | ||
| * <pre>{@code | ||
| * StudioManager.init() | ||
| * .studioUrl("https://cloud.langfuse.com") | ||
| * .tracingUrl("https://cloud.langfuse.com/api/public/otel/v1/traces") | ||
| * .addOtlpHeader("Authorization", "Basic " + base64Credentials) | ||
| * .initialize() | ||
| * .block(); | ||
| * }</pre> | ||
| * | ||
| * @param key The header name (e.g., "Authorization") | ||
| * @param value The header value (e.g., "Basic dXNlcjpwYXNz") | ||
| * @return This builder | ||
| */ | ||
| public Builder addOtlpHeader(String key, String value) { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ditto |
||
| configBuilder.addOtlpHeader(key, value); | ||
| return this; | ||
| } | ||
|
|
||
| /** | ||
| * Sets all OTLP headers for authentication. | ||
| * | ||
| * <p>These headers will be included in OTLP HTTP requests to the tracing endpoint. | ||
| * This method replaces any previously added headers. | ||
| * | ||
| * @param headers Map of header names to values | ||
| * @return This builder | ||
| */ | ||
| public Builder otlpHeaders(java.util.Map<String, String> headers) { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Prefer import statements for consistency with the rest of the project. |
||
| configBuilder.otlpHeaders(headers); | ||
| return this; | ||
| } | ||
|
|
||
| /** | ||
| * Initializes Studio integration. | ||
| * | ||
|
|
@@ -255,7 +294,10 @@ public Mono<Void> initialize() { | |
| traceEndpoint = config.getTracingUrl(); | ||
| } | ||
| TracerRegistry.register( | ||
| TelemetryTracer.builder().endpoint(traceEndpoint).build()); | ||
| TelemetryTracer.builder() | ||
| .endpoint(traceEndpoint) | ||
| .headers(config.getOtlpHeaders()) | ||
| .build()); | ||
| }) | ||
| .doOnError(e -> logger.error("Failed to initialize Studio", e)) | ||
| .onErrorResume( | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -20,6 +20,8 @@ | |
| import static org.junit.jupiter.api.Assertions.assertTrue; | ||
|
|
||
| import java.time.Duration; | ||
| import java.util.HashMap; | ||
| import java.util.Map; | ||
| import org.junit.jupiter.api.DisplayName; | ||
| import org.junit.jupiter.api.Test; | ||
|
|
||
|
|
@@ -129,4 +131,144 @@ void testBuilderEdgeCases() { | |
| assertEquals(Duration.ofHours(1), config2.getReconnectDelay()); | ||
| assertEquals(Duration.ofHours(24), config2.getReconnectMaxDelay()); | ||
| } | ||
|
|
||
| @Test | ||
| @DisplayName("Builder with addOtlpHeader should create config with single header") | ||
| void testBuilderWithAddOtlpHeader() { | ||
| StudioConfig config = | ||
| StudioConfig.builder() | ||
| .studioUrl("http://localhost:3000") | ||
| .project("TestProject") | ||
| .runName("test_run") | ||
| .addOtlpHeader("Authorization", "Basic dGVzdDp0ZXN0") | ||
| .build(); | ||
|
|
||
| assertNotNull(config.getOtlpHeaders()); | ||
| assertEquals(1, config.getOtlpHeaders().size()); | ||
| assertEquals("Basic dGVzdDp0ZXN0", config.getOtlpHeaders().get("Authorization")); | ||
| } | ||
|
|
||
| @Test | ||
| @DisplayName("Builder with multiple addOtlpHeader calls should accumulate headers") | ||
| void testBuilderWithMultipleAddOtlpHeaders() { | ||
| StudioConfig config = | ||
| StudioConfig.builder() | ||
| .studioUrl("http://localhost:3000") | ||
| .project("TestProject") | ||
| .runName("test_run") | ||
| .addOtlpHeader("Authorization", "Basic dGVzdDp0ZXN0") | ||
| .addOtlpHeader("X-Custom-Header", "custom-value") | ||
| .addOtlpHeader("X-Trace-Id", "trace-123") | ||
| .build(); | ||
|
|
||
| assertNotNull(config.getOtlpHeaders()); | ||
| assertEquals(3, config.getOtlpHeaders().size()); | ||
| assertEquals("Basic dGVzdDp0ZXN0", config.getOtlpHeaders().get("Authorization")); | ||
| assertEquals("custom-value", config.getOtlpHeaders().get("X-Custom-Header")); | ||
| assertEquals("trace-123", config.getOtlpHeaders().get("X-Trace-Id")); | ||
| } | ||
|
|
||
| @Test | ||
| @DisplayName("Builder with otlpHeaders map should create config with all headers") | ||
| void testBuilderWithOtlpHeadersMap() { | ||
| Map<String, String> headers = new HashMap<>(); | ||
| headers.put("Authorization", "Bearer token123"); | ||
| headers.put("X-Api-Key", "api-key-value"); | ||
|
|
||
| StudioConfig config = | ||
| StudioConfig.builder() | ||
| .studioUrl("http://localhost:3000") | ||
| .project("TestProject") | ||
| .runName("test_run") | ||
| .otlpHeaders(headers) | ||
| .build(); | ||
|
|
||
| assertNotNull(config.getOtlpHeaders()); | ||
| assertEquals(2, config.getOtlpHeaders().size()); | ||
| assertEquals("Bearer token123", config.getOtlpHeaders().get("Authorization")); | ||
| assertEquals("api-key-value", config.getOtlpHeaders().get("X-Api-Key")); | ||
| } | ||
|
|
||
| @Test | ||
| @DisplayName("Default config should have empty OTLP headers") | ||
| void testDefaultOtlpHeadersEmpty() { | ||
| StudioConfig config = | ||
| StudioConfig.builder() | ||
| .studioUrl("http://localhost:3000") | ||
| .project("TestProject") | ||
| .runName("test_run") | ||
| .build(); | ||
|
|
||
| assertNotNull(config.getOtlpHeaders()); | ||
| assertTrue(config.getOtlpHeaders().isEmpty()); | ||
| } | ||
|
|
||
| @Test | ||
| @DisplayName("OTLP headers should be immutable after build") | ||
| void testOtlpHeadersImmutability() { | ||
| StudioConfig config = | ||
| StudioConfig.builder() | ||
| .studioUrl("http://localhost:3000") | ||
| .project("TestProject") | ||
| .runName("test_run") | ||
| .addOtlpHeader("Authorization", "Basic dGVzdDp0ZXN0") | ||
| .build(); | ||
|
|
||
| Map<String, String> headers = config.getOtlpHeaders(); | ||
|
|
||
| try { | ||
| headers.put("X-New-Header", "should-fail"); | ||
| // If we reach here, the map is mutable (which is wrong) | ||
| assertTrue(false, "Headers map should be immutable"); | ||
| } catch (UnsupportedOperationException e) { | ||
| // Expected behavior - map is immutable | ||
| assertTrue(true); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Suggest using assertThrows here. |
||
| } | ||
| } | ||
|
|
||
| @Test | ||
| @DisplayName("Combining otlpHeaders and addOtlpHeader should work correctly") | ||
| void testCombiningHeaderMethods() { | ||
| Map<String, String> initialHeaders = new HashMap<>(); | ||
| initialHeaders.put("X-Initial", "initial-value"); | ||
|
|
||
| StudioConfig config = | ||
| StudioConfig.builder() | ||
| .studioUrl("http://localhost:3000") | ||
| .project("TestProject") | ||
| .runName("test_run") | ||
| .otlpHeaders(initialHeaders) | ||
| .addOtlpHeader("X-Additional", "additional-value") | ||
| .build(); | ||
|
|
||
| assertNotNull(config.getOtlpHeaders()); | ||
| assertEquals(2, config.getOtlpHeaders().size()); | ||
| assertEquals("initial-value", config.getOtlpHeaders().get("X-Initial")); | ||
| assertEquals("additional-value", config.getOtlpHeaders().get("X-Additional")); | ||
| } | ||
|
|
||
| @Test | ||
| @DisplayName("Langfuse-style authentication should work correctly") | ||
| void testLangfuseStyleAuthentication() { | ||
| String publicKey = "pk-lf-test"; | ||
| String secretKey = "sk-lf-test"; | ||
| String base64Credentials = | ||
| java.util.Base64.getEncoder() | ||
| .encodeToString((publicKey + ":" + secretKey).getBytes()); | ||
|
|
||
| StudioConfig config = | ||
| StudioConfig.builder() | ||
| .studioUrl("https://cloud.langfuse.com") | ||
| .tracingUrl("https://cloud.langfuse.com/api/public/otel/v1/traces") | ||
| .project("LangfuseProject") | ||
| .runName("langfuse_run") | ||
| .addOtlpHeader("Authorization", "Basic " + base64Credentials) | ||
| .build(); | ||
|
|
||
| assertNotNull(config.getOtlpHeaders()); | ||
| assertEquals(1, config.getOtlpHeaders().size()); | ||
| assertTrue(config.getOtlpHeaders().get("Authorization").startsWith("Basic ")); | ||
| assertEquals( | ||
| "https://cloud.langfuse.com/api/public/otel/v1/traces", config.getTracingUrl()); | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suggest adding a null check here.