Skip to content

Commit

Permalink
Add HttpTracing(Client|Service)Builder
Browse files Browse the repository at this point in the history
Motivation:
A user currently has to build `Tracing` by him/herself and set `currentTraceContext` manually.
We could add `HttpTracing(Client|Service)Builder` which builds a `Tracing` for a user.

Modifications:
- Add `HttpTracing(Client|Service)`Builder
- May fix a bug where `decorateScope` is not called
- Deprecation
  - The consturctor of `HttpTracingService` is not public anymore. A user should use the builder or the static factory method.

Result:
- A user can easidy build an `HttpTracing(Client|Service)` using builders.
  • Loading branch information
minwoox committed Jun 14, 2019
1 parent f4ef974 commit aea8255
Show file tree
Hide file tree
Showing 11 changed files with 760 additions and 120 deletions.
Expand Up @@ -55,32 +55,54 @@
* <p>This decorator puts trace data into HTTP headers. The specifications of header names and its values
* correspond to <a href="http://zipkin.io/">Zipkin</a>.
*/
public class HttpTracingClient extends SimpleDecoratingClient<HttpRequest, HttpResponse> {
public final class HttpTracingClient extends SimpleDecoratingClient<HttpRequest, HttpResponse> {

private static final Logger logger = LoggerFactory.getLogger(HttpTracingClient.class);

/**
* Returns a new builder.
*/
public static HttpTracingClientBuilder builder() {
return new HttpTracingClientBuilder();
}

/**
* Creates a new tracing {@link Client} decorator using the specified {@link Tracing} instance.
*/
public static Function<Client<HttpRequest, HttpResponse>, HttpTracingClient> decorator(Tracing tracing) {
return decorator(tracing, null);
}

/**
* Creates a new tracing {@link Client} decorator using the specified {@link Tracing} instance
* and the remote service name.
*/
public static Function<Client<HttpRequest, HttpResponse>, HttpTracingClient> decorator(
Tracing tracing, @Nullable String remoteServiceName) {
checkTracing(tracing);
return delegate -> new HttpTracingClient(delegate, tracing, remoteServiceName);
}

/**
* Creates a new tracing {@link Client} decorator using the specified {@link Tracing} instance.
*
* @deprecated Use {@link #decorator(Tracing)}.
*/
@Deprecated
public static Function<Client<HttpRequest, HttpResponse>, HttpTracingClient> newDecorator(Tracing tracing) {
return newDecorator(tracing, null);
return decorator(tracing, null);
}

/**
* Creates a new tracing {@link Client} decorator using the specified {@link Tracing} instance
* and remote service name.
* and the remote service name.
*
* @deprecated Use {@link #decorator(Tracing, String)}.
*/
@Deprecated
public static Function<Client<HttpRequest, HttpResponse>, HttpTracingClient> newDecorator(
Tracing tracing,
@Nullable String remoteServiceName) {
try {
ensureScopeUsesRequestContext(tracing);
} catch (IllegalStateException e) {
logger.warn("{} - it is appropriate to ignore this warning if this client is not being used " +
"inside an Armeria server (e.g., this is a normal spring-mvc tomcat server).",
e.getMessage());
}
return delegate -> new HttpTracingClient(delegate, tracing, remoteServiceName);
Tracing tracing, @Nullable String remoteServiceName) {
return decorator(tracing, remoteServiceName);
}

private final Tracer tracer;
Expand All @@ -91,8 +113,8 @@ public static Function<Client<HttpRequest, HttpResponse>, HttpTracingClient> new
/**
* Creates a new instance.
*/
protected HttpTracingClient(Client<HttpRequest, HttpResponse> delegate, Tracing tracing,
@Nullable String remoteServiceName) {
HttpTracingClient(Client<HttpRequest, HttpResponse> delegate, Tracing tracing,
@Nullable String remoteServiceName) {
super(delegate);
tracer = tracing.tracer();
injector = tracing.propagationFactory().create(AsciiStringKeyFactory.INSTANCE)
Expand Down Expand Up @@ -163,4 +185,14 @@ private void setRemoteEndpoint(Span span, RequestLog log) {
span.remoteIpAndPort(address.getHostAddress(), port);
}
}

static void checkTracing(Tracing tracing) {
try {
ensureScopeUsesRequestContext(tracing);
} catch (IllegalStateException e) {
logger.warn("{} - it is appropriate to ignore this warning if this client is not being used " +
"inside an Armeria server (e.g., this is a normal spring-mvc tomcat server).",
e.getMessage());
}
}
}
@@ -0,0 +1,70 @@
/*
* Copyright 2019 LINE Corporation
*
* LINE Corporation 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:
*
* https://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 com.linecorp.armeria.client.tracing;

import static com.linecorp.armeria.client.tracing.HttpTracingClient.checkTracing;
import static java.util.Objects.requireNonNull;

import java.util.function.Function;

import javax.annotation.Nullable;

import com.linecorp.armeria.client.Client;
import com.linecorp.armeria.common.HttpRequest;
import com.linecorp.armeria.common.HttpResponse;
import com.linecorp.armeria.common.tracing.AbstractTracingBuilder;

import brave.Tracing;

/**
* Builds a new {@link HttpTracingClient} or its decorator function.
*/
public final class HttpTracingClientBuilder extends AbstractTracingBuilder<HttpTracingClientBuilder> {

@Nullable
private String remoteServiceName;

/**
* Creates a new instance.
*/
HttpTracingClientBuilder() {}

/**
* Sets the remote service name.
*/
public HttpTracingClientBuilder remoteServiceName(String remoteServiceName) {
this.remoteServiceName = requireNonNull(remoteServiceName, "remoteServiceName");
return this;
}

/**
* Returns a newly-created {@link HttpTracingClient} based on the properties of this builder.
*/
public HttpTracingClient build(Client<HttpRequest, HttpResponse> delegate) {
final Tracing tracing = buildTracing();
checkTracing(tracing);
return new HttpTracingClient(delegate, tracing, remoteServiceName);
}

/**
* Returns a newly-created decorator that decorates a {@link Client} with a new {@link HttpTracingClient}
* based on the properties of this builder.
*/
public Function<Client<HttpRequest, HttpResponse>, HttpTracingClient> decorator() {
return this::build;
}
}
@@ -0,0 +1,226 @@
/*
* Copyright 2019 LINE Corporation
*
* LINE Corporation 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:
*
* https://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 com.linecorp.armeria.common.tracing;

import static com.google.common.base.Preconditions.checkState;
import static java.util.Objects.requireNonNull;

import javax.annotation.Nullable;

import brave.Clock;
import brave.ErrorParser;
import brave.Tracing;
import brave.Tracing.Builder;
import brave.handler.FinishedSpanHandler;
import brave.propagation.CurrentTraceContext;
import brave.propagation.Propagation;
import brave.propagation.Propagation.Factory;
import brave.sampler.Sampler;
import zipkin2.Span;
import zipkin2.reporter.Reporter;

/**
* Builds a new {@link AbstractTracingBuilder} or its decorator function.
*
* @param <B> the self type
*/
public abstract class AbstractTracingBuilder<B extends AbstractTracingBuilder<B>> {

@Nullable
private Builder tracingBuilder;

@Nullable
private Tracing tracing;

private boolean isCurrentTraceContextSet;

@SuppressWarnings("unchecked")
private B self() {
return (B) this;
}

/**
* Sets the {@link Tracing} of this builder.
*
* @throws IllegalStateException if other properties for building a new {@link Tracing} are already set
* in this builder
*/
public B tracing(Tracing tracing) {
checkState(tracingBuilder == null, "tracingBuilder is already set.");
this.tracing = requireNonNull(tracing, "tracing");
return self();
}

/**
* Sets the lower-case label of the remote node.
*
* @see Builder#localServiceName(String)
*/
public B localServiceName(String localServiceName) {
checkTracingAndBuilder();
tracingBuilder.localServiceName(localServiceName);
return self();
}

/**
* Sets the text representation of the primary IP address associated with this service.
*
* @see Builder#localIp(String)
* @see #localServiceName(String)
* @see #localPort(int)
*/
public B localIp(String localIp) {
checkTracingAndBuilder();
tracingBuilder.localIp(localIp);
return self();
}

/**
* Sets the primary listen port associated with this service.
*
* @see Builder#localPort(int)
* @see #localIp(String)
*/
public B localPort(int localPort) {
checkTracingAndBuilder();
tracingBuilder.localPort(localPort);
return self();
}

/**
* Sets the span {@link Reporter}.
*
* @see Builder#spanReporter(Reporter)
*/
public B spanReporter(Reporter<Span> spanReporter) {
checkTracingAndBuilder();
tracingBuilder.spanReporter(spanReporter);
return self();
}

/**
* Sets the {@link Clock}.
*
* @see Builder#clock(Clock)
*/
public B clock(Clock clock) {
checkTracingAndBuilder();
tracingBuilder.clock(clock);
return self();
}

/**
* Sets the {@link Sampler}.
*
* @see Builder#sampler(Sampler)
*/
public B sampler(Sampler sampler) {
checkTracingAndBuilder();
tracingBuilder.sampler(sampler);
return self();
}

/**
* Sets the {@link CurrentTraceContext}. If this is not set and {@link #tracing(Tracing)} is not invoked,
* {@link RequestContextCurrentTraceContext} will be set by default.
*
* @see Builder#currentTraceContext(CurrentTraceContext)
*/
public B currentTraceContext(CurrentTraceContext currentTraceContext) {
checkTracingAndBuilder();
if (!isCurrentTraceContextSet) {
isCurrentTraceContextSet = true;
}
tracingBuilder.currentTraceContext(currentTraceContext);
return self();
}

/**
* Sets the {@link Factory}.
*
* @see Builder#propagationFactory(Factory)
*/
public B propagationFactory(Propagation.Factory propagationFactory) {
checkTracingAndBuilder();
tracingBuilder.propagationFactory(propagationFactory);
return self();
}

/**
* Sets whether new root spans will have 128-bit trace IDs or not. {@code false} (64-bit) is set by default.
*/
public B traceId128Bit(boolean traceId128Bit) {
checkTracingAndBuilder();
tracingBuilder.traceId128Bit(traceId128Bit);
return self();
}

/**
* Sets whether the tracing system supports sharing a span ID between a {@link Span.Kind#CLIENT}
* and {@link Span.Kind#SERVER} span. {@code true} is set by default.
*
* @see Builder#supportsJoin(boolean)
*/
public B supportsJoin(boolean supportsJoin) {
checkTracingAndBuilder();
tracingBuilder.supportsJoin(supportsJoin);
return self();
}

/**
* Sets the {@link ErrorParser}.
*/
public B errorParser(ErrorParser errorParser) {
checkTracingAndBuilder();
tracingBuilder.errorParser(errorParser);
return self();
}

/**
* Sets the {@link FinishedSpanHandler}.
*
* @see Builder#addFinishedSpanHandler(FinishedSpanHandler)
*/
public B addFinishedSpanHandler(FinishedSpanHandler finishedSpanHandler) {
checkTracingAndBuilder();
tracingBuilder.addFinishedSpanHandler(finishedSpanHandler);
return self();
}

/**
* Call this to build a {@link Tracing} based on the properties of this builder.
*/
protected final Tracing buildTracing() {
if (tracing != null) {
return tracing;
}
if (tracingBuilder == null) {
tracingBuilder = Tracing.newBuilder();
}
if (!isCurrentTraceContextSet) {
tracingBuilder.currentTraceContext(RequestContextCurrentTraceContext.DEFAULT);
}
return tracingBuilder.build();
}

private void checkTracingAndBuilder() {
checkState(tracing == null, "tracing is already set.");
if (tracingBuilder == null) {
tracingBuilder = Tracing.newBuilder();
}
}
}

0 comments on commit aea8255

Please sign in to comment.