From f9ab9273b0be018fb145ca6d5ce113bce519b878 Mon Sep 17 00:00:00 2001 From: Simon Stewart Date: Tue, 28 Jan 2020 07:44:15 +0000 Subject: [PATCH] [tracing] Add easy support for Jaeger tracing --- java/maven_deps.bzl | 3 +- .../org/openqa/selenium/grid/log/BUILD.bazel | 1 + .../selenium/grid/log/JaegerTracing.java | 91 +++++++++++++++++++ .../selenium/grid/log/LoggingOptions.java | 19 +++- 4 files changed, 110 insertions(+), 4 deletions(-) create mode 100644 java/server/src/org/openqa/selenium/grid/log/JaegerTracing.java diff --git a/java/maven_deps.bzl b/java/maven_deps.bzl index 1fc66485fa55a..369e03e00f218 100644 --- a/java/maven_deps.bzl +++ b/java/maven_deps.bzl @@ -16,6 +16,7 @@ def selenium_java_deps(): "com.google.auto.service:auto-service-annotations:1.0-rc6", "com.squareup.okhttp3:okhttp:4.3.0", "com.typesafe.netty:netty-reactive-streams:2.0.4", +# "io.grpc:grpc-api:1.24.0", "io.lettuce:lettuce-core:5.2.1.RELEASE", "io.netty:netty-buffer:%s" % netty_version, "io.netty:netty-codec-haproxy:%s" % netty_version, @@ -25,7 +26,7 @@ def selenium_java_deps(): "io.netty:netty-transport:%s" % netty_version, "io.opentelemetry:opentelemetry-api:0.2.0", "io.opentelemetry:opentelemetry-exporters-inmemory:0.2.0", - "io.opentelemetry:opentelemetry-exporters-jaeger:0.2.0", +# "io.opentelemetry:opentelemetry-exporters-jaeger:0.2.0", "io.opentelemetry:opentelemetry-exporters-logging:0.2.0", "io.opentelemetry:opentelemetry-sdk:0.2.0", "it.ozimov:embedded-redis:0.7.2", diff --git a/java/server/src/org/openqa/selenium/grid/log/BUILD.bazel b/java/server/src/org/openqa/selenium/grid/log/BUILD.bazel index eae210d8a835b..c4a380a6c54ef 100644 --- a/java/server/src/org/openqa/selenium/grid/log/BUILD.bazel +++ b/java/server/src/org/openqa/selenium/grid/log/BUILD.bazel @@ -11,6 +11,7 @@ java_library( "//java/client/src/org/openqa/selenium/json", "//java/client/src/org/openqa/selenium/remote", "//java/server/src/org/openqa/selenium/grid/config", +# artifact("io.grpc:grpc-api"), artifact("io.opentelemetry:opentelemetry-api"), # artifact("io.opentelemetry:opentelemetry-exporters-jaeger"), artifact("io.opentelemetry:opentelemetry-exporters-logging"), diff --git a/java/server/src/org/openqa/selenium/grid/log/JaegerTracing.java b/java/server/src/org/openqa/selenium/grid/log/JaegerTracing.java new file mode 100644 index 0000000000000..9253632696c56 --- /dev/null +++ b/java/server/src/org/openqa/selenium/grid/log/JaegerTracing.java @@ -0,0 +1,91 @@ +package org.openqa.selenium.grid.log; + +import io.opentelemetry.sdk.trace.export.SpanExporter; + +import java.lang.reflect.Method; + +/** + * We use an awful lof of reflection here because it's the only way we can + * get this to work without requiring the selenium server take a dependency + * on Jaeger, which may not be needed in all cases. + */ +class JaegerTracing { + + static SpanExporter findJaegerExporter() { + String host = System.getProperty("JAEGER_AGENT_HOST"); + if (host == null) { + return null; + } + + String rawPort = System.getProperty("JAEGER_AGENT_PORT"); + int port = -1; + try { + port = Integer.parseInt(rawPort); + } catch (NumberFormatException ignored) { + return null; + } + if (port == -1) { + return null; + } + + try { + Object jaegerChannel = createManagedChannel(host, port); + SpanExporter toReturn = (SpanExporter) createJaegerGrpcSpanExporter(jaegerChannel); + + if (toReturn != null) { + System.out.printf("Attaching Jaeger tracing to %s:%s\n", host, port); + } + + return toReturn; + } catch (ReflectiveOperationException e) { + return null; + } + } + + private static Object createManagedChannel(String host, int port) throws ReflectiveOperationException { + // Equivalent to: + // ManagedChannelBuilder.forAddress(host, port).usePlaintext().build(); + + ClassLoader cl = Thread.currentThread().getContextClassLoader(); + + Class builderClazz = Class.forName("io.grpc.ManagedChannelBuilder", true, cl); + Method forAddress = builderClazz.getMethod("forAddress", String.class, int.class); + Object value = forAddress.invoke(null, host, port); + + Method usePlaintext = builderClazz.getMethod("usePlaintext"); + value = usePlaintext.invoke(value); + + Method build = builderClazz.getMethod("build"); + return build.invoke(value); + } + + private static Object createJaegerGrpcSpanExporter(Object jaegerChannel) throws ReflectiveOperationException { + // Equivalent to: + // return JaegerGrpcSpanExporter.newBuilder() + // .setServiceName("selenium") + // .setChannel(jaegerChannel) + // .setDeadline(30000) + // .build(); + + ClassLoader cl = Thread.currentThread().getContextClassLoader(); + + Class exporterClazz = Class.forName("io.opentelemetry.exporters.jaeger.JaegerGrpcSpanExporter", true, cl); + Method newBuilder = exporterClazz.getMethod("newBuilder"); + Object builderObj = newBuilder.invoke(exporterClazz); + + Class builderClazz = builderObj.getClass(); + + Method setServiceName = builderClazz.getMethod("setServiceName", String.class); + builderObj = setServiceName.invoke(builderObj, System.getProperty("JAEGER_SERVICE_NAME", "selenium")); + + Class managedChannelClazz = Class.forName("io.grpc.ManagedChannel", true, cl); + Method setChannel = builderClazz.getMethod("setChannel", managedChannelClazz); + builderObj = setChannel.invoke(builderObj, jaegerChannel); + + Method setDeadline = builderClazz.getMethod("setDeadline", long.class); + builderObj = setDeadline.invoke(builderObj, 30000); + + Method build = builderClazz.getMethod("build"); + return build.invoke(builderObj); + } +} diff --git a/java/server/src/org/openqa/selenium/grid/log/LoggingOptions.java b/java/server/src/org/openqa/selenium/grid/log/LoggingOptions.java index e5f3b443c9086..ff17630cc5232 100644 --- a/java/server/src/org/openqa/selenium/grid/log/LoggingOptions.java +++ b/java/server/src/org/openqa/selenium/grid/log/LoggingOptions.java @@ -19,13 +19,18 @@ import io.opentelemetry.exporters.logging.LoggingExporter; import io.opentelemetry.sdk.OpenTelemetrySdk; +import io.opentelemetry.sdk.trace.MultiSpanProcessor; +import io.opentelemetry.sdk.trace.SpanProcessor; import io.opentelemetry.sdk.trace.TracerSdkFactory; import io.opentelemetry.sdk.trace.export.SimpleSpansProcessor; +import io.opentelemetry.sdk.trace.export.SpanExporter; import io.opentelemetry.trace.Tracer; import org.openqa.selenium.grid.config.Config; import java.util.Arrays; import java.util.Enumeration; +import java.util.LinkedList; +import java.util.List; import java.util.Objects; import java.util.logging.Handler; import java.util.logging.LogManager; @@ -49,11 +54,19 @@ public boolean isUsingPlainLogs() { public Tracer getTracer() { TracerSdkFactory tracerFactory = OpenTelemetrySdk.getTracerFactory(); - tracerFactory.addSpanProcessor(SimpleSpansProcessor.newBuilder(new LoggingExporter()).build()); + + List exporters = new LinkedList<>(); +// exporters.add(SimpleSpansProcessor.newBuilder(new LoggingExporter()).build()); // 2020-01-28: The Jaeger exporter doesn't yet have a - // `TracerFactoryProvider`, so we shall look up the class directly, and - // beg for forgiveness. + // `TracerFactoryProvider`, so we shall look up the class using + // reflection, and beg for forgiveness later. + SpanExporter maybeJaeger = JaegerTracing.findJaegerExporter(); + if (maybeJaeger != null) { + exporters.add(SimpleSpansProcessor.newBuilder(maybeJaeger).build()); + } + + tracerFactory.addSpanProcessor(MultiSpanProcessor.create(exporters)); return tracerFactory.get("default"); }