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 = {
paths: [
{ 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: '@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 = {
},
},
{
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: {
// for configs allow to consume env variables from process.env directly
'no-restricted-properties': [ 0 ],
......
......@@ -14,6 +14,7 @@
/out/
/public/assets/
/public/envs.js
/analyze
# production
/build
......
......@@ -71,6 +71,9 @@ frontend:
NEXT_PUBLIC_VIEWS_TX_ADDITIONAL_FIELDS: "['fee_per_gas']"
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'}]"
OTEL_SDK_ENABLED: true
OTEL_EXPORTER_OTLP_ENDPOINT: http://jaeger-collector.jaeger.svc.cluster.local:4318
NEXT_OTEL_VERBOSE: 1
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
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
- [Safe{Core} address tags](ENVS.md#safecore-address-tags)
- [SUAVE chain](ENVS.md#suave-chain)
- [Sentry error monitoring](ENVS.md#sentry-error-monitoring)
- [OpenTelemetry](ENVS.md#opentelemetry)
- [3rd party services configuration](ENVS.md#external-services-configuration)
&nbsp;
......@@ -536,6 +537,16 @@ For blockchains that implementing SUAVE architecture additional fields will be s
&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
### 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 type { WindowProvider } from 'wagmi';
......@@ -15,13 +13,16 @@ export default function useProvider() {
const [ provider, setProvider ] = React.useState<WindowProvider>();
const [ wallet, setWallet ] = React.useState<WalletType>();
React.useEffect(() => {
const initializeProvider = React.useMemo(() => async() => {
if (!feature.isEnabled) {
return;
}
if (!('ethereum' in window && window.ethereum)) {
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
// 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
......@@ -73,5 +74,9 @@ export default function useProvider() {
}
}, []);
React.useEffect(() => {
initializeProvider();
}, [ initializeProvider ]);
return { provider, wallet };
}
const withBundleAnalyzer = require('@next/bundle-analyzer')({
enabled: process.env.BUNDLE_ANALYZER === 'true',
});
const withRoutes = require('nextjs-routes/config')({
outDir: 'nextjs',
});
......@@ -39,6 +43,9 @@ const moduleExports = {
headers,
output: 'standalone',
productionBrowserSourceMaps: process.env.GENERATE_SOURCEMAPS === 'true',
experimental: {
instrumentationHook: true,
},
};
module.exports = withRoutes(moduleExports);
module.exports = withBundleAnalyzer(withRoutes(moduleExports));
......@@ -16,6 +16,6 @@
"incremental": true,
"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"],
}
......@@ -14,8 +14,8 @@ const GoogleAnalytics = () => {
return (
<>
<Script src={ `https://www.googletagmanager.com/gtag/js?id=${ id }` }/>
<Script id="google-analytics">
<Script strategy="lazyOnload" src={ `https://www.googletagmanager.com/gtag/js?id=${ id }` }/>
<Script strategy="lazyOnload" id="google-analytics">
{ `
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
......
......@@ -47,8 +47,8 @@ const AdbutlerBanner = ({ className }: { className?: string }) => {
return (
<Flex className={ className } id="adBanner" h={{ base: '100px', lg: '90px' }}>
<Script id="ad-butler-1">{ connectAdbutler }</Script>
<Script id="ad-butler-2">{ placeAd }</Script>
<Script strategy="lazyOnload" id="ad-butler-1">{ connectAdbutler }</Script>
<Script strategy="lazyOnload" id="ad-butler-2">{ placeAd }</Script>
<div id="ad-banner"></div>
</Flex>
);
......
......@@ -21,7 +21,7 @@ const CoinzillaBanner = ({ className }: { className?: string }) => {
return (
<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>
</Flex>
);
......
......@@ -64,7 +64,7 @@ const Footer = () => {
},
{
icon: discordIcon,
iconSize: '18px',
iconSize: '24px',
text: 'Discord',
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