You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@solr.apache.org by st...@apache.org on 2023/08/24 13:21:34 UTC

[solr] branch main updated: SOLR-15367 Convert "rid" functionality into a default Tracer (#1854)

This is an automated email from the ASF dual-hosted git repository.

stillalex pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/solr.git


The following commit(s) were added to refs/heads/main by this push:
     new ce6c13e4180 SOLR-15367 Convert "rid" functionality into a default Tracer (#1854)
ce6c13e4180 is described below

commit ce6c13e41806233daaf25f1a7545c7aad34c169d
Author: Alex D <st...@apache.org>
AuthorDate: Thu Aug 24 06:21:27 2023 -0700

    SOLR-15367 Convert "rid" functionality into a default Tracer (#1854)
---
 solr/CHANGES.txt                                   |   2 +
 .../org/apache/solr/core/TracerConfigurator.java   |   7 ++
 .../apache/solr/util/tracing/SimplePropagator.java | 111 +++++++++++++++++++
 .../TestSimplePropagatorDistributedTracing.java    | 117 +++++++++++++++++++++
 .../solr/opentelemetry/TestDistributedTracing.java |   1 +
 .../apache/solr/cloud/MiniSolrCloudCluster.java    |  13 +++
 6 files changed, 251 insertions(+)

diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt
index ce100a932d6..8c903cc364d 100644
--- a/solr/CHANGES.txt
+++ b/solr/CHANGES.txt
@@ -19,6 +19,8 @@ Improvements
 
 * SOLR-16536: Replace OpenTracing instrumentation with OpenTelemetry (Alex Deparvu, janhoy)
 
+* SOLR-15367: Convert "rid" functionality into a default Tracer (Alex Deparvu, David Smiley)
+
 Optimizations
 ---------------------
 (No changes)
diff --git a/solr/core/src/java/org/apache/solr/core/TracerConfigurator.java b/solr/core/src/java/org/apache/solr/core/TracerConfigurator.java
index bc1798863f7..f9a6846ca91 100644
--- a/solr/core/src/java/org/apache/solr/core/TracerConfigurator.java
+++ b/solr/core/src/java/org/apache/solr/core/TracerConfigurator.java
@@ -19,17 +19,24 @@ package org.apache.solr.core;
 
 import io.opentelemetry.api.trace.Tracer;
 import org.apache.solr.util.plugin.NamedListInitializedPlugin;
+import org.apache.solr.util.tracing.SimplePropagator;
 import org.apache.solr.util.tracing.TraceUtils;
 
 /** Produces a {@link Tracer} from configuration. */
 public abstract class TracerConfigurator implements NamedListInitializedPlugin {
 
+  public static final boolean TRACE_ID_GEN_ENABLED =
+      !Boolean.getBoolean("solr.disable.alwaysOnTraceId");
+
   public static Tracer loadTracer(SolrResourceLoader loader, PluginInfo info) {
     if (info != null && info.isEnabled()) {
       TracerConfigurator configurator =
           loader.newInstance(info.className, TracerConfigurator.class);
       configurator.init(info.initArgs);
       return configurator.getTracer();
+
+    } else if (TRACE_ID_GEN_ENABLED) {
+      return SimplePropagator.load();
     } else {
       return TraceUtils.noop();
     }
diff --git a/solr/core/src/java/org/apache/solr/util/tracing/SimplePropagator.java b/solr/core/src/java/org/apache/solr/util/tracing/SimplePropagator.java
new file mode 100644
index 00000000000..01a72e0a1ff
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/util/tracing/SimplePropagator.java
@@ -0,0 +1,111 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.solr.util.tracing;
+
+import io.opentelemetry.api.GlobalOpenTelemetry;
+import io.opentelemetry.api.OpenTelemetry;
+import io.opentelemetry.api.trace.Tracer;
+import io.opentelemetry.context.Context;
+import io.opentelemetry.context.ContextKey;
+import io.opentelemetry.context.propagation.ContextPropagators;
+import io.opentelemetry.context.propagation.TextMapGetter;
+import io.opentelemetry.context.propagation.TextMapPropagator;
+import io.opentelemetry.context.propagation.TextMapSetter;
+import java.lang.invoke.MethodHandles;
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicLong;
+import org.apache.solr.logging.MDCLoggingContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Simple Http Header Propagator. When enabled, this will only propagate the trace id from the
+ * client to all internal requests. It is also in charge of generating a trace id if none exists.
+ *
+ * <p>Note: this is very similar in impl to
+ * io.opentelemetry.extension.incubator.propagation.PassThroughPropagator. we should consider
+ * replacing/upgrading once that becomes generally available
+ */
+public class SimplePropagator implements TextMapPropagator {
+  private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+  private static final String TRACE_HOST_NAME =
+      System.getProperty("trace_host_name", System.getProperty("host"));
+  private static final TextMapPropagator INSTANCE = new SimplePropagator();
+  private static final ContextKey<String> TRACE_ID_KEY = ContextKey.named("trace_id");
+
+  static final String TRACE_ID = System.getProperty("TRACE_ID", "X-Trace-Id");
+  private static final List<String> FIELDS = List.of(TRACE_ID);
+
+  private static final AtomicLong traceCounter = new AtomicLong(0);
+
+  private static volatile boolean loaded = false;
+
+  public static synchronized Tracer load() {
+    if (!loaded) {
+      log.info("OpenTelemetry tracer enabled with simple propagation only.");
+      OpenTelemetry otel =
+          OpenTelemetry.propagating(ContextPropagators.create(SimplePropagator.getInstance()));
+      GlobalOpenTelemetry.set(otel);
+      loaded = true;
+    }
+    return GlobalOpenTelemetry.getTracer("solr");
+  }
+
+  public static TextMapPropagator getInstance() {
+    return INSTANCE;
+  }
+
+  private SimplePropagator() {}
+
+  @Override
+  public Collection<String> fields() {
+    return FIELDS;
+  }
+
+  @Override
+  public <C> void inject(Context context, C carrier, TextMapSetter<C> setter) {
+    if (setter == null) {
+      return;
+    }
+    String traceId = context.get(TRACE_ID_KEY);
+    if (traceId != null) {
+      setter.set(carrier, TRACE_ID, traceId);
+    }
+  }
+
+  @Override
+  public <C> Context extract(Context context, C carrier, TextMapGetter<C> getter) {
+    String traceId = getter.get(carrier, TRACE_ID);
+    if (traceId == null) {
+      traceId = newTraceId();
+    }
+
+    MDCLoggingContext.setTracerId(traceId);
+    return context.with(TRACE_ID_KEY, traceId);
+  }
+
+  private static String newTraceId() {
+    return TRACE_HOST_NAME + "-" + traceCounter.incrementAndGet();
+  }
+
+  @Override
+  public String toString() {
+    return "SimplePropagator";
+  }
+}
diff --git a/solr/core/src/test/org/apache/solr/util/tracing/TestSimplePropagatorDistributedTracing.java b/solr/core/src/test/org/apache/solr/util/tracing/TestSimplePropagatorDistributedTracing.java
new file mode 100644
index 00000000000..0213fce7b9a
--- /dev/null
+++ b/solr/core/src/test/org/apache/solr/util/tracing/TestSimplePropagatorDistributedTracing.java
@@ -0,0 +1,117 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.solr.util.tracing;
+
+import io.opentelemetry.api.GlobalOpenTelemetry;
+import io.opentelemetry.api.trace.TracerProvider;
+import java.io.IOException;
+import org.apache.solr.client.solrj.SolrQuery;
+import org.apache.solr.client.solrj.SolrServerException;
+import org.apache.solr.client.solrj.impl.CloudSolrClient;
+import org.apache.solr.client.solrj.request.CollectionAdminRequest;
+import org.apache.solr.client.solrj.request.QueryRequest;
+import org.apache.solr.cloud.SolrCloudTestCase;
+import org.apache.solr.common.util.SuppressForbidden;
+import org.apache.solr.core.SolrCore;
+import org.apache.solr.logging.MDCLoggingContext;
+import org.apache.solr.update.processor.LogUpdateProcessorFactory;
+import org.apache.solr.util.LogListener;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+@SuppressForbidden(reason = "We need to use log4J2 classes directly to test MDC impacts")
+public class TestSimplePropagatorDistributedTracing extends SolrCloudTestCase {
+
+  private static final String COLLECTION = "collection1";
+
+  @BeforeClass
+  public static void setupCluster() throws Exception {
+    configureCluster(4).addConfig("conf", configset("cloud-minimal")).configure();
+
+    // tracer should be disabled
+    assertEquals(
+        "Expecting noop otel (propagating only)",
+        TracerProvider.noop(),
+        GlobalOpenTelemetry.get().getTracerProvider());
+
+    CollectionAdminRequest.createCollection(COLLECTION, "conf", 2, 2)
+        .process(cluster.getSolrClient());
+    cluster.waitForActiveCollection(COLLECTION, 2, 4);
+  }
+
+  @Test
+  public void test() throws IOException, SolrServerException {
+    CloudSolrClient cloudClient = cluster.getSolrClient();
+
+    // Indexing has trace ids
+    try (LogListener reqLog = LogListener.info(LogUpdateProcessorFactory.class.getName())) {
+      // verify all indexing events have trace id present
+      cloudClient.add(COLLECTION, sdoc("id", "1"));
+      cloudClient.add(COLLECTION, sdoc("id", "2"));
+      cloudClient.add(COLLECTION, sdoc("id", "3"));
+      var queue = reqLog.getQueue();
+      assertFalse(queue.isEmpty());
+      while (!queue.isEmpty()) {
+        var reqEvent = queue.poll();
+        String evTraceId = reqEvent.getContextData().getValue(MDCLoggingContext.TRACE_ID);
+        assertNotNull(evTraceId);
+      }
+
+      // TODO this doesn't work due to solr client creating the UpdateRequest without headers
+      //      // verify all events have the same 'custom' traceid
+      //      String traceId = "tidTestSimplePropagatorDistributedTracing0";
+      //      var doc = sdoc("id", "4");
+      //      UpdateRequest u = new UpdateRequest();
+      //      u.add(doc);
+      //      u.addHeader(SimplePropagator.TRACE_ID, traceId);
+      //      var r1 = u.process(cloudClient, COLLECTION);
+      //      assertEquals(0, r1.getStatus());
+      //      assertSameTraceId(reqLog, traceId);
+    }
+
+    // Searching has trace ids
+    try (LogListener reqLog = LogListener.info(SolrCore.class.getName() + ".Request")) {
+      // verify all query events have the same auto-generated traceid
+      var r1 = cloudClient.query(COLLECTION, new SolrQuery("*:*"));
+      assertEquals(0, r1.getStatus());
+      assertSameTraceId(reqLog, null);
+
+      // verify all query events have the same 'custom' traceid
+      String traceId = "tidTestSimplePropagatorDistributedTracing1";
+      var q = new QueryRequest(new SolrQuery("*:*"));
+      q.addHeader(SimplePropagator.TRACE_ID, traceId);
+      var r2 = q.process(cloudClient, COLLECTION);
+      assertEquals(0, r2.getStatus());
+      assertSameTraceId(reqLog, traceId);
+    }
+  }
+
+  private void assertSameTraceId(LogListener reqLog, String traceId) {
+    var queue = reqLog.getQueue();
+    assertFalse(queue.isEmpty());
+    if (traceId == null) {
+      traceId = queue.poll().getContextData().getValue(MDCLoggingContext.TRACE_ID);
+      assertNotNull(traceId);
+    }
+    while (!queue.isEmpty()) {
+      var reqEvent = queue.poll();
+      String evTraceId = reqEvent.getContextData().getValue(MDCLoggingContext.TRACE_ID);
+      assertEquals(traceId, evTraceId);
+    }
+  }
+}
diff --git a/solr/modules/opentelemetry/src/test/org/apache/solr/opentelemetry/TestDistributedTracing.java b/solr/modules/opentelemetry/src/test/org/apache/solr/opentelemetry/TestDistributedTracing.java
index dea832e87f6..f6e088e5168 100644
--- a/solr/modules/opentelemetry/src/test/org/apache/solr/opentelemetry/TestDistributedTracing.java
+++ b/solr/modules/opentelemetry/src/test/org/apache/solr/opentelemetry/TestDistributedTracing.java
@@ -54,6 +54,7 @@ public class TestDistributedTracing extends SolrCloudTestCase {
     configureCluster(4)
         .addConfig("config", TEST_PATH().resolve("collection1").resolve("conf"))
         .withSolrXml(TEST_PATH().resolve("solr.xml"))
+        .withTraceIdGenerationDisabled()
         .configure();
 
     assertNotEquals(
diff --git a/solr/test-framework/src/java/org/apache/solr/cloud/MiniSolrCloudCluster.java b/solr/test-framework/src/java/org/apache/solr/cloud/MiniSolrCloudCluster.java
index 3a4d41d0420..fdc64237a80 100644
--- a/solr/test-framework/src/java/org/apache/solr/cloud/MiniSolrCloudCluster.java
+++ b/solr/test-framework/src/java/org/apache/solr/cloud/MiniSolrCloudCluster.java
@@ -78,9 +78,11 @@ import org.apache.solr.common.util.IOUtils;
 import org.apache.solr.common.util.SolrNamedThreadFactory;
 import org.apache.solr.common.util.TimeSource;
 import org.apache.solr.core.CoreContainer;
+import org.apache.solr.core.TracerConfigurator;
 import org.apache.solr.embedded.JettyConfig;
 import org.apache.solr.embedded.JettySolrRunner;
 import org.apache.solr.util.TimeOut;
+import org.apache.solr.util.tracing.SimplePropagator;
 import org.apache.zookeeper.KeeperException;
 import org.eclipse.jetty.server.handler.HandlerWrapper;
 import org.eclipse.jetty.servlet.ServletHolder;
@@ -1044,6 +1046,7 @@ public class MiniSolrCloudCluster {
     private boolean useDistributedCollectionConfigSetExecution;
     private boolean useDistributedClusterStateUpdate;
     private boolean formatZkServer = true;
+    private boolean disableTraceIdGeneration = false;
 
     /**
      * Create a builder
@@ -1237,6 +1240,11 @@ public class MiniSolrCloudCluster {
           "solr.distributedClusterStateUpdates",
           Boolean.toString(useDistributedClusterStateUpdate));
 
+      // eager init to prevent OTEL init races caused by test setup
+      if (!disableTraceIdGeneration && TracerConfigurator.TRACE_ID_GEN_ENABLED) {
+        SimplePropagator.load();
+      }
+
       JettyConfig jettyConfig = jettyConfigBuilder.build();
       MiniSolrCloudCluster cluster =
           new MiniSolrCloudCluster(
@@ -1279,5 +1287,10 @@ public class MiniSolrCloudCluster {
       cluster.put(key, value);
       return this;
     }
+
+    public Builder withTraceIdGenerationDisabled() {
+      this.disableTraceIdGeneration = true;
+      return this;
+    }
   }
 }