Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce opentracing ScopeManager implementation based on our context #110

Merged
merged 1 commit into from
Mar 13, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
/*
* Copyright 2016-2019 Talsma ICT
*
* 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.
*/
package nl.talsmasoftware.context.opentracing;

import io.opentracing.Scope;
import io.opentracing.ScopeManager;
import io.opentracing.Span;
import nl.talsmasoftware.context.Context;
import nl.talsmasoftware.context.ContextManager;
import nl.talsmasoftware.context.threadlocal.AbstractThreadLocalContext;

import java.util.concurrent.atomic.AtomicBoolean;

/**
* Our own implementation of the opentracing {@linkplain ScopeManager}.
*
* <p>
* Manages opentracing {@linkplain Scope} and allows it to be nested within another active scope,
* taking care to restore the previous value when closing an active scope.
*
* <p>
* This manager is based on our {@linkplain nl.talsmasoftware.context.threadlocal.AbstractThreadLocalContext}
* implementation. Compared to the 'standard' {@linkplain io.opentracing.util.ThreadLocalScopeManager}
* this implementation has the following advantages:
* <ol>
* <li>Close is explicitly idempotent; closing more than once has no additional side-effects
* (even when finishOnClose is set to {@code true}).</li>
* <li>More predictable behaviour for out-of-order closing of scopes.
* Although this is explicitly unsupported by the opentracing specification,
* we think having consistent and predictable behaviour is an advantage.
* <li>Support for {@link nl.talsmasoftware.context.observer.ContextObserver}.
* See https://github.com/opentracing/opentracing-java/issues/334 explicitly wanting this.
* </ol>
*
* <p>
* Please note that this scope manager is not somehow automatically enabled.
* You will have to provide an instance to your tracer of choice when initializing it.
*
* <p>
* The <em>active span</em> that is automatically propagated when using this
* {@code opentracing-span-propagation} library in combination with
* the context aware support classes is from the registered ScopeManager
* from the {@linkplain io.opentracing.util.GlobalTracer}.
*
* @since 1.0.6
*/
public class ContextScopeManager implements ScopeManager, ContextManager<Span> {
/**
* Makes the given span the new active span.
*
* @param span The span to become the active span.
* @param finishSpanOnClose Whether the span should automatically finish when closing the resulting scope.
* @return The new active scope (must be closed from the same thread).
*/
@Override
public Scope activate(Span span, boolean finishSpanOnClose) {
return new ThreadLocalSpanContext(getClass(), span, finishSpanOnClose);
}

/**
* The currently active {@link Scope} containing the active span {@link Scope#span()}.
*
* @return the active scope, or {@code null} if none could be found.
*/
@Override
public Scope active() {
return ThreadLocalSpanContext.current();
}

/**
* Initializes a new context for the given {@linkplain Span}.
*
* @param value The span to activate.
* @return The new active 'Scope'.
* @see #activate(Span, boolean)
*/
@Override
public Context<Span> initializeNewContext(Span value) {
return new ThreadLocalSpanContext(getClass(), value, false);
}

/**
* @return The active span context (this is identical to the active scope).
* @see #active()
*/
@Override
public Context<Span> getActiveContext() {
return ThreadLocalSpanContext.current();
}

/**
* @return String representation for this context manager.
*/
public String toString() {
return getClass().getSimpleName();
}

private static final class ThreadLocalSpanContext extends AbstractThreadLocalContext<Span> implements Scope {
private final AtomicBoolean finishOnClose;

private ThreadLocalSpanContext(Class<? extends ContextManager<? super Span>> contextManagerType, Span newValue, boolean finishOnClose) {
super(contextManagerType, newValue);
this.finishOnClose = new AtomicBoolean(finishOnClose);
}

private static ThreadLocalSpanContext current() {
return current(ThreadLocalSpanContext.class);
}

@Override
public Span span() {
return value;
}

@Override
public void close() {
super.close();
if (finishOnClose.compareAndSet(true, false) && value != null) {
value.finish();
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
/*
* Copyright 2016-2019 Talsma ICT
*
* 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.
*/
package nl.talsmasoftware.context.opentracing;

import io.opentracing.Span;
import io.opentracing.mock.MockSpan;
import nl.talsmasoftware.context.ContextManager;
import nl.talsmasoftware.context.observer.ContextObserver;
import org.hamcrest.BaseMatcher;
import org.hamcrest.Description;
import org.hamcrest.Matcher;

import java.util.ArrayList;
import java.util.List;

import static java.util.Collections.synchronizedList;

public class ContextScopeManagerObserver implements ContextObserver<Span> {
static final List<Event> observed = synchronizedList(new ArrayList<Event>());

@Override
public Class<? extends ContextManager<Span>> getObservedContextManager() {
return ContextScopeManager.class;
}

@Override
public void onActivate(Span activatedContextValue, Span previousContextValue) {
observed.add(new Event(Event.Type.ACTIVATE, activatedContextValue));
}

@Override
public void onDeactivate(Span deactivatedContextValue, Span restoredContextValue) {
observed.add(new Event(Event.Type.DEACTIVATE, deactivatedContextValue));
}

static class Event {
enum Type {ACTIVATE, DEACTIVATE}

final Thread thread;
final Type type;
final Span value;

Event(Type type, Span value) {
this.thread = Thread.currentThread();
this.type = type;
this.value = value;
}

@Override
public String toString() {
return "Event{" + type + ", thread=" + thread.getName() + ", span=" + value + '}';
}
}

static class EventMatcher extends BaseMatcher<Event> {
Thread inThread;
Event.Type type;
Matcher<MockSpan> spanMatcher;

private EventMatcher(Event.Type type, Matcher<MockSpan> spanMatcher) {
this.type = type;
this.spanMatcher = spanMatcher;
}

static EventMatcher activated(Matcher<MockSpan> span) {
return new EventMatcher(Event.Type.ACTIVATE, span);
}

static EventMatcher deactivated(Matcher<MockSpan> span) {
return new EventMatcher(Event.Type.DEACTIVATE, span);
}

EventMatcher inThread(Thread thread) {
EventMatcher copy = new EventMatcher(type, spanMatcher);
copy.inThread = thread;
return copy;
}

@Override
public boolean matches(Object actual) {
if (!(actual instanceof Event)) return actual == null;
Event actualEv = (Event) actual;
return (inThread == null || inThread.equals(actualEv.thread))
&& type.equals(actualEv.type)
&& spanMatcher.matches(actualEv.value);
}

@Override
public void describeTo(Description description) {
description.appendText("Event ");
if (inThread != null) description.appendText("in thread ").appendText(inThread.getName());
description.appendValue(type).appendText(" ");
spanMatcher.describeTo(description);
}
}
}
Loading