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

io.micrometer.tracing.otel.bridge.OtelBaggageManager Maven / Gradle / Ivy

There is a newer version: 1.4.0
Show newest version
/**
 * Copyright 2022 the original author or 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
 *
 * https://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.micrometer.tracing.otel.bridge;

import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Deque;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.BiConsumer;

import io.micrometer.common.lang.Nullable;
import io.micrometer.tracing.BaggageInScope;
import io.micrometer.tracing.BaggageManager;
import io.micrometer.tracing.CurrentTraceContext;
import io.micrometer.tracing.TraceContext;
import io.opentelemetry.api.baggage.Baggage;
import io.opentelemetry.api.baggage.BaggageBuilder;
import io.opentelemetry.api.baggage.BaggageEntry;
import io.opentelemetry.api.baggage.BaggageEntryMetadata;
import io.opentelemetry.context.Context;

import static java.util.Collections.unmodifiableCollection;
import static java.util.Collections.unmodifiableMap;
import static java.util.function.Function.identity;
import static java.util.stream.Collectors.toMap;

/**
 * OpenTelemetry implementation of a {@link BaggageManager}.
 *
 * @author Marcin Grzejszczak
 * @since 1.0.0
 */
public class OtelBaggageManager implements BaggageManager {

    /**
     * Taken from one of the W3C OTel tests. Can't find it in a spec.
     */
    private static final String PROPAGATION_UNLIMITED = "propagation=unlimited";

    private final CurrentTraceContext currentTraceContext;

    private final List remoteFields;

    private final List tagFields;

    /**
     * Creates a new instance of {@link OtelBaggageManager}.
     * @param currentTraceContext current trace context
     * @param remoteFields remote fields names
     * @param tagFields tag fields names
     */
    public OtelBaggageManager(CurrentTraceContext currentTraceContext, List remoteFields,
            List tagFields) {
        this.currentTraceContext = currentTraceContext;
        this.remoteFields = remoteFields;
        this.tagFields = tagFields;
    }

    @Override
    public Map getAllBaggage() {
        return toMap(currentBaggage());
    }

    private Map toMap(CompositeBaggage compositeBaggage) {
        Map baggage = new HashMap<>();
        compositeBaggage.getEntries().forEach(entry -> baggage.put(entry.getKey(), entry.getValue()));
        return baggage;
    }

    @Override
    public Map getAllBaggage(TraceContext traceContext) {
        if (traceContext == null) {
            return getAllBaggage();
        }
        return toMap(baggage((OtelTraceContext) traceContext));
    }

    CompositeBaggage currentBaggage() {
        return baggage((OtelTraceContext) currentTraceContext.context());
    }

    private CompositeBaggage baggage(OtelTraceContext traceContext) {
        Context context = Context.current();
        Deque stack = new ArrayDeque<>();
        stack.addFirst(context);
        if (traceContext != null && traceContext.context() != null) {
            stack.addFirst(traceContext.context());
        }
        return new CompositeBaggage(stack);
    }

    @Override
    public io.micrometer.tracing.Baggage getBaggage(String name) {
        Entry entry = getBaggage(name, currentBaggage());
        return createNewEntryIfMissing(name, entry);
    }

    io.micrometer.tracing.Baggage createNewEntryIfMissing(String name, Entry entry) {
        if (entry == null) {
            return createBaggage(name);
        }
        return otelBaggage(entry);
    }

    private Entry getBaggage(String name, io.opentelemetry.api.baggage.Baggage baggage) {
        return entryForName(name, baggage);
    }

    @Override
    public io.micrometer.tracing.Baggage getBaggage(TraceContext traceContext, String name) {
        OtelTraceContext context = (OtelTraceContext) traceContext;
        LinkedList stack = new LinkedList<>();
        Context current = Context.current();
        Context traceContextContext = context.context();
        stack.addFirst(current);
        if (!Objects.equals(current, traceContextContext)) {
            stack.addFirst(traceContextContext);
        }
        Context ctx = removeFirst(stack);
        Entry entry = null;
        while (ctx != null && entry == null) {
            entry = getBaggage(name, Baggage.fromContext(ctx));
            ctx = removeFirst(stack);
        }
        if (entry != null) {
            return otelBaggage(context, entry);
        }
        return null;
    }

    Entry getEntry(OtelTraceContext traceContext, String name) {
        OtelTraceContext context = traceContext;
        Context ctx = context.context();
        return getBaggage(name, Baggage.fromContext(ctx));
    }

    Context removeFirst(Deque stack) {
        return stack.isEmpty() ? null : stack.removeFirst();
    }

