diff --git a/package-lock.json b/package-lock.json index e6400526d4..a04b62f8a1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26007,6 +26007,7 @@ "@netlify/run-utils": "^5.1.1", "@netlify/zip-it-and-ship-it": "9.25.3", "@opentelemetry/api": "^1.4.1", + "@opentelemetry/core": "^1.17.1", "@sindresorhus/slugify": "^2.0.0", "ansi-escapes": "^6.0.0", "chalk": "^5.0.0", @@ -26305,6 +26306,28 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "packages/build/node_modules/@opentelemetry/core": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-1.17.1.tgz", + "integrity": "sha512-I6LrZvl1FF97FQXPR0iieWQmKnGxYtMbWA1GrAXnLUR+B1Hn2m8KqQNEIlZAucyv00GBgpWkpllmULmZfG8P3g==", + "dependencies": { + "@opentelemetry/semantic-conventions": "1.17.1" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.7.0" + } + }, + "packages/build/node_modules/@opentelemetry/semantic-conventions": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.17.1.tgz", + "integrity": "sha512-xbR2U+2YjauIuo42qmE8XyJK6dYeRMLJuOlUP5SO4auET4VtOHOzgkRVOq+Ik18N+Xf3YPcqJs9dZMiDddz1eQ==", + "engines": { + "node": ">=14" + } + }, "packages/build/node_modules/chalk": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", diff --git a/packages/build/package.json b/packages/build/package.json index bf0dadc709..79b0793a71 100644 --- a/packages/build/package.json +++ b/packages/build/package.json @@ -76,6 +76,7 @@ "@netlify/run-utils": "^5.1.1", "@netlify/zip-it-and-ship-it": "9.25.3", "@opentelemetry/api": "^1.4.1", + "@opentelemetry/core": "^1.17.1", "@sindresorhus/slugify": "^2.0.0", "ansi-escapes": "^6.0.0", "chalk": "^5.0.0", diff --git a/packages/build/src/tracing/main.ts b/packages/build/src/tracing/main.ts index c15fad64c8..f90e1edf52 100644 --- a/packages/build/src/tracing/main.ts +++ b/packages/build/src/tracing/main.ts @@ -1,5 +1,8 @@ +import { readFile } from 'node:fs/promises' + import { HoneycombSDK } from '@honeycombio/opentelemetry-node' import { context, trace, propagation, SpanStatusCode, diag, DiagLogLevel, DiagLogger } from '@opentelemetry/api' +import { parseKeyPairsIntoRecord } from '@opentelemetry/core/build/src/baggage/utils.js' import { NodeSDK } from '@opentelemetry/sdk-node' import type { TracingOptions } from '../core/types.js' @@ -89,6 +92,20 @@ export const addErrorToActiveSpan = function (error: Error) { }) } +//** Loads the baggage attributes from a baggabe file which follows W3C Baggage specification */ +export const loadBaggageFromFile = async function (baggageFilePath: string) { + if (baggageFilePath.length == 0) return + let baggageString: string + try { + baggageString = await readFile(baggageFilePath, 'utf-8') + } catch (error) { + diag.error(error) + return + } + const parsedBaggage = parseKeyPairsIntoRecord(baggageString) + return setMultiSpanAttributes(parsedBaggage) +} + /** Attributes used for the root span of our execution */ export type RootExecutionAttributes = { 'build.id': string diff --git a/packages/build/tests/tracing/tests.js b/packages/build/tests/tracing/tests.js index 01d43fbd03..e12a82514b 100644 --- a/packages/build/tests/tracing/tests.js +++ b/packages/build/tests/tracing/tests.js @@ -1,8 +1,12 @@ +import { writeFile, rm } from 'fs/promises' + import { trace, TraceFlags } from '@opentelemetry/api' import { getBaggage } from '@opentelemetry/api/build/src/baggage/context-helpers.js' import test from 'ava' -import { setMultiSpanAttributes, startTracing, stopTracing } from '../../lib/tracing/main.js' +import { setMultiSpanAttributes, startTracing, stopTracing, loadBaggageFromFile } from '../../lib/tracing/main.js' + +const BAGGAGE_PATH = './baggage.dump' test('Tracing set multi span attributes', async (t) => { const ctx = setMultiSpanAttributes({ some: 'test', foo: 'bar' }) @@ -11,6 +15,67 @@ test('Tracing set multi span attributes', async (t) => { t.is(baggage.getEntry('foo').value, 'bar') }) +const testMatrixBaggageFile = [ + { + description: 'when baggageFilePath is blank', + input: { + baggageFilePath: '', + baggageFileContent: null, + }, + expects: { + somefield: undefined, + foo: undefined + } + }, + { + description: 'when baggageFilePath is set but file is empty', + input: { + baggageFilePath: BAGGAGE_PATH, + baggageFileContent: '', + }, + expects: { + 'somefield': undefined, + 'foo': undefined + } + }, + { + description: 'when baggageFilePath is set and has content', + input: { + baggageFilePath: BAGGAGE_PATH, + baggageFileContent: 'somefield=value,foo=bar', + }, + expects: { + somefield: { value: 'value' }, + foo: { value: 'bar' }, + } + } +] + +testMatrixBaggageFile.forEach((testCase) => { + test.serial(`Tracing baggage loading - ${testCase.description}`, async (t) => { + const { input, expects } = testCase + var ctx + try { + if (input.baggageFilePath.length > 0) { + await writeFile(input.baggageFilePath, input.baggageFileContent) + } + ctx = await loadBaggageFromFile(input.baggageFilePath) + } finally { + if (input.baggageFilePath.length > 0) await rm(input.baggageFilePath, { force: true }) + } + + const baggage = getBaggage(ctx) + + expects.values.forEach(([property, expected]) => { + if (expected == undefined) { + t.is(baggage.getEntry(property), expected) + } else { + t.is(baggage.getEntry(property).value, expected.value) + } + }) + }) +}) + const spanId = '6e0c63257de34c92' // The sampler is deterministic, meaning that for a given traceId it will always produce a `SAMPLED` or a `NONE` // consistently. More info in - https://opentelemetry.io/docs/specs/otel/trace/tracestate-probability-sampling/#consistent-probability-sampling