All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.kristofa.brave.http.ITHttpClient Maven / Gradle / Ivy

There is a newer version: 4.13.6
Show newest version
package com.github.kristofa.brave.http;

import com.github.kristofa.brave.Brave;
import com.github.kristofa.brave.IdConversion;
import com.github.kristofa.brave.InheritableServerClientAndLocalSpanState;
import com.github.kristofa.brave.Sampler;
import com.github.kristofa.brave.ServerSpan;
import com.github.kristofa.brave.SpanId;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.TimeUnit;
import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
import okhttp3.mockwebserver.RecordedRequest;
import okhttp3.mockwebserver.SocketPolicy;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import zipkin.Constants;
import zipkin.Endpoint;
import zipkin.TraceKeys;
import zipkin.internal.Util;
import zipkin2.Span;
import zipkin2.storage.InMemoryStorage;

import static java.util.Arrays.asList;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.entry;

public abstract class ITHttpClient {
  @Rule public ExpectedException thrown = ExpectedException.none();
  @Rule public MockWebServer server = new MockWebServer();

  Endpoint local = Endpoint.builder().serviceName("local").ipv4(127 << 24 | 1).port(100).build();
  InMemoryStorage storage = InMemoryStorage.newBuilder().build();

  protected Brave brave;
  C client;

  @Before
  public void setup() {
    brave = braveBuilder(Sampler.ALWAYS_SAMPLE).build();
    client = newClient(server.getPort());
  }

  /**
   * Make sure the client you return has retries disabled.
   */
  protected abstract C newClient(int port);

  protected abstract C newClient(int port, SpanNameProvider spanNameProvider);

  protected abstract void closeClient(C client) throws IOException;

  protected abstract void get(C client, String pathIncludingQuery) throws Exception;

  protected abstract void getAsync(C client, String pathIncludingQuery) throws Exception;

  @After
  public void close() throws IOException {
    closeClient(client);
  }

  @Test
  public void propagatesSpan() throws Exception {
    server.enqueue(new MockResponse());
    get(client, "/foo");

    RecordedRequest request = server.takeRequest();
    assertThat(request.getHeaders().toMultimap())
        .containsKeys("x-b3-traceId", "x-b3-spanId")
        .containsEntry("x-b3-sampled", asList("1"));
  }

  @Test
  public void usesExistingTraceId_server() throws Exception {
    server.enqueue(new MockResponse());

    brave.serverTracer().setStateUnknown("test");
    ServerSpan parent = brave.serverSpanThreadBinder().getCurrentServerSpan();
    try {
      get(client, "/foo");
    } finally {
      brave.serverTracer().clearCurrentSpan();
    }

    RecordedRequest request = server.takeRequest();
    assertThat(request.getHeader("x-b3-traceId"))
        .isEqualTo(Util.toLowerHex(parent.getSpan().getTrace_id()));
    assertThat(request.getHeader("x-b3-parentspanid"))
        .isEqualTo(IdConversion.convertToString(parent.getSpan().getId()));
  }

  @Test
  public void usesExistingTraceId_local() throws Exception {
    server.enqueue(new MockResponse());

    SpanId parent = brave.localTracer().startNewSpan(getClass().getSimpleName(), "test");
    try {
      get(client, "/foo");
    } finally {
      brave.localTracer().finishSpan();
    }

    RecordedRequest request = server.takeRequest();
    assertThat(request.getHeader("x-b3-traceId"))
        .isEqualTo(parent.traceIdString());
    assertThat(request.getHeader("x-b3-parentspanid"))
        .isEqualTo(IdConversion.convertToString(parent.spanId));
  }

  /**
   * This tests that the parent is determined at the time the request was made, not when the request
   * was executed.
   */
  @Test
  public void usesParentFromInvocationTime_local() throws Exception {
    server.enqueue(new MockResponse().setBodyDelay(1, TimeUnit.SECONDS));
    server.enqueue(new MockResponse());

    SpanId parent = brave.localTracer().startNewSpan(getClass().getSimpleName(), "test");
    try {
      getAsync(client, "/foo");
      getAsync(client, "/foo");
    } finally {
      brave.localTracer().finishSpan();
    }

    // changing the local span after the fact!
    brave.localTracer().startNewSpan(getClass().getSimpleName(), "test");

    try {
      for (int i = 0; i < 2; i++) {
        RecordedRequest request = server.takeRequest();
        assertThat(request.getHeader("x-b3-traceId"))
            .isEqualTo(parent.traceIdString());
        assertThat(request.getHeader("x-b3-parentspanid"))
            .isEqualTo(IdConversion.convertToString(parent.spanId));
      }
    } finally {
      brave.localTracer().finishSpan();
    }
  }

