forked from DataDog/dd-trace-js
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathindex.js
More file actions
160 lines (123 loc) · 4.51 KB
/
index.js
File metadata and controls
160 lines (123 loc) · 4.51 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
'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'
#requestsBySpanId = new WeakMap()
constructor (...args) {
super(...args)
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)
// Store request by span ID to handle cases where child spans are activated
const spanId = span.context()._spanId
this.#requestsBySpanId.set(spanId, 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()
// Cleanup: Remove request reference so IncomingMessage can be GC'd.
// WeakMap keys (_spanId) live as long as the span context, which is retained
// by the profiler and analytics pipeline long after the request finishes.
this.#requestsBySpanId.delete(span.context()._spanId)
}
pageLoad ({ page, isAppPath = false, isStatic = false }) {
const store = storage('legacy').getStore()
if (!store) return
const span = store.span
const spanId = span.context()._spanId
const parentSpanId = span.context()._parentId
// Try current span first, then parent span.
// This handles cases where pageLoad runs in a child span context
const req = this.#requestsBySpanId.get(spanId) ?? this.#requestsBySpanId.get(parentSpanId)
// 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