Skip to content

Commit 9316dc0

Browse files
timneutkensstyfle
andauthoredAug 25, 2021
Add default trace format that is exported automatically (#28461)
Co-authored-by: Steven <steven@ceriously.com>
1 parent 706547e commit 9316dc0

File tree

15 files changed

+258
-80
lines changed

15 files changed

+258
-80
lines changed
 

‎packages/next/build/babel/loader/index.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ const nextBabelLoaderOuter = function nextBabelLoaderOuter(
4040
) {
4141
const callback = this.async()
4242

43-
const loaderSpan = trace('next-babel-turbo-loader', this.currentTraceSpan?.id)
43+
const loaderSpan = this.currentTraceSpan.traceChild('next-babel-turbo-loader')
4444
loaderSpan
4545
.traceAsyncFn(() =>
4646
nextBabelLoader.call(this, loaderSpan, inputSource, inputSourceMap)

‎packages/next/build/babel/loader/types.d.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { loader } from 'next/dist/compiled/webpack/webpack'
22
import { Span } from '../../../telemetry/trace'
33

44
export interface NextJsLoaderContext extends loader.LoaderContext {
5-
currentTraceSpan?: Span
5+
currentTraceSpan: Span
66
}
77

88
export interface NextBabelLoaderOptions {

‎packages/next/build/index.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -130,11 +130,13 @@ export default async function build(
130130
const config: NextConfigComplete = await nextBuildSpan
131131
.traceChild('load-next-config')
132132
.traceAsyncFn(() => loadConfig(PHASE_PRODUCTION_BUILD, dir, conf))
133+
const distDir = path.join(dir, config.distDir)
134+
setGlobal('distDir', distDir)
135+
133136
const { target } = config
134137
const buildId: string = await nextBuildSpan
135138
.traceChild('generate-buildid')
136139
.traceAsyncFn(() => generateBuildId(config.generateBuildId, nanoid))
137-
const distDir = path.join(dir, config.distDir)
138140

139141
const customRoutes: CustomRoutes = await nextBuildSpan
140142
.traceChild('load-custom-routes')

‎packages/next/build/webpack-config.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,7 @@ export default async function getBaseWebpackConfig(
240240
entrypoints: WebpackEntrypoints
241241
rewrites: CustomRoutes['rewrites']
242242
isDevFallback?: boolean
243-
runWebpackSpan?: Span
243+
runWebpackSpan: Span
244244
}
245245
): Promise<webpack.Configuration> {
246246
const hasRewrites =

‎packages/next/build/webpack/loaders/next-serverless-loader/index.ts

+47-51
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import {
1111
ROUTES_MANIFEST,
1212
REACT_LOADABLE_MANIFEST,
1313
} from '../../../../shared/lib/constants'
14-
import { trace } from '../../../../telemetry/trace'
1514

1615
export type ServerlessLoaderQuery = {
1716
page: string
@@ -34,63 +33,61 @@ export type ServerlessLoaderQuery = {
3433
}
3534

3635
const nextServerlessLoader: webpack.loader.Loader = function () {
37-
const loaderSpan = trace('next-serverless-loader')
38-
return loaderSpan.traceFn(() => {
39-
const {
40-
distDir,
41-
absolutePagePath,
42-
page,
43-
buildId,
44-
canonicalBase,
45-
assetPrefix,
46-
absoluteAppPath,
47-
absoluteDocumentPath,
48-
absoluteErrorPath,
49-
absolute404Path,
50-
generateEtags,
51-
poweredByHeader,
52-
basePath,
53-
runtimeConfig,
54-
previewProps,
55-
loadedEnvFiles,
56-
i18n,
57-
}: ServerlessLoaderQuery =
58-
typeof this.query === 'string' ? parse(this.query.substr(1)) : this.query
59-
60-
const buildManifest = join(distDir, BUILD_MANIFEST).replace(/\\/g, '/')
61-
const reactLoadableManifest = join(
62-
distDir,
63-
REACT_LOADABLE_MANIFEST
64-
).replace(/\\/g, '/')
65-
const routesManifest = join(distDir, ROUTES_MANIFEST).replace(/\\/g, '/')
66-
67-
const escapedBuildId = escapeRegexp(buildId)
68-
const pageIsDynamicRoute = isDynamicRoute(page)
69-
70-
const encodedPreviewProps = devalue(
71-
JSON.parse(previewProps) as __ApiPreviewProps
72-
)
73-
74-
const envLoading = `
36+
const {
37+
distDir,
38+
absolutePagePath,
39+
page,
40+
buildId,
41+
canonicalBase,
42+
assetPrefix,
43+
absoluteAppPath,
44+
absoluteDocumentPath,
45+
absoluteErrorPath,
46+
absolute404Path,
47+
generateEtags,
48+
poweredByHeader,
49+
basePath,
50+
runtimeConfig,
51+
previewProps,
52+
loadedEnvFiles,
53+
i18n,
54+
}: ServerlessLoaderQuery =
55+
typeof this.query === 'string' ? parse(this.query.substr(1)) : this.query
56+
57+
const buildManifest = join(distDir, BUILD_MANIFEST).replace(/\\/g, '/')
58+
const reactLoadableManifest = join(distDir, REACT_LOADABLE_MANIFEST).replace(
59+
/\\/g,
60+
'/'
61+
)
62+
const routesManifest = join(distDir, ROUTES_MANIFEST).replace(/\\/g, '/')
63+
64+
const escapedBuildId = escapeRegexp(buildId)
65+
const pageIsDynamicRoute = isDynamicRoute(page)
66+
67+
const encodedPreviewProps = devalue(
68+
JSON.parse(previewProps) as __ApiPreviewProps
69+
)
70+
71+
const envLoading = `
7572
const { processEnv } = require('@next/env')
7673
processEnv(${Buffer.from(loadedEnvFiles, 'base64').toString()})
7774
`
7875

79-
const runtimeConfigImports = runtimeConfig
80-
? `
76+
const runtimeConfigImports = runtimeConfig
77+
? `
8178
const { setConfig } = require('next/config')
8279
`
83-
: ''
80+
: ''
8481

85-
const runtimeConfigSetter = runtimeConfig
86-
? `
82+
const runtimeConfigSetter = runtimeConfig
83+
? `
8784
const runtimeConfig = ${runtimeConfig}
8885
setConfig(runtimeConfig)
8986
`
90-
: 'const runtimeConfig = {}'
87+
: 'const runtimeConfig = {}'
9188

92-
if (page.match(API_ROUTE)) {
93-
return `
89+
if (page.match(API_ROUTE)) {
90+
return `
9491
${envLoading}
9592
${runtimeConfigImports}
9693
${
@@ -125,8 +122,8 @@ const nextServerlessLoader: webpack.loader.Loader = function () {
125122
})
126123
export default apiHandler
127124
`
128-
} else {
129-
return `
125+
} else {
126+
return `
130127
import 'next/dist/server/node-polyfill-fetch'
131128
import routesManifest from '${routesManifest}'
132129
import buildManifest from '${buildManifest}'
@@ -206,8 +203,7 @@ const nextServerlessLoader: webpack.loader.Loader = function () {
206203
})
207204
export { renderReqToHTML, render }
208205
`
209-
}
210-
})
206+
}
211207
}
212208

213209
export default nextServerlessLoader

‎packages/next/build/webpack/loaders/next-swc-loader.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ async function loaderTransform(parentTrace, source, inputSourceMap) {
123123
}
124124

125125
export default function swcLoader(inputSource, inputSourceMap) {
126-
const loaderSpan = trace('next-swc-loader', this.currentTraceSpan?.id)
126+
const loaderSpan = this.currentTraceSpan.traceChild('next-swc-loader')
127127
const callback = this.async()
128128
loaderSpan
129129
.traceAsyncFn(() =>

‎packages/next/build/webpack/plugins/build-stats-plugin.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ export default class BuildStatsPlugin {
118118
async (stats, callback) => {
119119
const compilerSpan = spans.get(compiler)
120120
try {
121-
const writeStatsSpan = trace('NextJsBuildStats', compilerSpan?.id)
121+
const writeStatsSpan = compilerSpan!.traceChild('NextJsBuildStats')
122122
await writeStatsSpan.traceAsyncFn(() => {
123123
return new Promise((resolve, reject) => {
124124
const statsJson = reduceSize(

‎packages/next/build/webpack/plugins/css-minimizer-plugin.ts

+3-4
Original file line numberDiff line numberDiff line change
@@ -71,9 +71,8 @@ export class CssMinimizerPlugin {
7171
},
7272
async (assets: any) => {
7373
const compilerSpan = spans.get(compiler)
74-
const cssMinimizerSpan = trace(
75-
'css-minimizer-plugin',
76-
compilerSpan?.id
74+
const cssMinimizerSpan = compilerSpan!.traceChild(
75+
'css-minimizer-plugin'
7776
)
7877
cssMinimizerSpan.setAttribute('webpackVersion', 5)
7978

@@ -83,7 +82,7 @@ export class CssMinimizerPlugin {
8382
files
8483
.filter((file) => CSS_REGEX.test(file))
8584
.map(async (file) => {
86-
const assetSpan = trace('minify-css', cssMinimizerSpan.id)
85+
const assetSpan = cssMinimizerSpan.traceChild('minify-css')
8786
assetSpan.setAttribute('file', file)
8887

8988
return assetSpan.traceAsyncFn(async () => {

‎packages/next/build/webpack/plugins/profiling-plugin.ts

+17-8
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,9 @@ function getNormalModuleLoaderHook(compilation: any) {
1515

1616
export class ProfilingPlugin {
1717
compiler: any
18-
runWebpackSpan: Span | undefined
18+
runWebpackSpan: Span
1919

20-
constructor({ runWebpackSpan }: { runWebpackSpan: Span | undefined }) {
20+
constructor({ runWebpackSpan }: { runWebpackSpan: Span }) {
2121
this.runWebpackSpan = runWebpackSpan
2222
}
2323
apply(compiler: any) {
@@ -35,14 +35,17 @@ export class ProfilingPlugin {
3535
attrs,
3636
onSetSpan,
3737
}: {
38-
parentSpan?: () => Span | undefined
38+
parentSpan?: () => Span
3939
attrs?: any
4040
onSetSpan?: (span: Span) => void
4141
} = {}
4242
) {
4343
let span: Span | undefined
4444
startHook.tap(pluginName, () => {
45-
span = trace(spanName, parentSpan?.()?.id, attrs ? attrs() : attrs)
45+
span = parentSpan
46+
? parentSpan().traceChild(spanName, attrs ? attrs() : attrs)
47+
: trace(spanName, undefined, attrs ? attrs() : attrs)
48+
4649
onSetSpan?.(span)
4750
})
4851
stopHook.tap(pluginName, () => {
@@ -103,10 +106,16 @@ export class ProfilingPlugin {
103106

104107
const issuerModule = compilation?.moduleGraph?.getIssuer(module)
105108

106-
const span = trace(
107-
`build-module${moduleType ? `-${moduleType}` : ''}`,
108-
issuerModule ? spans.get(issuerModule)?.id : compilerSpan.id
109-
)
109+
let span: Span
110+
111+
const spanName = `build-module${moduleType ? `-${moduleType}` : ''}`
112+
const issuerSpan: Span | undefined =
113+
issuerModule && spans.get(issuerModule)
114+
if (issuerSpan) {
115+
span = issuerSpan.traceChild(spanName)
116+
} else {
117+
span = compilerSpan.traceChild(spanName)
118+
}
110119
span.setAttribute('name', module.userRequest)
111120
spans.set(module, span)
112121
})

‎packages/next/server/dev/hot-reloader.ts

+6
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import { difference } from '../../build/utils'
2727
import { NextConfigComplete } from '../config-shared'
2828
import { CustomRoutes } from '../../lib/load-custom-routes'
2929
import { DecodeError } from '../../shared/lib/utils'
30+
import { Span, trace } from '../../telemetry/trace'
3031

3132
export async function renderScriptError(
3233
res: ServerResponse,
@@ -143,6 +144,7 @@ export default class HotReloader {
143144
private watcher: any
144145
private rewrites: CustomRoutes['rewrites']
145146
private fallbackWatcher: any
147+
private hotReloaderSpan: Span
146148
public isWebpack5: any
147149

148150
constructor(
@@ -174,6 +176,7 @@ export default class HotReloader {
174176
this.previewProps = previewProps
175177
this.rewrites = rewrites
176178
this.isWebpack5 = isWebpack5
179+
this.hotReloaderSpan = trace('hot-reloader')
177180
}
178181

179182
public async run(
@@ -283,6 +286,7 @@ export default class HotReloader {
283286
pagesDir: this.pagesDir,
284287
rewrites: this.rewrites,
285288
entrypoints: entrypoints.client,
289+
runWebpackSpan: this.hotReloaderSpan,
286290
}),
287291
getBaseWebpackConfig(this.dir, {
288292
dev: true,
@@ -292,6 +296,7 @@ export default class HotReloader {
292296
pagesDir: this.pagesDir,
293297
rewrites: this.rewrites,
294298
entrypoints: entrypoints.server,
299+
runWebpackSpan: this.hotReloaderSpan,
295300
}),
296301
])
297302
}
@@ -300,6 +305,7 @@ export default class HotReloader {
300305
if (this.fallbackWatcher) return
301306

302307
const fallbackConfig = await getBaseWebpackConfig(this.dir, {
308+
runWebpackSpan: this.hotReloaderSpan,
303309
dev: true,
304310
isServer: false,
305311
config: this.config,

‎packages/next/telemetry/trace/report/index.ts

+34-5
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import reportToConsole from './to-console'
33
import reportToZipkin from './to-zipkin'
44
import reportToJaeger from './to-jaeger'
55
import reportToTelemetry from './to-telemetry'
6+
import reportToJson from './to-json'
67

78
type Reporter = {
89
flushAll: () => Promise<void> | void
@@ -16,6 +17,31 @@ type Reporter = {
1617
) => void
1718
}
1819

20+
class MultiReporter implements Reporter {
21+
private reporters: Reporter[] = []
22+
23+
constructor(reporters: Reporter[]) {
24+
this.reporters = reporters
25+
}
26+
27+
async flushAll() {
28+
await Promise.all(this.reporters.map((reporter) => reporter.flushAll()))
29+
}
30+
31+
report(
32+
spanName: string,
33+
duration: number,
34+
timestamp: number,
35+
id: SpanId,
36+
parentId?: SpanId,
37+
attrs?: Object
38+
) {
39+
this.reporters.forEach((reporter) =>
40+
reporter.report(spanName, duration, timestamp, id, parentId, attrs)
41+
)
42+
}
43+
}
44+
1945
const target =
2046
process.env.TRACE_TARGET && process.env.TRACE_TARGET in TARGET
2147
? TARGET[process.env.TRACE_TARGET as TARGET]
@@ -27,14 +53,17 @@ if (process.env.TRACE_TARGET && !target) {
2753
)
2854
}
2955

30-
export let reporter: Reporter
56+
let traceTargetReporter: Reporter
3157

3258
if (target === TARGET.CONSOLE) {
33-
reporter = reportToConsole
59+
traceTargetReporter = reportToConsole
3460
} else if (target === TARGET.ZIPKIN) {
35-
reporter = reportToZipkin
61+
traceTargetReporter = reportToZipkin
3662
} else if (target === TARGET.JAEGER) {
37-
reporter = reportToJaeger
63+
traceTargetReporter = reportToJaeger
3864
} else {
39-
reporter = reportToTelemetry
65+
traceTargetReporter = reportToTelemetry
4066
}
67+
68+
// JSON is always reported to allow for diagnostics
69+
export const reporter = new MultiReporter([reportToJson, traceTargetReporter])

‎packages/next/telemetry/trace/report/to-jaeger.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import * as Log from '../../../build/output/log'
55
// Jaeger uses Zipkin's reporting
66
import { batcher } from './to-zipkin'
77

8-
let traceId = process.env.TRACE_ID
8+
let traceId: string
99
let batch: ReturnType<typeof batcher> | undefined
1010

1111
const localEndpoint = {
@@ -33,7 +33,7 @@ const reportToLocalHost = (
3333
attrs?: Object
3434
) => {
3535
if (!traceId) {
36-
traceId = process.env.TRACE_ID = randomBytes(8).toString('hex')
36+
traceId = process.env.TRACE_ID || randomBytes(8).toString('hex')
3737
logWebUrl()
3838
}
3939

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import { randomBytes } from 'crypto'
2+
import { batcher } from './to-zipkin'
3+
import { traceGlobals } from '../shared'
4+
import fs from 'fs'
5+
import path from 'path'
6+
7+
let writeStream: fs.WriteStream
8+
let traceId: string
9+
let batch: ReturnType<typeof batcher> | undefined
10+
11+
const reportToLocalHost = (
12+
name: string,
13+
duration: number,
14+
timestamp: number,
15+
id: string,
16+
parentId?: string,
17+
attrs?: Object
18+
) => {
19+
const distDir = traceGlobals.get('distDir')
20+
if (!distDir) {
21+
return
22+
}
23+
24+
if (!traceId) {
25+
traceId = process.env.TRACE_ID || randomBytes(8).toString('hex')
26+
}
27+
28+
if (!batch) {
29+
batch = batcher(async (events) => {
30+
if (!writeStream) {
31+
const tracesDir = path.join(distDir, 'traces')
32+
await fs.promises.mkdir(tracesDir, { recursive: true })
33+
const file = path.join(distDir, 'trace')
34+
writeStream = fs.createWriteStream(file, {
35+
flags: 'a',
36+
encoding: 'utf8',
37+
})
38+
}
39+
const eventsJson = JSON.stringify(events)
40+
try {
41+
await new Promise<void>((resolve, reject) => {
42+
writeStream.write(eventsJson + '\n', 'utf8', (err) => {
43+
err ? reject(err) : resolve()
44+
})
45+
})
46+
} catch (err) {
47+
console.log(err)
48+
}
49+
})
50+
}
51+
52+
batch.report({
53+
traceId,
54+
parentId,
55+
name,
56+
id,
57+
timestamp,
58+
duration,
59+
tags: attrs,
60+
})
61+
}
62+
63+
export default {
64+
flushAll: () =>
65+
batch
66+
? batch.flushAll().then(() => {
67+
writeStream.end('', 'utf8')
68+
})
69+
: undefined,
70+
report: reportToLocalHost,
71+
}

‎packages/next/telemetry/trace/report/to-zipkin.ts

+5-4
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { randomBytes } from 'crypto'
33
import fetch from 'node-fetch'
44
import * as Log from '../../../build/output/log'
55

6-
let traceId = process.env.TRACE_ID
6+
let traceId: string
77
let batch: ReturnType<typeof batcher> | undefined
88

99
const localEndpoint = {
@@ -21,7 +21,7 @@ type Event = {
2121
id: string
2222
timestamp: number
2323
duration: number
24-
localEndpoint: typeof localEndpoint
24+
localEndpoint?: typeof localEndpoint
2525
tags?: Object
2626
}
2727

@@ -42,8 +42,9 @@ export function batcher(reportEvents: (evts: Event[]) => Promise<void>) {
4242
events.push(event)
4343

4444
if (events.length > 100) {
45-
const report = reportEvents(events.slice())
45+
const evts = events.slice()
4646
events.length = 0
47+
const report = reportEvents(evts)
4748
queue.add(report)
4849
report.then(() => queue.delete(report))
4950
}
@@ -60,7 +61,7 @@ const reportToLocalHost = (
6061
attrs?: Object
6162
) => {
6263
if (!traceId) {
63-
traceId = process.env.TRACE_ID = randomBytes(8).toString('hex')
64+
traceId = process.env.TRACE_ID || randomBytes(8).toString('hex')
6465
Log.info(
6566
`Zipkin trace will be available on ${zipkinUrl}/zipkin/traces/${traceId}`
6667
)

‎scripts/send-trace-to-jaeger.mjs

+65
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import fs from 'fs'
2+
import eventStream from 'event-stream'
3+
import retry from 'async-retry'
4+
import fetch from 'node-fetch'
5+
6+
const file = fs.createReadStream(process.argv[2])
7+
8+
const localEndpoint = {
9+
serviceName: 'nextjs',
10+
ipv4: '127.0.0.1',
11+
port: 9411,
12+
}
13+
14+
// Jaeger supports Zipkin's reporting API
15+
const zipkinUrl = `http://${localEndpoint.ipv4}:${localEndpoint.port}`
16+
const jaegerWebUiUrl = `http://${localEndpoint.ipv4}:16686`
17+
const zipkinAPI = `${zipkinUrl}/api/v2/spans`
18+
19+
let loggedUrl = false
20+
21+
function logWebUrl(traceId) {
22+
console.log(
23+
`Jaeger trace will be available on ${jaegerWebUiUrl}/trace/${traceId}`
24+
)
25+
}
26+
file.pipe(eventStream.split()).pipe(
27+
eventStream.map((data, cb) => {
28+
if (data === '') {
29+
return cb(null, '')
30+
}
31+
32+
const eventsJson = JSON.parse(data).map((item) => {
33+
item.localEndpoint = localEndpoint
34+
return item
35+
})
36+
if (!loggedUrl) {
37+
logWebUrl(eventsJson[0].traceId)
38+
loggedUrl = true
39+
}
40+
retry(
41+
() =>
42+
// Send events to zipkin
43+
fetch(zipkinAPI, {
44+
method: 'POST',
45+
headers: { 'Content-Type': 'application/json' },
46+
body: JSON.stringify(eventsJson),
47+
}),
48+
{ minTimeout: 500, retries: 3, factor: 1 }
49+
)
50+
.then(async (res) => {
51+
if (res.status !== 202) {
52+
console.log({
53+
status: res.status,
54+
body: await res.text(),
55+
events: eventsJson,
56+
})
57+
}
58+
cb(null, '')
59+
})
60+
.catch((err) => {
61+
console.log(err)
62+
cb(null, '')
63+
})
64+
})
65+
)

0 commit comments

Comments
 (0)
Please sign in to comment.