diff --git a/dogstatsd-http/core/src/main/java/com/datadoghq/dogstatsd/http/ForwarderContext.java b/dogstatsd-http/core/src/main/java/com/datadoghq/dogstatsd/http/ForwarderContext.java index c3527997..60568e72 100644 --- a/dogstatsd-http/core/src/main/java/com/datadoghq/dogstatsd/http/ForwarderContext.java +++ b/dogstatsd-http/core/src/main/java/com/datadoghq/dogstatsd/http/ForwarderContext.java @@ -7,14 +7,17 @@ package com.datadoghq.dogstatsd.http; +import java.net.URI; import java.util.Map; /** Provides common parameters to the forwarder implementations. */ public class ForwarderContext { + private final URI baseUri; private final String localData; private final String externalData; - private ForwarderContext(final String localData, final String externalData) { + private ForwarderContext(final URI baseUri, final String localData, final String externalData) { + this.baseUri = baseUri; this.localData = localData; this.externalData = externalData; } @@ -28,6 +31,10 @@ public static Builder builder() { return new Builder(); } + public URI baseUri() { + return baseUri; + } + /** * Returns the local-data value: a container ID, an {@code in-} cgroup fallback, or null * when neither could be determined. @@ -63,6 +70,7 @@ public static final class Builder { private CgroupReader cgroupReader = new CgroupReader(); private String localData; private String externalData; + private String baseUri; private Builder() {} @@ -100,7 +108,19 @@ public Builder originDetectionEnabled(final boolean val) { return this; } - Builder environment(final Map val) { + /** + * Sets the base URI the series and sketches endpoints are resolved against. Defaults to the + * value of the {@code DD_DOGSTATSD_HTTP_URL} environment variable. + * + * @param val the base URI, or null to use the default. + * @return this builder. + */ + public Builder baseUri(final String uri) { + baseUri = uri; + return this; + } + + public Builder environment(final Map val) { env = new EnvMap(val); return this; } @@ -114,6 +134,7 @@ Builder cgroupReader(final CgroupReader val) { * Builds the context, running detection for any value not set explicitly. * * @return a new context. + * @throws URISyntaxException if baseUri value is not a valid URI. */ public ForwarderContext build() { String local = localData; @@ -128,7 +149,7 @@ public ForwarderContext build() { } } - return new ForwarderContext(local, external); + return new ForwarderContext(resolveBaseUri(), local, external); } boolean resolveOriginDetectionEnabled() { @@ -147,5 +168,20 @@ boolean resolveOriginDetectionEnabled() { || "n".equals(normalized) || "off".equals(normalized)); } + + URI resolveBaseUri() { + if (baseUri == null) { + baseUri = env.get("DD_DOGSTATSD_HTTP_URL"); + } + if (baseUri == null) { + throw new IllegalStateException( + "baseUri is not set and DD_DOGSTATSD_HTTP_URL is not defined"); + } + // Make sure baseUri acts as a prefix when we use it with URI#resolve later. + if (!baseUri.endsWith("/")) { + baseUri += "/"; + } + return URI.create(baseUri); + } } } diff --git a/dogstatsd-http/core/src/test/java/com/datadoghq/dogstatsd/http/ForwarderContextTest.java b/dogstatsd-http/core/src/test/java/com/datadoghq/dogstatsd/http/ForwarderContextTest.java index 2f11591f..32ec2602 100644 --- a/dogstatsd-http/core/src/test/java/com/datadoghq/dogstatsd/http/ForwarderContextTest.java +++ b/dogstatsd-http/core/src/test/java/com/datadoghq/dogstatsd/http/ForwarderContextTest.java @@ -32,6 +32,7 @@ public String getContainerID() { private static ForwarderContext.Builder builder(Map env, String containerID) { return ForwarderContext.builder() + .baseUri("http://localhost:8125") .environment(env) .cgroupReader(new StubCgroupReader(containerID)); } @@ -166,7 +167,10 @@ public void originDetectionEnabledEnvVar() { } boolean detects = - ForwarderContext.builder().environment(env).resolveOriginDetectionEnabled(); + ForwarderContext.builder() + .environment(env) + .baseUri("http://localhost:8125") + .resolveOriginDetectionEnabled(); assertEquals(c.value, c.detects, detects); } } @@ -234,6 +238,7 @@ public void emptyExternalEnvIsPassedThrough() { public void emptyEnvironmentDetectsLocalDataOnly() { ForwarderContext ctx = ForwarderContext.builder() + .baseUri("http://localhost:8125") .environment(Collections.emptyMap()) .cgroupReader(new StubCgroupReader("container-id")) .build(); diff --git a/dogstatsd-http/forwarder/src/main/java/com/datadoghq/dogstatsd/http/forwarder/Forwarder.java b/dogstatsd-http/forwarder/src/main/java/com/datadoghq/dogstatsd/http/forwarder/Forwarder.java index f29ad640..c763e98a 100644 --- a/dogstatsd-http/forwarder/src/main/java/com/datadoghq/dogstatsd/http/forwarder/Forwarder.java +++ b/dogstatsd-http/forwarder/src/main/java/com/datadoghq/dogstatsd/http/forwarder/Forwarder.java @@ -38,6 +38,7 @@ public class Forwarder extends Thread { final Duration requestTimeout; final Random rng = new Random(); + final URI baseUri; final String localData; final String externalData; @@ -61,6 +62,7 @@ public static Builder builder() { builder.whenFull, this.telemetry); this.requestTimeout = builder.requestTimeout; + this.baseUri = builder.baseUri; this.localData = builder.localData; this.externalData = builder.externalData; @@ -118,13 +120,12 @@ public void send(URI url, byte[] payload) throws InterruptedException { void runOnce(Map.Entry item) throws InterruptedException { Payload payload = item.getValue(); + final URI url = baseUri.resolve(payload.url); logger.log( - Level.INFO, - "sending {0} bytes to {1}", - new Object[] {payload.bytes.length, payload.url}); + Level.INFO, "sending {0} bytes to {1}", new Object[] {payload.bytes.length, url}); HttpRequest.Builder builder = - HttpRequest.newBuilder(payload.url).POST(BodyPublishers.ofByteArray(payload.bytes)); + HttpRequest.newBuilder(url).POST(BodyPublishers.ofByteArray(payload.bytes)); if (requestTimeout != null) { builder.timeout(requestTimeout); } @@ -242,6 +243,7 @@ public static final class Builder { private String localData; private String externalData; private boolean contextSet; + private URI baseUri; private Builder() {} @@ -322,18 +324,15 @@ public Builder requestTimeout(final Duration val) { * *

Defaults to {@code ForwarderContext.defaults()}. * - * @param context the context to take the values from, or {@code null}. + * @param context the context to take the values from. * @return this builder. */ public Builder context(final ForwarderContext context) { contextSet = true; - if (context == null) { - localData = null; - externalData = null; - } else { - localData = validateHeaderValue(context.localData()); - externalData = validateHeaderValue(context.externalData()); - } + Objects.requireNonNull(context); + baseUri = context.baseUri(); + localData = validateHeaderValue(context.localData()); + externalData = validateHeaderValue(context.externalData()); return this; } diff --git a/dogstatsd-http/forwarder/src/test/java/com/datadoghq/dogstatsd/http/forwarder/ForwarderTest.java b/dogstatsd-http/forwarder/src/test/java/com/datadoghq/dogstatsd/http/forwarder/ForwarderTest.java index 7a65d92d..fa7299f8 100644 --- a/dogstatsd-http/forwarder/src/test/java/com/datadoghq/dogstatsd/http/forwarder/ForwarderTest.java +++ b/dogstatsd-http/forwarder/src/test/java/com/datadoghq/dogstatsd/http/forwarder/ForwarderTest.java @@ -17,6 +17,7 @@ import com.datadoghq.dogstatsd.http.ForwarderContext; import java.net.URI; import java.time.Duration; +import java.util.HashMap; import java.util.Map; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicInteger; @@ -24,13 +25,18 @@ public class ForwarderTest { private static final URI URL = URI.create("http://localhost:0/"); + private static final Map emptyMap = new HashMap(); + + private static ForwarderContext.Builder contextBuilder() { + return ForwarderContext.builder().environment(emptyMap).baseUri("http://localhost:8125"); + } + + private static Forwarder.Builder builder() { + return Forwarder.builder().context(contextBuilder().build()); + } private static Forwarder newForwarder(long maxBytes, WhenFull whenFull) { - return Forwarder.builder() - .maxRequestsBytes(maxBytes) - .maxTries(1) - .whenFull(whenFull) - .build(); + return builder().maxRequestsBytes(maxBytes).maxTries(1).whenFull(whenFull).build(); } @Test @@ -47,7 +53,7 @@ public void builderRejectsInvalidValues() { /** A null request timeout is legal and means requests have no timeout at all. */ @Test public void nullRequestTimeoutIsAllowed() { - Forwarder f = Forwarder.builder().requestTimeout(null).build(); + Forwarder f = builder().requestTimeout(null).build(); assertNull(f.requestTimeout); } @@ -56,26 +62,16 @@ public void contextSuppliesOriginDetectionHeaders() { Forwarder f = Forwarder.builder() .context( - ForwarderContext.builder() - .localData("ci-abc") - .externalData("en-xyz") - .build()) + contextBuilder().localData("ci-abc").externalData("en-xyz").build()) .build(); assertEquals("ci-abc", f.localData); assertEquals("en-xyz", f.externalData); } - @Test - public void nullContextOmitsOriginDetectionHeaders() { - Forwarder f = Forwarder.builder().context(null).build(); - assertNull(f.localData); - assertNull(f.externalData); - } - /** Values that can't be sent as a header value are rejected where they're supplied. */ @Test public void contextRejectsUnsendableHeaderValue() { - ForwarderContext ctx = ForwarderContext.builder().localData("bad\nvalue").build(); + ForwarderContext ctx = contextBuilder().localData("bad\nvalue").build(); Forwarder.Builder b = Forwarder.builder(); assertThrows(IllegalArgumentException.class, () -> b.context(ctx)); }