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

Provide a tracer wrapper that can be used as basis for adding ex… #9

Merged
merged 4 commits into from
Jul 21, 2017
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
57 changes: 55 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,62 @@

This project provides API extensions to the core OpenTracing APIs.

## Observer
## APIs

TODO: Provide some code snippets for use of Observer API.
The APIs can be included using the following:

```xml
<dependency>
<groupId>io.opentracing.contrib</groupId>
<artifactId>opentracing-api-extensions</artifactId>
</dependency>

```

### Observer

The Observer API can be used to monitor Span related activity and perform additional
tasks.

There are two types of Observer, stateful and stateless.

* A stateful observer will use
identity information supplied with the `SpanData`, to maintain state information about a particular
`Span` instance - and at the appropriate time perform some action using the accumulated information.

* A stateless observer will not maintain any local state information about the `Span` instances, and
instead perform tasks directly in the callback that provides the event/information of interest (e.g. recording
metrics `onFinish` or logging events when `onLog` is called).

The benefit of a stateless approach is that the same observer instance (i.e. singleton) can be used for
all `Span` instances. Whereas the stateful approach will require an observer instance to be instantiated
for each call to `TracerObserver.onStart()`.


## Registering API extensions

There are two ways an extension API can be registered for use with a `Tracer`.

1) Native support within the `Tracer` implementation

Some `Tracer` implementations may decide to implement support for the extension APIs directly, in which
case an implementation specific mechanism will be provided for registering the APIs.

2) Using the extension `Tracer` wrapper

```xml
<dependency>
<groupId>io.opentracing.contrib</groupId>
<artifactId>opentracing-api-extensions-tracer</artifactId>
</dependency>

```

The `io.opentracing.contrib.api.tracer.APIExtensionsTracer` provides a single constructor which is supplied
the `Tracer` instance to be wrapped.

This class also provides `addTracerObserver` and `removeTracerObserver` methods to enable a `TracerObserver`
instance to be registered with the tracer wrapper, and perform relevant tasks when new spans are started.


## Release
Expand Down
46 changes: 46 additions & 0 deletions opentracing-api-extensions-tracer/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--

Copyright 2017 The OpenTracing Authors

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.

-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>opentracing-api-extensions-parent</artifactId>
<groupId>io.opentracing.contrib</groupId>
<version>0.0.2-SNAPSHOT</version>
</parent>

<artifactId>opentracing-api-extensions-tracer</artifactId>

<dependencies>
<dependency>
<groupId>io.opentracing.contrib</groupId>
<artifactId>opentracing-api-extensions</artifactId>
</dependency>

<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-all</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${version.junit}</version>
<scope>test</scope>
</dependency>
</dependencies>

</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,270 @@
/**
* Copyright 2017 The OpenTracing Authors
*
* 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 io.opentracing.contrib.api.tracer;

import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.TimeUnit;

import io.opentracing.Span;
import io.opentracing.SpanContext;
import io.opentracing.contrib.api.SpanData;
import io.opentracing.contrib.api.SpanObserver;

public class APIExtensionsSpan implements Span, SpanData {

private final Span wrappedSpan;

private String operationName;
private final long startTimestampMicro;
private long finishTimestampMicro;
private final long startTimeNano;
private long finishTimeNano;
private final Map<String,Object> tags;

private final List<SpanObserver> observers = new CopyOnWriteArrayList<SpanObserver>();

private final UUID correlationId = UUID.randomUUID();

/**
* This is the constructor for the extensions API span wrapper.
*
* @param span The span being wrapped
* @param operationName The operation name
* @param startTimestampMicro The start timestamp (microseconds)
* @param startTimeNano The start nano time, or 0 if the start timestamp was explicitly provided by the app
* @param tags The initial tags
*/
APIExtensionsSpan(Span span, String operationName,
long startTimestampMicro, long startTimeNano, Map<String,Object> tags) {
this.wrappedSpan = span;
this.operationName = operationName;
this.startTimestampMicro = startTimestampMicro;
this.startTimeNano = startTimeNano;
this.tags = tags;
}

/**
* This method adds a new {@link SpanObserver}.
*
* @param observer The observer
*/
public void addSpanObserver(SpanObserver observer) {
if (observer != null) {
observers.add(observer);
}
}

/**
* This method removes a {@link SpanObserver}.
*
* @param observer The observer
*/
public void removeSpanObserver(SpanObserver observer) {
if (observer != null) {
observers.remove(observer);
}
}

@Override
public SpanContext context() {
return wrappedSpan.context();
}

@Override
public Object getCorrelationId() {
return correlationId;
}

@Override
public long getStartTime() {
return startTimestampMicro;
}

@Override
public long getFinishTime() {
return finishTimestampMicro;
}

