-
Notifications
You must be signed in to change notification settings - Fork 62
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
fix(tracing): actually set the tracing options in the root context #5118
Changes from all commits
cd44968
8bc9de3
ed6ff40
7e44b07
18b3d77
eb62165
62cab00
3fb3182
edf7b83
51da7ef
2103437
705b93a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
# Tracing in Netlify Build | ||
|
||
Netlify Build relies on Open Telemetry tracing to emit trace data: | ||
|
||
- https://opentelemetry.io/docs/instrumentation/js/ | ||
|
||
In production, trace data is exported to [Honeycomb](https://ui.honeycomb.io). Buildbot is responsible for passing over | ||
trace information which allows build executions to be stitched together into a single trace across Buildbot and | ||
`@netlify/build`. The initialisation for this tracing SDK is done | ||
[here](https://github.com/netlify/build/blob/main/packages/build/src/tracing/main.ts). We also use an open telemetry | ||
collector in production. | ||
|
||
## Adding more instrumentation | ||
|
||
More data can be added by either generating more spans or adding more attributes to relevant stages. Check the Open | ||
Telemetry docs for manual instrumentation: | ||
|
||
- https://opentelemetry.io/docs/instrumentation/js/manual/ | ||
|
||
We also have some utility methods you can leverage to do this: | ||
|
||
- https://github.com/netlify/build/blob/main/packages/build/src/tracing/main.ts | ||
|
||
## Exporting data locally | ||
|
||
You can export trace data when running `@netlify/build` locally, to do so you just need to leverage the `tracing` | ||
[flag properties](https://github.com/netlify/build/blob/main/packages/build/src/core/flags.js#L194) to point to | ||
Honeycomb directly. For example: | ||
|
||
``` | ||
node packages/build/bin.js --debug --tracing.enabled=true --tracing.apiKey=<honeycomb-tracing-api-key> --tracing.httpProtocol=https --tracing.host=api.honeycomb.io --tracing.port=443 ../my-site | ||
``` | ||
|
||
The tracing API Key should be an Honeycomb environment API key. If testing things locally you can use the `dev` | ||
environment. |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -11,7 +11,9 @@ const DEFAULT_EDGE_FUNCTIONS_DIST = '.netlify/edge-functions-dist/' | |
const DEFAULT_FUNCTIONS_DIST = '.netlify/functions/' | ||
const DEFAULT_CACHE_DIR = '.netlify/cache/' | ||
const DEFAULT_STATSD_PORT = 8125 | ||
|
||
const DEFAULT_OTEL_TRACING_PORT = 4317 | ||
const DEFAULT_OTEL_ENDPOINT_PROTOCOL = 'http' | ||
|
||
export type ResolvedFlags = { | ||
env: Record<string, unknown> | ||
|
@@ -95,7 +97,14 @@ const getDefaultFlags = function ({ env: envOpt = {} }, combinedEnv) { | |
testOpts: {}, | ||
featureFlags: DEFAULT_FEATURE_FLAGS, | ||
statsd: { port: DEFAULT_STATSD_PORT }, | ||
tracing: { enabled: false, port: DEFAULT_OTEL_TRACING_PORT }, | ||
// tracing.apiKey defaults to '-' else we'll get warning logs if not using | ||
// honeycomb directly - https://github.com/honeycombio/honeycomb-opentelemetry-node/issues/201 | ||
Comment on lines
+100
to
+101
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Honeycomb emits a set of |
||
tracing: { | ||
enabled: false, | ||
apiKey: '-', | ||
httpProtocol: DEFAULT_OTEL_ENDPOINT_PROTOCOL, | ||
port: DEFAULT_OTEL_TRACING_PORT, | ||
}, | ||
timeline: 'build', | ||
quiet: false, | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -67,16 +67,17 @@ export const runStep = async function ({ | |
// Add relevant attributes to the upcoming span context | ||
const attributes: StepExecutionAttributes = { | ||
'build.execution.step.name': coreStepName, | ||
'build.execution.step.description': coreStepDescription, | ||
'build.execution.step.package_name': packageName, | ||
'build.execution.step.id': coreStepId, | ||
'build.execution.step.loaded_from': loadedFrom, | ||
'build.execution.step.origin': origin, | ||
'build.execution.step.event': event, | ||
} | ||
const spanCtx = setMultiSpanAttributes(attributes) | ||
// If there's no `coreStepId` then this is a plugin execution | ||
const spanName = `run-step-${coreStepId || 'plugin'}` | ||
Comment on lines
+77
to
+78
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. do you mind giving more information about the plugin vs steps? For example in this comment, it mentions we have core steps, build commands or plugin. I understand the meaning of the steps we do in buildbot, but not in here. Maybe this is a question too broad, so we can catch up on "Making Moves" |
||
|
||
return tracer.startActiveSpan(`run-step-${coreStepId}`, {}, spanCtx, async (span) => { | ||
return tracer.startActiveSpan(spanName, {}, spanCtx, async (span) => { | ||
const constantsA = await addMutableConstants({ constants, buildDir, netlifyConfig }) | ||
|
||
const shouldRun = await shouldRunStep({ | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,5 @@ | ||
import { HoneycombSDK } from '@honeycombio/opentelemetry-node' | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The |
||
import { context, trace, propagation, SpanStatusCode, diag, DiagLogLevel, DiagLogger } from '@opentelemetry/api' | ||
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-grpc' | ||
import { HttpInstrumentation } from '@opentelemetry/instrumentation-http' | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Couldn't manage to setup auto instrumentation. Seems like it's now working for
I've been thinking a bit about this and I think in the future we might want to have this tracing initialisation happen in a separate module we can use via
|
||
import { NodeSDK } from '@opentelemetry/sdk-node' | ||
|
||
import type { TracingOptions } from '../core/types.js' | ||
|
@@ -10,7 +9,11 @@ let sdk: NodeSDK | |
|
||
/** Given a simple logging function return a `DiagLogger`. Used to setup our system logger as the diag logger.*/ | ||
const getOtelLogger = function (logger: (...args: any[]) => void): DiagLogger { | ||
const otelLogger = (...args: any[]) => logger('[otel-traces]', ...args) | ||
const otelLogger = (...args: any[]) => { | ||
// Debug log msgs can be an array of 1 or 2 elements with the second element being an array fo multiple elements | ||
const msgs = args.flat(1) | ||
logger('[otel-traces]', ...msgs) | ||
} | ||
return { | ||
debug: otelLogger, | ||
info: otelLogger, | ||
|
@@ -25,14 +28,11 @@ export const startTracing = function (options: TracingOptions, logger: (...args: | |
if (!options.enabled) return | ||
if (sdk) return | ||
|
||
const traceExporter = new OTLPTraceExporter({ | ||
url: `http://${options.host}:${options.port}`, | ||
}) | ||
|
||
sdk = new NodeSDK({ | ||
sdk = new HoneycombSDK({ | ||
serviceName: ROOT_PACKAGE_JSON.name, | ||
traceExporter, | ||
instrumentations: [new HttpInstrumentation()], | ||
protocol: 'grpc', | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Both in prod and locally we'll always use |
||
apiKey: options.apiKey, | ||
endpoint: `${options.httpProtocol}://${options.host}:${options.port}`, | ||
}) | ||
|
||
// Set the diagnostics logger to our system logger. We also need to suppress the override msg | ||
|
@@ -43,7 +43,7 @@ export const startTracing = function (options: TracingOptions, logger: (...args: | |
|
||
// 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 | ||
trace.setSpanContext(context.active(), { | ||
return trace.setSpanContext(context.active(), { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is the actual change that ensures the |
||
traceId: options.traceId, | ||
spanId: options.parentSpanId, | ||
traceFlags: options.traceFlags, | ||
|
@@ -96,7 +96,6 @@ export type RootExecutionAttributes = { | |
/** Attributes used for the execution of each build step */ | ||
export type StepExecutionAttributes = { | ||
'build.execution.step.name': string | ||
'build.execution.step.description': string | ||
'build.execution.step.package_name': string | ||
'build.execution.step.id': string | ||
'build.execution.step.loaded_from': string | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
would it be:rootTrace
? is "Context" a thing in the JS/TS world?scratch that, I just saw the
context
bit on the other fileThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Context in this case is specifc to the
open-telemetry
JS implementation. In this case we're returning a context from:build/packages/build/src/tracing/main.ts
Lines 44 to 51 in edf7b83
See:
Also:
This context will hold the specific
traceId
,spanId
, etc. That we pass over from Buildbot.