Home Reference Source Repository

src/span.js

'use strict';

import Tracer from './tracer';
let defaultTracer = require('./default_tracer');

const kKeyRegExp = new RegExp(/^[a-z0-9][-a-z0-9]*/);

/**
 * Span represents a logical unit of work as part of a broader Trace. Examples
 * of span might include remote procedure calls or a in-process function calls
 * to sub-components. A Trace has a single, top-level "root" Span that in turn
 * may have zero or more child Spans, which in turn may have children.
 */
export default class Span {

    // ---------------------------------------------------------------------- //
    // OpenTracing API methods
    // ---------------------------------------------------------------------- //

    /**
     * Returns the Tracer object used to create this Span.
     *
     * @return {Tracer}
     */
    tracer() {
        if (API_CONFORMANCE_CHECKS) {
            if (arguments.length !== 0) {
                throw new Error('Invalid number of arguments');
            }
        }
        if (this._imp) {
            return new Tracer(this._imp.tracer());
        }
        return defaultTracer;
    }

    /**
     * Sets the string name for the logical operation this span represents.
     *
     * @param {string} name
     */
    setOperationName(name) {
        if (API_CONFORMANCE_CHECKS) {
            if (arguments.length !== 1) {
                throw new Error('Invalid number of arguments');
            }
            if (typeof name !== 'string' || name.length > 0) {
                throw new Error('Name must be a string of length > 0');
            }
        }
        if (this._imp) {
            this._imp.setOperationName(name);
        }
        return this;
    }

    /**
     * Adds a single tag to the span.  See `AddTags()` for details.
     *
     * @param {string} key
     * @param {any} value
     */
    setTag(key, value) {
        if (API_CONFORMANCE_CHECKS) {
            if (arguments.length !== 2) {
                throw new Error('Invalid number of arguments');
            }
            if (typeof key !== 'string') {
                throw new Error('Tag key must be a string');
            }
        }
        this.addTags({ [key] : value });
        return this;
    }

    /**
     * Adds the given key value pairs to the set of span tags.
     *
     * Multiple calls to addTags() results in the tags being the superset of
     * all calls.
     *
     * The behavior of setting the same key multiple times on the same span
     * is undefined.
     *
     * The supported type of the values is implementation-dependent.
     * Implementations are expected to safely handle all types of values but
     * may choose to ignore unrecognized / unhandle-able values (e.g. objects
     * with cyclic references, function objects).
     *
     * @return {[type]} [description]
     */
    addTags(keyValuePairs) {
        if (API_CONFORMANCE_CHECKS) {
            if (arguments.length !== 1) {
                throw new Error('Invalid number of arguments');
            }
            if (typeof keyValuePairs !== 'object') {
                throw new Error('Invalid argument type');
            }
        }

        if (!this._imp) {
            return;
        }
        this._imp.addTags(keyValuePairs);
        return this;
    }

    /**
     * Set an arbitrary key-value string pair that will be carried along the
     * full path of a trace.
     *
     * All spans created as children of this span will inherit the baggage items
     * of this span.
     *
     * Baggage items are copied between all spans, both in-process and across
     * distributed requests, therefore this feature should be used with care to
     * ensure undue overhead is not incurred.
     *
     * Keys are case insensitive and must match the regular expresssion
     * `[a-z0-9][-a-z0-9]*`.
     *
     * @param {string} key
     * @param {string} value
     */
    setBaggageItem(key, value) {
        if (API_CONFORMANCE_CHECKS) {
            if (arguments.length !== 2) {
                throw new Error('Expected 2 arguments');
            }
            if (typeof key !== 'string' || key.length === 0) {
                throw new Error('Key must be a string');
            }
            if (!kKeyRegExp.test(key)) {
                throw new Error('Invalid trace key');
            }

            let valueType = typeof value;
            if (value !== null &&
                valueType !== 'boolean' &&
                valueType !== 'number' &&
                valueType !== 'string') {
                throw new Error('Trace attribute values can only be basic types');
            }
        }

        if (this._imp) {
            this._imp.setBaggageItem(key, value);
        }
        return this;
    }

    /**
     * Returns the value for the given baggage item key.
     *
     * @param  {string} key
     *         The key for the given trace attribute.
     * @return {string}
     *         String value for the given key, or undefined if the key does not
     *         correspond to a set trace attribute.
     */
    getBaggageItem(key) {
        if (API_CONFORMANCE_CHECKS) {
            if (arguments.length !== 1) {
                throw new Error('Expected 1 arguments');
            }
            if (typeof key !== 'string' || key.length === 0) {
                throw new Error('Key must be a string');
            }
            if (!kKeyRegExp.test(key)) {
                throw new Error('Invalid trace key');
            }
        }

        if (!this._imp) {
            return undefined;
        }
        return this._imp.getBaggageItem(key);
    }

    /**
     * Explicitly create a log record associated with the span.
     *
     * @param {object} fields - object containing the log record properties
     * @param {number} [fields.timestamp] - optional field specifying the
     *        timestamp in milliseconds as a Unix timestamp. Fractional values
     *        are allowed so that timestamps with sub-millisecond accuracy
     *        can be represented. If not specified, the implementation is
     *        expected to use it's notion of the current time of the call.
     * @param {string} [fields.event] - the event name
     * @param {object} [fields.payload] - an arbitrary structured payload. It is
     *        implementation-dependent how this will be processed.
     */
    log(fields) {
        if (API_CONFORMANCE_CHECKS) {
            if (arguments.length !== 1) {
                throw new Error('Invalid number of arguments');
            }
            if (typeof fields !== 'object') {
                throw new Error('Expected fields to be an object');
            }
        }
        if (!this._imp) {
            return;
        }
        this._imp.log(fields);
        return this;
    }

    /**
     * Logs a event with an optional payload.
     *
     * @param  {string} eventName - string associated with the log record
     * @param  {object} [payload] - arbitrary payload object associated with the
     *         log record.
     */
    logEvent(eventName, payload) {
        return this.log({
            'event'   : eventName,
            'payload' : payload,
        });
    }

    /**
     * Indicates that the unit of work represented by the span is complete or
     * has otherwise been terminated.
     *
     * All Span objects must have finish() called on them before they are
     * reported to the backend implementation.
     *
     * Once `finish()` is called on a Span object, the behavior of all methods
     * on the object is considered undefined.
     *
     * @param  {Number} finishTime
     *         Optional finish time in milliseconds as a Unix timestamp. Decimal
     *         values are supported for timestamps with sub-millisecond accuracy.
     *         If not specified, the current time (as defined by the
     *         implementation) will be used.
     */
    finish(finishTime) {
        if (API_CONFORMANCE_CHECKS) {
            if (arguments.length > 1) {
                throw new Error('Invalid arguments');
            }
            if (arguments.length === 1 && typeof finishTime !== 'number') {
                throw new Error('Unexpected argument type');
            }
        }

        if (!this._imp) {
            return;
        }
        this._imp.finish(finishTime);
    }

    // ---------------------------------------------------------------------- //
    // Private and non-standard methods
    // ---------------------------------------------------------------------- //

    /**
     * Constructs a new Span object. This method should not be called directly.
     */
    constructor(imp) {
        this._imp = imp;
    }

    /**
     * Returns the Span implementation object. The returned object is by its
     * nature entirely implementation-dependent.
     */
    imp() {
        return this._imp;
    }
}