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

Open Telemetry integration #1284

Merged
merged 6 commits into from Aug 1, 2023
Merged

Open Telemetry integration #1284

merged 6 commits into from Aug 1, 2023

Conversation

marcopiraccini
Copy link
Contributor

@marcopiraccini marcopiraccini commented Jul 20, 2023

This for the open telemetry integration
It's a WIP, missing:

  • W3C tracer
  • Add Baggage (actually removed, probably not necessary)
  • Integration with service
  • Integration with db
  • Integration with client [OpenAPI]
  • Integration with client [GraphQL]
  • Integration with composer
  • Integration with runtime
  • Reference Docs
  • Guide

Copy link
Member

@mcollina mcollina left a 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.

@@ -0,0 +1,79 @@
const fp = require('fastify-plugin')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const fp = require('fastify-plugin')
'use strict'
const fp = require('fastify-plugin')

@@ -0,0 +1,79 @@
const fp = require('fastify-plugin')
const { registerInstrumentations } = require('@opentelemetry/instrumentation')
Copy link
Member

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

@@ -0,0 +1,79 @@
const fp = require('fastify-plugin')
const { registerInstrumentations } = require('@opentelemetry/instrumentation')
const { HttpInstrumentation } = require('@opentelemetry/instrumentation-http')
Copy link
Member

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

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')
Copy link
Member

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

return {
get activeSpan () {
return request.span
}
Copy link
Member

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?

}

// expose the API as a request decorator
app.decorateRequest('openTelemetry', openTelemetry)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
app.decorateRequest('openTelemetry', openTelemetry)
app.decorateRequest('openTelemetry', openTelemetry)
app.decorateRequest('span') // Tells V8 to optimize this property

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 }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return { tracer, exporter: exporterObj }
return { tracer, exporter: exporterObj, provider }

}

async function setupTelemetry (app, opts) {
const { tracer, exporter } = setupProvider(app, opts)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const { tracer, exporter } = setupProvider(app, opts)
const { tracer, exporter, provider } = setupProvider(app, opts)

async function setupTelemetry (app, opts) {
const { tracer, exporter } = setupProvider(app, opts)
// We decorate this to inspect it in tests
app.decorate('openTelemetryExporter', exporter)
Copy link
Member

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

const { tracer, exporter } = setupProvider(app, opts)
// We decorate this to inspect it in tests
app.decorate('openTelemetryExporter', exporter)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
app.addHook('onClose', async function () {
await provider.shutdown()
})

packages/telemetry/package.json Outdated Show resolved Hide resolved
additionalProperties: false
}

module.exports = AuthSchema
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
module.exports = AuthSchema
module.exports = TelemetrySchema

@@ -0,0 +1,45 @@
const { test } = require('tap')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const { test } = require('tap')
'use strict'
const { test } = require('tap')

packages/telemetry/test/telemetry.test.js Show resolved Hide resolved
// 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)
Copy link
Member

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

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@marcopiraccini marcopiraccini force-pushed the opentel-integration branch 4 times, most recently from b621fd0 to 6a3dd58 Compare July 24, 2023 16:04
Copy link
Member

@mcollina mcollina left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good work!

@@ -0,0 +1,7 @@
const telemetry = require('./telemetry')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const telemetry = require('./telemetry')
'use strict'
const telemetry = require('./telemetry')

"@opentelemetry/resources": "^1.15.0",
"@opentelemetry/sdk-trace-base": "^1.15.0",
"@opentelemetry/semantic-conventions": "^1.15.0",
"fastify": "^4.20.0",
Copy link
Member

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

@mcollina
Copy link
Member

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.

@mcollina mcollina dismissed their stale review July 25, 2023 08:40

code is now ok!

@@ -28,6 +28,7 @@
"typescript": "^5.1.3"
},
"dependencies": {
"@platformatic/telemetry": "workspace:*",
Copy link
Member

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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ptal

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: {} }
Copy link
Member

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

app.addHook('onRequest', async (req, reply) => {
const newClient = Object.create(client)
if (getHeaders) {
newClient[kGetHeaders] = getHeaders.bind(newClient, req, reply)
}
req[name] = newClient
})
, and then pass it down.

Copy link
Contributor Author

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

packages/client/index.js Outdated Show resolved Hide resolved
@marcopiraccini marcopiraccini changed the title open telemetry integration [WIP] Open Telemetry integration [WIP] Jul 27, 2023
@marcopiraccini marcopiraccini marked this pull request as ready for review July 27, 2023 08:49
@marcopiraccini marcopiraccini changed the title Open Telemetry integration [WIP] Open Telemetry integration Jul 27, 2023
@mcollina
Copy link
Member

code LGTM but there is a conflict and docs are missing.

Signed-off-by: marcopiraccini <marco.piraccini@gmail.com>
Signed-off-by: marcopiraccini <marco.piraccini@gmail.com>
Signed-off-by: marcopiraccini <marco.piraccini@gmail.com>
Signed-off-by: marcopiraccini <marco.piraccini@gmail.com>
Copy link
Member

@mcollina mcollina left a 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>
Copy link
Member

@mcollina mcollina left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm

Signed-off-by: marcopiraccini <marco.piraccini@gmail.com>
@mcollina mcollina merged commit 350774d into main Aug 1, 2023
78 checks passed
@mcollina mcollina deleted the opentel-integration branch August 1, 2023 07:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants