
org.zalando.tracer.Tracer Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of tracer-core Show documentation
Show all versions of tracer-core Show documentation
Tracing requests through a distributed system.
package org.zalando.tracer;
/*
*
* Tracer
*
* Copyright (C) 2015 Zalando SE
*
* Licensed 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.
*
*/
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import lombok.Singular;
import java.util.concurrent.Callable;
import java.util.function.BiConsumer;
import java.util.function.Function;
import static com.google.common.collect.Maps.toMap;
import static java.util.Arrays.asList;
/**
* A tracer is a lifecycle manager of one or more {@link Trace traces}. Traces are identified by name, e.g.
* {@code X-Trace-ID} and, during an active lifecycle, they can have a value, e.g.
* {@code 5dc90f18-231f-11e6-a17d-0f84f5d57f60}. Values can either be provided on a per-lifecycle basis or generated
* on-demand by a {@link Generator generator}. Lifecycle events, i.e. start and stop, can be observed by registering
* one or more {@link TraceListener listeners}.
*
* @see Trace
* @see Generator
* @see TraceListener
*/
public interface Tracer {
/**
* Starts a new trace lifecycle without transporting any trace identifiers.
*
* The most common use case would be to manage a trace lifecycle in a scheduled background job or during tests.
*
*
* tracer.start();
*
* try {
* // do some background work here
* } finally {
* tracer.stop();
* }
*
*
* @throws IllegalStateException if this tracer is already started
*/
default void start() {
start(name -> null);
}
/**
* Starts a new trace lifecycle with the possibility to transport given trace identifiers.
*
* The most common use case would be to read headers from an incoming HTTP request and hand them over to this
* tracer in order to persist them:
*
*
* tracer.start(request::getHeader)
*
* try {
* // continue with processing the request
* } finally {
* tracer.stop();
* }
*
*
* If the provider returns null a value will be created using the configured {@link Generator generator}.
*
*
* @param provider a provider for trace identifiers, will be called with individual trace names
* @throws IllegalStateException if this tracer is already started
*/
void start(final Function provider);
/**
* Retrieves the {@link Trace trace} with the given name. The returned instance is a thread-safe live-view that
* allows to observe future changes to that trace's lifecycle.
*
* This can be used to manually access a trace value, e.g. for additional audit logging:
*
*
* entity.setModifiedBy(trace.getValue());
* db.persist(entity);
*
*
* @param name the name of the trace
* @return the trace
* @throws IllegalArgumentException if no trace was configured for the given name
*/
Trace get(final String name);
/**
* Iterates all configured traces and allows to observe their current values.
*
* The most common use case woule be to pass all traces onto another system by adding them to an outgoing
* HTTP request:
*
*
* tracer.forEach(request::addHeader);
*
*
* @param consumer a consumer for traces, will be called with trace name-value pairs
* @throws IllegalStateException if this tracer not started
*/
void forEach(final BiConsumer consumer);
/**
* Provides an immutable snapshot of all configured traces mapped by name to their current values.
*
* @return an immutable snapshot of all traces
* @throws IllegalStateException if this tracer not started
*/
default ImmutableMap snapshot() {
final ImmutableMap.Builder builder = ImmutableMap.builder();
forEach(builder::put);
return builder.build();
}
/**
* Stops the current lifecycle.
*
* @throws IllegalStateException if this tracer is not started
*/
void stop();
/**
* Creates a {@link Runnable runnable}, based on the given original, that will manage a complete new lifecycle
* for every invocation,
*
* @param task the original runnable
* @return a runnable that will add lifecycle management to the given runnable
* @see #delegate(Runnable, Function)
*/
default Runnable manage(final Runnable task) {
return delegate(task, name -> null);
}
/**
* Creates a {@link Runnable runnable}, based on the given original, that will preserve the current state of
* all traces for every invocation.
*
* @param task the original runnable
* @return a runnable that will add lifecycle management to the given runnable
* @throws IllegalStateException if this tracer is not started
* @see #snapshot()
* @see #delegate(Runnable, Function)
*/
default Runnable preserve(final Runnable task) {
return delegate(task, snapshot()::get);
}
/**
* Creates a delegating {@link Runnable runnable}, based on the given original, that will manage the tracer
* lifecycle for every invocation. Initial trace values can be seeded with the given provider.
*
* @param task the original runnable
* @param provider a provider for trace identifiers, will be called with individual trace names
* @return a runnable that will add lifecycle management to the given runnable
* @see #start(Function)
* @see #manage(Runnable)
* @see #preserve(Runnable)
*/
default Runnable delegate(final Runnable task, final Function provider) {
return () -> {
start(provider);
try {
task.run();
} finally {
stop();
}
};
}
/**
* Creates a {@link Callable callable}, based on the given original, that will manage a complete new lifecycle
* for every invocation,
*
* @param task the original callable
* @return a callable that will add lifecycle management to the given callable
* @see #delegate(Callable, Function)
*/
default Callable manage(final Callable task) {
return delegate(task, name -> null);
}
/**
* Creates a {@link Callable callable}, based on the given original, that will preserve the current state of
* all traces for every invocation.
*
* @param task the original callable
* @return a callable that will add lifecycle management to the given callable
* @throws IllegalStateException if this tracer is not started
* @see #snapshot()
* @see #delegate(Callable, Function)
*/
default Callable preserve(final Callable task) {
return delegate(task, snapshot()::get);
}
/**
* Creates a delegating {@link Callable callable}, based on the given original, that will manage the tracer
* lifecycle for every invocation. Initial trace values can be seeded with the given provider.
*
* @param task the original callable
* @param provider a provider for trace identifiers, will be called with individual trace names
* @return a callable that will add lifecycle management to the given callable
* @see #start(Function)
* @see #manage(Callable)
* @see #preserve(Callable)
*/
default Callable delegate(final Callable task, final Function provider) {
return () -> {
start(provider);
try {
return task.call();
} finally {
stop();
}
};
}
/**
* Creates a new tracer that will manage the given traces. All traces will be using the {@link UUIDGenerator} by
* default. The returned instance will not have any {@link TraceListener listeners} attached to it.
*
* @param names trace names to be configured
* @return a new tracer
*/
static Tracer create(final String... names) {
return builder().traces(asList(names)).build();
}
@lombok.Builder(builderClassName = "Builder")
static Tracer create(
@Singular final ImmutableList traces,
@Singular("trace") final ImmutableMap customs,
@Singular final ImmutableList listeners) {
final UUIDGenerator defaultGenerator = new UUIDGenerator();
final ImmutableMap combined = ImmutableMap.builder()
.putAll(customs)
.putAll(toMap(traces, trace -> defaultGenerator))
.build();
return new DefaultTracer(combined, listeners);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy