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

com.newrelic.opentracing.LambdaSpan Maven / Gradle / Ivy

Go to download

New Relic OpenTracing Tracer implementation for instrumenting AWS Lambda functions.

The newest version!
/*
 * Copyright 2020 New Relic Corporation. All rights reserved.
 * SPDX-License-Identifier: Apache-2.0
 */

package com.newrelic.opentracing;

import com.newrelic.opentracing.events.Event;
import com.newrelic.opentracing.util.SpanCategoryDetection;
import com.newrelic.opentracing.util.Stacktraces;
import com.newrelic.opentracing.util.TimeUtil;
import io.opentracing.Span;
import io.opentracing.SpanContext;
import io.opentracing.tag.Tag;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;

public class LambdaSpan extends Event implements Span {

    private LambdaSpanContext context;
    private long durationInMicros; // open tracing duration is micro-seconds
    private String operationName;

    private final String type;
    private final long startTimeInNanos; // used to compute accurate duration
    private final long timestamp; // start (epoch) time in milli-seconds
    private final String guid;
    private final String parentId;
    private final boolean isRootSpan;

    private final Map tags = new HashMap<>();
    private final Map logs = new HashMap<>();
    private final Map baggage = new HashMap<>();
    private final AtomicBoolean isFinished = new AtomicBoolean(false);

    LambdaSpan(String operationName, long timestamp, long startTimeInNanos, Map tags, LambdaSpan parentSpan, String guid) {
        this.type = "Span";
        this.operationName = operationName;
        this.timestamp = timestamp;
        this.startTimeInNanos = startTimeInNanos;
        if (tags != null) { this.tags.putAll(tags); }
        this.guid = guid;
        this.isRootSpan = parentSpan == null;
        this.parentId = parentSpan == null ? null : parentSpan.guid();
    }

    public String guid() {
        return guid;
    }

    public String getOperationName() {
        return operationName;
    }

    public long getDurationInMicros() {
        return durationInMicros;
    }

    public float getDurationInSeconds() {
        return durationInMicros / TimeUtil.MICROSECONDS_PER_SECOND;
    }

    public long getTimestamp() {
        return timestamp;
    }

    void setContext(LambdaSpanContext context) {
        this.context = context;
    }

    public boolean isRootSpan() {
        return isRootSpan;
    }

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

    public Object getTag(String key) {
        if (key == null) {
            return null;
        }
        return tags.get(key);
    }

    public LogEntry getLog(String eventName) {
        return logs.get(eventName);
    }

    @Override
    public Span setTag(String key, String value) {
        if (key != null && value != null) {
            tags.put(key, value);
        }
        return this;
    }

    @Override
    public Span setTag(String key, boolean value) {
        if (key != null) {
            tags.put(key, value);
        }
        return this;
    }

    @Override
    public Span setTag(String key, Number value) {
        if (key != null && value != null) {
            tags.put(key, value);
        }
        return this;
    }

    @Override
    public  Span setTag(Tag tag, T value) {
        if (tag != null) {
            if (tag.getKey() != null && value != null) {
                tags.put(tag.getKey(), value);
            }
        }
        return this;
    }

    @Override
    public Span setOperationName(String operationName) {
        this.operationName = operationName;
        return this;
    }

    @Override
    public Span log(Map fields) {
        return log(System.currentTimeMillis(), fields);
    }

    @Override
    public Span log(long timestampMicroseconds, Map fields) {
        final long timeInMillis = timestampMicroseconds / TimeUtil.MICROSECONDS_PER_MILLISECOND;
        for (Map.Entry entry : fields.entrySet()) {
            log(timeInMillis, entry.getKey(), entry.getValue());
        }
        return this;
    }

    @Override
    public Span log(String event) {
        return log(System.currentTimeMillis(), event);
    }

    @Override
    public Span log(long timestampMicroseconds, String event) {
        return log(timestampMicroseconds / TimeUtil.MICROSECONDS_PER_MILLISECOND, "event", event);
    }

