newrelic
Version:
New Relic agent
185 lines (159 loc) • 4.31 kB
JavaScript
'use strict'
const truncate = require('../util/byte-limit').truncate
const NAMES = require('../metrics/names')
const HTTP_LIBRARY = 'http'
const SPAN_KIND_ATTRIBUTE = 'client'
const CATEGORIES = {
HTTP: 'http',
DATASTORE: 'datastore',
GENERIC: 'generic'
}
const EMPTY_USER_ATTRS = Object.freeze(Object.create(null))
const EMPTY_AGENT_ATTRS = Object.freeze(Object.create(null))
/**
* General span event class.
*
* Do not construct directly, instead use one of the static `from*` methods such
* as `SpanEvent.fromSegment`.
*
* @private
* @class
*/
class SpanEvent {
constructor() {
this.type = 'Span'
this.traceId = null
this.guid = null
this.parentId = null
this.transactionId = null
this.sampled = null
this.priority = null
this.name = null
this.category = CATEGORIES.GENERIC
// Timestamp in milliseconds, duration in seconds. Yay consistency!
this.timestamp = null
this.duration = null
}
static get CATEGORIES() {
return CATEGORIES
}
static get DatastoreSpanEvent() {
return DatastoreSpanEvent
}
static get HttpSpanEvent() {
return HttpSpanEvent
}
/**
* Constructs a `SpanEvent` from the given segment.
*
* The constructed span event will contain extra data depending on the
* category of the segment.
*
* @param {TraceSegment} segment - The segment to turn into a span event.
* @param {?string} [parentId=null] - The ID of the segment's parent.
*
* @return {SpanEvent} The constructed event.
*/
static fromSegment(segment, parentId) {
let span = null
if (HttpSpanEvent.testSegment(segment)) {
span = new HttpSpanEvent(segment.parameters)
} else if (DatastoreSpanEvent.testSegment(segment)) {
span = new DatastoreSpanEvent(segment.parameters)
} else {
span = new SpanEvent()
}
const tx = segment.transaction
span.traceId = tx.traceId || tx.id
span.guid = segment.id
span.parentId = parentId || null
span.transactionId = tx.id
span.sampled = tx.sampled
span.priority = tx.priority
span.name = segment.name
if (tx.baseSegment === segment) {
span['nr.entryPoint'] = true
}
// Timestamp in milliseconds, duration in seconds. Yay consistency!
span.timestamp = segment.timer.start
span.duration = segment.timer.getDurationInMillis() / 1000
return span
}
toJSON() {
const attrs = Object.create(null)
for (let key in this) {
if (this[key] != null) {
attrs[key] = this[key]
}
}
return [
attrs,
EMPTY_USER_ATTRS,
EMPTY_AGENT_ATTRS
]
}
}
/**
* Span event class for external requests.
*
* @private
* @class
*/
class HttpSpanEvent extends SpanEvent {
constructor(parameters) {
super()
this.category = CATEGORIES.HTTP
this.component = parameters.library || HTTP_LIBRARY
this['span.kind'] = SPAN_KIND_ATTRIBUTE
if (parameters.url) {
this['http.url'] = parameters.url
}
if (parameters.procedure) {
this['http.method'] = parameters.procedure
}
}
static testSegment(segment) {
return segment.name.startsWith(NAMES.EXTERNAL.PREFIX)
}
}
/**
* Span event class for datastore operations and queries.
*
* @private
* @class.
*/
class DatastoreSpanEvent extends SpanEvent {
constructor(parameters) {
super()
this.category = CATEGORIES.DATASTORE
this['span.kind'] = SPAN_KIND_ATTRIBUTE
if (parameters.product) {
this.component = parameters.product
}
if (parameters.sql_obfuscated) {
this['db.statement'] = _truncate(parameters.sql_obfuscated)
} else if (parameters.sql) {
this['db.statement'] = _truncate(parameters.sql)
}
if (parameters.database_name) {
this['db.instance'] = parameters.database_name
}
if (parameters.host) {
this['peer.hostname'] = parameters.host
if (parameters.port_path_or_id) {
this['peer.address'] = `${parameters.host}:${parameters.port_path_or_id}`
}
}
}
static testSegment(segment) {
return segment.name.startsWith(NAMES.DB.PREFIX)
}
}
function _truncate(val) {
let truncated = truncate(val, 1997)
if (truncated !== val) {
truncated += '...'
}
return truncated
}
module.exports = SpanEvent