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

OpenTelemetry bridge #1631

Merged
merged 117 commits into from
Mar 21, 2022
Merged
Show file tree
Hide file tree
Changes from 77 commits
Commits
Show all changes
117 commits
Select commit Hold shift + click to select a range
27fab55
Fist working draft
felixbarny Jan 22, 2021
517e037
Lazily parse tracestate
felixbarny Jan 22, 2021
605c893
Use built-in Context
felixbarny Jan 22, 2021
844f097
Translate OTel attributes to intake API
felixbarny Jan 22, 2021
1a28319
Refine instrumentations
felixbarny Jan 22, 2021
204bea7
Mark as experimental
felixbarny Jan 22, 2021
a7b65c9
Add enable_experimental_instrumentations option
felixbarny Jan 22, 2021
c89b977
polishing
felixbarny Jan 22, 2021
87e8420
Extract advice to separate class
felixbarny Jan 23, 2021
8f63bf1
more polishing
felixbarny Jan 23, 2021
9b2e489
Map OTel semantic convention attributes to data model
felixbarny Jan 25, 2021
a7899d9
Mark spans non-discardable on context propagation
felixbarny Jan 25, 2021
56594eb
Remove construction of URL fields that are filled on APM Server
felixbarny Jan 25, 2021
ca4e5a6
Add license headers
felixbarny Jan 25, 2021
31c5b97
Revert "Remove construction of URL fields that are filled on APM Server"
felixbarny Jan 25, 2021
f04c94f
Map destination details of external spans
felixbarny Jan 26, 2021
1d72e4a
Avoid calling method that's @since Java 9
felixbarny Jan 26, 2021
316c65b
Fix packaging and shading
felixbarny Jan 27, 2021
4bf7349
Add docs
felixbarny Jan 29, 2021
083d297
Add changelog
felixbarny Jan 29, 2021
a9788b0
Merge remote-tracking branch 'origin/master' into opentelemetry-bridge
felixbarny Jan 29, 2021
5465c38
Document when OTel bridge has been added
felixbarny Jan 29, 2021
970e425
Update to OTel 0.15.0 and test older versions too
felixbarny Jan 29, 2021
233179d
Implement review suggestions
felixbarny Feb 3, 2021
d9e5498
Merge remote-tracking branch 'origin/master' into opentelemetry-bridge
felixbarny Feb 5, 2021
a85d5f4
Bubble up operation modes to apis.asciidoc
felixbarny Feb 5, 2021
b4f69e6
Log warning once if unsupported APIs are used
felixbarny Feb 5, 2021
430ca09
Be more clear about what is and what's not supported
felixbarny Feb 11, 2021
39f086c
Update and require OTel 0.16.0
felixbarny Feb 11, 2021
02cbb45
Remove smurf naming convention
felixbarny Feb 11, 2021
66b4e22
Merge remote-tracking branch 'origin/master' into opentelemetry-bridge
felixbarny Feb 11, 2021
4ff5c4f
Set outcome
felixbarny Feb 11, 2021
7398cca
Merge remote-tracking branch 'origin/master' into opentelemetry-bridge
felixbarny Mar 2, 2021
d8f37df
Update to otel 1.0.0
felixbarny Mar 2, 2021
c03d09f
Merge remote-tracking branch 'upstream/master' into opentelemetry-bridge
SylvainJuge Jun 14, 2021
21c8002
fix advice class names
SylvainJuge Jun 14, 2021
6faddb1
fix url test
SylvainJuge Jun 15, 2021
bb1b599
Merge branch 'master' of github.com:elastic/apm-agent-java into opent…
SylvainJuge Aug 19, 2021
27ebcfd
post-merge update
SylvainJuge Aug 19, 2021
28dd7d5
add missing ivy test dependency
SylvainJuge Aug 20, 2021
d021714
test versions 1.0.x->1.5.0
SylvainJuge Aug 20, 2021
81daba2
add few tests for coverage
SylvainJuge Aug 20, 2021
fe86a0a
fix doc release version
SylvainJuge Aug 20, 2021
fe0501c
update docs
SylvainJuge Aug 20, 2021
a296813
introduce ElasticContext to rule them all
SylvainJuge Sep 7, 2021
ac1d10f
bridge otel root context
SylvainJuge Sep 14, 2021
de91e3f
code cleanup
SylvainJuge Sep 14, 2021
2db9716
code cleanup + update test to 1.6.0
SylvainJuge Sep 14, 2021
8d88a51
Merge branch 'master' of github.com:elastic/apm-agent-java into opent…
SylvainJuge Sep 14, 2021
717d51f
add missing license header
SylvainJuge Sep 14, 2021
1b0804b
minor tweaks
SylvainJuge Sep 15, 2021
11964eb
Merge branch 'master' of github.com:elastic/apm-agent-java into opent…
SylvainJuge Sep 20, 2021
b86d00e
fix doc update test fail msg
SylvainJuge Sep 21, 2021
5c77c00
add span kind + wip on shared spec
SylvainJuge Oct 25, 2021
214ae74
add wip gherkin spec
SylvainJuge Oct 25, 2021
86e5b1c
add missing file headers
SylvainJuge Oct 25, 2021
f0629cc
wip http + db span mapping
SylvainJuge Oct 25, 2021
73f29a3
finalize 1st tep of bridge
SylvainJuge Oct 26, 2021
5bc47b2
minor cleanup
SylvainJuge Oct 26, 2021
a2d6da8
replace 'custom' with 'unknown' for bridge
SylvainJuge Nov 2, 2021
9fd61a0
update json spec for span type/subtype
SylvainJuge Nov 2, 2021
776fb2a
Merge remote-tracking branch 'origin/master' into opentelemetry-bridge
felixbarny Nov 15, 2021
8b13a40
Use AssignReturned annotations
felixbarny Dec 1, 2021
9a8a616
Merge remote-tracking branch 'origin/master' into opentelemetry-bridge
felixbarny Dec 1, 2021
bcbfef7
Fix parent project version
felixbarny Dec 1, 2021
c058bb2
wip main cucumber tasks
SylvainJuge Dec 6, 2021
7c6549c
Merge branch 'master' into opentelemetry-bridge
SylvainJuge Dec 6, 2021
ab10fbe
infer on otel span end + basic outcome mapping
SylvainJuge Dec 6, 2021
e38ce0a
pom cleanup
SylvainJuge Dec 6, 2021
dde7cd6
code cleanup
SylvainJuge Dec 7, 2021
fccfd52
set outcome for otel spans
SylvainJuge Dec 7, 2021
4c5341f
fix tests
SylvainJuge Dec 7, 2021
d08186b
do not infer outcome from lack of exception
SylvainJuge Dec 7, 2021
5f4ce60
fix documentation
SylvainJuge Dec 7, 2021
0ecddde
fix/cleanup documentation
SylvainJuge Dec 7, 2021
a7ab69a
Merge branch 'master' of github.com:elastic/apm-agent-java into opent…
SylvainJuge Dec 7, 2021
8951c30
update changelog
SylvainJuge Dec 7, 2021
544d965
Merge branch 'main' of github.com:elastic/apm-agent-java into opentel…
SylvainJuge Feb 7, 2022
805b21d
fix sdk logger change
SylvainJuge Feb 7, 2022
6ec3ac3
add latest versions for test coverage
SylvainJuge Feb 7, 2022
a318c61
update generated doc
SylvainJuge Feb 7, 2022
a10814d
compile agent sdk for java 7
SylvainJuge Feb 7, 2022
fc5c5e2
fix doc links
SylvainJuge Feb 7, 2022
64b393b
Apply suggestions from code review (docs links)
SylvainJuge Feb 8, 2022
1c94a7b
Merge branch 'main' of github.com:elastic/apm-agent-java into opentel…
SylvainJuge Feb 8, 2022
a71e8ad
aim for release in 1.30.0
SylvainJuge Feb 8, 2022
8f50b8c
Merge branch 'opentelemetry-bridge' of github.com:felixbarny/apm-agen…
SylvainJuge Feb 8, 2022
097ab8a
add missing otel attributes & kind serialization
SylvainJuge Feb 11, 2022
1d874fb
Merge branch 'main' of github.com:elastic/apm-agent-java into opentel…
SylvainJuge Feb 21, 2022
8099d37
prevent NPE when Context.root() is not called first
SylvainJuge Feb 22, 2022
a4fd963
move to a single module for opentelemetry
SylvainJuge Feb 24, 2022
6bae41e
move to dedicated sub-module for opentelemetry
SylvainJuge Feb 24, 2022
54d961f
capture OTel API version when possible
SylvainJuge Feb 28, 2022
6b55f88
minimize changelog diff
SylvainJuge Mar 3, 2022
bf95b39
clarify some comments/doc
SylvainJuge Mar 3, 2022
71b53fa
properly reformat
SylvainJuge Mar 3, 2022
42751d9
minor fix to otel mvn dependencies
SylvainJuge Mar 3, 2022
1f59e49
remove unused context propagator
SylvainJuge Mar 3, 2022
f91dcdf
bump version to 1.30.0
SylvainJuge Mar 3, 2022
eaf672a
Merge branch 'main' of github.com:elastic/apm-agent-java into opentel…
SylvainJuge Mar 3, 2022
5506146
Merge branch 'main' of github.com:elastic/apm-agent-java into opentel…
SylvainJuge Mar 3, 2022
26a7d60
prevent multiple root contexts
SylvainJuge Mar 15, 2022
f5cdebc
fix pebkc
SylvainJuge Mar 15, 2022
984d375
set implicit active parent only at startSpan
SylvainJuge Mar 15, 2022
4070339
fix docs (attempt for menu)
SylvainJuge Mar 15, 2022
e1a00e5
fix API docs menu integration
SylvainJuge Mar 16, 2022
72bce1f
trim whitespace
SylvainJuge Mar 16, 2022
404b0ce
Merge branch 'main' of github.com:elastic/apm-agent-java into opentel…
SylvainJuge Mar 16, 2022
3493ee1
fix links to public-api
SylvainJuge Mar 16, 2022
89ec40f
Merge branch 'main' of github.com:elastic/apm-agent-java into opentel…
SylvainJuge Mar 17, 2022
6e63eef
Merge branch 'main' of github.com:elastic/apm-agent-java into opentel…
SylvainJuge Mar 17, 2022
e4b85c1
fix generated doc
SylvainJuge Mar 17, 2022
72e5d24
doc: try removing float blocks
SylvainJuge Mar 17, 2022
eaaa292
fix API menu
SylvainJuge Mar 17, 2022
1481a3a
Merge branch 'main' of github.com:elastic/apm-agent-java into opentel…
SylvainJuge Mar 18, 2022
020ed20
fix new module version
SylvainJuge Mar 18, 2022
c887091
Merge branch 'main' into opentelemetry-bridge
SylvainJuge Mar 21, 2022
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
1 change: 1 addition & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ updates:
- dependency-name: "com.datastax.cassandra:cassandra-driver-core"
- dependency-name: "com.datastax.oss:java-driver-core"
- dependency-name: "io.micrometer:*"
- dependency-name: "io.opentelemetry:opentelemetry-api"
ignore:
- dependency-name: "net.bytebuddy:byte-buddy-agent"
# We deliberately want to keep this older version of Byte Buddy for our runtime attach test
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ endif::[]