@Override
public Span setOperationName(String operationName) {
wrappedSpan.setOperationName(operationName);
this.operationName = operationName;
for (SpanObserver observer : observers) {
observer.onSetOperationName(this, operationName);
}
return this;
}

@Override
public String getOperationName() {
return operationName;
}

@Override
public String getBaggageItem(String name) {
return wrappedSpan.getBaggageItem(name);
}

@Override
public Span setBaggageItem(String name, String value) {
wrappedSpan.setBaggageItem(name, value);
for (SpanObserver observer : observers) {
observer.onSetBaggageItem(this, name, value);
}
return this;
}

@Override
public Span log(Map<String, ?> fields) {
wrappedSpan.log(fields);
return handleLog(TimeUnit.MILLISECONDS.toMicros(System.currentTimeMillis()), fields);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you replace the System.currentTimeMillis with a Clock? This way, you don't lose the micro precision (not to mention that it's far easier to test with Clock)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you do change this to use a Clock, there are other places that would need to be changed as well

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately Clock is 1.8, and we are trying to remain 1.6 compatible for now. Hopefully that decision won't last forever.

}

@Override
public Span log(long timestampMicroseconds, Map<String, ?> fields) {
wrappedSpan.log(timestampMicroseconds, fields);
return handleLog(timestampMicroseconds, fields);
}

private Span handleLog(long timestampMicroseconds, Map<String, ?> fields) {
for (SpanObserver observer : observers) {
observer.onLog(this, timestampMicroseconds, fields);
}
return this;
}

@Override
public Span log(String event) {
wrappedSpan.log(event);
return handleLog(TimeUnit.MILLISECONDS.toMicros(System.currentTimeMillis()), event);
}

@Override
public Span log(long timestampMicroseconds, String event) {
wrappedSpan.log(timestampMicroseconds, event);
return handleLog(timestampMicroseconds, event);
}

private Span handleLog(long timestampMicroseconds, String event) {
for (SpanObserver observer : observers) {
observer.onLog(this, timestampMicroseconds, event);
}
return this;
}

@Override
public Span setTag(String key, String value) {
wrappedSpan.setTag(key, value);
return handleSetTag(key, value);
}

@Override
public Span setTag(String key, boolean value) {
wrappedSpan.setTag(key, value);
return handleSetTag(key, value);
}

@Override
public Span setTag(String key, Number value) {
wrappedSpan.setTag(key, value);
return handleSetTag(key, value);
}

private Span handleSetTag(String key, Object value) {
tags.put(key, value);
for (SpanObserver observer : observers) {
observer.onSetTag(this, key, value);
}
return this;
}

@Override
public Map<String,Object> getTags() {
return Collections.unmodifiableMap(tags);
}

@Override
public String getStringTag(String key) {
Object value = tags.get(key);
if (value instanceof String) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd rather to a toString() instead.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was discussed in the PR related to the API, and decided that non-string tags should not be returned as strings, as null is used as a way to understand it is not a string type.

return (String) value;
}
return null;
}

@Override
public Number getNumberTag(String key) {
Object value = tags.get(key);
if (value instanceof Number) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I try to get a "int" from a string value, I would expect an exception, not null (at least, that's what happen in other APIs out there). If returning null is indeed what you think your callers would expect (or want), then please add a javadoc about it (sorry if it's added somewhere already)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

return (Number) value;
}
return null;
}

@Override
public Boolean getBooleanTag(String key) {
Object value = tags.get(key);
if (value instanceof Boolean) {
return (Boolean) value;
}
return null;
}

@Override
public void finish() {
wrappedSpan.finish();
// Only set the finish nano time if not explicitly providing a timestamp
finishTimeNano = System.nanoTime();
handleFinish(TimeUnit.MILLISECONDS.toMicros(System.currentTimeMillis()));
}

@Override
public void finish(long finishMicros) {
wrappedSpan.finish(finishMicros);
handleFinish(finishMicros);
}

private void handleFinish(long finishMicros) {
finishTimestampMicro = finishMicros;
for (SpanObserver observer : observers) {
observer.onFinish(this, finishMicros);
}
}

@Override
public long getDuration() {
// If start or finish nano times are not available, then use timestamps
if (startTimeNano == 0 || finishTimeNano == 0) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When this can be 0?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

return finishTimestampMicro == 0 ? 0 : finishTimestampMicro - startTimestampMicro;
}
return TimeUnit.NANOSECONDS.toMicros(finishTimeNano - startTimeNano);
}

@SuppressWarnings("deprecation")
@Override
public Span log(String eventName, Object payload) {
return wrappedSpan.log(eventName, payload);
}

@SuppressWarnings("deprecation")
@Override
public Span log(long timestampMicroseconds, String eventName, Object payload) {
return wrappedSpan.log(timestampMicroseconds, eventName, payload);
}

}
Loading