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
Open Telemetry integration #1284
Conversation
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.
Unfortunately this uses the async_hooks based context manager, which slows things down quite drastically (15-20%), which is really not something we should be encouraging.
We should use the "manual" module that those use and just plug into Fastify hooks ourselves.
packages/telemetry/index.js
Outdated
@@ -0,0 +1,79 @@ | |||
const fp = require('fastify-plugin') |
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.
const fp = require('fastify-plugin') | |
'use strict' | |
const fp = require('fastify-plugin') |
packages/telemetry/index.js
Outdated
@@ -0,0 +1,79 @@ | |||
const fp = require('fastify-plugin') | |||
const { registerInstrumentations } = require('@opentelemetry/instrumentation') |
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.
don't use this module
packages/telemetry/index.js
Outdated
@@ -0,0 +1,79 @@ | |||
const fp = require('fastify-plugin') | |||
const { registerInstrumentations } = require('@opentelemetry/instrumentation') | |||
const { HttpInstrumentation } = require('@opentelemetry/instrumentation-http') |
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.
don't use this module
packages/telemetry/index.js
Outdated
const { registerInstrumentations } = require('@opentelemetry/instrumentation') | ||
const { HttpInstrumentation } = require('@opentelemetry/instrumentation-http') | ||
const { ConsoleSpanExporter, BatchSpanProcessor, SimpleSpanProcessor, InMemorySpanExporter } = require('@opentelemetry/sdk-trace-base') | ||
const { NodeTracerProvider } = require('@opentelemetry/sdk-trace-node') |
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.
don't use this module
packages/telemetry/index.js
Outdated
return { | ||
get activeSpan () { | ||
return request.span | ||
} |
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.
why is this a getter instead of a property?
packages/telemetry/index.js
Outdated
} | ||
|
||
// expose the API as a request decorator | ||
app.decorateRequest('openTelemetry', openTelemetry) |
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.
app.decorateRequest('openTelemetry', openTelemetry) | |
app.decorateRequest('openTelemetry', openTelemetry) | |
app.decorateRequest('span') // Tells V8 to optimize this property |
packages/telemetry/index.js
Outdated
const spanProcessor = ['memory', 'console'].includes(exporter.type) ? new SimpleSpanProcessor(exporterObj) : new BatchSpanProcessor(exporterObj) | ||
provider.addSpanProcessor(spanProcessor) | ||
const tracer = provider.getTracer(moduleName, moduleVersion) | ||
return { tracer, exporter: exporterObj } |
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.
return { tracer, exporter: exporterObj } | |
return { tracer, exporter: exporterObj, provider } |
packages/telemetry/index.js
Outdated
} | ||
|
||
async function setupTelemetry (app, opts) { | ||
const { tracer, exporter } = setupProvider(app, opts) |
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.
const { tracer, exporter } = setupProvider(app, opts) | |
const { tracer, exporter, provider } = setupProvider(app, opts) |
packages/telemetry/index.js
Outdated
async function setupTelemetry (app, opts) { | ||
const { tracer, exporter } = setupProvider(app, opts) | ||
// We decorate this to inspect it in tests | ||
app.decorate('openTelemetryExporter', exporter) |
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.
I would include the full object returned by setupProvider
here
packages/telemetry/index.js
Outdated
const { tracer, exporter } = setupProvider(app, opts) | ||
// We decorate this to inspect it in tests | ||
app.decorate('openTelemetryExporter', exporter) | ||
|
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.
app.addHook('onClose', async function () { | |
await provider.shutdown() | |
}) |
packages/telemetry/schema.js
Outdated
additionalProperties: false | ||
} | ||
|
||
module.exports = AuthSchema |
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.
module.exports = AuthSchema | |
module.exports = TelemetrySchema |
@@ -0,0 +1,45 @@ | |||
const { test } = require('tap') |
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.
const { test } = require('tap') | |
'use strict' | |
const { test } = require('tap') |
packages/telemetry/index.js
Outdated
// We use a SimpleSpanProcessor for the console/memory exporters and a BatchSpanProcessor for the others. | ||
const spanProcessor = ['memory', 'console'].includes(exporter.type) ? new SimpleSpanProcessor(exporterObj) : new BatchSpanProcessor(exporterObj) | ||
provider.addSpanProcessor(spanProcessor) | ||
const tracer = provider.getTracer(moduleName, moduleVersion) |
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.
I'm not sure we want to use moduleName
moduleVersion
here
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.
This is by spec: https://opentelemetry.io/docs/specs/otel/trace/api/#get-a-tracer
b621fd0
to
6a3dd58
Compare
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.
good work!
packages/telemetry/index.js
Outdated
@@ -0,0 +1,7 @@ | |||
const telemetry = require('./telemetry') |
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.
const telemetry = require('./telemetry') | |
'use strict' | |
const telemetry = require('./telemetry') |
packages/telemetry/package.json
Outdated
"@opentelemetry/resources": "^1.15.0", | ||
"@opentelemetry/sdk-trace-base": "^1.15.0", | ||
"@opentelemetry/semantic-conventions": "^1.15.0", | ||
"fastify": "^4.20.0", |
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.
I don't think this is needed
Regarding the service and db integration, I think we should be overriding each resolver and adding a wrapper that starts and close a span while it's processing it. |
packages/client/package.json
Outdated
@@ -28,6 +28,7 @@ | |||
"typescript": "^5.1.3" | |||
}, | |||
"dependencies": { | |||
"@platformatic/telemetry": "workspace:*", |
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.
should be a devDep
equal(clientSpan.attributes['response.statusCode'], 200) | ||
|
||
// console.log('receivedHeaders', receivedHeaders) | ||
// TODO: Check headers |
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.
ptal
packages/client/index.js
Outdated
query, | ||
variables | ||
async function graphql (url, log, headers, query, variables, openTelemetry) { | ||
const { span, headers: telemetryHeaders } = openTelemetry ? openTelemetry.startSpanClient(url.toString(), { method: 'POST' }) : { span: null, headers: {} } |
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.
I wonder if this propagates the headers correctly because the context is empty.
I think you need to get the OpenTelemetry context in
platformatic/packages/client/index.js
Lines 229 to 235 in 234330b
app.addHook('onRequest', async (req, reply) => { | |
const newClient = Object.create(client) | |
if (getHeaders) { | |
newClient[kGetHeaders] = getHeaders.bind(newClient, req, reply) | |
} | |
req[name] = newClient | |
}) |
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.
You are right, we need in client to actually recreate the context from the headers, otherwise in the case of:
C -> A -> B
...we don't propagte the traceId
through A (a new one is created).
Fixing.
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.
51de1ba
to
b356a22
Compare
647f350
to
ae6560c
Compare
code LGTM but there is a conflict and docs are missing. |
1f125bb
to
993cc01
Compare
Signed-off-by: marcopiraccini <marco.piraccini@gmail.com>
993cc01
to
1851842
Compare
Signed-off-by: marcopiraccini <marco.piraccini@gmail.com>
778799a
to
ebdbf00
Compare
Signed-off-by: marcopiraccini <marco.piraccini@gmail.com>
Signed-off-by: marcopiraccini <marco.piraccini@gmail.com>
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.
Can you add a short guide on how to set up OpenTelemetry with Composer, Service and a Zipkin destination?
Signed-off-by: marcopiraccini <marco.piraccini@gmail.com>
a01b955
to
cc9b723
Compare
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.
lgtm
589706e
to
becf90a
Compare
Signed-off-by: marcopiraccini <marco.piraccini@gmail.com>
483a341
to
84825a7
Compare
This for the open telemetry integration
It's a WIP, missing:
Add Baggage (actually removed, probably not necessary)service
db
client
[OpenAPI]client
[GraphQL]composer
runtime