Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add support for loading a tracing baggage dump in WC3 Baggage format #5343

Merged
merged 8 commits into from
Oct 26, 2023
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions packages/build/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
5 changes: 5 additions & 0 deletions packages/build/src/core/flags.js
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,11 @@ Default: false`,
describe: 'Trace flags containing the trace settings for the given trace ID',
hidden: true,
},
'tracing.baggageFilePath': {
string: true,
describe: '',
hidden: true,
},
offline: {
boolean: true,
describe: `Do not send requests to the Netlify API to retrieve site settings.
Expand Down
1 change: 1 addition & 0 deletions packages/build/src/core/normalize_flags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ const getDefaultFlags = function ({ env: envOpt = {} }, combinedEnv) {
sampleRate: 1,
httpProtocol: DEFAULT_OTEL_ENDPOINT_PROTOCOL,
port: DEFAULT_OTEL_TRACING_PORT,
baggageFilePath: '',
},
timeline: 'build',
quiet: false,
Expand Down
1 change: 1 addition & 0 deletions packages/build/src/core/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,4 +86,5 @@ export type TracingOptions = {
traceId: string
traceFlags: number
parentSpanId: string
baggageFilePath: string
}
35 changes: 31 additions & 4 deletions packages/build/src/tracing/main.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { readFileSync } from 'node:fs'

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'
Expand Down Expand Up @@ -44,14 +47,19 @@ export const startTracing = function (options: TracingOptions, logger: (...args:

sdk.start()

// Loads the contents of the passed baggageFilePath into the baggage
const baggageCtx = loadBaggageFromFile(options.baggageFilePath)

// Sets the current trace ID and span ID based on the options received
// this is used as a way to propagate trace context from Buildbot
return trace.setSpanContext(context.active(), {
const ctx = trace.setSpanContext(baggageCtx, {
traceId: options.traceId,
spanId: options.parentSpanId,
traceFlags: options.traceFlags,
isRemote: true,
})

return ctx
}

/** Stops the tracing service if there's one running. This will flush any ongoing events */
Expand All @@ -67,13 +75,15 @@ export const stopTracing = async function () {
}
}

/** Sets attributes to be propagated across child spans under the current context */
/** Sets attributes to be propagated across child spans under the current active context */
export const setMultiSpanAttributes = function (attributes: { [key: string]: string }) {
const currentBaggage = propagation.getBaggage(context.active())
// Create a baggage if there's none
let baggage = currentBaggage === undefined ? propagation.createBaggage() : currentBaggage
Object.entries(attributes).forEach((entry) => {
baggage = baggage.setEntry(entry[0], { value: entry[1] })
Object.entries(attributes).forEach(([key, value]) => {
baggage = baggage.setEntry(key, { value })
})

return propagation.setBaggage(context.active(), baggage)
}

Expand All @@ -89,6 +99,23 @@ export const addErrorToActiveSpan = function (error: Error) {
})
}

//** Loads the baggage attributes from a baggabe file which follows W3C Baggage specification */
export const loadBaggageFromFile = function (baggageFilePath: string) {
if (baggageFilePath.length === 0) {
diag.warn('Empty baggage file path provided, no context loaded')
return context.active()
}
let baggageString: string
try {
baggageString = readFileSync(baggageFilePath, 'utf-8')
} catch (error) {
diag.error(error)
return context.active()
}
const parsedBaggage = parseKeyPairsIntoRecord(baggageString)
return setMultiSpanAttributes(parsedBaggage)
}

/** Attributes used for the root span of our execution */
export type RootExecutionAttributes = {
'build.id': string
Expand Down
75 changes: 73 additions & 2 deletions packages/build/tests/tracing/tests.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
import { trace, TraceFlags } from '@opentelemetry/api'
import { writeFile, mkdir, rm } from 'fs/promises'
import { fileURLToPath } from 'url'

import { trace, TraceFlags, propagation } 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 FIXTURES_DIR = fileURLToPath(new URL('fixtures', import.meta.url))
4xposed marked this conversation as resolved.
Show resolved Hide resolved
const BAGGAGE_PATH = `${FIXTURES_DIR}/baggage.dump`

test('Tracing set multi span attributes', async (t) => {
const ctx = setMultiSpanAttributes({ some: 'test', foo: 'bar' })
Expand All @@ -11,6 +17,70 @@ 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
if (input.baggageFilePath.length > 0) {
await mkdir(FIXTURES_DIR, { recursive: true })
await writeFile(input.baggageFilePath, input.baggageFileContent)
}

const ctx = loadBaggageFromFile(input.baggageFilePath)
const baggage = propagation.getBaggage(ctx)

Object.entries(expects).forEach(([property, expected]) => {
if (input.baggageFilePath === '') {
t.is(baggage, undefined)
} else {
4xposed marked this conversation as resolved.
Show resolved Hide resolved
if (expected === undefined) {
t.is(baggage.getEntry(property), expected)
} else {
t.is(baggage.getEntry(property).value, expected.value)
}
}
})
if (input.baggageFilePath.length > 0) {
rm(input.baggageFilePath, { force: true })
}
4xposed marked this conversation as resolved.
Show resolved Hide resolved
})
})

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
Expand Down Expand Up @@ -78,6 +148,7 @@ testMatrix.forEach((testCase) => {
traceId: input.traceId,
traceFlags: input.traceFlags,
parentSpanId: spanId,
baggageFilePath: '',
},
noopLogger,
)
Expand Down