UNPKG

newrelic

Version:
305 lines (259 loc) 10.2 kB
/* * Copyright 2020 New Relic Corporation. All rights reserved. * SPDX-License-Identifier: Apache-2.0 */ 'use strict' const StreamingSpanAttributes = require('./streaming-span-attributes') const { truncate } = require('../util/byte-limit') const Config = require('../config') const { DESTINATIONS } = require('../config/attribute-filter') const { addSpanKind, isEntryPointSpan, reparentSpan, shouldCreateSpan, HTTP_LIBRARY, REGEXS, SPAN_KIND, CATEGORIES } = require('./helpers') /** * Specialized span event class for use with infinite streaming. * Currently designed to be sent over grpc via the v1.proto definition. * * @private * @class */ class StreamingSpanEvent { /** * @param {*} traceId TraceId for the Span. * @param {object} agentAttributes Initial set of agent attributes. * Must be pre-filtered and truncated. * @param {object} customAttributes Initial set of custom attributes. * Must be pre-filtered and truncated. */ constructor(traceId, agentAttributes = {}, customAttributes) { this._traceId = traceId this._intrinsicAttributes = new StreamingSpanAttributes() this._intrinsicAttributes.addAttribute('traceId', traceId) this._intrinsicAttributes.addAttribute('type', 'Span') this._intrinsicAttributes.addAttribute('category', CATEGORIES.GENERIC) this._customAttributes = new StreamingSpanAttributes(customAttributes) const { host, port, ...agentAttrs } = agentAttributes this._agentAttributes = new StreamingSpanAttributes(agentAttrs) if (host) { this.addAgentAttribute('server.address', host) } if (port) { this.addAgentAttribute('server.port', port, true) } } /** * Add a key/value pair to the Span's intrinsics collection. * * @param {string} key Name of the attribute to be stored. * @param {string|boolean|number} value Value of the attribute to be stored. */ addIntrinsicAttribute(key, value) { this._intrinsicAttributes.addAttribute(key, value) } getIntrinsicAttributes() { return this._intrinsicAttributes } /** * Add a key/value pair to the Span's custom/user attributes collection. * * @param {string} key Name of the attribute to be stored. * @param {string|boolean|number} value Value of the attribute to be stored. * @param {boolean} [truncateExempt] Set to true if attribute should not be truncated. */ addCustomAttribute(key, value, truncateExempt = false) { const shouldKeep = this._checkFilter(key) if (shouldKeep) { const processedValue = truncateExempt ? value : _truncate(value) this._customAttributes.addAttribute(key, processedValue) } } /** * Add a key/value pair to the Span's agent attributes collection. * * @param {string} key Name of the attribute to be stored. * @param {string|boolean|number} value Value of the attribute to be stored. * @param {boolean} [truncateExempt] Set to true if attribute should not be truncated. */ addAgentAttribute(key, value, truncateExempt = false) { const shouldKeep = this._checkFilter(key) if (shouldKeep) { const processedValue = truncateExempt ? value : _truncate(value) this._agentAttributes.addAttribute(key, processedValue) } } _checkFilter(key) { const { attributeFilter } = Config.getInstance() const dest = attributeFilter.filterSegment(DESTINATIONS.SPAN_EVENT, key) return dest & DESTINATIONS.SPAN_EVENT } toStreamingFormat() { // Attributes are pre-formatted. return { trace_id: this._traceId, intrinsics: this._intrinsicAttributes, user_attributes: this._customAttributes, agent_attributes: this._agentAttributes } } static fromSegment({ segment, transaction, parentId = null, isRoot = false, inProcessSpans }) { const entryPoint = isEntryPointSpan({ segment, transaction }) if (!inProcessSpans && !shouldCreateSpan({ entryPoint, segment, transaction })) { return null } const spanContext = segment.getSpanContext() // Since segments already hold span agent attributes and we want to leverage // filtering, we add to the segment attributes prior to processing. if (spanContext.hasError && !transaction.hasIgnoredErrorStatusCode()) { const details = spanContext.errorDetails segment.addSpanAttribute('error.message', details.message) segment.addSpanAttribute('error.class', details.type) if (details.expected) { segment.addSpanAttribute('error.expected', details.expected) } } const agentAttributes = segment.attributes.get(DESTINATIONS.SPAN_EVENT) const customAttributes = spanContext.customAttributes.get(DESTINATIONS.SPAN_EVENT) const traceId = transaction.traceId let span = null if (StreamingHttpSpanEvent.isHttpSegment(segment)) { span = new StreamingHttpSpanEvent(traceId, agentAttributes, customAttributes) } else if (StreamingDatastoreSpanEvent.isDatastoreSegment(segment)) { span = new StreamingDatastoreSpanEvent(traceId, agentAttributes, customAttributes) } else { span = new StreamingSpanEvent(traceId, agentAttributes, customAttributes) } for (const [key, value] of Object.entries(spanContext.intrinsicAttributes)) { span.addIntrinsicAttribute(key, value) } const newParentId = reparentSpan({ inProcessSpans, isRoot, segment, transaction, parentId }) span.addIntrinsicAttribute('guid', segment.id) span.addIntrinsicAttribute('parentId', newParentId) span.addIntrinsicAttribute('transactionId', transaction.id) span.addIntrinsicAttribute('sampled', transaction.sampled) span.addIntrinsicAttribute('priority', transaction.priority) span.addIntrinsicAttribute('name', segment.name) if (isRoot) { span.addIntrinsicAttribute('trustedParentId', transaction.traceContext.trustedParentId) if (transaction.traceContext.tracingVendors) { span.addIntrinsicAttribute('tracingVendors', transaction.traceContext.tracingVendors) } } // Only set this if it will be `true`. Must be `null` otherwise. if (entryPoint) { span.addIntrinsicAttribute('nr.entryPoint', true) } // Timestamp in milliseconds, duration in seconds. Yay consistency! span.addIntrinsicAttribute('timestamp', segment.timer.start) span.addIntrinsicAttribute('duration', segment.timer.getDurationInMillis() / 1000) addSpanKind({ segment, span }) return span } } /** * Specialized span event class for external requests for use with infinite streaming. * Currently designed to be sent over grpc via the v1.proto definition. * * @private * @class */ class StreamingHttpSpanEvent extends StreamingSpanEvent { /** * @param {*} traceId TraceId for the Span. * @param {object} agentAttributes Initial set of agent attributes. * Must be pre-filtered and truncated. * @param {object} customAttributes Initial set of custom attributes. * Must be pre-filtered and truncated. */ constructor(traceId, agentAttributes, customAttributes) { // remove mapped attributes before creating other agentAttributes const { library, url, hostname, procedure, ...agentAttrs } = agentAttributes super(traceId, agentAttrs, customAttributes) this.addIntrinsicAttribute('category', CATEGORIES.HTTP) this.addIntrinsicAttribute('component', library || HTTP_LIBRARY) this.addIntrinsicAttribute('span.kind', SPAN_KIND.CLIENT) if (url) { this.addAgentAttribute('http.url', url) } if (hostname) { this.addAgentAttribute('server.address', hostname) } if (procedure) { this.addAgentAttribute('http.method', procedure) this.addAgentAttribute('http.request.method', procedure) } } static isHttpSegment(segment) { return REGEXS.CLIENT.EXTERNAL.test(segment.name) } } /** * Specialized span event class for datastore operations and queries for use with * infinite streaming. * Currently designed to be sent over grpc via the v1.proto definition. * * @private * @class. */ class StreamingDatastoreSpanEvent extends StreamingSpanEvent { /** * @param {*} traceId TraceId for the Span. * @param {object} agentAttributes Initial set of agent attributes. * Must be pre-filtered and truncated. * @param {object} customAttributes Initial set of custom attributes. * Must be pre-filtered and truncated. */ /* eslint-disable camelcase */ constructor(traceId, agentAttributes, customAttributes) { // remove mapped attributes before creating other agentAttributes const { product, collection, sql, sql_obfuscated, database_name, port_path_or_id, ...agentAttrs } = agentAttributes super(traceId, agentAttrs, customAttributes) this.addIntrinsicAttribute('category', CATEGORIES.DATASTORE) this.addIntrinsicAttribute('span.kind', SPAN_KIND.CLIENT) if (product) { this.addIntrinsicAttribute('component', product) this.addAgentAttribute('db.system', product) } if (collection) { this.addAgentAttribute('db.collection', collection) } if (sql || sql_obfuscated) { let finalSql = null if (sql_obfuscated) { finalSql = _truncate(agentAttributes.sql_obfuscated) } else if (sql) { finalSql = _truncate(agentAttributes.sql) } // Flag as exempt from normal attribute truncation this.addAgentAttribute('db.statement', finalSql, true) } if (database_name) { this.addAgentAttribute('db.instance', database_name) } if (agentAttributes.host) { this.addAgentAttribute('peer.hostname', agentAttributes.host) if (port_path_or_id) { const address = `${agentAttributes.host}:${port_path_or_id}` this.addAgentAttribute('peer.address', address) this.addAgentAttribute('server.port', port_path_or_id, true) } } } /* eslint-enable camelcase */ static isDatastoreSegment(segment) { return REGEXS.CLIENT.DATASTORE.test(segment.name) } } function _truncate(val) { let truncated = truncate(val, 1997) if (truncated !== val) { truncated += '...' } return truncated } module.exports = StreamingSpanEvent