    private Entry entryForName(String name, io.opentelemetry.api.baggage.Baggage baggage) {
        return Entry.fromBaggage(baggage)
            .stream()
            .filter(e -> e.getKey().equalsIgnoreCase(name))
            .findFirst()
            .orElse(null);
    }

    private io.micrometer.tracing.Baggage otelBaggage(Entry entry) {
        return new OtelBaggageInScope(this, this.currentTraceContext, this.tagFields, entry);
    }

    private io.micrometer.tracing.Baggage otelBaggage(OtelTraceContext otelTraceContext, Entry entry) {
        return new OtelBaggageInScope(this, this.currentTraceContext, otelTraceContext, this.tagFields, entry);
    }

    @Override
    @Deprecated
    public io.micrometer.tracing.Baggage createBaggage(String name) {
        return createBaggage(name, null);
    }

    @Override
    @Deprecated
    public io.micrometer.tracing.Baggage createBaggage(String name, String value) {
        io.micrometer.tracing.Baggage baggage = baggageWithValue(name, value);
        return baggage.set(value);
    }

    @Override
    public BaggageInScope createBaggageInScope(String name, String value) {
        return baggageWithValue(name, value).makeCurrent();
    }

    @Override
    public BaggageInScope createBaggageInScope(TraceContext traceContext, String name, String value) {
        return baggageWithValue(name, value).makeCurrent(traceContext, value);
    }

    private io.micrometer.tracing.Baggage baggageWithValue(String name, @Nullable String value) {
        boolean remoteField = this.remoteFields.stream()
            .map(String::toLowerCase)
            .anyMatch(s -> s.equals(name.toLowerCase()));
        BaggageEntryMetadata entryMetadata = BaggageEntryMetadata.create(propagationString(remoteField));
        Entry entry = new Entry(name, value, entryMetadata);
        return new OtelBaggageInScope(this, this.currentTraceContext, this.tagFields, entry);
    }

    private String propagationString(boolean remoteField) {
        String propagation = "";
        if (remoteField) {
            propagation = PROPAGATION_UNLIMITED;
        }
        return propagation;
    }

}

class CompositeBaggage implements io.opentelemetry.api.baggage.Baggage {

    private final Collection entries;

    private final Map baggageEntries;

    CompositeBaggage(Deque stack) {
        this.entries = unmodifiableCollection(createEntries(stack));
        this.baggageEntries = unmodifiableMap(this.entries.stream().collect(toMap(Entry::getKey, identity())));
    }

    private Collection createEntries(Deque stack) {
        // parent baggage foo=bar
        // child baggage foo=baz - we want the last one to override the previous one
        Map map = new HashMap<>();
        Iterator iterator = stack.descendingIterator();
        while (iterator.hasNext()) {
            Context next = iterator.next();
            Baggage baggage = Baggage.fromContext(next);
            baggage.forEach((key, value) -> map.put(key, new Entry(key, value.getValue(), value.getMetadata())));
        }

        return map.values();
    }

    Collection getEntries() {
        return this.entries;
    }

    @Override
    public int size() {
        return this.entries.size();
    }

    @Override
    public void forEach(BiConsumer consumer) {
        this.entries.forEach(entry -> consumer.accept(entry.getKey(), entry));
    }

    @Override
    public Map asMap() {
        return this.baggageEntries;
    }

    @Override
    public String getEntryValue(String entryKey) {
        return this.entries.stream()
            .filter(entry -> entryKey.equals(entry.getKey()))
            .map(Entry::getValue)
            .findFirst()
            .orElse(null);
    }

    @Override
    public BaggageBuilder toBuilder() {
        return Baggage.builder();
    }

}

class Entry implements BaggageEntry {

    final String key;

    final String value;

    final BaggageEntryMetadata entryMetadata;

    Entry(String key, String value, BaggageEntryMetadata entryMetadata) {
        this.key = key;
        this.value = value;
        this.entryMetadata = entryMetadata;
    }

    static List fromBaggage(Baggage baggage) {
        List list = new ArrayList<>(baggage.size());
        baggage.forEach((key, value) -> list.add(new Entry(key, value.getValue(), value.getMetadata())));
        return list;
    }

    public String getKey() {
        return this.key;
    }

    @Override
    public String getValue() {
        return this.value;
    }

    @Override
    public BaggageEntryMetadata getMetadata() {
        return this.entryMetadata;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }
        Entry entry = (Entry) o;
        return Objects.equals(this.key, entry.key) && Objects.equals(this.value, entry.value)
                && Objects.equals(this.entryMetadata, entry.entryMetadata);
    }

    @Override
    public int hashCode() {
        return Objects.hash(this.key, this.value, this.entryMetadata);
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy