Skip to content

Commit af5f08f

Browse files
committed
feat: support APM Server intake API version 2 (#465)
Closes #356
1 parent 52b9d52 commit af5f08f

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

58 files changed

+1334
-2101
lines changed

docs/agent-api.asciidoc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1084,7 +1084,7 @@ Defaults to `unnamed`
10841084
You can alternatively set this via <<span-type,`span.type`>>.
10851085
Defaults to `custom.code`
10861086

1087-
When a span is started it will measure the time until <<span-end,`span.end()`>> or <<span-truncate,`span.truncate()`>> is called.
1087+
When a span is started it will measure the time until <<span-end,`span.end()`>> is called.
10881088

10891089
See <<span-api,Span API>> docs for details on how to use custom spans.
10901090

docs/span-api.asciidoc

Lines changed: 1 addition & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ Defaults to `unnamed`
7373
You can alternatively set this via <<span-type,`span.type`>>.
7474
Defaults to `custom.code`
7575

76-
When a span is started it will measure the time until <<span-end,`span.end()`>> or <<span-truncate,`span.truncate()`>> is called.
76+
When a span is started it will measure the time until <<span-end,`span.end()`>> is called.
7777

7878
[[span-end]]
7979
==== `span.end()`
@@ -86,20 +86,3 @@ span.end()
8686
End the span.
8787
If the span has already ended,
8888
nothing happens.
89-
90-
A span that isn't ended before the parent transaction ends will be <<span-truncate,truncated>>.
91-
92-
[[span-truncate]]
93-
==== `span.truncate()`
94-
95-
[source,js]
96-
----
97-
span.truncate()
98-
----
99-
100-
Truncates and ends the span.
101-
If the span is already ended or truncated,
102-
nothing happens.
103-
104-
A truncated span is a special type of ended span.
105-
It's used to indicate that the measured event took longer than the duration recorded by the span.

docs/transaction-api.asciidoc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ Think of the transaction result as equivalent to the status code of an HTTP resp
5757
transaction.end([result])
5858
----
5959

60-
Ends the transaction and <<span-truncate,truncates>> all un-ended child spans.
60+
Ends the transaction.
6161
If the transaction has already ended,
6262
nothing happens.
6363

lib/agent.js

Lines changed: 54 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,9 @@ var connect = require('./middleware/connect')
1515
var Filters = require('./filters')
1616
var Instrumentation = require('./instrumentation')
1717
var parsers = require('./parsers')
18-
var request = require('./request')
1918
var stackman = require('./stackman')
2019
var symbols = require('./symbols')
20+
var truncate = require('./truncate')
2121

2222
var IncomingMessage = http.IncomingMessage
2323
var ServerResponse = http.ServerResponse
@@ -32,6 +32,7 @@ function Agent () {
3232

3333
this._instrumentation = new Instrumentation(this)
3434
this._filters = new Filters()
35+
this._apmServer = null
3536

3637
this._config()
3738
}
@@ -48,6 +49,10 @@ Object.defineProperty(Agent.prototype, 'currentTransaction', {
4849
}
4950
})
5051

52+
Agent.prototype.destroy = function () {
53+
if (this._apmServer) this._apmServer.destroy()
54+
}
55+
5156
Agent.prototype.startTransaction = function () {
5257
return this._instrumentation.startTransaction.apply(this._instrumentation, arguments)
5358
}
@@ -125,15 +130,35 @@ Agent.prototype.start = function (opts) {
125130
})
126131
}
127132

