dd-trace
Version:
Datadog APM tracing client for JavaScript
205 lines (167 loc) • 6.55 kB
JavaScript
'use strict'
// TODO: either instrument all or none of the render functions
const analyticsSampler = require('../../dd-trace/src/analytics_sampler')
function createWrapHandleRequest (tracer, config) {
return function wrapHandleRequest (handleRequest) {
return function handleRequestWithTrace (req, res, pathname, query) {
return trace(tracer, config, req, res, () => handleRequest.apply(this, arguments))
}
}
}
function createWrapHandleApiRequest (tracer, config) {
return function wrapHandleApiRequest (handleApiRequest) {
return function handleApiRequestWithTrace (req, res, pathname, query) {
return trace(tracer, config, req, res, () => {
const promise = handleApiRequest.apply(this, arguments)
promise.then(handled => {
if (!handled) return
const page = getPageFromPath(pathname, this.dynamicRoutes)
addPage(req, page)
})
return promise
})
}
}
}
function createWrapRenderToResponse (tracer, config) {
return function wrapRenderToResponse (renderToResponse) {
return function renderToResponseWithTrace (ctx) {
return trace(tracer, config, ctx.req, ctx.res, () => renderToResponse.apply(this, arguments))
}
}
}
function createWrapRenderErrorToResponse (tracer, config) {
return function wrapRenderErrorToResponse (renderErrorToResponse) {
return function renderErrorToResponseWithTrace (ctx) {
return trace(tracer, config, ctx.req, ctx.res, () => renderErrorToResponse.apply(this, arguments))
}
}
}
function createWrapRenderToHTML (tracer, config) {
return function wrapRenderToHTML (renderToHTML) {
return function renderToHTMLWithTrace (req, res, pathname, query, parsedUrl) {
return trace(tracer, config, req, res, () => renderToHTML.apply(this, arguments))
}
}
}
function createWrapRenderErrorToHTML (tracer, config) {
return function wrapRenderErrorToHTML (renderErrorToHTML) {
return function renderErrorToHTMLWithTrace (err, req, res, pathname, query) {
return trace(tracer, config, req, res, () => renderErrorToHTML.apply(this, arguments))
}
}
}
function createWrapFindPageComponents (tracer, config) {
return function wrapFindPageComponents (findPageComponents) {
return function findPageComponentsWithTrace (pathname, query) {
const result = findPageComponents.apply(this, arguments)
const span = tracer.scope().active()
const req = span && span._nextReq
if (result) {
addPage(req, pathname)
}
return result
}
}
}
function getPageFromPath (page, dynamicRoutes = []) {
for (const dynamicRoute of dynamicRoutes) {
if (dynamicRoute.page.startsWith('/api') && dynamicRoute.match(page)) {
return dynamicRoute.page
}
}
return page
}
function trace (tracer, config, req, res, handler) {
const scope = tracer.scope()
if (req._datadog_next) return scope.activate(req._datadog_next.span, handler)
const childOf = scope.active()
const tags = {
'service.name': config.service || tracer._service,
'resource.name': req.method,
'span.type': 'web',
'span.kind': 'server',
'http.method': req.method
}
const span = tracer.startSpan('next.request', { childOf, tags })
analyticsSampler.sample(span, config.measured, true)
req._datadog_next = { span }
const promise = scope.activate(span, handler)
// HACK: Store the request object on the span for findPageComponents.
// TODO: Use CLS when it will be available in core.
span._nextReq = req
promise.then(
() => finish(span, config, req, res),
err => finish(span, config, req, res, err)
)
return promise
}
function addPage (req, page) {
if (!req || !req._datadog_next) return
req._datadog_next.span.addTags({
'resource.name': `${req.method} ${page}`.trim(),
'next.page': page
})
}
function finish (span, config, req, res, err) {
span.setTag('error', err || !config.validateStatus(res.statusCode))
span.addTags({
'http.status_code': res.statusCode
})
config.hooks.request(span, req, res)
span.finish()
}
function normalizeConfig (config) {
const hooks = getHooks(config)
const validateStatus = typeof config.validateStatus === 'function'
? config.validateStatus
: code => code < 500
return Object.assign({}, config, { hooks, validateStatus })
}
function getHooks (config) {
const noop = () => {}
const request = (config.hooks && config.hooks.request) || noop
return { request }
}
module.exports = [
{
name: 'next',
versions: ['>=9.5 <11.1'],
file: 'dist/next-server/server/next-server.js',
patch ({ default: Server }, tracer, config) {
config = normalizeConfig(config)
this.wrap(Server.prototype, 'handleRequest', createWrapHandleRequest(tracer, config))
this.wrap(Server.prototype, 'handleApiRequest', createWrapHandleApiRequest(tracer, config))
this.wrap(Server.prototype, 'renderToHTML', createWrapRenderToHTML(tracer, config))
this.wrap(Server.prototype, 'renderErrorToHTML', createWrapRenderErrorToHTML(tracer, config))
this.wrap(Server.prototype, 'findPageComponents', createWrapFindPageComponents(tracer, config))
},
unpatch ({ default: Server }) {
this.unwrap(Server.prototype, 'handleRequest')
this.unwrap(Server.prototype, 'handleApiRequest')
this.unwrap(Server.prototype, 'renderToHTML')
this.unwrap(Server.prototype, 'renderErrorToHTML')
this.unwrap(Server.prototype, 'findPageComponents')
}
},
{
name: 'next',
versions: ['>=11.1'],
file: 'dist/server/next-server.js',
patch ({ default: Server }, tracer, config) {
config = normalizeConfig(config)
this.wrap(Server.prototype, 'handleRequest', createWrapHandleRequest(tracer, config))
this.wrap(Server.prototype, 'handleApiRequest', createWrapHandleApiRequest(tracer, config))
this.wrap(Server.prototype, 'renderToResponse', createWrapRenderToResponse(tracer, config))
this.wrap(Server.prototype, 'renderErrorToResponse', createWrapRenderErrorToResponse(tracer, config))
this.wrap(Server.prototype, 'findPageComponents', createWrapFindPageComponents(tracer, config))
},
unpatch ({ default: Server }) {
this.unwrap(Server.prototype, 'handleRequest')
this.unwrap(Server.prototype, 'handleApiRequest')
this.unwrap(Server.prototype, 'renderToResponse')
this.unwrap(Server.prototype, 'renderErrorToResponse')
this.unwrap(Server.prototype, 'findPageComponents')
}
}
]