From 14520b03ffc275dd75008d7a936bf233e56be471 Mon Sep 17 00:00:00 2001 From: sergiosentry <109162568+serglom21@users.noreply.github.com> Date: Tue, 10 Sep 2024 17:14:24 -0400 Subject: [PATCH 1/4] replace metrics --- react/src/components/Checkout.jsx | 38 ++++++----- react/src/components/Products.jsx | 77 ++++++++++++++++------- react/src/utils/measureRequestDuration.js | 13 ++-- 3 files changed, 84 insertions(+), 44 deletions(-) diff --git a/react/src/components/Checkout.jsx b/react/src/components/Checkout.jsx index 2662a5b11..bafaf85e6 100644 --- a/react/src/components/Checkout.jsx +++ b/react/src/components/Checkout.jsx @@ -39,8 +39,8 @@ function Checkout({ backend, rageclick, cart }) { } const [form, setForm] = useState(initialFormValues); - async function checkout(cart) { - Sentry.metrics.increment('checkout.click'); + async function checkout(cart, checkout_span) { + checkout_span.setAttribute("checkout.click.span", 1); const stopMeasurement = measureRequestDuration('/checkout'); const response = await fetch(backend + '/checkout?v2=true', { method: 'POST', @@ -50,28 +50,32 @@ function Checkout({ backend, rageclick, cart }) { form: form, }), }) - .catch((err) => { - Sentry.metrics.increment('checkout.error', 1, { - tags: { status: 500 }, - }); - return { ok: false, status: 500 }; + .catch((err) => { + checkout_span.setAttributes({ + "checkout.error": 1, + "status": 500 }) - .then((res) => { - stopMeasurement(); - return res; - }); + return { ok: false, status: 500 }; + }) + .then((res) => { + stopMeasurement(); + return res; + }); if (!response.ok) { - Sentry.metrics.increment('checkout.error', 1, { - tags: { status: response.status }, - }); + checkout_span.setAttributes({ + "checkout.error": 1, + "status": response.status + }) + throw new Error( [response.status, response.statusText || ' Internal Server Error'].join( ' -' ) ); } - Sentry.metrics.increment('checkout.success'); - Sentry.metrics.distribution('checkout.order.total', cart.total); + checkout_span.setAttribute("checkout.success", 1) + checkout_span.setAttribute("checkout.order.total", cart.total); + return response; } function generateUrl(product_id) { @@ -108,7 +112,7 @@ function Checkout({ backend, rageclick, cart }) { setLoading(true); try { - await checkout(cart); + await checkout(cart, span); } catch (error) { Sentry.captureException(error); hadError = true; diff --git a/react/src/components/Products.jsx b/react/src/components/Products.jsx index 563882c03..5c617ea1b 100644 --- a/react/src/components/Products.jsx +++ b/react/src/components/Products.jsx @@ -40,6 +40,7 @@ function Products({ frontendSlowdown, backend }) { // intentionally supposed to be slow function renderProducts(data) { + console.log(Sentry.getActiveSpan()) try { // Trigger a Sentry 'Performance Issue' in the case of // a frontend slowdown @@ -79,28 +80,54 @@ function Products({ frontendSlowdown, backend }) { related to the async keyword + babel transform, hence why it probably got fixed with hooks (no transform on that class method anymore)" */ - useEffect(() => { - // getProducts handles error responses differently, depending on the browser used - function getProducts(frontendSlowdown) { - [('/api', '/connect', '/organization')].forEach((endpoint) => { - fetch(backend + endpoint, { - method: 'GET', - headers: { 'Content-Type': 'application/json' }, - }).catch((err) => { - // If there's an error, it won't stop the Products http request and page from loading - Sentry.captureException(err); - }); - }); - - // When triggering a frontend-only slowdown, use the products-join endpoint - // because it returns product data with a fast backend response. - // Otherwise use the /products endpoint, which provides a slow backend response. - const productsEndpoint = determineProductsEndpoint(); - const stopMeasurement = measureRequestDuration(productsEndpoint); - fetch(backend + productsEndpoint, { + function getProducts(frontendSlowdown) { + [('/api', '/connect', '/organization')].forEach((endpoint, activeSpan) => { + fetch(backend + endpoint, { method: 'GET', headers: { 'Content-Type': 'application/json' }, - }) + }).catch((err) => { + // If there's an error, it won't stop the Products http request and page from loading + Sentry.captureException(err); + }); + }); + const productsEndpoint = determineProductsEndpoint(); + const stopMeasurement = measureRequestDuration(productsEndpoint); + fetch(backend + productsEndpoint, { + method: 'GET', + headers: { 'Content-Type': 'application/json' }, + }) + .then((result) => { + if (!result.ok) { + Sentry.setContext('err', { + status: result.status, + statusText: result.statusText, + }); + return Promise.reject(); + } else { + return result.json(); + } + }) + .then(renderProducts) + .catch((err) => { + return { ok: false, status: 500 }; + }).then((res) => { + stopMeasurement() + return res + }); + + /* + + // When triggering a frontend-only slowdown, use the products-join endpoint + // because it returns product data with a fast backend response. + // Otherwise use the /products endpoint, which provides a slow backend response. + Sentry.withActiveSpan(null, () => { + Sentry.startSpan({ name: "Products" }, () => { + const productsEndpoint = determineProductsEndpoint(); + const stopMeasurement = measureRequestDuration(productsEndpoint); + fetch(backend + productsEndpoint, { + method: 'GET', + headers: { 'Content-Type': 'application/json' }, + }) .then((result) => { if (!result.ok) { Sentry.setContext('err', { @@ -119,9 +146,15 @@ function Products({ frontendSlowdown, backend }) { stopMeasurement() return res }); - } + }) + })*/ + + } - getProducts(frontendSlowdown); + useEffect(() => { + Sentry.startSpan({ name: "Fetch Products"}, () => { + getProducts(frontendSlowdown); + }) }, []); return products.length > 0 ? ( diff --git a/react/src/utils/measureRequestDuration.js b/react/src/utils/measureRequestDuration.js index ae17e21ab..a7d830cd2 100644 --- a/react/src/utils/measureRequestDuration.js +++ b/react/src/utils/measureRequestDuration.js @@ -6,16 +6,19 @@ import * as Sentry from '@sentry/react'; * @param {string} endpoint the endpoint that was called * @returns {() => void} a function to stop the measurement */ -export default function measureRequestDuration(endpoint) { +export default function measureRequestDuration(endpoint, requestSpan) { const start = Date.now(); function stopMeasurement() { const end = Date.now(); const duration = end - start; - Sentry.metrics.distribution('request.duration', duration, { - unit: 'millisecond', - tags: { endpoint } - }); + if (requestSpan !== undefined) { + requestSpan.setAttributes({ + "request.duration": duration, + "unit": "milisecond", + "endpoint": endpoint + }) + } } return stopMeasurement; From fb8bd05b0bc600e0636ae063d76d89d973aef910 Mon Sep 17 00:00:00 2001 From: Gideon Lapshun Date: Wed, 22 Jan 2025 12:08:21 -0500 Subject: [PATCH 2/4] Squashed update metrics with span attributes --- env-config/example.env | 17 ++++++- react/config-overrides.js | 25 ++++++++++ react/src/components/Cart.jsx | 7 +++ react/src/components/Checkout.jsx | 8 +++- react/src/components/Products.jsx | 79 ++++++++----------------------- react/src/utils/cart.js | 21 ++++++++ 6 files changed, 96 insertions(+), 61 deletions(-) create mode 100644 react/config-overrides.js create mode 100644 react/src/utils/cart.js diff --git a/env-config/example.env b/env-config/example.env index 287874489..b1f2fcd9b 100644 --- a/env-config/example.env +++ b/env-config/example.env @@ -1,5 +1,20 @@ SENTRY_ORG= +NEXT_PUBLIC_DSN= +NEXT_PUBLIC_ENVIRONMENT= +NEXT_RELEASE_PACKAGE_NAME= +NEXT_SENTRY_PROJECT= +NEXT_SOURCEMAPS_DIR=build/static/js +NEXT_SOURCEMAPS_URL_PREFIX=~/static/js +NEXT_PUBLIC_FLASK_BACKEND= +NEXT_PUBLIC_EXPRESS_BACKEND= +NEXT_PUBLIC_SPRINGBOOT_BACKEND= +NEXT_PUBLIC_ASPNETCORE_BACKEND= +NEXT_PUBLIC_LARAVEL_BACKEND= +NEXT_PUBLIC_RUBY_BACKEND= +NEXT_PUBLIC_RUBYONRAILS_BACKEND= +NEXT_LOCAL_PORT=3000 # only needed in local.env + REACT_APP_DSN= REACT_APP_ENGINE_SERVICE= REACT_APP_ENVIRONMENT= @@ -78,4 +93,4 @@ CRONSPYTHON_APP_DSN= CRONSPYTHON_MONITOR_SLUG= CRONSPYTHON_DEPLOY_HOST= CRONSPYTHON_DEPLOY_DIR= -CRONSPYTHON_CRONTAB_USER= # if change this must clear crontab for previus user or will have 2 running simult. \ No newline at end of file +CRONSPYTHON_CRONTAB_USER= # if change this must clear crontab for previus user or will have 2 running simult. diff --git a/react/config-overrides.js b/react/config-overrides.js new file mode 100644 index 000000000..272fa44e3 --- /dev/null +++ b/react/config-overrides.js @@ -0,0 +1,25 @@ +const SentryWebpackPlugin = require('@sentry/webpack-plugin'); +const reactsourceMapPlugin = require('@acemarke/react-prod-sourcemaps'); + +module.exports = function override(config, env) { + //do stuff with the webpack config... + config.plugins.push( + reactsourceMapPlugin.WebpackReactSourcemapsPlugin({ + mode: 'strict', + }) + ); + + config.plugins.push( + SentryWebpackPlugin.sentryWebpackPlugin({ + authToken: process.env.SENTRY_AUTH_TOKEN, + include: '.', + org: 'demo', + project: 'javascript-react', + ignoreFile: '.sentrycliignore', + ignore: ['webpack.config.js'], + configFile: 'sentry.properties', + reactComponentAnnotation: {enabled:true}, + }) + ); + return config; +}; diff --git a/react/src/components/Cart.jsx b/react/src/components/Cart.jsx index b3e37f49f..a84130d1f 100644 --- a/react/src/components/Cart.jsx +++ b/react/src/components/Cart.jsx @@ -4,8 +4,15 @@ import * as Sentry from '@sentry/react'; import Button from './ButtonLink'; import { connect } from 'react-redux'; import { setProducts, addProduct, removeProduct } from '../actions'; +import countItemsInCart from '../utils/cart'; function Cart({ cart, removeProduct, addProduct }) { + console.log("Cart component rendered with cart:", cart); + const itemsInCart = countItemsInCart(cart); + console.log("Items in cart calculated in Cart component:", itemsInCart); + const span = Sentry.startInactiveSpan({ name: "items_added_to_cart", op: "function"}); + span.setAttribute("items_in_cart", itemsInCart); + span.end(); return (

Cart

diff --git a/react/src/components/Checkout.jsx b/react/src/components/Checkout.jsx index bafaf85e6..473237cc3 100644 --- a/react/src/components/Checkout.jsx +++ b/react/src/components/Checkout.jsx @@ -5,6 +5,7 @@ import './checkout.css'; import * as Sentry from '@sentry/react'; import { connect } from 'react-redux'; import Loader from 'react-loader-spinner'; +import countItemsInCart from '../utils/cart'; function Checkout({ backend, rageclick, cart }) { const navigate = useNavigate(); @@ -40,7 +41,12 @@ function Checkout({ backend, rageclick, cart }) { const [form, setForm] = useState(initialFormValues); async function checkout(cart, checkout_span) { - checkout_span.setAttribute("checkout.click.span", 1); + console.log("Checkout called with cart:", cart); + console.log("Cart quantities:", cart.quantities); + const itemsInCart = countItemsInCart(cart); + console.log("Calculated itemsInCart:", itemsInCart); + checkout_span.setAttribute("checkout.click", 1); + checkout_span.setAttribute("items_at_checkout", itemsInCart); const stopMeasurement = measureRequestDuration('/checkout'); const response = await fetch(backend + '/checkout?v2=true', { method: 'POST', diff --git a/react/src/components/Products.jsx b/react/src/components/Products.jsx index 5c617ea1b..469de28f4 100644 --- a/react/src/components/Products.jsx +++ b/react/src/components/Products.jsx @@ -40,7 +40,6 @@ function Products({ frontendSlowdown, backend }) { // intentionally supposed to be slow function renderProducts(data) { - console.log(Sentry.getActiveSpan()) try { // Trigger a Sentry 'Performance Issue' in the case of // a frontend slowdown @@ -80,7 +79,7 @@ function Products({ frontendSlowdown, backend }) { related to the async keyword + babel transform, hence why it probably got fixed with hooks (no transform on that class method anymore)" */ - function getProducts(frontendSlowdown) { + async function getProducts(frontendSlowdown) { [('/api', '/connect', '/organization')].forEach((endpoint, activeSpan) => { fetch(backend + endpoint, { method: 'GET', @@ -91,70 +90,32 @@ function Products({ frontendSlowdown, backend }) { }); }); const productsEndpoint = determineProductsEndpoint(); - const stopMeasurement = measureRequestDuration(productsEndpoint); - fetch(backend + productsEndpoint, { - method: 'GET', - headers: { 'Content-Type': 'application/json' }, - }) - .then((result) => { - if (!result.ok) { + Sentry.startSpan({ name: "Fetch Products"}, async (span) => { + const stopMeasurement = measureRequestDuration(productsEndpoint, span); + const response = await fetch(backend + productsEndpoint, { + method: 'GET', + headers: { 'Content-Type': 'application/json' }, + }); + const data = await response.json(); + + if (!response.ok) { Sentry.setContext('err', { - status: result.status, - statusText: result.statusText, + status: response.status, + statusText: response.statusText, }); - return Promise.reject(); - } else { - return result.json(); + return; } + renderProducts(data); + stopMeasurement(); }) - .then(renderProducts) - .catch((err) => { - return { ok: false, status: 500 }; - }).then((res) => { - stopMeasurement() - return res - }); - - /* - - // When triggering a frontend-only slowdown, use the products-join endpoint - // because it returns product data with a fast backend response. - // Otherwise use the /products endpoint, which provides a slow backend response. - Sentry.withActiveSpan(null, () => { - Sentry.startSpan({ name: "Products" }, () => { - const productsEndpoint = determineProductsEndpoint(); - const stopMeasurement = measureRequestDuration(productsEndpoint); - fetch(backend + productsEndpoint, { - method: 'GET', - headers: { 'Content-Type': 'application/json' }, - }) - .then((result) => { - if (!result.ok) { - Sentry.setContext('err', { - status: result.status, - statusText: result.statusText, - }); - return Promise.reject(); - } else { - return result.json(); - } - }) - .then(renderProducts) - .catch((err) => { - return { ok: false, status: 500 }; - }).then((res) => { - stopMeasurement() - return res - }); - }) - })*/ - } useEffect(() => { - Sentry.startSpan({ name: "Fetch Products"}, () => { - getProducts(frontendSlowdown); - }) + try { + getProducts(frontendSlowdown) + } catch (error) { + Sentry.captureException(error) + } }, []); return products.length > 0 ? ( diff --git a/react/src/utils/cart.js b/react/src/utils/cart.js new file mode 100644 index 000000000..ab58703fd --- /dev/null +++ b/react/src/utils/cart.js @@ -0,0 +1,21 @@ +export default function countItemsInCart(cart) { + console.log("countItemsInCart called with:", cart); + let totalItems = 0; + + if (!cart || !cart.quantities) { + console.log("Cart or cart.quantities is undefined!"); + return totalItems; + } + + console.log("Cart quantities object:", cart.quantities); + console.log("Object.values(cart.quantities):", Object.values(cart.quantities)); + + totalItems = Object.values(cart.quantities) + .reduce((sum, quantity) => { + console.log("Adding quantity:", quantity); + return sum + quantity; + }, 0); + + console.log("Final total:", totalItems); + return totalItems; +} \ No newline at end of file From 6cfc0e8cf360e638c57f70d4e26dd467151e135b Mon Sep 17 00:00:00 2001 From: Gideon Lapshun Date: Tue, 28 Jan 2025 21:53:43 -0500 Subject: [PATCH 3/4] fix export countItems and clean up logs --- react/src/components/Checkout.jsx | 19 ++++++++++++++++++- react/src/utils/cart.js | 7 +------ 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/react/src/components/Checkout.jsx b/react/src/components/Checkout.jsx index e77c84e55..8eee4647c 100644 --- a/react/src/components/Checkout.jsx +++ b/react/src/components/Checkout.jsx @@ -44,8 +44,25 @@ function Checkout({ backend, rageclick, checkout_success, cart }) { const [form, setForm] = useState(initialFormValues); async function checkout(cart, checkout_span) { - + console.log("Checkout called with cart:", cart); + console.log("Checkout span:", checkout_span); const itemsInCart = countItemsInCart(cart); + console.log("Calculated itemsInCart:", itemsInCart); + + if (!checkout_span || typeof checkout_span.setAttribute !== 'function') { + console.error("Invalid checkout_span object:", checkout_span); + return; + } + + checkout_span.setAttribute("checkout.click", 1); + checkout_span.setAttribute("items_at_checkout", itemsInCart); + + // Verify attributes were set + console.log("Span attributes after setting:", { + click: checkout_span.getAttribute("checkout.click"), + items: checkout_span.getAttribute("items_at_checkout") + }); + let tags = { 'backendType': getTag('backendType'), 'cexp': getTag('cexp'), 'items_at_checkout': itemsInCart, 'checkout.click': 1 }; checkout_span.setAttributes(tags); const stopMeasurement = measureRequestDuration('/checkout'); diff --git a/react/src/utils/cart.js b/react/src/utils/cart.js index ab58703fd..887748c13 100644 --- a/react/src/utils/cart.js +++ b/react/src/utils/cart.js @@ -1,5 +1,4 @@ -export default function countItemsInCart(cart) { - console.log("countItemsInCart called with:", cart); +export function countItemsInCart(cart) { let totalItems = 0; if (!cart || !cart.quantities) { @@ -7,15 +6,11 @@ export default function countItemsInCart(cart) { return totalItems; } - console.log("Cart quantities object:", cart.quantities); - console.log("Object.values(cart.quantities):", Object.values(cart.quantities)); - totalItems = Object.values(cart.quantities) .reduce((sum, quantity) => { console.log("Adding quantity:", quantity); return sum + quantity; }, 0); - console.log("Final total:", totalItems); return totalItems; } \ No newline at end of file From 1d62ab63e1ccdbc921efa65fea59a429d769479d Mon Sep 17 00:00:00 2001 From: Gideon Lapshun Date: Tue, 28 Jan 2025 21:55:17 -0500 Subject: [PATCH 4/4] remove config-overrides.js --- .gitignore | 1 + react/config-overrides.js | 25 ------------------------- 2 files changed, 1 insertion(+), 25 deletions(-) delete mode 100644 react/config-overrides.js diff --git a/.gitignore b/.gitignore index 39d0ceb6b..02c1f28a9 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ express/node_modules /.pnp .pnp.js .vercel +react/config-overrides.js # testing /coverage diff --git a/react/config-overrides.js b/react/config-overrides.js deleted file mode 100644 index 272fa44e3..000000000 --- a/react/config-overrides.js +++ /dev/null @@ -1,25 +0,0 @@ -const SentryWebpackPlugin = require('@sentry/webpack-plugin'); -const reactsourceMapPlugin = require('@acemarke/react-prod-sourcemaps'); - -module.exports = function override(config, env) { - //do stuff with the webpack config... - config.plugins.push( - reactsourceMapPlugin.WebpackReactSourcemapsPlugin({ - mode: 'strict', - }) - ); - - config.plugins.push( - SentryWebpackPlugin.sentryWebpackPlugin({ - authToken: process.env.SENTRY_AUTH_TOKEN, - include: '.', - org: 'demo', - project: 'javascript-react', - ignoreFile: '.sentrycliignore', - ignore: ['webpack.config.js'], - configFile: 'sentry.properties', - reactComponentAnnotation: {enabled:true}, - }) - ); - return config; -};