From 189b3625d3a4167ccc4343056bab5be6553b2bbf Mon Sep 17 00:00:00 2001 From: chefjackson <116779127+chefjackson@users.noreply.github.com> Date: Tue, 31 Oct 2023 16:42:10 +0800 Subject: [PATCH] chore: Add datadog integration (#8254) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ### πŸ€– Generated by Copilot at eddc0ff ### Summary πŸΆπŸ“πŸ•΅οΈβ€β™‚οΈ This pull request adds logging and monitoring of the web app and the web worker using Datadog browser logs. It adds a new dependency for `@datadog/browser-logs`, a new environment variable for `NEXT_PUBLIC_DATADOG_CLIENT_TOKEN`, and some utility files for configuring and polyfilling the logger. It also removes an unused dependency for `react-fast-marquee`. > _Sing, O Muse, of the glorious deeds of the web app makers,_ > _Who, with skill and cunning, added the logs of Datadog,_ > _The swift and faithful messenger of the cloud-born Zeus,_ > _To monitor and trace their code, both in browser and in worker._ ### Walkthrough * Add and configure Datadog browser logs library for logging and monitoring web app and web worker ([link](https://github.com/pancakeswap/pancake-frontend/pull/8254/files?diff=unified&w=0#diff-c8e463bbf676c362fbc68386873a43c51bfb1a1bb136822808f18c7f2c9fb591R9), [link](https://github.com/pancakeswap/pancake-frontend/pull/8254/files?diff=unified&w=0#diff-14b60f636e1a2b0061da57aaf231cb1ed15a5dc0c592425ed82e58fec95d42d8R35), [link](https://github.com/pancakeswap/pancake-frontend/pull/8254/files?diff=unified&w=0#diff-cb95365019f7ba2030863c54e03dafdfde7363d4004781eab13cf7e887fa31deL1-R6), [link](https://github.com/pancakeswap/pancake-frontend/pull/8254/files?diff=unified&w=0#diff-cb95365019f7ba2030863c54e03dafdfde7363d4004781eab13cf7e887fa31deR19), [link](https://github.com/pancakeswap/pancake-frontend/pull/8254/files?diff=unified&w=0#diff-cb95365019f7ba2030863c54e03dafdfde7363d4004781eab13cf7e887fa31deL31-R35), [link](https://github.com/pancakeswap/pancake-frontend/pull/8254/files?diff=unified&w=0#diff-cb95365019f7ba2030863c54e03dafdfde7363d4004781eab13cf7e887fa31deR41-R42), [link](https://github.com/pancakeswap/pancake-frontend/pull/8254/files?diff=unified&w=0#diff-a6e33c8aa8509a097c0e5157cbd63aa8e3f373104ea573615f15d7c6804582fbR1-R32), [link](https://github.com/pancakeswap/pancake-frontend/pull/8254/files?diff=unified&w=0#diff-96b658c988c8717a5c20e2588b1d8c183008f94ee7a20c4d7acd8cdf1beb8515R1-R103), [link](https://github.com/pancakeswap/pancake-frontend/pull/8254/files?diff=unified&w=0#diff-32824c984905bb02bc7ffcef96a77addd1f1602cff71a11fbbfdd7f53ee026bbR562-R564), [link](https://github.com/pancakeswap/pancake-frontend/pull/8254/files?diff=unified&w=0#diff-32824c984905bb02bc7ffcef96a77addd1f1602cff71a11fbbfdd7f53ee026bbR4984-R4998)) * Remove `react-fast-marquee` dependency and replace it with custom component for scrolling text effect ([link](https://github.com/pancakeswap/pancake-frontend/pull/8254/files?diff=unified&w=0#diff-14b60f636e1a2b0061da57aaf231cb1ed15a5dc0c592425ed82e58fec95d42d8L92-R94)) * Update `entities` dependency from version 4.4.0 to 4.5.0 for encoding and decoding HTML entities ([link](https://github.com/pancakeswap/pancake-frontend/pull/8254/files?diff=unified&w=0#diff-32824c984905bb02bc7ffcef96a77addd1f1602cff71a11fbbfdd7f53ee026bbL9452-R9470), [link](https://github.com/pancakeswap/pancake-frontend/pull/8254/files?diff=unified&w=0#diff-32824c984905bb02bc7ffcef96a77addd1f1602cff71a11fbbfdd7f53ee026bbL15228-R15246), [link](https://github.com/pancakeswap/pancake-frontend/pull/8254/files?diff=unified&w=0#diff-32824c984905bb02bc7ffcef96a77addd1f1602cff71a11fbbfdd7f53ee026bbL15422-R15441), [link](https://github.com/pancakeswap/pancake-frontend/pull/8254/files?diff=unified&w=0#diff-32824c984905bb02bc7ffcef96a77addd1f1602cff71a11fbbfdd7f53ee026bbL21486-R21504)) --- apps/web/.env.example | 1 + apps/web/package.json | 3 +- apps/web/src/quote-worker.ts | 22 +++--- apps/web/src/utils/datadog.ts | 34 +++++++++ apps/web/src/utils/log.ts | 5 +- apps/web/src/utils/workerPolyfill.ts | 103 +++++++++++++++++++++++++++ pnpm-lock.yaml | 28 ++++++-- 7 files changed, 180 insertions(+), 16 deletions(-) create mode 100644 apps/web/src/utils/datadog.ts create mode 100644 apps/web/src/utils/workerPolyfill.ts diff --git a/apps/web/.env.example b/apps/web/.env.example index 5675e3e95a95e..0a7e3ec8862fe 100644 --- a/apps/web/.env.example +++ b/apps/web/.env.example @@ -6,3 +6,4 @@ SERVER_NODE_REAL_API_ETH= SERVER_NODE_REAL_API_GOERLI= NEXT_PUBLIC_MERCURYO_WIDGET_ID= NEXT_PUBLIC_WALLCHAIN_BSC_KEY= +NEXT_PUBLIC_DATADOG_CLIENT_TOKEN= diff --git a/apps/web/package.json b/apps/web/package.json index 1089a58dddc3d..116af88bb2534 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -32,6 +32,7 @@ "node": ">=16.0.0" }, "dependencies": { + "@datadog/browser-logs": "^5.1.0", "@gelatonetwork/limit-orders-lib": "^4.1.0", "@gnosis.pm/safe-apps-wagmi": "^1.2.0", "@next/bundle-analyzer": "13.0.7", @@ -90,8 +91,8 @@ "react-datepicker": "^4.8.0", "react-device-detect": "^2.1.2", "react-dom": "^18.2.0", - "react-redux": "^8.0.5", "react-fast-marquee": "^1.6.0", + "react-redux": "^8.0.5", "react-transition-group": "^4.4.5", "react-window": "^1.8.7", "recharts": "2.1.15", diff --git a/apps/web/src/quote-worker.ts b/apps/web/src/quote-worker.ts index c530cce136f36..23564f719393f 100644 --- a/apps/web/src/quote-worker.ts +++ b/apps/web/src/quote-worker.ts @@ -1,7 +1,9 @@ +import 'utils/workerPolyfill' import { SmartRouter } from '@pancakeswap/smart-router/evm' import { Call } from 'state/multicall/actions' import { fetchChunk } from 'state/multicall/fetchChunk' import { getViemClients } from 'utils/viem' +import { getLogger } from 'utils/datadog' const { parseCurrency, parseCurrencyAmount, parsePool, serializeTrade } = SmartRouter.Transformer @@ -14,6 +16,7 @@ export type WorkerGetBestTradeEvent = [ ] const fetch_ = fetch +const logger = getLogger('quote-rpc', { forwardErrorsToLogs: false }) const fetchWithLogging = async (url: RequestInfo | URL, init?: RequestInit) => { const start = Date.now() @@ -27,15 +30,18 @@ const fetchWithLogging = async (url: RequestInfo | URL, init?: RequestInit) => { const response = await fetch_(url, init) const end = Date.now() if (urlString && size) { - if (!urlString.includes('vercel-vitals.axiom.co')) { - if (process.env.NEXT_PUBLIC_VERCEL_ENV !== 'production') { - // eslint-disable-next-line no-console - console.log('QuoteRPC', { - url: urlString, - size, - time: end - start, - status: response.status, + if (!urlString.includes('datadoghq.com')) { + try { + logger.info('Quote RPC', { + rpc: { + duration: end - start, + url: urlString, + size, + status: response.status, + }, }) + } catch (e) { + console.error(e) } } } diff --git a/apps/web/src/utils/datadog.ts b/apps/web/src/utils/datadog.ts new file mode 100644 index 0000000000000..e6835b19eaeaa --- /dev/null +++ b/apps/web/src/utils/datadog.ts @@ -0,0 +1,34 @@ +import { datadogLogs, LogsInitConfiguration } from '@datadog/browser-logs' + +// TODO: move to env if needed +const DATA_DOG_SITE = 'us3.datadoghq.com' + +try { + datadogLogs.init({ + clientToken: process.env.NEXT_PUBLIC_DATADOG_CLIENT_TOKEN || '', + env: process.env.NEXT_PUBLIC_VERCEL_ENV, + version: process.env.NEXT_PUBLIC_VERCEL_GIT_COMMIT_SHA, + site: DATA_DOG_SITE, + forwardErrorsToLogs: true, + sessionSampleRate: 100, + service: 'pancakeswap-web', + }) +} catch (e) { + console.error(e) +} + +export function getLogger(name: string, config?: Partial) { + const logger = datadogLogs.getLogger(name) + if (logger) { + return logger + } + return datadogLogs.createLogger(name, { + handler: process.env.NEXT_PUBLIC_VERCEL_ENV === 'production' ? 'http' : ['console', 'http'], + context: { + service: `pancakeswap-web-${name}`, + ...config, + }, + }) +} + +export const logger = getLogger('main') diff --git a/apps/web/src/utils/log.ts b/apps/web/src/utils/log.ts index 2ed33ef09cfbb..1cbacfc7ccba1 100644 --- a/apps/web/src/utils/log.ts +++ b/apps/web/src/utils/log.ts @@ -1,5 +1,6 @@ import { Currency } from '@pancakeswap/swap-sdk-core' -import { log } from 'next-axiom' + +import { logger } from './datadog' export const logTx = ({ account, hash, chainId }: { account: string; hash: string; chainId: number }) => { fetch(`/api/_log/${account}/${chainId}/${hash}`) @@ -25,7 +26,7 @@ export const logSwap = ({ type: 'V2Swap' | 'SmartSwap' | 'StableSwap' | 'MarketMakerSwap' | 'V3SmartSwap' }) => { try { - log.info(type, { + logger.info(type, { inputAddress: input.isToken ? input.address.toLowerCase() : input.symbol, outputAddress: output.isToken ? output.address.toLowerCase() : output.symbol, inputAmount, diff --git a/apps/web/src/utils/workerPolyfill.ts b/apps/web/src/utils/workerPolyfill.ts new file mode 100644 index 0000000000000..df9a0073bd3e7 --- /dev/null +++ b/apps/web/src/utils/workerPolyfill.ts @@ -0,0 +1,103 @@ +globalThis.document = { + readyState: 'complete', + getElementsByTagName: () => [] as any, + // @ts-expect-error partial polyfill for @datadog/browser-logs + location: { + hostname: 'hostname.com', + href: '', + }, + get cookie() { + return '' + }, + set cookie(cookie: any) {}, + addEventListener: () => {}, + removeEventListener: () => {}, +} + +globalThis.window = { + TextEncoder: globalThis.TextEncoder, + fetch: globalThis.fetch, + // @ts-expect-error partial polyfill for @datadog/browser-logs + location: { + hostname: '', + href: '', + }, + addEventListener: () => {}, + removeEventListener: () => {}, +} + +globalThis.performance = { + // @ts-expect-error partial polyfill for @datadog/browser-logs + timing: { + navigationStart: 0, + }, + now: () => 0, +} + +class XMLHttpRequestWithFetch { + private method: string | null + + private url: string | null + + private status: number + + private listeners: any + + constructor() { + this.method = null + this.url = null + this.status = 500 + this.listeners = {} + } + + open(method: string, url: string) { + this.method = method + this.url = url + } + + addEventListener(eventName: string, cb: any) { + if (!this.listeners[eventName]) { + this.listeners[eventName] = [] + } + this.listeners[eventName].push(cb) + } + + removeEventListener(eventName: string, cb: any) { + const handlers = this.listeners[eventName] + if (handlers) { + const restOfHandlers = handlers.filter((callback: any) => callback !== cb) + if (restOfHandlers && restOfHandlers.length) { + this.listeners[eventName] = restOfHandlers + } else { + delete this.listeners[eventName] + } + } + } + + send(data: any) { + let _body = data + if (typeof data === 'object') { + _body = JSON.stringify(data) + } + if (!this.url) { + return + } + fetch(this.url, { + method: this.method || 'GET', + body: _body, + }) + .then((response) => { + this.status = response.status + // notify all listeners that we're done + Object.keys(this.listeners).forEach((event) => { + this.listeners[event].forEach((handler: any) => handler(response)) + }) + }) + .catch(() => { + // @TODO: probably we should handle the failing case. + }) + } +} + +// @ts-expect-error partial polyfill for @datadog/browser-logs +globalThis.XMLHttpRequest = XMLHttpRequestWithFetch diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 35307228deed8..543b2a4935698 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -644,6 +644,9 @@ importers: apps/web: dependencies: + '@datadog/browser-logs': + specifier: ^5.1.0 + version: 5.1.0 '@gelatonetwork/limit-orders-lib': specifier: ^4.1.0 version: 4.1.0(encoding@0.1.13) @@ -6068,6 +6071,21 @@ packages: - supports-color dev: true + /@datadog/browser-core@5.1.0: + resolution: {integrity: sha512-KZMLXcJqA59TiXF4nFEaUEE23LhflxvLhv4LfGyhH9DIX7Be3bCJqrEyXajZHXYQiXUoZSsImC20w1d6kSf+KA==} + dev: false + + /@datadog/browser-logs@5.1.0: + resolution: {integrity: sha512-UtYboTfSgkg6fiArJxDqO25GpXsAReCREd3EgU0VKi75BfSW3StR7WOfbzKuy0X1s4tpbfhlC7ogKqBGInCprg==} + peerDependencies: + '@datadog/browser-rum': 5.1.0 + peerDependenciesMeta: + '@datadog/browser-rum': + optional: true + dependencies: + '@datadog/browser-core': 5.1.0 + dev: false + /@discoveryjs/json-ext@0.5.7: resolution: {integrity: sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==} engines: {node: '>=10.0.0'} @@ -10511,7 +10529,7 @@ packages: engines: {node: '>=14'} dependencies: '@babel/types': 7.21.5 - entities: 4.4.0 + entities: 4.5.0 dev: false /@svgr/plugin-jsx@8.0.1(@svgr/core@8.0.0): @@ -16284,7 +16302,7 @@ packages: dependencies: domelementtype: 2.3.0 domhandler: 5.0.3 - entities: 4.4.0 + entities: 4.5.0 dev: false /dom-walk@0.1.2: @@ -16478,8 +16496,8 @@ packages: ansi-colors: 4.1.3 dev: true - /entities@4.4.0: - resolution: {integrity: sha512-oYp7156SP8LkeGD0GF85ad1X9Ai79WtRsZ2gxJqtBuzH+98YUV6jkHEKlZkMbcrjJjIVJNIDP/3WL9wQkoPbWA==} + /entities@4.5.0: + resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} engines: {node: '>=0.12'} /envinfo@7.8.1: @@ -22543,7 +22561,7 @@ packages: /parse5@7.1.2: resolution: {integrity: sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==} dependencies: - entities: 4.4.0 + entities: 4.5.0 dev: true /parseurl@1.3.3: