Commit ec765edb authored by tom goriunov's avatar tom goriunov Committed by GitHub

OpenTelemetry and minor perf fixes (#1343)

* OT init

* add bundle analyzer and change external scripts load strategy

* exporters for prod

* update discord icon and lazy load metamask package

* Fixing opentelemetry url

* add logs and url schema

* Testing NODE_ENV and opentelemetry

* Using jaeger as opentelemetry trace collector

* move envs to build time

* Fixing opentelemetry URL

* fix trace exporter for span processor

* ENV configuration

* filter out incoming requests

* describe feature in docs

* clean up

* Changing otlp endpoint

* fix tests

---------
Co-authored-by: default avatarNick Zenchik <n.zenchik@gmail.com>
parent ac89ff3d
...@@ -2,6 +2,8 @@ const RESTRICTED_MODULES = { ...@@ -2,6 +2,8 @@ const RESTRICTED_MODULES = {
paths: [ paths: [
{ name: 'dayjs', message: 'Please use lib/date/dayjs.ts instead of directly importing dayjs' }, { name: 'dayjs', message: 'Please use lib/date/dayjs.ts instead of directly importing dayjs' },
{ name: '@chakra-ui/icons', message: 'Using @chakra-ui/icons is prohibited. Please use regular svg-icon instead (see examples in "icons/" folder)' }, { name: '@chakra-ui/icons', message: 'Using @chakra-ui/icons is prohibited. Please use regular svg-icon instead (see examples in "icons/" folder)' },
{ name: '@metamask/providers', message: 'Please lazy-load @metamask/providers or use useProvider hook instead' },
{ name: '@metamask/post-message-stream', message: 'Please lazy-load @metamask/post-message-stream or use useProvider hook instead' },
], ],
}; };
...@@ -307,7 +309,15 @@ module.exports = { ...@@ -307,7 +309,15 @@ module.exports = {
}, },
}, },
{ {
files: [ '*.config.ts', '*.config.js', 'playwright/**', 'deploy/tools/**', 'middleware.ts', 'nextjs/**' ], files: [
'*.config.ts',
'*.config.js',
'playwright/**',
'deploy/tools/**',
'middleware.ts',
'nextjs/**',
'instrumentation*.ts',
],
rules: { rules: {
// for configs allow to consume env variables from process.env directly // for configs allow to consume env variables from process.env directly
'no-restricted-properties': [ 0 ], 'no-restricted-properties': [ 0 ],
......
...@@ -14,6 +14,7 @@ ...@@ -14,6 +14,7 @@
/out/ /out/
/public/assets/ /public/assets/
/public/envs.js /public/envs.js
/analyze
# production # production
/build /build
......
...@@ -71,6 +71,9 @@ frontend: ...@@ -71,6 +71,9 @@ frontend:
NEXT_PUBLIC_VIEWS_TX_ADDITIONAL_FIELDS: "['fee_per_gas']" NEXT_PUBLIC_VIEWS_TX_ADDITIONAL_FIELDS: "['fee_per_gas']"
NEXT_PUBLIC_USE_NEXT_JS_PROXY: true NEXT_PUBLIC_USE_NEXT_JS_PROXY: true
NEXT_PUBLIC_VIEWS_NFT_MARKETPLACES: "[{'name':'LooksRare','collection_url':'https://goerli.looksrare.org/collections/{hash}','instance_url':'https://goerli.looksrare.org/collections/{hash}/{id}','logo_url':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/nft-marketplace-logos/looks-rare.png'}]" NEXT_PUBLIC_VIEWS_NFT_MARKETPLACES: "[{'name':'LooksRare','collection_url':'https://goerli.looksrare.org/collections/{hash}','instance_url':'https://goerli.looksrare.org/collections/{hash}/{id}','logo_url':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/nft-marketplace-logos/looks-rare.png'}]"
OTEL_SDK_ENABLED: true
OTEL_EXPORTER_OTLP_ENDPOINT: http://jaeger-collector.jaeger.svc.cluster.local:4318
NEXT_OTEL_VERBOSE: 1
envFromSecret: envFromSecret:
NEXT_PUBLIC_SENTRY_DSN: ref+vault://deployment-values/blockscout/dev/review?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/NEXT_PUBLIC_SENTRY_DSN NEXT_PUBLIC_SENTRY_DSN: ref+vault://deployment-values/blockscout/dev/review?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/NEXT_PUBLIC_SENTRY_DSN
SENTRY_CSP_REPORT_URI: ref+vault://deployment-values/blockscout/dev/review?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/SENTRY_CSP_REPORT_URI SENTRY_CSP_REPORT_URI: ref+vault://deployment-values/blockscout/dev/review?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/SENTRY_CSP_REPORT_URI
......
...@@ -49,6 +49,7 @@ Please be aware that all environment variables prefixed with `NEXT_PUBLIC_` will ...@@ -49,6 +49,7 @@ Please be aware that all environment variables prefixed with `NEXT_PUBLIC_` will
- [Safe{Core} address tags](ENVS.md#safecore-address-tags) - [Safe{Core} address tags](ENVS.md#safecore-address-tags)
- [SUAVE chain](ENVS.md#suave-chain) - [SUAVE chain](ENVS.md#suave-chain)
- [Sentry error monitoring](ENVS.md#sentry-error-monitoring) - [Sentry error monitoring](ENVS.md#sentry-error-monitoring)
- [OpenTelemetry](ENVS.md#opentelemetry)
- [3rd party services configuration](ENVS.md#external-services-configuration) - [3rd party services configuration](ENVS.md#external-services-configuration)
&nbsp; &nbsp;
...@@ -536,6 +537,16 @@ For blockchains that implementing SUAVE architecture additional fields will be s ...@@ -536,6 +537,16 @@ For blockchains that implementing SUAVE architecture additional fields will be s
&nbsp; &nbsp;
### OpenTelemetry
OpenTelemetry SDK for Node.js app could be enabled by passing `OTEL_SDK_ENABLED=true` variable. Configure the OpenTelemetry Protocol Exporter by using the generic environment variables described in the [OT docs](https://opentelemetry.io/docs/specs/otel/protocol/exporter/#configuration-options).
| Variable | Type| Description | Compulsoriness | Default value | Example value |
| --- | --- | --- | --- | --- | --- |
| OTEL_SDK_ENABLED | `boolean` | Flag to enable the feature | Required | `false` | `true` |
&nbsp;
## External services configuration ## External services configuration
### Google ReCaptcha ### Google ReCaptcha
......
This diff is collapsed.
/* eslint-disable no-console */
import { diag, DiagConsoleLogger, DiagLogLevel } from '@opentelemetry/api';
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';
import { OTLPMetricExporter } from '@opentelemetry/exporter-metrics-otlp-proto';
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
import { Resource } from '@opentelemetry/resources';
import {
PeriodicExportingMetricReader,
ConsoleMetricExporter,
} from '@opentelemetry/sdk-metrics';
import { NodeSDK } from '@opentelemetry/sdk-node';
import { SimpleSpanProcessor } from '@opentelemetry/sdk-trace-node';
import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions';
diag.setLogger(new DiagConsoleLogger(), DiagLogLevel.INFO);
const traceExporter = new OTLPTraceExporter();
const sdk = new NodeSDK({
resource: new Resource({
[SemanticResourceAttributes.SERVICE_NAME]: 'blockscout_frontend',
[SemanticResourceAttributes.SERVICE_VERSION]: process.env.NEXT_PUBLIC_GIT_TAG || process.env.NEXT_PUBLIC_GIT_COMMIT_SHA || 'unknown_version',
[SemanticResourceAttributes.SERVICE_INSTANCE_ID]:
process.env.NEXT_PUBLIC_APP_INSTANCE ||
process.env.NEXT_PUBLIC_APP_HOST?.replace('.blockscout.com', '').replaceAll('-', '_') ||
'unknown_app',
}),
spanProcessor: new SimpleSpanProcessor(traceExporter),
traceExporter,
metricReader: new PeriodicExportingMetricReader({
exporter:
process.env.NODE_ENV === 'production' ?
new OTLPMetricExporter() :
new ConsoleMetricExporter(),
}),
instrumentations: [
getNodeAutoInstrumentations({
'@opentelemetry/instrumentation-http': {
ignoreIncomingRequestHook: (request) => {
try {
if (!request.url) {
return false;
}
const url = new URL(request.url, `http://${ request.headers.host }`);
if (
url.pathname.startsWith('/_next/static/') ||
url.pathname.startsWith('/_next/data/') ||
url.pathname.startsWith('/assets/') ||
url.pathname.startsWith('/static/') ||
url.pathname.startsWith('/favicon/') ||
url.pathname.startsWith('/envs.js')
) {
return true;
}
} catch (error) {}
return false;
},
},
}),
],
});
if (process.env.OTEL_SDK_ENABLED) {
sdk.start();
process.on('SIGTERM', () => {
sdk
.shutdown()
.then(() => console.log('Tracing terminated'))
.catch((error) => console.log('Error terminating tracing', error))
.finally(() => process.exit(0));
});
}
export async function register() {
if (process.env.NEXT_RUNTIME === 'nodejs') {
await import('./instrumentation.node');
}
}
import { WindowPostMessageStream } from '@metamask/post-message-stream';
import { initializeProvider } from '@metamask/providers';
import React from 'react'; import React from 'react';
import type { WindowProvider } from 'wagmi'; import type { WindowProvider } from 'wagmi';
...@@ -15,13 +13,16 @@ export default function useProvider() { ...@@ -15,13 +13,16 @@ export default function useProvider() {
const [ provider, setProvider ] = React.useState<WindowProvider>(); const [ provider, setProvider ] = React.useState<WindowProvider>();
const [ wallet, setWallet ] = React.useState<WalletType>(); const [ wallet, setWallet ] = React.useState<WalletType>();
React.useEffect(() => { const initializeProvider = React.useMemo(() => async() => {
if (!feature.isEnabled) { if (!feature.isEnabled) {
return; return;
} }
if (!('ethereum' in window && window.ethereum)) { if (!('ethereum' in window && window.ethereum)) {
if (feature.wallets.includes('metamask') && window.navigator.userAgent.includes('Firefox')) { if (feature.wallets.includes('metamask') && window.navigator.userAgent.includes('Firefox')) {
const { WindowPostMessageStream } = (await import('@metamask/post-message-stream'));
const { initializeProvider } = (await import('@metamask/providers'));
// workaround for MetaMask in Firefox // workaround for MetaMask in Firefox
// Firefox blocks MetaMask injection script because of our CSP for 'script-src' // Firefox blocks MetaMask injection script because of our CSP for 'script-src'
// so we have to inject it manually while the issue is not fixed // so we have to inject it manually while the issue is not fixed
...@@ -73,5 +74,9 @@ export default function useProvider() { ...@@ -73,5 +74,9 @@ export default function useProvider() {
} }
}, []); }, []);
React.useEffect(() => {
initializeProvider();
}, [ initializeProvider ]);
return { provider, wallet }; return { provider, wallet };
} }
const withBundleAnalyzer = require('@next/bundle-analyzer')({
enabled: process.env.BUNDLE_ANALYZER === 'true',
});
const withRoutes = require('nextjs-routes/config')({ const withRoutes = require('nextjs-routes/config')({
outDir: 'nextjs', outDir: 'nextjs',
}); });
...@@ -39,6 +43,9 @@ const moduleExports = { ...@@ -39,6 +43,9 @@ const moduleExports = {
headers, headers,
output: 'standalone', output: 'standalone',
productionBrowserSourceMaps: process.env.GENERATE_SOURCEMAPS === 'true', productionBrowserSourceMaps: process.env.GENERATE_SOURCEMAPS === 'true',
experimental: {
instrumentationHook: true,
},
}; };
module.exports = withRoutes(moduleExports); module.exports = withBundleAnalyzer(withRoutes(moduleExports));
...@@ -16,6 +16,6 @@ ...@@ -16,6 +16,6 @@
"incremental": true, "incremental": true,
"baseUrl": ".", "baseUrl": ".",
}, },
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "decs.d.ts", "global.d.ts"], "include": ["next-env.d.ts", "**/*.ts", "**/*.node.ts", "**/*.tsx", "decs.d.ts", "global.d.ts"],
"exclude": ["node_modules", "node_modules_linux", "./deploy/tools/envs-validator"], "exclude": ["node_modules", "node_modules_linux", "./deploy/tools/envs-validator"],
} }
...@@ -14,8 +14,8 @@ const GoogleAnalytics = () => { ...@@ -14,8 +14,8 @@ const GoogleAnalytics = () => {
return ( return (
<> <>
<Script src={ `https://www.googletagmanager.com/gtag/js?id=${ id }` }/> <Script strategy="lazyOnload" src={ `https://www.googletagmanager.com/gtag/js?id=${ id }` }/>
<Script id="google-analytics"> <Script strategy="lazyOnload" id="google-analytics">
{ ` { `
window.dataLayer = window.dataLayer || []; window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);} function gtag(){dataLayer.push(arguments);}
......
...@@ -47,8 +47,8 @@ const AdbutlerBanner = ({ className }: { className?: string }) => { ...@@ -47,8 +47,8 @@ const AdbutlerBanner = ({ className }: { className?: string }) => {
return ( return (
<Flex className={ className } id="adBanner" h={{ base: '100px', lg: '90px' }}> <Flex className={ className } id="adBanner" h={{ base: '100px', lg: '90px' }}>
<Script id="ad-butler-1">{ connectAdbutler }</Script> <Script strategy="lazyOnload" id="ad-butler-1">{ connectAdbutler }</Script>
<Script id="ad-butler-2">{ placeAd }</Script> <Script strategy="lazyOnload" id="ad-butler-2">{ placeAd }</Script>
<div id="ad-banner"></div> <div id="ad-banner"></div>
</Flex> </Flex>
); );
......
...@@ -21,7 +21,7 @@ const CoinzillaBanner = ({ className }: { className?: string }) => { ...@@ -21,7 +21,7 @@ const CoinzillaBanner = ({ className }: { className?: string }) => {
return ( return (
<Flex className={ className } id="adBanner" h={{ base: '100px', lg: '90px' }}> <Flex className={ className } id="adBanner" h={{ base: '100px', lg: '90px' }}>
<Script src="https://coinzillatag.com/lib/display.js"/> <Script strategy="lazyOnload" src="https://coinzillatag.com/lib/display.js"/>
<div className="coinzilla" data-zone="C-26660bf627543e46851"></div> <div className="coinzilla" data-zone="C-26660bf627543e46851"></div>
</Flex> </Flex>
); );
......
...@@ -64,7 +64,7 @@ const Footer = () => { ...@@ -64,7 +64,7 @@ const Footer = () => {
}, },
{ {
icon: discordIcon, icon: discordIcon,
iconSize: '18px', iconSize: '24px',
text: 'Discord', text: 'Discord',
url: 'https://discord.gg/blockscout', url: 'https://discord.gg/blockscout',
}, },
......
This source diff could not be displayed because it is too large. You can view the blob instead.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment