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 all 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.4",
"@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
84 changes: 82 additions & 2 deletions packages/build/tests/tracing/tests.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import { trace, TraceFlags } from '@opentelemetry/api'
import { writeFile, rm, mkdtemp } from 'fs/promises'
import { tmpdir } from 'os'
import { join } from 'path'

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'

test('Tracing set multi span attributes', async (t) => {
const ctx = setMultiSpanAttributes({ some: 'test', foo: 'bar' })
Expand All @@ -11,6 +15,81 @@ 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.dump',
baggageFileContent: '',
},
expects: {
somefield: undefined,
foo: undefined,
},
},
{
description: 'when baggageFilePath is set and has content',
input: {
baggageFilePath: 'baggage.dump',
baggageFileContent: 'somefield=value,foo=bar',
},
expects: {
somefield: { value: 'value' },
foo: { value: 'bar' },
},
},
]

let baggagePath
test.before(async () => {
baggagePath = await mkdtemp(join(tmpdir(), 'baggage-path-'))
})

test.after(async () => {
await rm(baggagePath, { recursive: true })
})

testMatrixBaggageFile.forEach((testCase) => {
test.serial(`Tracing baggage loading - ${testCase.description}`, async (t) => {
const { input, expects } = testCase

// We only want to write the file if it's a non-empty string '', while we still want to test scenario
let filePath = input.baggageFilePath
if (input.baggageFilePath.length > 0) {
filePath = `${baggagePath}/${input.baggageFilePath}`
await writeFile(filePath, input.baggageFileContent)
}

const ctx = loadBaggageFromFile(filePath)
const baggage = propagation.getBaggage(ctx)

// When there's no file we test that baggage is not set
if (input.baggageFilePath === '') {
t.is(baggage, undefined)
return
}

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