128-
this._instrumentation.start()
133+
this._apmServer = new ElasticAPMHttpClient({
134+
// metadata
135+
agentName: 'nodejs',
136+
agentVersion: version,
137+
serviceName: this._conf.serviceName,
138+
serviceVersion: this._conf.serviceVersion,
139+
frameworkName: this._conf.frameworkName,
140+
frameworkVersion: this._conf.frameworkVersion,
141+
hostname: this._conf.hostname,
129142

130-
this._httpClient = new ElasticAPMHttpClient({
143+
// Sanitize conf
144+
truncateStringsAt: config.INTAKE_STRING_MAX_SIZE, // TODO: Do we need to set this (it's the default value)
145+
146+
// HTTP conf
131147
secretToken: this._conf.secretToken,
132148
userAgent: userAgent,
133149
serverUrl: this._conf.serverUrl,
134150
rejectUnauthorized: this._conf.verifyServerCert,
135-
serverTimeout: this._conf.serverTimeout * 1000
151+
serverTimeout: this._conf.serverTimeout * 1000,
152+
153+
// Streaming conf
154+
size: this._conf.apiRequestSize,
155+
time: this._conf.apiRequestTime * 1000
136156
})
157+
this._apmServer.on('error', err => {
158+
this.logger.error('An error occrued while communicating with the APM Server:', err.message)
159+
})
160+
161+
this._instrumentation.start()
137162

138163
Error.stackTraceLimit = this._conf.stackTraceLimit
139164
if (this._conf.captureExceptions) this.handleUncaughtExceptions()
@@ -281,8 +306,25 @@ Agent.prototype.captureError = function (err, opts, cb) {
281306
}
282307

283308
function send (error) {
284-
agent.logger.info('logging error %s with Elastic APM', id)
285-
request.errors(agent, [error], cb)
309+
error = agent._filters.process(error) // TODO: Update filter to expect this format
310+
311+
if (!error) {
312+
agent.logger.debug('error ignored by filter %o', {id: id})
313+
cb()
314+
return
315+
}
316+
317+
truncate.error(error, agent._conf)
318+
319+
if (agent._apmServer) {
320+
agent.logger.info(`Sending error ${id} to Elastic APM`)
321+
agent._apmServer.sendError(error, function () {
322+
agent._apmServer.flush(cb)
323+
})
324+
} else {
325+
// TODO: Swallow this error just as it's done in agent.flush()?
326+
process.nextTick(cb.bind(null, new Error('cannot capture error before agent is started')))
327+
}
286328
}
287329
}
288330

@@ -308,7 +350,12 @@ Agent.prototype.handleUncaughtExceptions = function (cb) {
308350
}
309351

310352
Agent.prototype.flush = function (cb) {
311-
this._instrumentation.flush(cb)
353+
if (this._apmServer) {
354+
this._apmServer.flush(cb)
355+
} else {
356+
this.logger.warn(new Error('cannot flush agent before it is started'))
357+
process.nextTick(cb)
358+
}
312359
}
313360

314361
Agent.prototype.lambda = function wrapLambda (type, fn) {

lib/config.js

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
'use strict'
22

33
var fs = require('fs')
4-
var os = require('os')
54
var path = require('path')
65

76
var consoleLogLevel = require('console-log-level')
@@ -29,7 +28,8 @@ var DEFAULTS = {
2928
verifyServerCert: true,
3029
active: true,
3130
logLevel: 'info',
32-
hostname: os.hostname(),
31+
apiRequestSize: 1024 * 1024, // TODO: Is this the right default
32+
apiRequestTime: 10, // TODO: Is this the right default. Should this be ms?
3333
stackTraceLimit: 50,
3434
captureExceptions: true,
3535
filterHttpHeaders: true,
@@ -45,10 +45,10 @@ var DEFAULTS = {
4545
sourceLinesSpanAppFrames: 0,
4646
sourceLinesSpanLibraryFrames: 0,
4747
errorMessageMaxLength: 2048,
48-
flushInterval: 10,
48+
flushInterval: 10, // TODO: Deprecate
4949
transactionMaxSpans: Infinity,
5050
transactionSampleRate: 1.0,
51-
maxQueueSize: 100,
51+
maxQueueSize: 100, // TODO: Deprecate
5252
serverTimeout: 30,
5353
disableInstrumentations: []
5454
}
@@ -62,6 +62,8 @@ var ENV_TABLE = {
6262
active: 'ELASTIC_APM_ACTIVE',
6363
logLevel: 'ELASTIC_APM_LOG_LEVEL',
6464
hostname: 'ELASTIC_APM_HOSTNAME',
65+
apiRequestSize: 'ELASTIC_APM_API_REQUEST_SIZE',
66+
apiRequestTime: 'ELASTIC_APM_API_REQUEST_TIME',
6567
frameworkName: 'ELASTIC_APM_FRAMEWORK_NAME',
6668
frameworkVersion: 'ELASTIC_APM_FRAMEWORK_VERSION',
6769
stackTraceLimit: 'ELASTIC_APM_STACK_TRACE_LIMIT',
@@ -99,6 +101,8 @@ var BOOL_OPTS = [
99101
]
100102

101103
var NUM_OPTS = [
104+
'apiRequestSize',
105+
'apiRequestTime',
102106
'stackTraceLimit',
103107
'abortedErrorThreshold',
104108
'sourceLinesErrorAppFrames',

lib/instrumentation/index.js

Lines changed: 32 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,11 @@
33
var fs = require('fs')
44
var path = require('path')
55

6-
var AsyncValuePromise = require('async-value-promise')
76
var hook = require('require-in-the-middle')
87
var semver = require('semver')
98

10-
var Queue = require('../queue')
11-
var request = require('../request')
129
var Transaction = require('./transaction')
10+
var truncate = require('../truncate')
1311
var shimmer = require('./shimmer')
1412

1513
var MODULES = [
@@ -43,7 +41,6 @@ module.exports = Instrumentation
4341

4442
function Instrumentation (agent) {
4543
this._agent = agent
46-
this._queue = null
4744
this._started = false
4845
this.currentTransaction = null
4946
}
@@ -56,19 +53,6 @@ Instrumentation.prototype.start = function () {
5653
var self = this
5754
this._started = true
5855

59-
var qopts = {
60-
flushInterval: this._agent._conf.flushInterval,
61-
maxQueueSize: this._agent._conf.maxQueueSize,
62-
logger: this._agent.logger
63-
}
64-
this._queue = new Queue(qopts, function onFlush (transactions, done) {
65-
AsyncValuePromise.all(transactions).then(function (transactions) {
66-
if (self._agent._conf.active && transactions.length > 0) {
67-
request.transactions(self._agent, transactions, done)
68-
}
69-
}, done)
70-
})
71-
7256
if (this._agent._conf.asyncHooks && semver.gte(process.version, '8.2.0')) {
7357
require('./async-hooks')(this)
7458
} else {
@@ -106,27 +90,44 @@ Instrumentation.prototype._patchModule = function (exports, name, version, enabl
10690
}
10791

10892
Instrumentation.prototype.addEndedTransaction = function (transaction) {
93+
var agent = this._agent
94+
10995
if (this._started) {
110-
var queue = this._queue
96+
var payload = agent._filters.process(transaction._encode()) // TODO: Update filter to expect this format
97+
if (!payload) return agent.logger.debug('transaction ignored by filter %o', {id: transaction.id})
98+
truncate.transaction(payload)
99+
agent.logger.debug('sending transaction %o', {id: transaction.id})
100+
agent._apmServer.sendTransaction(payload)
101+
} else {
102+
agent.logger.debug('ignoring transaction %o', {id: transaction.id})
103+
}
104+
}
111105

112-
this._agent.logger.debug('adding transaction to queue %o', {id: transaction.id})
106+
Instrumentation.prototype.addEndedSpan = function (span) {
107+
var agent = this._agent
113108

114-
var payload = new AsyncValuePromise()
109+
if (this._started) {
110+
agent.logger.debug('encoding span %o', {trans: span.transaction.id, name: span.name, type: span.type})
111+
span._encode(function (err, payload) {
112+
if (err) {
113+
agent.logger.error('error encoding span %o', {trans: span.transaction.id, name: span.name, type: span.type, error: err.message})
114+
return
115+
}
115116

116-
payload.catch(function (err) {
117-
this._agent.logger.error('error encoding transaction %s: %s', transaction.id, err.message)
118-
})
117+
payload = agent._filters.process(payload) // TODO: Update filter to expect this format
119118

120-
// Add the transaction payload to the queue instead of the transation
121-
// object it self to free up the transaction for garbage collection
122-
transaction._encode(function (err, _payload) {
123-
if (err) payload.reject(err)
124-
else payload.resolve(_payload)
125-
})
119+
if (!payload) {
120+
agent.logger.debug('span ignored by filter %o', {trans: span.transaction.id, name: span.name, type: span.type})
121+
return
122+
}
126123

127-
queue.add(payload)
124+
truncate.span(payload)
125+
126+
agent.logger.debug('sending span %o', {trans: span.transaction.id, name: span.name, type: span.type})
127+
if (agent._apmServer) agent._apmServer.sendSpan(payload)
128+
})
128129
} else {
129-
this._agent.logger.debug('ignoring transaction %o', {id: transaction.id})
130+
agent.logger.debug('ignoring span %o', {trans: span.transaction.id, name: span.name, type: span.type})
130131
}
131132
}
132133

@@ -216,7 +217,3 @@ Instrumentation.prototype._recoverTransaction = function (trans) {
216217

217218
this.currentTransaction = trans
218219
}
219-
220-
Instrumentation.prototype.flush = function (cb) {
221-
this._queue.flush(cb)
222-
}

lib/instrumentation/span.js

Lines changed: 5 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ module.exports = Span
1414
function Span (transaction) {
1515
this.transaction = transaction
1616
this.started = false
17-
this.truncated = false
1817
this.ended = false
1918
this.name = null
2019
this.type = null
@@ -50,18 +49,6 @@ Span.prototype.customStackTrace = function (stackObj) {
5049
this._recordStackTrace(stackObj)
5150
}
5251

53-
Span.prototype.truncate = function () {
54-
if (!this.started) {
55-
this._agent.logger.debug('tried to truncate non-started span - ignoring %o', {id: this.transaction.id, name: this.name, type: this.type})
56-
return
57-
} else if (this.ended) {
58-
this._agent.logger.debug('tried to truncate already ended span - ignoring %o', {id: this.transaction.id, name: this.name, type: this.type})
59-
return
60-
}
61-
this.truncated = true
62-
this.end()
63-
}
64-
6552
Span.prototype.end = function () {
6653
if (!this.started) {
6754
this._agent.logger.debug('tried to call span.end() on un-started span %o', {id: this.transaction.id, name: this.name, type: this.type})
@@ -75,8 +62,8 @@ Span.prototype.end = function () {
7562
this._agent._instrumentation._recoverTransaction(this.transaction)
7663

7764
this.ended = true
78-
this._agent.logger.debug('ended span %o', {id: this.transaction.id, name: this.name, type: this.type, truncated: this.truncated})
79-
this.transaction._recordEndedSpan(this)
65+
this._agent.logger.debug('ended span %o', {id: this.transaction.id, name: this.name, type: this.type})
66+
this._agent._instrumentation.addEndedSpan(this)
8067
}
8168

8269
Span.prototype.duration = function () {
@@ -157,8 +144,10 @@ Span.prototype._encode = function (cb) {
157144
}
158145

159146
var payload = {
147+
transactionId: self.transaction.id,
148+
timestamp: self.transaction.timestamp,
160149
name: self.name,
161-
type: self.truncated ? self.type + '.truncated' : self.type,
150+
type: self.type,
162151
start: self.offsetTime(),
163152
duration: self.duration()
164153
}

0 commit comments

Comments
 (0)