[float]
===== Features
* Add experimental OpenTelemetry API bridge - {pull}1631[#1631]

[float]
===== Bug fixes
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import co.elastic.apm.agent.impl.stacktrace.StacktraceConfiguration;
import co.elastic.apm.agent.impl.transaction.AbstractSpan;
import co.elastic.apm.agent.impl.transaction.BinaryHeaderGetter;
import co.elastic.apm.agent.impl.transaction.ElasticContext;
import co.elastic.apm.agent.impl.transaction.Span;
import co.elastic.apm.agent.impl.transaction.TextHeaderGetter;
import co.elastic.apm.agent.impl.transaction.TraceContext;
Expand Down Expand Up @@ -86,13 +87,13 @@ public class ElasticApmTracer implements Tracer {
private final ObjectPool<ErrorCapture> errorPool;
private final Reporter reporter;
private final ObjectPoolFactory objectPoolFactory;
// Maintains a stack of all the activated spans
// Maintains a stack of all the activated spans/contexts
// This way its easy to retrieve the bottom of the stack (the transaction)
// Also, the caller does not have to keep a reference to the previously active span, as that is maintained by the stack
private final ThreadLocal<Deque<AbstractSpan<?>>> activeStack = new ThreadLocal<Deque<AbstractSpan<?>>>() {
private final ThreadLocal<Deque<ElasticContext<?>>> activeStack = new ThreadLocal<Deque<ElasticContext<?>>>() {
@Override
protected Deque<AbstractSpan<?>> initialValue() {
return new ArrayDeque<AbstractSpan<?>>();
protected Deque<ElasticContext<?>> initialValue() {
return new ArrayDeque<ElasticContext<?>>();
}
};

Expand Down Expand Up @@ -255,7 +256,7 @@ private Transaction createTransaction() {
@Override
@Nullable
public Transaction currentTransaction() {
final AbstractSpan<?> bottomOfStack = activeStack.get().peekLast();
final ElasticContext<?> bottomOfStack = activeStack.get().peekLast();
return bottomOfStack != null ? bottomOfStack.getTransaction() : null;
}

Expand Down Expand Up @@ -476,6 +477,12 @@ public ObjectPoolFactory getObjectPoolFactory() {
@Override
@Nullable
public AbstractSpan<?> getActive() {
ElasticContext<?> active = activeStack.get().peek();
return active != null ? active.getSpan() : null;
}

@Nullable
public ElasticContext<?> getActiveContext() {
return activeStack.get().peek();
}

Expand Down Expand Up @@ -680,54 +687,105 @@ public <T> T getLifecycleListener(Class<T> listenerClass) {
return null;
}

public void activate(AbstractSpan<?> span) {
@Nullable
public ElasticContext<?> currentContext() {
return activeStack.get().peek();
}

public void activate(ElasticContext<?> context) {
if (logger.isDebugEnabled()) {
logger.debug("Activating {} on thread {}", span, Thread.currentThread().getId());
}
span.incrementReferences();
List<ActivationListener> activationListeners = getActivationListeners();
for (int i = 0, size = activationListeners.size(); i < size; i++) {
try {
activationListeners.get(i).beforeActivate(span);
} catch (Error e) {
throw e;
} catch (Throwable t) {
logger.warn("Exception while calling {}#beforeActivate", activationListeners.get(i).getClass().getSimpleName(), t);
logger.debug("Activating {} on thread {}", context, Thread.currentThread().getId());
}

ElasticContext<?> currentContext = currentContext();
ElasticContext<?> newContext = context;

AbstractSpan<?> span = context.getSpan();
if (span != null) {
span.incrementReferences();
triggerActivationListeners(span, true);
} else if(currentContext != null) {
// when there is no span attached to the context we are attaching to but there is one in the current
// context, we just propagate to the context that will be activated.
span = currentContext.getSpan();
if (span != null) {
newContext = context.withActiveSpan(span);
}
}
activeStack.get().push(span);

activeStack.get().push(newContext);
}

public Scope activateInScope(final ElasticContext<?> context) {
// already in scope
if (getActiveContext() == context) {
return Scope.NoopScope.INSTANCE;
}
context.activate();

if (context instanceof Scope) {
// we can take shortcut and avoid creating a separate object
return (Scope) context;
}
return new Scope() {
@Override
public void close() {
context.deactivate();
}
};
}

public void deactivate(AbstractSpan<?> span) {
public void deactivate(ElasticContext<?> context) {
if (logger.isDebugEnabled()) {
logger.debug("Deactivating {} on thread {}", span, Thread.currentThread().getId());
logger.debug("Deactivating {} on thread {}", context, Thread.currentThread().getId());
}
ElasticContext<?> activeContext = activeStack.get().poll();
AbstractSpan<?> span = context.getSpan();

if (activeContext != context && context == span) {
// when context has been upgraded, we need to deactivate the original span
activeContext = context;
}

try {
final Deque<AbstractSpan<?>> stack = activeStack.get();
assertIsActive(span, stack.poll());
List<ActivationListener> activationListeners = getActivationListeners();
for (int i = 0, size = activationListeners.size(); i < size; i++) {
try {
// `this` is guaranteed to not be recycled yet as the reference count is only decremented after this method has executed
activationListeners.get(i).afterDeactivate(span);
} catch (Error e) {
throw e;
} catch (Throwable t) {
logger.warn("Exception while calling {}#afterDeactivate", activationListeners.get(i).getClass().getSimpleName(), t);
}
assertIsActive(context, activeContext);

if (null != span) {
triggerActivationListeners(span, false);
}
} finally {
span.decrementReferences();
if (null != span) {
span.decrementReferences();
}
}
}

private void assertIsActive(AbstractSpan<?> span, @Nullable AbstractSpan<?> currentlyActive) {
if (span != currentlyActive) {
logger.warn("Deactivating a span ({}) which is not the currently active span ({}). " +
"This can happen when not properly deactivating a previous span.", span, currentlyActive);
private void assertIsActive(ElasticContext<?> context, @Nullable ElasticContext<?> currentlyActive) {
if (context != currentlyActive) {
logger.warn("Deactivating a context ({}) which is not the currently active one ({}). " +
"This can happen when not properly deactivating a previous span or context.", context, currentlyActive);

if (assertionsEnabled) {
throw new AssertionError("Deactivating a span that is not the active one");
throw new AssertionError("Deactivating a context that is not the active one");
}
}
}

private void triggerActivationListeners(AbstractSpan<?> span, boolean isActivate) {
List<ActivationListener> activationListeners = getActivationListeners();
for (int i = 0, size = activationListeners.size(); i < size; i++) {
ActivationListener listener = activationListeners.get(i);
try {
if (isActivate) {
listener.beforeActivate(span);
} else {
// `this` is guaranteed to not be recycled yet as the reference count is only decremented after this method has executed
listener.afterDeactivate(span);
}
} catch (Error e) {
throw e;
} catch (Throwable t) {
logger.warn("Exception while calling {}#{}", listener.getClass().getSimpleName(), isActivate ? "beforeActivate" : "afterDeactivate", t);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,6 @@ public StringBuilder getFull() {

/**
* Updates full URL from current state of {@literal this}. Must be called after all other Url fields are set.
*
* @return url
*/
private void updateFull() {
// inspired by org.apache.catalina.connector.Request.getRequestURL
Expand Down Expand Up @@ -262,7 +260,7 @@ public void parseAndFillFromFull() {
}
}

private static int normalizePort(int port, @Nullable String protocol) {
public static int normalizePort(int port, @Nullable String protocol) {
int portValue = port;
if (portValue < 0 && protocol != null) {
// Work around java.net.URL bug
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,13 @@
import org.slf4j.LoggerFactory;

import javax.annotation.Nullable;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;

public abstract class AbstractSpan<T extends AbstractSpan<T>> implements Recyclable {
public abstract class AbstractSpan<T extends AbstractSpan<T>> implements Recyclable, ElasticContext<T> {
public static final int PRIO_USER_SUPPLIED = 1000;
public static final int PRIO_HIGH_LEVEL_FRAMEWORK = 100;
public static final int PRIO_METHOD_SIGNATURE = 100;
Expand Down Expand Up @@ -105,6 +107,11 @@ public abstract class AbstractSpan<T extends AbstractSpan<T>> implements Recycla

private boolean hasCapturedExceptions;

@Nullable
private OTelSpanKind otelKind = null;

private final Map<String, Object> otelAttributes = new HashMap<>();

Comment on lines +112 to +116
Copy link
Contributor

Choose a reason for hiding this comment

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

How come this is here and not in OTelSpan? I assume it's because of serialization and trying to follow are the guidelines:

  1. we don't want a separate OTel branch under AbstractSpan, so we cannot extend or implement the internal types in the OTel plugin
  2. OTelSpan wraps an AbastractSpan, so it knows about it but not vice versa

I would be happy if we can lose any OTel stuff in our internal code as much as possible.

With the attributes it's easy since it's only a Map<String, Object> so just renaming to attributes would do. For the otelKind it's also easy - since we only need its toString implementation, the field can be of type Object and renamed kind and OTelSpanKind can go into the plugin.

However, this will not accommodate future additions (like OTel events?) and it doesn't lose the OTel-specific serialization from our general-purpose serializer.

Proposal

Maybe a more generic solution is to introduce another interface to the co.elastic.apm.agent.impl.context package:

interface CustomContext extends Recyclable
{
  serialize(JsonWriter);
}

The plugin will implement it with attributes and kind and the serialize implementation would be the current DslJsonSerializer#serializeOTel implementation. It may eliminate the need to copy the attributes to an additional map.
It will be another AbstractSpan field that will be serialized in the appropriate order.

Copy link
Member

Choose a reason for hiding this comment

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

I will try to look at an alternative for this, maybe close to what you suggested.

Copy link
Member Author

Choose a reason for hiding this comment

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

I'm skeptical that pulling out the attributes out of AbstractSpan is possible. Think about the case where someone uses the OTel API to add a custom attribute to a span or transaction that has been created by the auto-instrumentation of the agent.
The OTelSpan is just an ephemeral bridge object where we should think twice before adding state to it.

Copy link
Contributor

@eyalkoren eyalkoren Mar 16, 2022

Choose a reason for hiding this comment

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

That's right, we shouldn't add a state to OTelSpan, the AbstractSpan would still own the bridge context. It's not where the data resides, only where the implementation resides. I prefer the bridge to maintain this part of the context, which is tightly coupled with the OTel API.

This is what I propose (calling it FreeFormContext because the internal context already has room for custom context):
We add another context type that has a very loose API:

interface FreeFormContext extends Recyclable
{
  serialize(JsonWriter);
}

Anything that implements it is also responsible for proper serialization of it. Not sure about recycling, but I think that as long as we don't allow multiple types of that within the same runtime, it can be set once and then only recycled. That is only relevant if we really find a proper way to recycle these spans of course.

Then we add another context property to spans:

public abstract class AbstractSpan<T extends AbstractSpan<T>> implements Recyclable {
    ....
    public abstract AbstractContext getContext();
    ....
    public abstract void setFreeFormContext(FreeFormContext);
    public abstract FreeFormContext getFreeFormContext();
}

The bridge will have a OTelSpanContext implements FreeFormContext and instead of doing:

span.getOtelAttributes().put("key", "value");

OTelSpan would do something like:

((OTelSpanContext)span.getFreeFormContext()).setAttribute("key", "value");

I hope this makes sense.

Copy link
Member

Choose a reason for hiding this comment

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

I see what you mean here, but I'm not sure that we can generically handle the JSON serialization part in an elegant way.
We could even allow the plugin to create dedicated sub-classes of Transaction and Span for that purpose.

For example, we have to assume that the serialized form will always be in the same place in JSON.
In this case, we have an extra otel attribute: span.otel object with span.otel.kind and span.otel.attributes, but any other implementation could use a different structure.

While I like the idea to keep the core of the agent as OTel-agnostic as possible, I don't think that we should handle this right now:

  • we only have a single implementation, thus it's probably too early to create an abstraction that will fit a future abstraction.
  • I don't see any other possible implementation candidate for this.
  • it's only a couple of fields, thus it seems an acceptable compromise, and we can always revisit this later.

Copy link
Contributor

Choose a reason for hiding this comment

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

but I'm not sure that we can generically handle the JSON serialization part in an elegant way.

Wouldn't a copy+paste of the current serialization work without any change?

For example, we have to assume that the serialized form will always be in the same place in JSON.

JSON relies on nesting. You can assume that you are in the right place within the "parent" node serialization because it is the one invoking you and once you are invoked, you serialize the nested tree as you know it should be.

While I like the idea to keep the core of the agent as OTel-agnostic as possible, I don't think that we should handle this right now: ...

I see it exactly the opposite 😄
Even though it seems it is going this direction, we don't yet know whether using us with OTel is going to be the absolute norm. It seemed to go there with OpenTracing before. Currently we add fields to our core to support niche cases.
I don't think about other implementations, it's not about abstraction for the purpose of polymorphism, it's abstraction for the purpose of proper responsibility separation - the bridge should know about the core, the core should not know about the bridge.

That said, I am only explaining to emphasize my rationale, it's not something I will insist on so feel free to make the call and leave as is.

public int getReferenceCount() {
return references.get();
}
Expand Down Expand Up @@ -133,9 +140,6 @@ public boolean isDiscarded() {
return discardRequested && getTraceContext().isDiscardable();
}

@Nullable
public abstract Transaction getTransaction();

private static class ChildDurationTimer implements Recyclable {

private AtomicInteger activeChildren = new AtomicInteger();
Expand Down Expand Up @@ -356,6 +360,8 @@ public void resetState() {
outcome = null;
userOutcome = null;
hasCapturedExceptions = false;
otelKind = null;
otelAttributes.clear();
}

public Span createSpan() {
Expand Down Expand Up @@ -491,28 +497,21 @@ private boolean hasChildId(Id spanId) {
return false;
}

@Override
public T activate() {
tracer.activate(this);
return (T) this;
}

@Override
public T deactivate() {
tracer.deactivate(this);
return (T) this;
}

@Override
public Scope activateInScope() {
// already in scope
if (tracer.getActive() == this) {
return Scope.NoopScope.INSTANCE;
}
activate();
return new Scope() {
@Override
public void close() {
deactivate();
}
};
return tracer.activateInScope(this);
}

/**
Expand Down Expand Up @@ -663,4 +662,30 @@ public T withUserOutcome(Outcome outcome) {
return thiz();
}

@Override
public ElasticContext<T> withActiveSpan(AbstractSpan<?> span) {
// for internal spans the active span is only stored implicitly in the stack, hence we have no requirement
// to have any other kind of context storage.
return this;
}

@Override
public AbstractSpan<?> getSpan() {
return this;
}

public T withOtelKind(OTelSpanKind kind) {
this.otelKind = kind;
return thiz();
}

@Nullable
public OTelSpanKind getOtelKind() {
return otelKind;
}

public Map<String, Object> getOtelAttributes() {
return otelAttributes;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. 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
*
* 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 co.elastic.apm.agent.impl.transaction;

import co.elastic.apm.agent.impl.Scope;

import javax.annotation.Nullable;

public interface ElasticContext<T extends ElasticContext<T>> {

/**
* Makes the context active
*
* @return this
*/
T activate();

/**
* Deactivates context
*
* @return this
*/
T deactivate();

/**
* Activates context in a scope
*
* @return active scope that will deactivate context when closed
*/
Scope activateInScope();

/**
* Adds a span as active within context. Might return a different context instance if required, for example
* when the context implementation is immutable and thus can't be modified.
*
* @param span span to add to the context
* @return context with activated span
*/
ElasticContext<T> withActiveSpan(AbstractSpan<?> span);

/**
* @return the span/transaction that is associated to this context, {@literal null} if there is none
*/
@Nullable
AbstractSpan<?> getSpan();

/**
* @return transaction associated to this context, {@literal null} if there is none
*/
@Nullable
Transaction getTransaction();
}
Loading