0
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2025-01-20 22:42:53 -05:00

Added Sentry instrumentation for get helpers (#19576)

no issue

- To help debug ABORTED_GET_HELPER errors, this PR adds Sentry
instrumentation to the get helpers
- It also adds the homepage, any pages/posts, the tag page, and the
author page to the list of transactions that will send to Sentry
This commit is contained in:
Chris Raible 2024-01-24 18:50:48 -08:00 committed by GitHub
parent 428be0f9ef
commit 794ef85968
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 51 additions and 27 deletions

View file

@ -7,6 +7,7 @@ const {hbs} = require('../services/handlebars');
const logging = require('@tryghost/logging'); const logging = require('@tryghost/logging');
const errors = require('@tryghost/errors'); const errors = require('@tryghost/errors');
const tpl = require('@tryghost/tpl'); const tpl = require('@tryghost/tpl');
const Sentry = require('@sentry/node');
const _ = require('lodash'); const _ = require('lodash');
const jsonpath = require('jsonpath'); const jsonpath = require('jsonpath');
@ -206,9 +207,21 @@ module.exports = async function get(resource, options) {
// Parse the options we're going to pass to the API // Parse the options we're going to pass to the API
apiOptions = parseOptions(ghostGlobals, this, apiOptions); apiOptions = parseOptions(ghostGlobals, this, apiOptions);
let apiOptionsString = Object.entries(apiOptions)
.map(([key, value]) => ` ${key}="${value}"`)
.join('');
apiOptions.context = {member: data.member}; apiOptions.context = {member: data.member};
try { try {
const spanName = `{{#get "${resource}"${apiOptionsString}}} ${data.member ? 'member' : 'public'}`;
const result = await Sentry.startSpan({
op: 'frontend.helpers.get',
name: spanName,
tags: {
resource,
...apiOptions,
context: data.member ? 'member' : 'public'
}
}, async (span) => {
const response = await makeAPICall(resource, controllerName, action, apiOptions); const response = await makeAPICall(resource, controllerName, action, apiOptions);
// prepare data properties for use with handlebars // prepare data properties for use with handlebars
@ -218,6 +231,7 @@ module.exports = async function get(resource, options) {
// used for logging details of slow requests // used for logging details of slow requests
returnedRowsCount = response[resource] && response[resource].length; returnedRowsCount = response[resource] && response[resource].length;
span?.setTag('returnedRows', returnedRowsCount);
// block params allows the theme developer to name the data using something like // block params allows the theme developer to name the data using something like
// `{{#get "posts" as |result pageInfo|}}` // `{{#get "posts" as |result pageInfo|}}`
@ -232,6 +246,8 @@ module.exports = async function get(resource, options) {
data: data, data: data,
blockParams: blockParams blockParams: blockParams
}); });
});
return result;
} catch (error) { } catch (error) {
logging.error(error); logging.error(error);
data.error = error.message; data.error = error.message;

View file

@ -58,11 +58,15 @@ const beforeSend = function (event, hint) {
}; };
const ALLOWED_HTTP_TRANSACTIONS = [ const ALLOWED_HTTP_TRANSACTIONS = [
'/ghost/api', '/ghost/api', // any Ghost API call
'/members/api' '/members/api', // any Members API call
'/:slug', // any frontend post/page
'/author', // any frontend author page
'/tag' // any frontend tag page
].map((path) => { ].map((path) => {
// Sentry names HTTP transactions like: "<HTTP_METHOD> <PATH>" i.e. "GET /ghost/api/content/settings" // Sentry names HTTP transactions like: "<HTTP_METHOD> <PATH>" i.e. "GET /ghost/api/content/settings"
return new RegExp(`^(GET|POST|PUT|DELETE)\\s(?<path>${path}\\/.+)`); // Match any of the paths above with any HTTP method, and also the homepage "GET /"
return new RegExp(`^(GET|POST|PUT|DELETE)\\s(?<path>${path}\\/.+|\\/$)`);
}); });
const beforeSendTransaction = function (event) { const beforeSendTransaction = function (event) {

View file

@ -156,7 +156,7 @@ describe('UNIT: sentry', function () {
}); });
}); });
describe('beforeTransaction', function () { describe('beforeSendTransaction', function () {
it('filters transactions based on an allow list', function () { it('filters transactions based on an allow list', function () {
sentry = require('../../../core/shared/sentry'); sentry = require('../../../core/shared/sentry');
@ -166,7 +166,11 @@ describe('UNIT: sentry', function () {
{transaction: 'GET /ghost/api/settings'}, {transaction: 'GET /ghost/api/settings'},
{transaction: 'PUT /members/api/member'}, {transaction: 'PUT /members/api/member'},
{transaction: 'POST /ghost/api/tiers'}, {transaction: 'POST /ghost/api/tiers'},
{transaction: 'DELETE /members/api/member'} {transaction: 'DELETE /members/api/member'},
{transaction: 'GET /'},
{transaction: 'GET /:slug/options(edit)?/'},
{transaction: 'GET /author/:slug'},
{transaction: 'GET /tag/:slug'}
]; ];
allowedTransactions.forEach((transaction) => { allowedTransactions.forEach((transaction) => {