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

com.vaadin.extension.instrumentation.client.ObjectMapSpanData Maven / Gradle / Ivy

There is a newer version: 3.0.0
Show newest version
/*-
 * Copyright (C) 2022 Vaadin Ltd
 *
 * This program is available under Vaadin Commercial License and Service Terms.
 *
 *
 * See  for the full
 * license.
 */
package com.vaadin.extension.instrumentation.client;

import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;

import io.opentelemetry.api.common.AttributeKey;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.common.AttributesBuilder;
import io.opentelemetry.api.trace.SpanContext;
import io.opentelemetry.api.trace.SpanKind;
import io.opentelemetry.api.trace.TraceFlags;
import io.opentelemetry.api.trace.TraceState;
import io.opentelemetry.sdk.common.InstrumentationLibraryInfo;
import io.opentelemetry.sdk.common.InstrumentationScopeInfo;
import io.opentelemetry.sdk.resources.Resource;
import io.opentelemetry.sdk.resources.ResourceBuilder;
import io.opentelemetry.sdk.trace.data.EventData;
import io.opentelemetry.sdk.trace.data.LinkData;
import io.opentelemetry.sdk.trace.data.SpanData;
import io.opentelemetry.sdk.trace.data.StatusData;

import com.vaadin.extension.Constants;

/**
 * This represents the data for an observability span. It is constructed from a
 * map of objects sent by the Frontend Observability module.
 */
public class ObjectMapSpanData implements SpanData {
    private final SpanContext spanContext;
    private final SpanContext parentSpanContext;
    private final Resource resource;
    private final InstrumentationLibraryInfo instrumentationLibraryInfo;
    private final InstrumentationScopeInfo instrumentationScopeInfo;
    private final String name;
    private final SpanKind kind;
    private final long startEpochNanos;
    private final long endEpochNanos;
    private final Attributes attributes;
    private final List events;
    private final List links;
    private final StatusData status;

    /**
     * This takes the ID of the installed handler and three object maps
     * representing the resource, scope and span of the observability trace.
     *
     * @param frontendId The ID of the observability handler
     * @param resource The resource object map
     * @param scope The scope object map
     * @param span The span object map
     */
    @SuppressWarnings("unchecked")
    public ObjectMapSpanData(String frontendId, Map resource,
            Map scope, Map span) {
        String traceId = (String) span.get("traceId");
        String spanId = (String) span.get("spanId");
        this.spanContext = SpanContext.create(traceId, spanId,
                TraceFlags.getDefault(), TraceState.getDefault());

        String parentSpanId = (String) span.get("parentSpanId");
        this.parentSpanContext = SpanContext.create(traceId, parentSpanId,
                TraceFlags.getDefault(), TraceState.getDefault());

        ResourceBuilder resourceBuilder = Resource.builder();
        this.resource = resourceBuilder
                .putAll(getAttributes(resource).build())
                .build();

        this.instrumentationLibraryInfo =
                InstrumentationLibraryInfo.create(
                        (String) scope.get("name"),
                        (String) scope.get("version"));

        this.instrumentationScopeInfo = InstrumentationScopeInfo.create(
                (String) scope.get("name"), (String) scope.get("version"),
                null);

        this.name = "Frontend: " + span.get("name");
        this.kind = SpanKind.CLIENT;
        this.startEpochNanos = (long) span.get("startTimeUnixNano");
        this.endEpochNanos = (long) span.get("endTimeUnixNano");

        AttributesBuilder attributesBuilder = getAttributes(span);
        this.attributes = attributesBuilder
                .put(Constants.FRONTEND_ID, frontendId)
                .build();

        this.events = new ArrayList<>();
        for (Map event :
                (Collection>) span.get("events")) {
            EventData eventData = EventData.create(
                    (long) event.get("timeUnixNano"),
                    (String) event.get("name"),
                    getAttributes(event).build());
            events.add(eventData);
        }

        this.links = Collections.emptyList();

        Map status = (Map) span.get("status");
        this.status = ((int) status.get("code") == 0) ? StatusData.ok() :
                StatusData.error();
    }

