diff --git a/ghost/core/core/frontend/helpers/get.js b/ghost/core/core/frontend/helpers/get.js index e682f3bd3a..2f01045448 100644 --- a/ghost/core/core/frontend/helpers/get.js +++ b/ghost/core/core/frontend/helpers/get.js @@ -7,6 +7,7 @@ const {hbs} = require('../services/handlebars'); const logging = require('@tryghost/logging'); const errors = require('@tryghost/errors'); const tpl = require('@tryghost/tpl'); +const Sentry = require('@sentry/node'); const _ = require('lodash'); const jsonpath = require('jsonpath'); @@ -206,32 +207,47 @@ module.exports = async function get(resource, options) { // Parse the options we're going to pass to the API apiOptions = parseOptions(ghostGlobals, this, apiOptions); + let apiOptionsString = Object.entries(apiOptions) + .map(([key, value]) => ` ${key}="${value}"`) + .join(''); apiOptions.context = {member: data.member}; - try { - const response = await makeAPICall(resource, controllerName, action, apiOptions); + 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); - // prepare data properties for use with handlebars - if (response[resource] && response[resource].length) { - response[resource].forEach(prepareContextResource); - } - - // used for logging details of slow requests - returnedRowsCount = response[resource] && response[resource].length; - - // block params allows the theme developer to name the data using something like - // `{{#get "posts" as |result pageInfo|}}` - const blockParams = [response[resource]]; - if (response.meta && response.meta.pagination) { - response.pagination = response.meta.pagination; - blockParams.push(response.meta.pagination); - } - - // Call the main template function - return options.fn(response, { - data: data, - blockParams: blockParams + // prepare data properties for use with handlebars + if (response[resource] && response[resource].length) { + response[resource].forEach(prepareContextResource); + } + + // used for logging details of slow requests + returnedRowsCount = response[resource] && response[resource].length; + span?.setTag('returnedRows', returnedRowsCount); + + // block params allows the theme developer to name the data using something like + // `{{#get "posts" as |result pageInfo|}}` + const blockParams = [response[resource]]; + if (response.meta && response.meta.pagination) { + response.pagination = response.meta.pagination; + blockParams.push(response.meta.pagination); + } + + // Call the main template function + return options.fn(response, { + data: data, + blockParams: blockParams + }); }); + return result; } catch (error) { logging.error(error); data.error = error.message; diff --git a/ghost/core/core/shared/sentry.js b/ghost/core/core/shared/sentry.js index 24f58228e6..6a54a66c24 100644 --- a/ghost/core/core/shared/sentry.js +++ b/ghost/core/core/shared/sentry.js @@ -58,11 +58,15 @@ const beforeSend = function (event, hint) { }; const ALLOWED_HTTP_TRANSACTIONS = [ - '/ghost/api', - '/members/api' + '/ghost/api', // any Ghost API call + '/members/api', // any Members API call + '/:slug', // any frontend post/page + '/author', // any frontend author page + '/tag' // any frontend tag page ].map((path) => { // Sentry names HTTP transactions like: " " i.e. "GET /ghost/api/content/settings" - return new RegExp(`^(GET|POST|PUT|DELETE)\\s(?${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}\\/.+|\\/$)`); }); const beforeSendTransaction = function (event) { diff --git a/ghost/core/test/unit/shared/sentry.test.js b/ghost/core/test/unit/shared/sentry.test.js index 84464d3fc7..8b69e6d774 100644 --- a/ghost/core/test/unit/shared/sentry.test.js +++ b/ghost/core/test/unit/shared/sentry.test.js @@ -156,7 +156,7 @@ describe('UNIT: sentry', function () { }); }); - describe('beforeTransaction', function () { + describe('beforeSendTransaction', function () { it('filters transactions based on an allow list', function () { sentry = require('../../../core/shared/sentry'); @@ -166,7 +166,11 @@ describe('UNIT: sentry', function () { {transaction: 'GET /ghost/api/settings'}, {transaction: 'PUT /members/api/member'}, {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) => {