UNPKG

dd-trace

Version:

Datadog APM tracing client for JavaScript

148 lines (113 loc) 3.92 kB
'use strict' const ServerPlugin = require('../../dd-trace/src/plugins/server') const { storage } = require('../../datadog-core') const analyticsSampler = require('../../dd-trace/src/analytics_sampler') const { COMPONENT } = require('../../dd-trace/src/constants') const web = require('../../dd-trace/src/plugins/util/web') const errorPages = new Set(['/404', '/500', '/_error', '/_not-found', '/_not-found/page']) class NextPlugin extends ServerPlugin { static id = 'next' constructor (...args) { super(...args) this._requests = new WeakMap() this.addSub('apm:next:page:load', message => this.pageLoad(message)) } bindStart ({ req, res }) { const store = storage('legacy').getStore() const childOf = store ? store.span : store const span = this.tracer.startSpan(this.operationName(), { childOf, tags: { [COMPONENT]: this.constructor.id, 'service.name': this.config.service || this.serviceName(), 'resource.name': req.method, 'span.type': 'web', 'span.kind': 'server', 'http.method': req.method }, integrationName: this.constructor.id }) analyticsSampler.sample(span, this.config.measured, true) this._requests.set(span, req) return { ...store, span } } error ({ span, error }) { if (!span) { const store = storage('legacy').getStore() if (!store) return span = store.span } this.addError(error, span) } finish ({ req, res, nextRequest = {} }) { const store = storage('legacy').getStore() if (!store) return const span = store.span const error = span.context()._tags.error const requestError = req.error || nextRequest.error if (requestError) { // prioritize user-set errors from API routes span.setTag('error', requestError) web.addError(req, requestError) } else if (error) { // general error handling span.setTag('error', error) web.addError(req, requestError || error) } else if (!this.config.validateStatus(res.statusCode)) { // where there's no error, we still need to validate status span.setTag('error', true) web.addError(req, true) } span.addTags({ 'http.status_code': res.statusCode }) this.config.hooks.request(span, req, res) span.finish() } pageLoad ({ page, isAppPath = false, isStatic = false }) { const store = storage('legacy').getStore() if (!store) return const span = store.span const req = this._requests.get(span) // safeguard against missing req in complicated timeout scenarios if (!req) return // Only use error page names if there's not already a name const current = span.context()._tags['next.page'] const isErrorPage = errorPages.has(page) if (current && isErrorPage) { return } // remove ending /route or /page for appDir projects // need to check if not an error page too, as those are marked as app directory // in newer versions if (isAppPath && !isErrorPage) page = page.slice(0, Math.max(0, page.lastIndexOf('/'))) // handle static resource if (isStatic) { page = req.url.includes('_next/static') ? '/_next/static/*' : '/public/*' } span.addTags({ [COMPONENT]: this.constructor.id, 'resource.name': `${req.method} ${page}`.trim(), 'next.page': page }) web.setRoute(req, page) } configure (config) { return super.configure(normalizeConfig(config)) } } function normalizeConfig (config) { const hooks = getHooks(config) const validateStatus = typeof config.validateStatus === 'function' ? config.validateStatus : code => code < 500 return { ...config, hooks, validateStatus } } const noop = () => {} function getHooks (config) { const request = config.hooks?.request ?? noop return { request } } module.exports = NextPlugin