@uyu423/pinpoint-node-agent
Version:
Pinpoint node agent provided by NAVER (Personalized version)
168 lines (146 loc) • 5.81 kB
JavaScript
/**
* Pinpoint Node.js Agent
* Copyright 2020-present NAVER Corp.
* Apache License v2.0
*/
const endOfStream = require('end-of-stream')
const url = require('url')
const log = require('../utils/logger')
const IdGenerator = require('../context/id-generator')
const AsyncIdAccessor = require('../context/async-id-accessor')
const DefaultAnnotationKey = require('../constant/annotation-key').DefaultAnnotationKey
const RequestHeaderUtils = require('../instrumentation/request-header-utils')
const GeneralMethodDescriptor = require('../constant/method-descriptor').GeneralMethodDescriptor
const AntPathMatcher = require('../utils/ant-path-matcher')
let pathMatcher
const getPathMatcher = () => {
if (!pathMatcher) {
pathMatcher = new AntPathMatcher(require('../config').getConfig())
}
return pathMatcher
}
exports.clearPathMatcher = function () {
pathMatcher = null
}
exports.instrumentRequest = function (agent, moduleName) {
return function (original) {
return function (event, req, res) {
if (event === 'request') {
if (log.isDebug()) {
log.debug('intercepted request event call to %s.Server.prototype.emit', moduleName)
}
if (log.isInfo()) {
log.info('start instrumentRequest')
}
const requestData = RequestHeaderUtils.read(req)
if (!getPathMatcher().matchPath(requestData.rpcName)) {
const trace = agent.createTraceObject(requestData)
if (trace && trace.canSampled()) {
recordRequest(trace.spanRecorder, requestData)
trace.spanRecorder.recordApi(GeneralMethodDescriptor.SERVER_REQUEST)
}
endOfStream(res, function (err) {
if (!err) {
if (trace && trace.canSampled()) {
trace.spanRecorder.recordAttribute(DefaultAnnotationKey.HTTP_STATUS_CODE, this.statusCode)
return agent.completeTraceObject(trace)
}
}
// Handle case where res.end is called after an error occurred on the
// stream (e.g. if the underlying socket was prematurely closed)
const end = res.end
res.end = function () {
const result = end.apply(this, arguments)
if (trace && trace.canSampled()) {
if (this.statusCode) {
trace.spanRecorder.recordAttribute(DefaultAnnotationKey.HTTP_STATUS_CODE, this.statusCode)
}
return agent.completeTraceObject(trace)
}
return result
}
})
}
}
return original.apply(this, arguments)
}
}
}
const ServiceTypeCode = require('../constant/service-type').ServiceTypeCode
exports.traceOutgoingRequest = function (agent, moduleName) {
return function (original) {
return function () {
const req = original.apply(this, arguments)
const trace = agent.traceContext.currentTraceObject()
if (log.isDebug()) {
if (trace) {
log.debug(`traceOutgoingRequest trace: ${trace}`)
} else {
log.debug(`traceOutgoingRequest No sampled trace`)
}
}
if (!trace) return req
if (!trace.canSampled()) {
RequestHeaderUtils.writeHTTPSampled(req)
return req
}
let spanEventRecorder
let asyncId
// If request object has an asyncId, use request object's one
if (arguments && arguments[0] && AsyncIdAccessor.getAsyncId(arguments[0])) {
asyncId = AsyncIdAccessor.getAsyncId(arguments[0]);
} else {
spanEventRecorder = trace.traceBlockBegin();
spanEventRecorder.recordServiceType(ServiceTypeCode.ASYNC_HTTP_CLIENT_INTERNAL)
spanEventRecorder.recordApiDesc('http.request')
asyncId = spanEventRecorder.recordNextAsyncId()
trace.traceBlockEnd(spanEventRecorder);
}
const httpURL = req._headers.host + url.parse(req.path).pathname
const destinationId = req._headers.host
const nextSpanId = IdGenerator.next
RequestHeaderUtils.write(req, agent, nextSpanId, destinationId)
const asyncTrace = trace.newAsyncTraceWithId(asyncId)
const asyncEventRecorder = asyncTrace.traceAsyncBegin()
asyncEventRecorder.recordServiceType(ServiceTypeCode.ASYNC_HTTP_CLIENT)
asyncEventRecorder.recordApiDesc(req.method)
asyncEventRecorder.recordHTTPURL(httpURL)
asyncEventRecorder.recordNextSpanId(nextSpanId)
asyncEventRecorder.recordDestinationId(destinationId)
req.on('response', onresponse)
return req
function onresponse(res) {
log.debug('intercepted http.ClientcRequest response event %o', {id: httpURL})
// Inspired by:
// https://github.com/nodejs/node/blob/9623ce572a02632b7596452e079bba066db3a429/lib/events.js#L258-L274
if (res.prependListener) {
res.prependListener('end', onEnd)
} else {
const existing = res._events && res._events.end
if (!existing) {
res.on('end', onEnd)
} else {
if (typeof existing === 'function') {
res._events.end = [onEnd, existing]
} else {
existing.unshift(onEnd)
}
}
}
}
function onEnd() {
log.debug('intercepted http.IncomingMessage end event %o', {id: httpURL})
if (asyncTrace) {
asyncEventRecorder.recordAttribute(DefaultAnnotationKey.HTTP_STATUS_CODE, this.statusCode)
asyncTrace.traceAsyncEnd(asyncEventRecorder)
}
}
}
}
}
const recordRequest = (recorder, requestData) => {
recorder.recordRpc(requestData.rpcName)
recorder.recordEndPoint(requestData.endPoint)
recorder.recordRemoteAddr(requestData.remoteAddress)
}