-
Notifications
You must be signed in to change notification settings - Fork 6
DO_NOT_MERGE: add instrumentation #1051
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
Draft
gmaclennan
wants to merge
3
commits into
main
Choose a base branch
from
chore/instrumentation
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,117 @@ | ||
| import { context as _context, trace } from '@opentelemetry/api' | ||
|
|
||
| /** @import { Context, Span, TracerProvider } from '@opentelemetry/api' */ | ||
| /** @import { ExtendedSpanOptions, SpanCallback, TracingHelper } from './types.js' */ | ||
|
|
||
| // https://www.w3.org/TR/trace-context/#examples-of-http-traceparent-headers | ||
| // If traceparent ends with -00 this trace will not be sampled | ||
| // the query engine needs the `10` for the span and trace id otherwise it does not parse this | ||
| const nonSampledTraceParent = `00-10-10-00` | ||
|
|
||
| /** | ||
| * @implements {TracingHelper} | ||
| */ | ||
| export class ActiveTracingHelper { | ||
| #tracerProvider | ||
|
|
||
| /** | ||
| * @param {object} opts | ||
| * @param {TracerProvider} opts.tracerProvider | ||
| */ | ||
| constructor({ tracerProvider }) { | ||
| this.#tracerProvider = tracerProvider | ||
| } | ||
|
|
||
| /** | ||
| * @returns {boolean} | ||
| */ | ||
| isEnabled() { | ||
| return true | ||
| } | ||
|
|
||
| /** | ||
| * @param {Context} [context] | ||
| * @returns {string} | ||
| */ | ||
| getTraceParent(context) { | ||
| const span = trace.getSpanContext(context ?? _context.active()) | ||
| if (span) { | ||
| return `00-${span.traceId}-${span.spanId}-0${span.traceFlags}` | ||
| } | ||
| return nonSampledTraceParent | ||
| } | ||
|
|
||
| /** | ||
| * @returns {Context | undefined} | ||
| */ | ||
| getActiveContext() { | ||
| return _context.active() | ||
| } | ||
|
|
||
| /** | ||
| * @template R | ||
| * @param {string | ExtendedSpanOptions} options | ||
| * @param {SpanCallback<R>} callback | ||
| * @returns {R} | ||
| */ | ||
| runInChildSpan(options, callback) { | ||
| if (typeof options === 'string') { | ||
| options = { name: options } | ||
| } | ||
|
|
||
| const tracer = this.#tracerProvider.getTracer('comapeo') | ||
| const context = options.context ?? this.getActiveContext() | ||
| const name = `comapeo:core:${options.name}` | ||
|
|
||
| // these spans will not be nested by default even in recursive calls | ||
| // it's useful for showing middleware sequentially instead of nested | ||
| if (options.active === false) { | ||
| const span = tracer.startSpan(name, options, context) | ||
| return endSpan(span, callback(span, context)) | ||
| } | ||
|
|
||
| // by default spans are "active", which means context is propagated in | ||
| // nested calls, which is useful for representing most of the calls | ||
| return tracer.startActiveSpan(name, options, (span) => | ||
| endSpan(span, callback(span, context)) | ||
| ) | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * @template T | ||
| * @param {Span} span | ||
| * @param {T} result | ||
| * @returns {T} | ||
| */ | ||
| function endSpan(span, result) { | ||
| if (isPromiseLike(result)) { | ||
| return /** @type {T} */ ( | ||
| result.then( | ||
| (value) => { | ||
| span.end() | ||
| return value | ||
| }, | ||
| (reason) => { | ||
| span.end() | ||
| throw reason | ||
| } | ||
| ) | ||
| ) | ||
| } | ||
| span.end() | ||
| return result | ||
| } | ||
|
|
||
| /** | ||
| * @param {unknown} obj | ||
| * @returns {obj is PromiseLike<unknown>} | ||
| */ | ||
| function isPromiseLike(obj) { | ||
| return ( | ||
| !!obj && | ||
| (typeof obj === 'object' || typeof obj === 'function') && | ||
| 'then' in obj && | ||
| typeof obj.then === 'function' | ||
| ) | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,73 @@ | ||
| import { trace } from '@opentelemetry/api' | ||
| import { | ||
| InstrumentationBase, | ||
| InstrumentationNodeModuleDefinition, | ||
| } from '@opentelemetry/instrumentation' | ||
|
|
||
| import { ActiveTracingHelper } from './ActiveTracingHelper.js' | ||
| import { | ||
| GLOBAL_INSTRUMENTATION_ACCESSOR_KEY, | ||
| MODULE_NAME, | ||
| NAME, | ||
| VERSION, | ||
| } from './constants.js' | ||
|
|
||
| /** @import {TracerProvider} from '@opentelemetry/api' */ | ||
| /** @import {InstrumentationConfig} from '@opentelemetry/instrumentation' */ | ||
| /** @import {ComapeoCoreInstrumentationGlobalValue} from './types.js' */ | ||
|
|
||
| export class ComapeoCoreInstrumentation extends InstrumentationBase { | ||
| /** | ||
| * @type {TracerProvider | undefined} | ||
| * @private | ||
| */ | ||
| tracerProvider | ||
|
|
||
| /** | ||
| * @param {InstrumentationConfig} [config={}] | ||
| */ | ||
| constructor(config = {}) { | ||
| super(NAME, VERSION, config) | ||
| } | ||
|
|
||
| /** | ||
| * @param {TracerProvider} tracerProvider | ||
| * @returns {void} | ||
| */ | ||
| setTracerProvider(tracerProvider) { | ||
| this.tracerProvider = tracerProvider | ||
| } | ||
|
|
||
| /** | ||
| * @returns {InstrumentationNodeModuleDefinition[]} | ||
| */ | ||
| init() { | ||
| const module = new InstrumentationNodeModuleDefinition(MODULE_NAME, [ | ||
| VERSION, | ||
| ]) | ||
|
|
||
| return [module] | ||
| } | ||
|
|
||
| enable() { | ||
| /** @type {ComapeoCoreInstrumentationGlobalValue} */ | ||
| const globalValue = { | ||
| helper: new ActiveTracingHelper({ | ||
| tracerProvider: this.tracerProvider ?? trace.getTracerProvider(), | ||
| }), | ||
| } | ||
|
|
||
| global[GLOBAL_INSTRUMENTATION_ACCESSOR_KEY] = globalValue | ||
| } | ||
|
|
||
| disable() { | ||
| delete global[GLOBAL_INSTRUMENTATION_ACCESSOR_KEY] | ||
| } | ||
|
|
||
| /** | ||
| * @returns {boolean} | ||
| */ | ||
| isEnabled() { | ||
| return Boolean(global[GLOBAL_INSTRUMENTATION_ACCESSOR_KEY]) | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,60 @@ | ||
| /** @import { TracingHelper } from './types.js' */ | ||
|
|
||
| /** @type {TracingHelper} */ | ||
| export const disabledTracingHelper = { | ||
| isEnabled() { | ||
| return false | ||
| }, | ||
| getTraceParent() { | ||
| // https://www.w3.org/TR/trace-context/#examples-of-http-traceparent-headers | ||
| // If traceparent ends with -00 this trace will not be sampled | ||
| // the query engine needs the `10` for the span and trace id otherwise it does not parse this | ||
| return `00-10-10-00` | ||
| }, | ||
|
|
||
| getActiveContext() { | ||
| return undefined | ||
| }, | ||
|
|
||
| runInChildSpan(_, callback) { | ||
| return callback() | ||
| }, | ||
| } | ||
|
|
||
| /** | ||
| * Tracing helper that can dynamically switch between enabled/disabled states | ||
| * Needed because tracing can be disabled and enabled with the calls to | ||
| * PrismaInstrumentation::disable/enable at any point | ||
| * @implements {TracingHelper} | ||
| */ | ||
| class DynamicTracingHelper { | ||
| isEnabled() { | ||
| return this.#getGlobalTracingHelper().isEnabled() | ||
| } | ||
| /** @type {TracingHelper['getTraceParent']} */ | ||
| getTraceParent(context) { | ||
| return this.#getGlobalTracingHelper().getTraceParent(context) | ||
| } | ||
|
|
||
| getActiveContext() { | ||
| return this.#getGlobalTracingHelper().getActiveContext() | ||
| } | ||
|
|
||
| /** @type {TracingHelper['runInChildSpan']} */ | ||
| runInChildSpan(options, callback) { | ||
| return this.#getGlobalTracingHelper().runInChildSpan(options, callback) | ||
| } | ||
|
|
||
| /** @returns {TracingHelper} */ | ||
| #getGlobalTracingHelper() { | ||
| const fallbackPrismaInstrumentationGlobal = | ||
| globalThis.COMAPEO_CORE_INSTRUMENTATION | ||
|
|
||
| return fallbackPrismaInstrumentationGlobal?.helper ?? disabledTracingHelper | ||
| } | ||
| } | ||
|
|
||
| /** @returns {TracingHelper} */ | ||
| export function getTracingHelper() { | ||
| return new DynamicTracingHelper() | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| export const VERSION = '1.0.0' | ||
|
|
||
| export const GLOBAL_INSTRUMENTATION_ACCESSOR_KEY = | ||
| 'COMAPEO_CORE_INSTRUMENTATION' | ||
|
|
||
| export const NAME = '@comapeo/core-instrumentation' | ||
|
|
||
| export const MODULE_NAME = '@comapeo/core' |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,52 @@ | ||
| import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-proto' | ||
| import { NodeSDK } from '@opentelemetry/sdk-node' | ||
| import { ComapeoCoreInstrumentation } from './ComapeoCoreInstrumentation.js' | ||
|
|
||
| // Initialize the NodeSDK | ||
| const sdk = new NodeSDK({ | ||
| serviceName: 'comapeo-core-service-test', | ||
| traceExporter: new OTLPTraceExporter(), | ||
| instrumentations: [new ComapeoCoreInstrumentation()], | ||
| resourceDetectors: [], | ||
| }) | ||
|
|
||
| // Start the SDK | ||
| sdk.start() | ||
|
|
||
| import { KeyManager } from '@mapeo/crypto' | ||
| import { MapeoManager } from '../mapeo-manager.js' | ||
| import RAM from 'random-access-memory' | ||
| import Fastify from 'fastify' | ||
|
|
||
| // Ensures batched spans are flushed before exit | ||
| process.on('beforeExit', async () => { | ||
| try { | ||
| await sdk.shutdown() | ||
| console.log('Tracing shut down successfully') | ||
| } catch (err) { | ||
| console.error('Error shutting down tracing', err) | ||
| } finally { | ||
| process.exit(0) | ||
| } | ||
| }) | ||
|
|
||
| const projectMigrationsFolder = new URL( | ||
| '../../drizzle/project', | ||
| import.meta.url | ||
| ).pathname | ||
| const clientMigrationsFolder = new URL('../../drizzle/client', import.meta.url) | ||
| .pathname | ||
|
|
||
| const manager = new MapeoManager({ | ||
| rootKey: KeyManager.generateRootKey(), | ||
| projectMigrationsFolder, | ||
| clientMigrationsFolder, | ||
| dbFolder: ':memory:', | ||
| coreStorage: () => new RAM(), | ||
| fastify: Fastify(), | ||
| }) | ||
|
|
||
| await manager.createProject({ | ||
| name: 'project', | ||
| projectDescription: 'test project', | ||
| }) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| import * as Sentry from '@sentry/node' | ||
| import { ComapeoCoreInstrumentation } from './ComapeoCoreInstrumentation.js' | ||
|
|
||
| Sentry.init({ | ||
| dsn: 'https://fb7efc7b3426ff0de896fcf90c86cf47@o4507148235702272.ingest.us.sentry.io/4509510714785792', | ||
| debug: true, | ||
| tracesSampleRate: 1.0, // Adjust this value to control the sampling rate | ||
| openTelemetryInstrumentations: [new ComapeoCoreInstrumentation()], | ||
| }) | ||
|
|
||
| import { KeyManager } from '@mapeo/crypto' | ||
| import { MapeoManager } from '../mapeo-manager.js' | ||
| import RAM from 'random-access-memory' | ||
| import Fastify from 'fastify' | ||
|
|
||
| process.on('beforeExit', async () => { | ||
| try { | ||
| await Sentry.flush(2000) | ||
| console.log('Tracing shut down successfully') | ||
| } catch (err) { | ||
| console.error('Error shutting down tracing', err) | ||
| } finally { | ||
| process.exit(0) | ||
| } | ||
| }) | ||
|
|
||
| const projectMigrationsFolder = new URL( | ||
| '../../drizzle/project', | ||
| import.meta.url | ||
| ).pathname | ||
| const clientMigrationsFolder = new URL('../../drizzle/client', import.meta.url) | ||
| .pathname | ||
|
|
||
| const manager = new MapeoManager({ | ||
| rootKey: KeyManager.generateRootKey(), | ||
| projectMigrationsFolder, | ||
| clientMigrationsFolder, | ||
| dbFolder: ':memory:', | ||
| coreStorage: () => new RAM(), | ||
| fastify: Fastify(), | ||
| }) | ||
|
|
||
| await manager.createProject({ | ||
| name: 'project', | ||
| projectDescription: 'test project', | ||
| }) |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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 prefer
globalThistoglobalsince it's the standard and is more future proof