All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.helidon.tracing.providers.opentelemetry.OpenTelemetrySpan Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 2022, 2024 Oracle and/or its affiliates.
 *
 * 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.helidon.tracing.providers.opentelemetry;

import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;

import io.helidon.common.context.Contexts;
import io.helidon.tracing.Scope;
import io.helidon.tracing.Span;
import io.helidon.tracing.SpanContext;
import io.helidon.tracing.SpanListener;
import io.helidon.tracing.WritableBaggage;

import io.opentelemetry.api.baggage.Baggage;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.common.AttributesBuilder;
import io.opentelemetry.api.trace.StatusCode;
import io.opentelemetry.context.Context;

class OpenTelemetrySpan implements Span {
    private static final System.Logger LOGGER = System.getLogger(OpenTelemetrySpan.class.getName());
    private final io.opentelemetry.api.trace.Span delegate;
    private final Baggage baggage;
    private final List spanListeners;
    private Limited limited;

    OpenTelemetrySpan(io.opentelemetry.api.trace.Span span, List spanListeners) {
        this(span, new MutableOpenTelemetryBaggage(), spanListeners);
    }

    OpenTelemetrySpan(io.opentelemetry.api.trace.Span span, Baggage baggage, List spanListeners) {
        delegate = span;
        this.baggage = baggage;
        this.spanListeners = spanListeners;
    }

    @Override
    public Span tag(String key, String value) {
        delegate.setAttribute(key, value);
        return this;
    }

    @Override
    public Span tag(String key, Boolean value) {
        delegate.setAttribute(key, value);
        return this;
    }

    @Override
    public Span tag(String key, Number value) {
        if (value instanceof Double || value instanceof Float) {
            delegate.setAttribute(key, value.doubleValue());
        } else {
            delegate.setAttribute(key, value.longValue());
        }
        return this;
    }

    @Override
    public void status(Status status) {
        switch (status) {
        case OK -> delegate.setStatus(StatusCode.OK);
        case ERROR -> delegate.setStatus(StatusCode.ERROR);
        default -> {
        }
        }
    }

    @Override
    public SpanContext context() {
        return new OpenTelemetrySpanContext(otelContextWithSpanAndBaggage());
    }

    @Override
    public void addEvent(String name, Map attributes) {
        delegate.addEvent(name, toAttributes(attributes));
    }

    @Override
    public void end() {
        delegate.end();
        HelidonOpenTelemetry.invokeListeners(spanListeners, LOGGER, (listener -> listener.ended(limited())));
    }

    @Override
    public void end(Throwable t) {
        delegate.recordException(t);
        delegate.setStatus(StatusCode.ERROR);
        delegate.end();
        HelidonOpenTelemetry.invokeListeners(spanListeners, LOGGER, listener -> listener.ended(limited(), t));
    }

    @Override
    public Scope activate() {
        io.opentelemetry.context.Scope scope = otelContextWithSpanAndBaggage().makeCurrent();
        var result = new OpenTelemetryScope(this, scope, spanListeners);
        HelidonOpenTelemetry.invokeListeners(spanListeners, LOGGER, listener -> listener.activated(limited(), result.limited()));
        return result;
    }

    @Override
    public Span baggage(String key, String value) {
        if (baggage instanceof WritableBaggage writableBaggage) {
            writableBaggage.set(key, value);
        } else {
            throw new SpanListener.ForbiddenOperationException(
                    "Attempt to set baggage on a span with read-only baggage (perhaps from context");
        }
        return this;
    }

    @Override
    public Optional baggage(String key) {
        return Optional.ofNullable(baggage.getEntryValue(key));
    }

    @Override
    public WritableBaggage baggage() {
        return baggage instanceof WritableBaggage writableBaggage ? writableBaggage : writableBaggage(baggage);
    }

    @Override
    public  T unwrap(Class spanClass) {
        if (spanClass.isInstance(delegate)) {
            return spanClass.cast(delegate);
        }
        if (spanClass.isInstance(this)) {
            return spanClass.cast(this);
        }
        throw new IllegalArgumentException("Cannot provide an instance of " + spanClass.getName()
                                                   + ", telemetry span is: " + delegate.getClass().getName());
    }

    Limited limited() {
        if (limited !=  null) {
            return limited;
        }
        if (spanListeners.isEmpty()) {
            return null;
        }
        limited = new Limited(this);
        return limited;
    }

    // Check if OTEL Context is already available in Global Helidon Context.
    // If not – use Current context.
    private static Context getContext() {
        return Contexts.context()
                .flatMap(ctx -> ctx.get(Context.class))
                .orElseGet(Context::current);
    }

    /**
     * Writable wrapper around non-writable baggage.
     *
     * 

* Used only if the baggage in the current context is not our writable variety when it is extracted and attached to * the current span. This should be very rare. *

* * @param baggage non-writable baggage to wrap * @return wrapper */ private static WritableBaggage writableBaggage(Baggage baggage) { return new WritableBaggage() { @Override public WritableBaggage set(String key, String value) { throw new SpanListener.ForbiddenOperationException("Attempt to modify read-only baggage"); } @Override public Optional get(String key) { return Optional.ofNullable(baggage.getEntryValue(key)); } @Override public Set keys() { return baggage.asMap().keySet(); } @Override public boolean containsKey(String key) { return baggage.asMap().containsKey(key); } }; } private Context otelContextWithSpanAndBaggage() { // Because the Helidon tracing API links baggage with a span, any OTel context we create for the span // needs to have the baggage with it. return getContext().with(delegate).with(baggage); } private Attributes toAttributes(Map attributes) { AttributesBuilder builder = Attributes.builder(); attributes.forEach((key, value) -> { if (value instanceof Long l) { builder.put(key, l); } else if (value instanceof Boolean b) { builder.put(key, b); } else if (value instanceof Double d) { builder.put(key, d); } else { builder.put(key, String.valueOf(value)); } }); return builder.build(); } private record Limited(OpenTelemetrySpan delegate) implements Span { @Override public Span tag(String key, String value) { delegate.tag(key, value); return this; } @Override public Span tag(String key, Boolean value) { delegate.tag(key, value); return this; } @Override public Span tag(String key, Number value) { delegate.tag(key, value); return this; } @Override public void status(Status status) { throw new SpanListener.ForbiddenOperationException(); } @Override public SpanContext context() { return delegate.context(); } @Override public void addEvent(String name, Map attributes) { delegate.addEvent(name, attributes); } @Override public void end() { throw new SpanListener.ForbiddenOperationException(); } @Override public void end(Throwable t) { throw new SpanListener.ForbiddenOperationException(); } @Override public Scope activate() { throw new SpanListener.ForbiddenOperationException(); } @Override public Span baggage(String key, String value) { delegate.baggage().set(key, value); return this; } @Override public Optional baggage(String key) { return delegate.baggage().get(key); } @Override public WritableBaggage baggage() { return delegate.baggage(); } @Override public T unwrap(Class spanClass) { if (spanClass.isInstance(this)) { return spanClass.cast(this); } return delegate.unwrap(spanClass); } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy