Skip to content

Commit

Permalink
feat: Add support for loading a tracing baggage dump in WC3 Baggage f…
Browse files Browse the repository at this point in the history
…ormat (#5343)

* feat: add support for loading a tracing baggage dump in wc3 baggage format

* feat: expose flag to set baggageFilePath

* fix: set ctx after loadBaggageFile

* chore: move context around and change to readFileSync

* fix: update tests after context changes and sync file reading

* fix: update tests with PR review comments

* fix: fix variable shadowing

---------

Co-authored-by: JGAntunes <joao@netlify.com>
  • Loading branch information
4xposed and JGAntunes authored Oct 26, 2023
1 parent 9ef1a15 commit 3d64536
Show file tree
Hide file tree
Showing 7 changed files with 144 additions and 6 deletions.
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

0 comments on commit 3d64536

Please sign in to comment.