    private Span log(long timestampInMillis, String eventName, Object value) {
        if (value instanceof StackTraceElement[]) {
            value = Stacktraces.stackTracesToStrings((StackTraceElement[]) value);
        }

        if (value != null) {
            if ("event".equals(eventName) && "error".equals(value)) {
                context.setError();
            }
            logs.put(eventName, new LogEntry(timestampInMillis, value));
        }

        return this;
    }

    private long getEpochTimestampMicroseconds(long nanoTime) {
        // Calculate how many micros have elapsed since the start time
        long microsSinceStart = TimeUnit.NANOSECONDS.toMicros(nanoTime - startTimeInNanos);

        // Add micros since the start to start time to get a better estimate of current time
        return TimeUnit.NANOSECONDS.toMicros(startTimeInNanos) + microsSinceStart;
    }

    @Override
    public Span setBaggageItem(String key, String value) {
        baggage.put(key, value);
        return this;
    }

    @Override
    public String getBaggageItem(String key) {
        return baggage.get(key);
    }

    @Override
    public void finish() {
        finish(getEpochTimestampMicroseconds(System.nanoTime()));
    }

    @Override
    public void finish(long finishMicros) {
        if (isFinished.compareAndSet(false, true)) {
            durationInMicros = finishMicros - TimeUnit.NANOSECONDS.toMicros(startTimeInNanos);
            recordTransactionInfo();
            context.collect();
        }
    }

    /**
     * Must be called before spanFinished.
     */
    private void recordTransactionInfo() {
        if (isRootSpan) {
            context.setTransactionDuration(getDurationInSeconds());

            String transactionType = "Other";

            Object eventSourceArnTag = getTag("aws.lambda.eventSource.arn");
            final String eventSourceArn = eventSourceArnTag instanceof String ? (String) eventSourceArnTag : "";
            if (eventSourceArn.startsWith("arn:aws:iam") || eventSourceArn.startsWith("arn:aws:elasticloadbalancing")) {
                transactionType = "WebTransaction";
            }

            final String arn = (String) getTag("aws.lambda.arn");
            if (arn != null && arn.contains(":")) {
                context.setTransactionName(transactionType, arn.substring(arn.lastIndexOf(":") + 1));
            }
        }
    }

    public Map getTags() {
        return filterNullMapEntries(tags);
    }

    public Map filterNullMapEntries(Map map) {
        map.keySet().removeAll(Collections.singleton(null));
        map.values().removeAll(Collections.singleton(null));
        return map;
    }

    public boolean isSampled() {
        return context.isSampled();
    }

    @Override
    public Map getIntrinsics() {
        final Map intrinsics = new HashMap<>();
        intrinsics.put("type", type);
        intrinsics.put("name", operationName);
        intrinsics.put("timestamp", timestamp);
        intrinsics.put("duration", getDurationInSeconds()); // duration as float in seconds
        intrinsics.put("category", SpanCategoryDetection.detectSpanCategory(this).toString());

        if (isRootSpan) {
            intrinsics.put("nr.entryPoint", true);
        }

        // Get the parentId directly from the parent Span or inbound distributed tracing payload, if either exists.
        if (parentId != null && !parentId.isEmpty()) {
            intrinsics.put("parentId", parentId);
        } else if (context != null) {
            String contextParentId = context.getParentId();
            if (contextParentId != null) {
                intrinsics.put("parentId", contextParentId);
            }
        }

        if (context != null) {
            intrinsics.put("transactionId", context.getTransactionId());
            intrinsics.putAll(context.getDistributedTracingAttributes());
        }

        return intrinsics;
    }

    @Override
    public Map getUserAttributes() {
        Map userAtts = new HashMap<>();

        final Map tags = this.getTags();
        if (tags != null) {
            userAtts.putAll(tags);
        }

        userAtts.remove("error");
        return userAtts;
    }

    @Override
    public Map getAgentAttributes() {
        return new HashMap<>();
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy