From 31f8d35effc28b5d4d9270b9bd84e4d36e3c569a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9E=84=EC=9A=B0=EC=B0=AC?= Date: Wed, 27 Dec 2023 14:32:39 +0900 Subject: [PATCH] =?UTF-8?q?=ED=94=84=EB=A1=9C=EB=8D=95=EC=85=98=20?= =?UTF-8?q?=EC=97=90=EB=9F=AC=20=EC=B6=94=EC=A0=81=20=EA=B0=9C=EC=84=A0?= =?UTF-8?q?=EC=9D=84=20=EC=9C=84=ED=95=9C=20Sentry=20=ED=99=98=EA=B2=BD=20?= =?UTF-8?q?=EA=B5=AC=EC=B6=95=20(#770)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/package-lock.json | 182 ++++++++++++++++++ frontend/package.json | 1 + frontend/src/api/interceptors.ts | 12 ++ .../src/components/common/Error/Error.tsx | 17 ++ .../common/ErrorBoundary/ErrorBoundary.tsx | 9 + frontend/src/index.tsx | 17 ++ 6 files changed, 238 insertions(+) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 9234a045c..17878a644 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -12,6 +12,7 @@ "@emotion/react": "^11.11.1", "@emotion/styled": "^11.11.0", "@googlemaps/react-wrapper": "^1.1.35", + "@sentry/react": "^7.89.0", "@tanstack/react-query": "^5.13.4", "assert": "^2.1.0", "axios": "^1.4.0", @@ -4062,6 +4063,110 @@ "node": ">=14.0.0" } }, + "node_modules/@sentry-internal/feedback": { + "version": "7.89.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-7.89.0.tgz", + "integrity": "sha512-Uz1A/ex3X/cvHjS3C9qH8ENa1Le7Jesxl3Umuv3n1e/rvoPxo2VFNCgDrpsM1mcg2XwiPkmpyMAVxDMgB0A7JQ==", + "dependencies": { + "@sentry/core": "7.89.0", + "@sentry/types": "7.89.0", + "@sentry/utils": "7.89.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@sentry-internal/tracing": { + "version": "7.89.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/tracing/-/tracing-7.89.0.tgz", + "integrity": "sha512-cSwno2NYteBBqOvcm/ue9cJxGGl2uffG4laEyLR9y4we+bYxigfx/Ki2TFOtwXrv5o59eRAtN1lpzaAf43yfBw==", + "dependencies": { + "@sentry/core": "7.89.0", + "@sentry/types": "7.89.0", + "@sentry/utils": "7.89.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@sentry/browser": { + "version": "7.89.0", + "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-7.89.0.tgz", + "integrity": "sha512-84hZOymrKyfjQnbD6d/PhlHCsQHjUGCqVU/K6e7g+JiB1DNZOsfZTkLj6Xj8OQzvwKHWzB9egmJN6IA7dUSJXQ==", + "dependencies": { + "@sentry-internal/feedback": "7.89.0", + "@sentry-internal/tracing": "7.89.0", + "@sentry/core": "7.89.0", + "@sentry/replay": "7.89.0", + "@sentry/types": "7.89.0", + "@sentry/utils": "7.89.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@sentry/core": { + "version": "7.89.0", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.89.0.tgz", + "integrity": "sha512-aU3wfZ+tyFi4T06fOH3z5xnTyMzwvzyEohYOmnQnDrqNgvDzjWkyeUzWse9FaFiut8lBN9O+Pd2H0ucPBMPEhQ==", + "dependencies": { + "@sentry/types": "7.89.0", + "@sentry/utils": "7.89.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@sentry/react": { + "version": "7.89.0", + "resolved": "https://registry.npmjs.org/@sentry/react/-/react-7.89.0.tgz", + "integrity": "sha512-SoMmwAgAq/GtfR5loM9MSeyxyZAwB6v61C2ENRKcp8JwMf6Ho0usaauUxd5HAnkTbLrapLUDgw9XbUt2XYj8Pw==", + "dependencies": { + "@sentry/browser": "7.89.0", + "@sentry/types": "7.89.0", + "@sentry/utils": "7.89.0", + "hoist-non-react-statics": "^3.3.2" + }, + "engines": { + "node": ">=8" + }, + "peerDependencies": { + "react": "15.x || 16.x || 17.x || 18.x" + } + }, + "node_modules/@sentry/replay": { + "version": "7.89.0", + "resolved": "https://registry.npmjs.org/@sentry/replay/-/replay-7.89.0.tgz", + "integrity": "sha512-iD1E1SbW8Ca34Fn0slM9E0Kh0DjhkZGerPFKqbwjXR7QKCx+ljMRKaCQhb4bBKfVNYZORxpwY4PLJLQ+k6WHJg==", + "dependencies": { + "@sentry-internal/tracing": "7.89.0", + "@sentry/core": "7.89.0", + "@sentry/types": "7.89.0", + "@sentry/utils": "7.89.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@sentry/types": { + "version": "7.89.0", + "resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.89.0.tgz", + "integrity": "sha512-5Rqt6vIP652p01ypUaEIrELjsHF0vUnzj/JFz+i7nXv6w77GPpNzeIlMYdnauBIgJhLUvYCTQaOPV9GhgZhc4g==", + "engines": { + "node": ">=8" + } + }, + "node_modules/@sentry/utils": { + "version": "7.89.0", + "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.89.0.tgz", + "integrity": "sha512-t6qDQajdAjZ6LPraAWO00ZjvDbNH82DoVGV/2o4C5MBPCutJGTGyWIpI2tliYPZPPx+3C2m5L757zh1dCzrgUg==", + "dependencies": { + "@sentry/types": "7.89.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/@sinclair/typebox": { "version": "0.27.8", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", @@ -24443,6 +24548,83 @@ "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.8.0.tgz", "integrity": "sha512-mrfKqIHnSZRyIzBcanNJmVQELTnX+qagEDlcKO90RgRBVOZGSGvZKeDihTRfWcqoDn5N/NkUcwWTccnpN18Tfg==" }, + "@sentry-internal/feedback": { + "version": "7.89.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-7.89.0.tgz", + "integrity": "sha512-Uz1A/ex3X/cvHjS3C9qH8ENa1Le7Jesxl3Umuv3n1e/rvoPxo2VFNCgDrpsM1mcg2XwiPkmpyMAVxDMgB0A7JQ==", + "requires": { + "@sentry/core": "7.89.0", + "@sentry/types": "7.89.0", + "@sentry/utils": "7.89.0" + } + }, + "@sentry-internal/tracing": { + "version": "7.89.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/tracing/-/tracing-7.89.0.tgz", + "integrity": "sha512-cSwno2NYteBBqOvcm/ue9cJxGGl2uffG4laEyLR9y4we+bYxigfx/Ki2TFOtwXrv5o59eRAtN1lpzaAf43yfBw==", + "requires": { + "@sentry/core": "7.89.0", + "@sentry/types": "7.89.0", + "@sentry/utils": "7.89.0" + } + }, + "@sentry/browser": { + "version": "7.89.0", + "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-7.89.0.tgz", + "integrity": "sha512-84hZOymrKyfjQnbD6d/PhlHCsQHjUGCqVU/K6e7g+JiB1DNZOsfZTkLj6Xj8OQzvwKHWzB9egmJN6IA7dUSJXQ==", + "requires": { + "@sentry-internal/feedback": "7.89.0", + "@sentry-internal/tracing": "7.89.0", + "@sentry/core": "7.89.0", + "@sentry/replay": "7.89.0", + "@sentry/types": "7.89.0", + "@sentry/utils": "7.89.0" + } + }, + "@sentry/core": { + "version": "7.89.0", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.89.0.tgz", + "integrity": "sha512-aU3wfZ+tyFi4T06fOH3z5xnTyMzwvzyEohYOmnQnDrqNgvDzjWkyeUzWse9FaFiut8lBN9O+Pd2H0ucPBMPEhQ==", + "requires": { + "@sentry/types": "7.89.0", + "@sentry/utils": "7.89.0" + } + }, + "@sentry/react": { + "version": "7.89.0", + "resolved": "https://registry.npmjs.org/@sentry/react/-/react-7.89.0.tgz", + "integrity": "sha512-SoMmwAgAq/GtfR5loM9MSeyxyZAwB6v61C2ENRKcp8JwMf6Ho0usaauUxd5HAnkTbLrapLUDgw9XbUt2XYj8Pw==", + "requires": { + "@sentry/browser": "7.89.0", + "@sentry/types": "7.89.0", + "@sentry/utils": "7.89.0", + "hoist-non-react-statics": "^3.3.2" + } + }, + "@sentry/replay": { + "version": "7.89.0", + "resolved": "https://registry.npmjs.org/@sentry/replay/-/replay-7.89.0.tgz", + "integrity": "sha512-iD1E1SbW8Ca34Fn0slM9E0Kh0DjhkZGerPFKqbwjXR7QKCx+ljMRKaCQhb4bBKfVNYZORxpwY4PLJLQ+k6WHJg==", + "requires": { + "@sentry-internal/tracing": "7.89.0", + "@sentry/core": "7.89.0", + "@sentry/types": "7.89.0", + "@sentry/utils": "7.89.0" + } + }, + "@sentry/types": { + "version": "7.89.0", + "resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.89.0.tgz", + "integrity": "sha512-5Rqt6vIP652p01ypUaEIrELjsHF0vUnzj/JFz+i7nXv6w77GPpNzeIlMYdnauBIgJhLUvYCTQaOPV9GhgZhc4g==" + }, + "@sentry/utils": { + "version": "7.89.0", + "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.89.0.tgz", + "integrity": "sha512-t6qDQajdAjZ6LPraAWO00ZjvDbNH82DoVGV/2o4C5MBPCutJGTGyWIpI2tliYPZPPx+3C2m5L757zh1dCzrgUg==", + "requires": { + "@sentry/types": "7.89.0" + } + }, "@sinclair/typebox": { "version": "0.27.8", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", diff --git a/frontend/package.json b/frontend/package.json index ab073b2fc..3acbcc8d7 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -38,6 +38,7 @@ "@emotion/react": "^11.11.1", "@emotion/styled": "^11.11.0", "@googlemaps/react-wrapper": "^1.1.35", + "@sentry/react": "^7.89.0", "@tanstack/react-query": "^5.13.4", "assert": "^2.1.0", "axios": "^1.4.0", diff --git a/frontend/src/api/interceptors.ts b/frontend/src/api/interceptors.ts index feb1f447d..676473a04 100644 --- a/frontend/src/api/interceptors.ts +++ b/frontend/src/api/interceptors.ts @@ -1,3 +1,5 @@ +import * as Sentry from '@sentry/react'; + import type { AxiosError, InternalAxiosRequestConfig } from 'axios'; import { HTTPError } from '@api/HTTPError'; @@ -30,6 +32,11 @@ export const checkAndSetToken = (config: InternalAxiosRequestConfig) => { }; export const handleTokenError = async (error: AxiosError) => { + Sentry.withScope((scope) => { + scope.setLevel('error'); + Sentry.captureMessage(`[TokenError] ${window.location.href} \n ${error.response?.data}`); + }); + const originalRequest = error.config; if (!error.response || !originalRequest) throw new Error('에러가 발생했습니다.'); @@ -64,6 +71,11 @@ export const handleTokenError = async (error: AxiosError) => }; export const handleAPIError = (error: AxiosError) => { + Sentry.withScope((scope) => { + scope.setLevel('error'); + Sentry.captureMessage(`[APIError] ${window.location.href} \n ${error.response?.data}`); + }); + if (!error.response) throw error; const { data, status } = error.response; diff --git a/frontend/src/components/common/Error/Error.tsx b/frontend/src/components/common/Error/Error.tsx index ca21ce61a..8e7b182be 100644 --- a/frontend/src/components/common/Error/Error.tsx +++ b/frontend/src/components/common/Error/Error.tsx @@ -1,5 +1,11 @@ +import * as Sentry from '@sentry/react'; + +import { useEffect } from 'react'; + import { useRecoilValue } from 'recoil'; +import axios from 'axios'; + import { Box, Button, Flex, Heading, Text } from 'hang-log-design-system'; import { @@ -33,6 +39,17 @@ const Error = ({ statusCode = HTTP_STATUS_CODE.NOT_FOUND, errorCode, resetError const { handleTokenError } = useTokenError(); + useEffect(() => { + if (statusCode === HTTP_STATUS_CODE.NOT_FOUND) { + axios.get('https://geolocation-db.com/json/').then((res) => { + Sentry.withScope((scope) => { + scope.setLevel('warning'); + Sentry.captureMessage(`[Warning] ${window.location.href} from ${res.data.IPv4}`); + }); + }); + } + }, [statusCode]); + if (!isHTTPError) return null; if (errorCode && errorCode > ERROR_CODE.TOKEN_ERROR_RANGE) { diff --git a/frontend/src/components/common/ErrorBoundary/ErrorBoundary.tsx b/frontend/src/components/common/ErrorBoundary/ErrorBoundary.tsx index 478693848..adc03c3d8 100644 --- a/frontend/src/components/common/ErrorBoundary/ErrorBoundary.tsx +++ b/frontend/src/components/common/ErrorBoundary/ErrorBoundary.tsx @@ -1,3 +1,5 @@ +import * as Sentry from '@sentry/react'; + import type { ComponentType, PropsWithChildren } from 'react'; import { Component } from 'react'; @@ -27,6 +29,13 @@ class ErrorBoundary extends Component, Sta return { hasError: true, error }; } + componentDidCatch(error: Error | HTTPError): void { + Sentry.withScope((scope) => { + scope.setLevel('error'); + Sentry.captureMessage(`[${error.name}] ${window.location.href}`); + }); + } + resetErrorBoundary = () => { const { onReset } = this.props; const { error } = this.state; diff --git a/frontend/src/index.tsx b/frontend/src/index.tsx index b5ed18ae5..2cc12630a 100644 --- a/frontend/src/index.tsx +++ b/frontend/src/index.tsx @@ -1,4 +1,5 @@ import { Global } from '@emotion/react'; +import * as Sentry from '@sentry/react'; import { StrictMode } from 'react'; import { createRoot } from 'react-dom/client'; @@ -21,6 +22,8 @@ import { GlobalStyle } from '@styles/index'; import { worker } from '@mocks/browser'; const main = async () => { + const tracePropagationTargets: (RegExp | string)[] = [/^https:\/\/hanglog\.(com|site)/]; + if (process.env.NODE_ENV === 'development') { await worker.start({ serviceWorker: { @@ -30,6 +33,20 @@ const main = async () => { }); } + Sentry.init({ + dsn: process.env.SENTRY_DSN, + integrations: [ + new Sentry.BrowserTracing({ + tracePropagationTargets, + }), + new Sentry.Replay(), + ], + + tracesSampleRate: 1.0, + replaysSessionSampleRate: 0.1, + replaysOnErrorSampleRate: 1.0, + }); + const root = createRoot(document.querySelector('#root') as Element); root.render(