diff --git a/build.gradle b/build.gradle index 487046897..db6f62e36 100644 --- a/build.gradle +++ b/build.gradle @@ -15,6 +15,7 @@ ext.jerseyVersion = '2.22.2' ext.slf4jVersion = '1.7.16' ext.apacheHttpComponentsVersion = '4.1.2' ext.gsonVersion = '2.8.0' +ext.tracerResolverVersion = '0.1.0' ext.junitVersion = '4.12' ext.mockitoVersion = '2.2.28' diff --git a/jaeger-core/README.md b/jaeger-core/README.md index 439166f48..d82c0d126 100644 --- a/jaeger-core/README.md +++ b/jaeger-core/README.md @@ -25,6 +25,42 @@ Tracer tracer = config.getTracer(); The `config` objects lazily builds and configures Jaeger Tracer. Multiple calls to `getTracer()` return the same instance. + +#### Configuration via Environment + +It is also possible to obtain a `com.uber.jaeger.Configuration` object configured using properties specified +as environment variables or system properties. A value specified as a system property will override a value +specified as an environment variable for the same property name. + +```java +Configuration config = Configuration.fromEnv(); +``` + +The property names are: + +Property | Required | Description +--- | --- | --- +JAEGER_SERVICE_NAME | yes | The service name +JAEGER_AGENT_HOST | no | The hostname for communicating with agent via UDP +JAEGER_AGENT_PORT | no | The port for communicating with agent via UDP +JAEGER_REPORTER_LOG_SPANS | no | Whether the reporter should also log the spans +JAEGER_REPORTER_MAX_QUEUE_SIZE | no | The reporter's maximum queue size +JAEGER_REPORTER_FLUSH_INTERVAL | no | The reporter's flush interval (ms) +JAEGER_SAMPLER_TYPE | no | The sampler type +JAEGER_SAMPLER_PARAM | no | The sampler parameter (number) +JAEGER_SAMPLER_MANAGER_HOST_PORT | no | The host name and port when using the remote controlled sampler + + +#### Obtaining Tracer via TracerResolver + +Jaeger's Java Client also provides an implementation of the +[TracerResolver](https://github.com/opentracing-contrib/java-tracerresolver), allowing a `Tracer` to be +obtained in a vendor neutral manner. This `TracerResolver` implementation uses the configuration via +environment approach described above. + +More information about using the `TracerResolver` can be found [here](../jaeger-tracerresolver/README.md). + + ### Development The last two parameters to `new Configuration()` allow control over configuration of the Sampler and Reporter. diff --git a/jaeger-core/src/main/java/com/uber/jaeger/Configuration.java b/jaeger-core/src/main/java/com/uber/jaeger/Configuration.java index f40720d67..5251c2797 100644 --- a/jaeger-core/src/main/java/com/uber/jaeger/Configuration.java +++ b/jaeger-core/src/main/java/com/uber/jaeger/Configuration.java @@ -37,12 +37,66 @@ import com.uber.jaeger.samplers.RemoteControlledSampler; import com.uber.jaeger.samplers.Sampler; import com.uber.jaeger.senders.UdpSender; + +import java.text.NumberFormat; +import java.text.ParseException; + import lombok.extern.slf4j.Slf4j; @Slf4j public class Configuration { public static final double DEFAULT_SAMPLING_PROBABILITY = 0.001; + /** + * Prefix for all properties used to configure the Jaeger tracer. + */ + public static final String JAEGER_PREFIX = "JAEGER_"; + + /** + * The host name used to locate the agent. + */ + public static final String JAEGER_AGENT_HOST = JAEGER_PREFIX + "AGENT_HOST"; + + /** + * The port used to locate the agent. + */ + public static final String JAEGER_AGENT_PORT = JAEGER_PREFIX + "AGENT_PORT"; + + /** + * Whether the reporter should log the spans. + */ + public static final String JAEGER_REPORTER_LOG_SPANS = JAEGER_PREFIX + "REPORTER_LOG_SPANS"; + + /** + * The maximum queue size for use when reporting spans remotely. + */ + public static final String JAEGER_REPORTER_MAX_QUEUE_SIZE = JAEGER_PREFIX + "REPORTER_MAX_QUEUE_SIZE"; + + /** + * The flush interval when reporting spans remotely. + */ + public static final String JAEGER_REPORTER_FLUSH_INTERVAL = JAEGER_PREFIX + "REPORTER_FLUSH_INTERVAL"; + + /** + * The sampler type. + */ + public static final String JAEGER_SAMPLER_TYPE = JAEGER_PREFIX + "SAMPLER_TYPE"; + + /** + * The sampler parameter (number). + */ + public static final String JAEGER_SAMPLER_PARAM = "JAEGER_SAMPLER_PARAM"; + + /** + * The sampler manager host:port. + */ + public static final String JAEGER_SAMPLER_MANAGER_HOST_PORT = JAEGER_PREFIX + "SAMPLER_MANAGER_HOST_PORT"; + + /** + * The service name. + */ + public static final String JAEGER_SERVICE_NAME = JAEGER_PREFIX + "SERVICE_NAME"; + /** * The serviceName that the tracer will use */ @@ -85,6 +139,27 @@ public Configuration( statsFactory = new StatsFactoryImpl(new NullStatsReporter()); } + public static Configuration fromEnv() { + return new Configuration(getProperty(JAEGER_SERVICE_NAME), + getSamplerConfigurationFromEnv(), getReporterConfigurationFromEnv()); + } + + static ReporterConfiguration getReporterConfigurationFromEnv() { + return new ReporterConfiguration( + getPropertyAsBoolean(JAEGER_REPORTER_LOG_SPANS), + getProperty(JAEGER_AGENT_HOST), + getPropertyAsInt(JAEGER_AGENT_PORT), + getPropertyAsInt(JAEGER_REPORTER_FLUSH_INTERVAL), + getPropertyAsInt(JAEGER_REPORTER_MAX_QUEUE_SIZE)); + } + + static SamplerConfiguration getSamplerConfigurationFromEnv() { + return new SamplerConfiguration( + getProperty(JAEGER_SAMPLER_TYPE), + getPropertyAsNum(JAEGER_SAMPLER_PARAM), + getProperty(JAEGER_SAMPLER_MANAGER_HOST_PORT)); + } + public Tracer.Builder getTracerBuilder() { Metrics metrics = new Metrics(statsFactory); Reporter reporter = reporterConfig.getReporter(metrics); @@ -282,4 +357,41 @@ private static String stringOrDefault(String value, String defaultValue) { private static Number numberOrDefault(Number value, Number defaultValue) { return value != null ? value : defaultValue; } + + private static String getProperty(String name) { + return System.getProperty(name, System.getenv(name)); + } + + private static Integer getPropertyAsInt(String name) { + String value = getProperty(name); + if (value != null) { + try { + return Integer.parseInt(value); + } catch (NumberFormatException e) { + log.error("Failed to parse integer for property '" + name + "' with value '" + value + "'", e); + } + } + return null; + } + + private static Number getPropertyAsNum(String name) { + String value = getProperty(name); + if (value != null) { + try { + return NumberFormat.getInstance().parse(value); + } catch (ParseException e) { + log.error("Failed to parse number for property '" + name + "' with value '" + value + "'", e); + } + } + return null; + } + + private static Boolean getPropertyAsBoolean(String name) { + String value = getProperty(name); + if (value != null) { + return Boolean.valueOf(value); + } + return null; + } + } diff --git a/jaeger-core/src/test/java/com/uber/jaeger/ConfigurationTest.java b/jaeger-core/src/test/java/com/uber/jaeger/ConfigurationTest.java new file mode 100644 index 000000000..55cd417d8 --- /dev/null +++ b/jaeger-core/src/test/java/com/uber/jaeger/ConfigurationTest.java @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2017, Uber Technologies, Inc + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.uber.jaeger; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import com.uber.jaeger.Configuration.ReporterConfiguration; +import com.uber.jaeger.Configuration.SamplerConfiguration; +import com.uber.jaeger.samplers.ConstSampler; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +public class ConfigurationTest { + + @Before + @After + public void clearProperties() { + // Explicitly clear all properties + System.clearProperty(Configuration.JAEGER_AGENT_HOST); + System.clearProperty(Configuration.JAEGER_AGENT_PORT); + System.clearProperty(Configuration.JAEGER_REPORTER_LOG_SPANS); + System.clearProperty(Configuration.JAEGER_REPORTER_MAX_QUEUE_SIZE); + System.clearProperty(Configuration.JAEGER_REPORTER_FLUSH_INTERVAL); + System.clearProperty(Configuration.JAEGER_SAMPLER_TYPE); + System.clearProperty(Configuration.JAEGER_SAMPLER_PARAM); + System.clearProperty(Configuration.JAEGER_SAMPLER_MANAGER_HOST_PORT); + System.clearProperty(Configuration.JAEGER_SERVICE_NAME); + } + + @Test + public void testFromEnv() { + System.setProperty(Configuration.JAEGER_SERVICE_NAME, "Test"); + assertNotNull(Configuration.fromEnv().getTracer()); + } + + @Test + public void testSamplerConst() { + System.setProperty(Configuration.JAEGER_SAMPLER_TYPE, ConstSampler.TYPE); + System.setProperty(Configuration.JAEGER_SAMPLER_PARAM, "1"); + SamplerConfiguration samplerConfig = Configuration.getSamplerConfigurationFromEnv(); + assertEquals(ConstSampler.TYPE, samplerConfig.getType()); + assertEquals(1, samplerConfig.getParam().intValue()); + } + + @Test + public void testSamplerConstInvalidParam() { + System.setProperty(Configuration.JAEGER_SAMPLER_TYPE, ConstSampler.TYPE); + System.setProperty(Configuration.JAEGER_SAMPLER_PARAM, "X"); + SamplerConfiguration samplerConfig = Configuration.getSamplerConfigurationFromEnv(); + assertEquals(ConstSampler.TYPE, samplerConfig.getType()); + assertNull(samplerConfig.getParam()); + } + + @Test + public void testReporterConfiguration() { + System.setProperty(Configuration.JAEGER_REPORTER_LOG_SPANS, "true"); + System.setProperty(Configuration.JAEGER_AGENT_HOST, "MyHost"); + System.setProperty(Configuration.JAEGER_AGENT_PORT, "1234"); + System.setProperty(Configuration.JAEGER_REPORTER_FLUSH_INTERVAL, "500"); + System.setProperty(Configuration.JAEGER_REPORTER_MAX_QUEUE_SIZE, "1000"); + ReporterConfiguration reporterConfig = Configuration.getReporterConfigurationFromEnv(); + assertTrue(reporterConfig.getLogSpans()); + assertEquals("MyHost", reporterConfig.getAgentHost()); + assertEquals(1234, reporterConfig.getAgentPort().intValue()); + assertEquals(500, reporterConfig.getFlushIntervalMs().intValue()); + assertEquals(1000, reporterConfig.getMaxQueueSize().intValue()); + } + + @Test + public void testReporterConfigurationInvalidFlushInterval() { + System.setProperty(Configuration.JAEGER_REPORTER_FLUSH_INTERVAL, "X"); + ReporterConfiguration reporterConfig = Configuration.getReporterConfigurationFromEnv(); + assertNull(reporterConfig.getFlushIntervalMs()); + } + + @Test + public void testReporterConfigurationInvalidLogSpans() { + System.setProperty(Configuration.JAEGER_REPORTER_LOG_SPANS, "X"); + ReporterConfiguration reporterConfig = Configuration.getReporterConfigurationFromEnv(); + assertFalse(reporterConfig.getLogSpans()); + } + +} diff --git a/jaeger-tracerresolver/README.md b/jaeger-tracerresolver/README.md new file mode 100644 index 000000000..3e1c0b671 --- /dev/null +++ b/jaeger-tracerresolver/README.md @@ -0,0 +1,23 @@ +# Jaeger Tracer Resolver + +This module provides a Jaeger implementation for the [TracerResolver](https://github.com/opentracing-contrib/java-tracerresolver). This mechanism provides a vendor neutral approach for obtaining a `Tracer` using the JDK +`ServiceLoader`. + + +## Maven Dependency +```xml + + com.uber.jaeger + jaeger-tracerresolver + $jaegerVersion + +``` + +## Usage + +```java +Tracer tracer = TracerResolver.resolveTracer(); +``` + +This tracer is configured via environment variables (or system properties). The properties are +described [here](../jaeger-core/README.md). \ No newline at end of file diff --git a/jaeger-tracerresolver/build.gradle b/jaeger-tracerresolver/build.gradle new file mode 100644 index 000000000..ed435d8ed --- /dev/null +++ b/jaeger-tracerresolver/build.gradle @@ -0,0 +1,17 @@ +description = 'TracerResolver implementation for Jaeger Tracer' + +dependencies { + compile project(':jaeger-core') + compile group: 'io.opentracing.contrib', name: 'opentracing-tracerresolver', version: tracerResolverVersion + + testCompile group: 'junit', name: 'junit', version: junitVersion + + signature 'org.codehaus.mojo.signature:java16:1.1@signature' +} + +jar { + from sourceSets.main.output + manifest { + attributes('Implementation-Title': 'jaeger-tracerresolver', 'Implementation-Version': project.version) + } +} diff --git a/jaeger-tracerresolver/src/main/java/com/uber/jaeger/tracerresolver/JaegerTracerResolver.java b/jaeger-tracerresolver/src/main/java/com/uber/jaeger/tracerresolver/JaegerTracerResolver.java new file mode 100644 index 000000000..35ab3af1e --- /dev/null +++ b/jaeger-tracerresolver/src/main/java/com/uber/jaeger/tracerresolver/JaegerTracerResolver.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2017, Uber Technologies, Inc + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.uber.jaeger.tracerresolver; + +import com.uber.jaeger.Configuration; + +import io.opentracing.contrib.tracerresolver.TracerResolver; + +public class JaegerTracerResolver extends TracerResolver { + + @Override + protected io.opentracing.Tracer resolve() { + return Configuration.fromEnv().getTracer(); + } + +} diff --git a/jaeger-tracerresolver/src/main/resources/META-INF/services/io.opentracing.contrib.tracerresolver.TracerResolver b/jaeger-tracerresolver/src/main/resources/META-INF/services/io.opentracing.contrib.tracerresolver.TracerResolver new file mode 100644 index 000000000..4fb6a55f6 --- /dev/null +++ b/jaeger-tracerresolver/src/main/resources/META-INF/services/io.opentracing.contrib.tracerresolver.TracerResolver @@ -0,0 +1 @@ +com.uber.jaeger.tracerresolver.JaegerTracerResolver diff --git a/jaeger-tracerresolver/src/test/java/com/uber/jaeger/tracerresolver/JaegerTracerResolverTest.java b/jaeger-tracerresolver/src/test/java/com/uber/jaeger/tracerresolver/JaegerTracerResolverTest.java new file mode 100644 index 000000000..f505c19ba --- /dev/null +++ b/jaeger-tracerresolver/src/test/java/com/uber/jaeger/tracerresolver/JaegerTracerResolverTest.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2017, Uber Technologies, Inc + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.uber.jaeger.tracerresolver; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import com.uber.jaeger.Configuration; + +import io.opentracing.Tracer; +import io.opentracing.contrib.tracerresolver.TracerResolver; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +public class JaegerTracerResolverTest { + + @Before + @After + public void clearProperties() { + // Explicitly clear all TracerResolver properties + System.clearProperty(Configuration.JAEGER_SERVICE_NAME); + } + + @Test + public void testResolveTracerNoServiceName() { + assertNull(TracerResolver.resolveTracer()); + } + + @Test + public void testResolveTracerDefault() { + System.setProperty(Configuration.JAEGER_SERVICE_NAME, "MyService"); + Tracer tracer = TracerResolver.resolveTracer(); + assertNotNull(tracer); + assertTrue(tracer instanceof com.uber.jaeger.Tracer); + } + +} diff --git a/settings.gradle b/settings.gradle index fcf3b63c3..d248fcb47 100644 --- a/settings.gradle +++ b/settings.gradle @@ -8,3 +8,4 @@ include 'jaeger-zipkin' include 'jaeger-crossdock' include 'jaeger-thrift' include 'jaeger-apachehttpclient' +include 'jaeger-tracerresolver'