0
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2025-03-11 02:12:21 -05:00

Added custom Sentry integration for Knex.js (#19315)

no refs

Added custom Sentry integration for Knex.js to trace database queries in
Sentry
This commit is contained in:
Michael Barrett 2023-12-12 11:09:49 +00:00
parent f91d046f5e
commit bd6bfe13c0
No known key found for this signature in database
3 changed files with 82 additions and 3 deletions

View file

@ -483,7 +483,7 @@ async function bootGhost({backend = true, frontend = true, server = true} = {})
// Sentry must be initialized early, but requires config // Sentry must be initialized early, but requires config
debug('Begin: Load sentry'); debug('Begin: Load sentry');
require('./shared/sentry'); const sentry = require('./shared/sentry');
debug('End: Load sentry'); debug('End: Load sentry');
// Step 2 - Start server with minimal app in global maintenance mode // Step 2 - Start server with minimal app in global maintenance mode
@ -502,6 +502,9 @@ async function bootGhost({backend = true, frontend = true, server = true} = {})
debug('Begin: Get DB ready'); debug('Begin: Get DB ready');
await initDatabase({config}); await initDatabase({config});
bootLogger.log('database ready'); bootLogger.log('database ready');
sentry.initQueryTracing(
require('./server/data/db/connection')
);
debug('End: Get DB ready'); debug('End: Get DB ready');
// Step 4 - Load Ghost with all its services // Step 4 - Load Ghost with all its services

View file

@ -0,0 +1,67 @@
/**
* @typedef {import('knex').Knex.Client} KnexClient
*/
/**
* @typedef {import('@sentry/types').Integration} SentryIntegration
*/
/**
* Sentry Knex tracing integration
*
* @implements {SentryIntegration}
*/
class SentryKnexTracingIntegration {
static id = 'Knex';
name = SentryKnexTracingIntegration.id;
/** @type {KnexClient} */
#knex;
/** @type {Map} */
#spanCache = new Map();
/**
* @param {KnexClient} knex
*/
constructor(knex) {
this.#knex = knex;
}
/**
* @param {Function} addGlobalEventProcessor
* @param {Function} getCurrentHub
*/
setupOnce(addGlobalEventProcessor, getCurrentHub) {
this.#knex.on('query', (query) => {
const scope = getCurrentHub().getScope();
const parentSpan = scope?.getSpan();
const span = parentSpan?.startChild({
op: 'db.query',
description: query.sql
});
if (span) {
this.#spanCache.set(query.__knexQueryUid, span);
}
});
const handleQueryExecuted = (err, query) => {
const queryId = query.__knexQueryUid;
const span = this.#spanCache.get(queryId);
if (span) {
span.finish();
this.#spanCache.delete(queryId);
}
};
this.#knex.on('query-response', handleQueryExecuted);
this.#knex.on('query-error', handleQueryExecuted);
}
}
module.exports = SentryKnexTracingIntegration;

View file

@ -1,4 +1,5 @@
const config = require('./config'); const config = require('./config');
const SentryKnexTracingIntegration = require('./SentryKnexTracingIntegration');
const sentryConfig = config.get('sentry'); const sentryConfig = config.get('sentry');
const errors = require('@tryghost/errors'); const errors = require('@tryghost/errors');
@ -93,7 +94,14 @@ if (sentryConfig && !sentryConfig.disabled) {
}), }),
tracingHandler: Sentry.Handlers.tracingHandler(), tracingHandler: Sentry.Handlers.tracingHandler(),
captureException: Sentry.captureException, captureException: Sentry.captureException,
beforeSend: beforeSend beforeSend: beforeSend,
initQueryTracing: (knex) => {
if (sentryConfig.tracing?.enabled === true) {
const integration = new SentryKnexTracingIntegration(knex);
Sentry.addIntegration(integration);
}
}
}; };
} else { } else {
const expressNoop = function (req, res, next) { const expressNoop = function (req, res, next) {
@ -106,6 +114,7 @@ if (sentryConfig && !sentryConfig.disabled) {
requestHandler: expressNoop, requestHandler: expressNoop,
errorHandler: expressNoop, errorHandler: expressNoop,
tracingHandler: expressNoop, tracingHandler: expressNoop,
captureException: noop captureException: noop,
initQueryTracing: noop
}; };
} }