    @SuppressWarnings({"rawtypes", "unchecked"})
    private static AttributesBuilder getAttributes(Map node) {
        AttributesBuilder builder = Attributes.builder();
        if (node.containsKey("attributes")) {
            Collection> attributes =
                    (Collection>) node.get("attributes");
            for (Map attribute : attributes) {
                String key = (String) attribute.get("key");
                Map value =
                        (Map) attribute.get("value");
                Map.Entry entry =
                        attributeValue(key, value);
                if (entry != null) {
                    builder.put(entry.getKey(), entry.getValue());
                }
            }
        }
        return builder;
    }

    // https://open-telemetry.github.io/opentelemetry-js/interfaces/_opentelemetry_otlp_transformer.IAnyValue.html
    @SuppressWarnings({"rawtypes", "unchecked"})
    private static Map.Entry attributeValue(
            String key, Map value) {
        if (value.containsKey("stringValue")) {
            String stringValue = (String) value.get("stringValue");
            return new AbstractMap.SimpleImmutableEntry<>(
                    AttributeKey.stringKey(key), stringValue);
        } else if (value.containsKey("intValue")) {
            Number numberValue = (Number) value.get("intValue");
            return new AbstractMap.SimpleImmutableEntry<>(
                    AttributeKey.longKey(key), numberValue.longValue());
        } else if (value.containsKey("boolValue")) {
            Boolean boolValue = (Boolean) value.get("boolValue");
            return new AbstractMap.SimpleImmutableEntry<>(
                    AttributeKey.booleanKey(key), boolValue);
        } else if (value.containsKey("doubleValue")) {
            Double doubleValue = (Double) value.get("doubleValue");
            return new AbstractMap.SimpleImmutableEntry<>(
                    AttributeKey.doubleKey(key), doubleValue);
        } else if (value.containsKey("arrayValue")) {
            AttributeKey attributeKey = null;
            List values = new ArrayList<>();
            for (Map item :
                    (Collection>) value.get("arrayValue")) {
                Map.Entry entry = attributeValue(key,
                        item);
                if (entry != null) {
                    values.add(entry.getValue());
                    if (attributeKey == null) {
                        // As per specs, the array MUST be homogeneous,
                        // i.e., it MUST NOT contain values of different
                        // types.
                        attributeKey = entry.getKey();
                    }
                }
            }
            if (attributeKey == null) {
                // Rejecting empty array, as the type can't be determined
                return null;
            }

            switch (attributeKey.getType()) {
            case LONG:
                attributeKey = AttributeKey.longArrayKey(key);
                break;
            case DOUBLE:
                attributeKey = AttributeKey.doubleArrayKey(key);
                break;
            case STRING:
                attributeKey = AttributeKey.stringArrayKey(key);
                break;
            case BOOLEAN:
                attributeKey = AttributeKey.booleanArrayKey(key);
                break;
            default:
                // Arrays can contain only primitive values
                attributeKey = null;
                break;
            }
            if (attributeKey == null) {
                return null;
            }
            return new AbstractMap.SimpleImmutableEntry<>(attributeKey,
                    values);
        }
        return null;
    }

    @Override
    public SpanContext getSpanContext() {
        return spanContext;
    }

    @Override
    public SpanContext getParentSpanContext() {
        return parentSpanContext;
    }

    @Override
    public Resource getResource() {
        return resource;
    }

    @Override
    @Deprecated
    public InstrumentationLibraryInfo getInstrumentationLibraryInfo() {
        return instrumentationLibraryInfo;
    }

    @Override
    public InstrumentationScopeInfo getInstrumentationScopeInfo() {
        return instrumentationScopeInfo;
    }

    @Override
    public String getName() {
        return name;
    }

    @Override
    public SpanKind getKind() {
        return kind;
    }

    @Override
    public long getStartEpochNanos() {
        return startEpochNanos;
    }

    @Override
    public long getEndEpochNanos() {
        return endEpochNanos;
    }

    @Override
    public Attributes getAttributes() {
        return attributes;
    }

    @Override
    public List getEvents() {
        return events;
    }

    @Override
    public List getLinks() {
        return links;
    }

    @Override
    public StatusData getStatus() {
        return status;
    }

    @Override
    public boolean hasEnded() {
        return true;
    }

    @Override
    public int getTotalRecordedEvents() {
        return events.size();
    }

    @Override
    public int getTotalRecordedLinks() {
        return links.size();
    }

    @Override
    public int getTotalAttributeCount() {
        return attributes.size();
    }
}