  @Test
  public void usesParentFromInvocationTime_server() throws Exception {
    server.enqueue(new MockResponse().setBodyDelay(1, TimeUnit.SECONDS));
    server.enqueue(new MockResponse());

    brave.serverTracer().setStateUnknown("test");
    ServerSpan parent = brave.serverSpanThreadBinder().getCurrentServerSpan();
    try {
      getAsync(client, "/foo");
      getAsync(client, "/foo");
    } finally {
      brave.serverTracer().clearCurrentSpan();
    }

    // changing the server span after the fact!
    brave.serverTracer().setStateUnknown("test");

    try {
      for (int i = 0; i < 2; i++) {
        RecordedRequest request = server.takeRequest();
        assertThat(request.getHeader("x-b3-traceId"))
            .isEqualTo(Util.toLowerHex(parent.getSpan().getTrace_id()));
        assertThat(request.getHeader("x-b3-parentspanid"))
            .isEqualTo(IdConversion.convertToString(parent.getSpan().getId()));
      }
    } finally {
      brave.serverTracer().clearCurrentSpan();
    }
  }

  @Test
  public void propagates_sampledFalse() throws Exception {
    brave = braveBuilder(Sampler.NEVER_SAMPLE).build();
    close();
    client = newClient(server.getPort());

    server.enqueue(new MockResponse());
    get(client, "/foo");

    RecordedRequest request = server.takeRequest();
    assertThat(request.getHeaders().toMultimap())
        .doesNotContainKeys("x-b3-traceId", "x-b3-parentSpanId", "x-b3-spanId")
        .containsEntry("x-b3-sampled", asList("0"));
  }

  @Test
  public void reportsClientAnnotationsToZipkin() throws Exception {
    server.enqueue(new MockResponse());
    get(client, "/foo");

    assertThat(collectedSpans())
        .extracting(Span::kind)
        .containsOnly(Span.Kind.CLIENT);
  }

  @Test
  public void defaultSpanNameIsMethodName() throws Exception {
    server.enqueue(new MockResponse());
    get(client, "/foo");

    assertThat(collectedSpans())
        .extracting(Span::name)
        .containsExactly("get");
  }

  @Test
  public void supportsSpanNameProvider() throws Exception {
    close();
    client = newClient(server.getPort(), r -> r.getUri().getPath());

    server.enqueue(new MockResponse());
    get(client, "/foo");

    assertThat(collectedSpans())
        .extracting(Span::name)
        .containsExactly("/foo");
  }

  @Test
  public void addsStatusCodeWhenNotOk() throws Exception {
    server.enqueue(new MockResponse().setResponseCode(404));

    try {
      get(client, "/foo");
    } catch (RuntimeException e) {
      // some clients think 404 is an error
    }

    assertThat(collectedSpans())
        .flatExtracting(s -> s.tags().entrySet())
        .contains(entry(TraceKeys.HTTP_STATUS_CODE, "404"));
  }

  @Test
  public void reportsSpanOnTransportException() throws Exception {
    server.enqueue(new MockResponse().setSocketPolicy(SocketPolicy.DISCONNECT_AT_START));

    try {
      get(client, "/foo");
    } catch (Exception e) {
      // ok, but the span should include an error!
    }

    assertThat(collectedSpans()).hasSize(1);
  }

  @Test
  public void addsErrorTagOnTransportException() throws Exception {
    reportsSpanOnTransportException();

    assertThat(collectedSpans())
        .flatExtracting(s -> s.tags().keySet())
        .contains(Constants.ERROR);
  }

  @Test
  public void httpUrlTagIncludesQueryParams() throws Exception {
    String path = "/foo?z=2&yAA=1";

    server.enqueue(new MockResponse());
    get(client, path);

    assertThat(collectedSpans())
        .flatExtracting(s -> s.tags().entrySet())
        .contains(entry(TraceKeys.HTTP_URL, server.url(path).toString()));
  }

  Brave.Builder braveBuilder(Sampler sampler) {
    com.twitter.zipkin.gen.Endpoint localEndpoint = com.twitter.zipkin.gen.Endpoint.builder()
        .ipv4(local.ipv4)
        .ipv6(local.ipv6)
        .port(local.port)
        .serviceName(local.serviceName)
        .build();
    return new Brave.Builder(new InheritableServerClientAndLocalSpanState(localEndpoint))
        .spanReporter(s -> storage.spanConsumer().accept(asList(s)))
        .traceSampler(sampler);
  }

  List collectedSpans() {
    List> result = storage.spanStore().getTraces();
    assertThat(result).hasSize(1);
    return result.get(0);
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy