diff --git a/docker-compose.yml b/docker-compose.yml index 3e6fe3ba58..6c143496d2 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -62,6 +62,10 @@ services: SERVER_URL: http://127.0.0.1:${HYPERDX_API_PORT} OPAMP_PORT: ${HYPERDX_OPAMP_PORT} OTEL_EXPORTER_OTLP_ENDPOINT: 'http://otel-collector:4318' + # Per-signal OTLP endpoints (override OTEL_EXPORTER_OTLP_ENDPOINT for individual signals) + # OTEL_EXPORTER_OTLP_TRACES_ENDPOINT: 'http://otel-collector:4318/v1/traces' + # OTEL_EXPORTER_OTLP_METRICS_ENDPOINT: 'http://otel-collector:4318/v1/metrics' + # OTEL_EXPORTER_OTLP_LOGS_ENDPOINT: 'http://otel-collector:4318/v1/logs' OTEL_SERVICE_NAME: 'hdx-oss-app' USAGE_STATS_ENABLED: ${USAGE_STATS_ENABLED:-true} DEFAULT_CONNECTIONS: diff --git a/packages/api/.env.development b/packages/api/.env.development index 52421a6c66..c6563f782f 100644 --- a/packages/api/.env.development +++ b/packages/api/.env.development @@ -11,6 +11,10 @@ NODE_ENV=development OTEL_SERVICE_NAME="hdx-oss-dev-api" OTEL_RESOURCE_ATTRIBUTES="service.version=dev" OTEL_EXPORTER_OTLP_ENDPOINT="http://localhost:${HDX_DEV_OTEL_HTTP_PORT}" +# Per-signal OTLP endpoints (override the base endpoint for individual signals) +# OTEL_EXPORTER_OTLP_TRACES_ENDPOINT= +# OTEL_EXPORTER_OTLP_METRICS_ENDPOINT= +# OTEL_EXPORTER_OTLP_LOGS_ENDPOINT= PORT=${HYPERDX_API_PORT} OPAMP_PORT=${HYPERDX_OPAMP_PORT} REDIS_URL=redis://localhost:6379 diff --git a/packages/app/.env.development b/packages/app/.env.development index 26a5a09943..cce06dab5e 100644 --- a/packages/app/.env.development +++ b/packages/app/.env.development @@ -7,4 +7,8 @@ OTEL_SERVICE_NAME="hdx-oss-dev-app" PORT=${HYPERDX_APP_PORT} NODE_OPTIONS="--max-http-header-size=131072" NEXT_PUBLIC_HYPERDX_BASE_PATH= -NEXT_PUBLIC_OTEL_EXPORTER_OTLP_ENDPOINT="http://localhost:${HDX_DEV_OTEL_HTTP_PORT:-4318}" \ No newline at end of file +NEXT_PUBLIC_OTEL_EXPORTER_OTLP_ENDPOINT="http://localhost:${HDX_DEV_OTEL_HTTP_PORT:-4318}" +# Per-signal OTLP endpoints (override the base endpoint for individual signals) +# NEXT_PUBLIC_OTEL_EXPORTER_OTLP_TRACES_ENDPOINT= +# NEXT_PUBLIC_OTEL_EXPORTER_OTLP_METRICS_ENDPOINT= +# NEXT_PUBLIC_OTEL_EXPORTER_OTLP_LOGS_ENDPOINT= diff --git a/packages/app/Dockerfile b/packages/app/Dockerfile index e8a1aa2e4c..56edc5f284 100644 --- a/packages/app/Dockerfile +++ b/packages/app/Dockerfile @@ -43,9 +43,15 @@ COPY --from=common-utils-builder /app/packages/common-utils ./packages/common-ut # Expose custom env variables to the browser (needs NEXT_PUBLIC_ prefix) # doc: https://nextjs.org/docs/pages/building-your-application/configuring/environment-variables#bundling-environment-variables-for-the-browser ARG OTEL_EXPORTER_OTLP_ENDPOINT +ARG OTEL_EXPORTER_OTLP_TRACES_ENDPOINT +ARG OTEL_EXPORTER_OTLP_METRICS_ENDPOINT +ARG OTEL_EXPORTER_OTLP_LOGS_ENDPOINT ARG OTEL_SERVICE_NAME ARG IS_LOCAL_MODE ENV NEXT_PUBLIC_OTEL_EXPORTER_OTLP_ENDPOINT $OTEL_EXPORTER_OTLP_ENDPOINT +ENV NEXT_PUBLIC_OTEL_EXPORTER_OTLP_TRACES_ENDPOINT $OTEL_EXPORTER_OTLP_TRACES_ENDPOINT +ENV NEXT_PUBLIC_OTEL_EXPORTER_OTLP_METRICS_ENDPOINT $OTEL_EXPORTER_OTLP_METRICS_ENDPOINT +ENV NEXT_PUBLIC_OTEL_EXPORTER_OTLP_LOGS_ENDPOINT $OTEL_EXPORTER_OTLP_LOGS_ENDPOINT ENV NEXT_PUBLIC_OTEL_SERVICE_NAME $OTEL_SERVICE_NAME ENV NEXT_PUBLIC_IS_LOCAL_MODE $IS_LOCAL_MODE diff --git a/packages/app/pages/_app.tsx b/packages/app/pages/_app.tsx index ca8fc72e65..12f1bf5c69 100644 --- a/packages/app/pages/_app.tsx +++ b/packages/app/pages/_app.tsx @@ -7,7 +7,6 @@ import { env } from 'next-runtime-env'; import randomUUID from 'crypto-randomuuid'; import { enableMapSet } from 'immer'; import { QueryParamProvider } from 'use-query-params'; -import HyperDX from '@hyperdx/browser'; import { MutationCache, QueryCache, @@ -24,6 +23,7 @@ import { MANTINE_FONT_MAP, } from '@/config/fonts'; import { ibmPlexMono, inter, roboto, robotoMono } from '@/fonts'; +import HyperDX from '@/hdx-browser'; import { AppThemeProvider, useAppTheme } from '@/theme/ThemeProvider'; import { ThemeWrapper } from '@/ThemeWrapper'; import { NextApiConfigResponseData } from '@/types'; @@ -155,6 +155,8 @@ export default function MyApp({ Component, pageProps }: AppPropsWithLayout) { service: _jsonData.serviceName, // tracePropagationTargets: [new RegExp(hostname ?? 'localhost', 'i')], url: _jsonData.collectorUrl, + tracesUrl: _jsonData.collectorTracesUrl, + logsUrl: _jsonData.collectorLogsUrl, }); } else { console.warn('No API key found to enable OTEL exporter'); diff --git a/packages/app/pages/api/config.ts b/packages/app/pages/api/config.ts index c0419bea98..cf17d1c925 100644 --- a/packages/app/pages/api/config.ts +++ b/packages/app/pages/api/config.ts @@ -4,7 +4,10 @@ import { HDX_API_KEY, HDX_COLLECTOR_URL, HDX_EXPORTER_ENABLED, + HDX_LOGS_COLLECTOR_URL, + HDX_METRICS_COLLECTOR_URL, HDX_SERVICE_NAME, + HDX_TRACES_COLLECTOR_URL, } from '@/config'; import type { NextApiConfigResponseData } from '@/types'; @@ -15,6 +18,9 @@ export default function handler( res.status(200).json({ apiKey: HDX_EXPORTER_ENABLED ? HDX_API_KEY : undefined, collectorUrl: HDX_COLLECTOR_URL, + collectorTracesUrl: HDX_TRACES_COLLECTOR_URL, + collectorMetricsUrl: HDX_METRICS_COLLECTOR_URL, + collectorLogsUrl: HDX_LOGS_COLLECTOR_URL, serviceName: HDX_SERVICE_NAME, appVersion: process.env.NEXT_PUBLIC_APP_VERSION, }); diff --git a/packages/app/src/components/AppNav/AppNav.tsx b/packages/app/src/components/AppNav/AppNav.tsx index a703ec90bb..23c81aa45f 100644 --- a/packages/app/src/components/AppNav/AppNav.tsx +++ b/packages/app/src/components/AppNav/AppNav.tsx @@ -2,7 +2,6 @@ import { useCallback, useEffect, useMemo } from 'react'; import Link from 'next/link'; import Router, { useRouter } from 'next/router'; import cx from 'classnames'; -import HyperDX from '@hyperdx/browser'; import { isBuilderSavedChartConfig } from '@hyperdx/common-utils/dist/guards'; import { AlertState, @@ -36,6 +35,7 @@ import { AlertStatusIcon } from '@/components/AlertStatusIcon'; import { IS_LOCAL_MODE } from '@/config'; import { Dashboard, useDashboards } from '@/dashboard'; import { useFavorites } from '@/favorites'; +import HyperDX from '@/hdx-browser'; import InstallInstructionModal from '@/InstallInstructionsModal'; import OnboardingChecklist from '@/OnboardingChecklist'; import { useSavedSearches } from '@/savedSearch'; diff --git a/packages/app/src/config.ts b/packages/app/src/config.ts index 653aed63d7..48db45a017 100644 --- a/packages/app/src/config.ts +++ b/packages/app/src/config.ts @@ -37,6 +37,15 @@ export const HDX_COLLECTOR_URL = process.env.NEXT_PUBLIC_OTEL_EXPORTER_OTLP_ENDPOINT ?? process.env.OTEL_EXPORTER_OTLP_ENDPOINT ?? 'http://localhost:4318'; +export const HDX_TRACES_COLLECTOR_URL = + process.env.NEXT_PUBLIC_OTEL_EXPORTER_OTLP_TRACES_ENDPOINT ?? + process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT; +export const HDX_METRICS_COLLECTOR_URL = + process.env.NEXT_PUBLIC_OTEL_EXPORTER_OTLP_METRICS_ENDPOINT ?? + process.env.OTEL_EXPORTER_OTLP_METRICS_ENDPOINT; +export const HDX_LOGS_COLLECTOR_URL = + process.env.NEXT_PUBLIC_OTEL_EXPORTER_OTLP_LOGS_ENDPOINT ?? + process.env.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT; export const IS_DEV = NODE_ENV === 'development'; export const IS_OSS = process.env.NEXT_PUBLIC_IS_OSS ?? 'true' === 'true'; diff --git a/packages/app/src/hdx-browser.ts b/packages/app/src/hdx-browser.ts new file mode 100644 index 0000000000..c8a735e003 --- /dev/null +++ b/packages/app/src/hdx-browser.ts @@ -0,0 +1,134 @@ +import Rum from '@hyperdx/otel-web'; +import SessionRecorder from '@hyperdx/otel-web-session-recorder'; +import type { Attributes } from '@opentelemetry/api'; +import type { ResourceAttributes } from '@opentelemetry/resources'; + +type InitConfig = { + advancedNetworkCapture?: boolean; + apiKey: string; + blockClass?: string; + consoleCapture?: boolean; + debug?: boolean; + disableReplay?: boolean; + ignoreClass?: string; + maskAllInputs?: boolean; + maskAllText?: boolean; + maskClass?: string; + recordCanvas?: boolean; + sampling?: Parameters[0]['sampling']; + service: string; + tracePropagationTargets?: (string | RegExp)[]; + url?: string; + tracesUrl?: string; + logsUrl?: string; + otelResourceAttributes?: ResourceAttributes; +}; + +const DEFAULT_URL = 'https://in-otel.hyperdx.io'; + +let _advancedNetworkCapture = false; + +function init({ + advancedNetworkCapture = false, + apiKey, + blockClass, + consoleCapture = false, + debug = false, + disableReplay = false, + ignoreClass, + maskAllInputs = true, + maskAllText = false, + maskClass, + recordCanvas = false, + sampling, + service, + tracePropagationTargets, + url, + tracesUrl, + logsUrl, + otelResourceAttributes, +}: InitConfig): void { + if (typeof window === 'undefined') { + return; + } + + const urlBase = url ?? DEFAULT_URL; + const resolvedTracesUrl = tracesUrl ?? `${urlBase}/v1/traces`; + const resolvedLogsUrl = logsUrl ?? `${urlBase}/v1/logs`; + + _advancedNetworkCapture = advancedNetworkCapture; + + Rum.init({ + debug, + url: resolvedTracesUrl, + allowInsecureUrl: true, + apiKey, + applicationName: service, + globalAttributes: otelResourceAttributes as Attributes | undefined, + instrumentations: { + visibility: true, + console: consoleCapture, + fetch: { + ...(tracePropagationTargets != null + ? { propagateTraceHeaderCorsUrls: tracePropagationTargets } + : {}), + advancedNetworkCapture: () => _advancedNetworkCapture, + }, + xhr: { + ...(tracePropagationTargets != null + ? { propagateTraceHeaderCorsUrls: tracePropagationTargets } + : {}), + advancedNetworkCapture: () => _advancedNetworkCapture, + }, + }, + }); + + if (!disableReplay) { + SessionRecorder.init({ + apiKey, + blockClass, + debug, + ignoreClass, + maskAllInputs, + maskTextClass: maskClass, + maskTextSelector: maskAllText ? '*' : undefined, + recordCanvas, + sampling, + url: resolvedLogsUrl, + }); + } +} + +function addAction(name: string, attributes?: Attributes): void { + if (typeof window === 'undefined') return; + Rum.addAction(name, attributes); +} + +function setGlobalAttributes(attributes: Record): void { + if (typeof window === 'undefined') return; + Rum.setGlobalAttributes(attributes); +} + +function enableAdvancedNetworkCapture(): void { + _advancedNetworkCapture = true; +} + +function getSessionId(): string | undefined { + return Rum.getSessionId(); +} + +function recordException(error: any, attributes?: Attributes): void { + if (typeof window === 'undefined') return; + Rum.recordException(error, attributes); +} + +const HyperDXBrowser = { + init, + addAction, + setGlobalAttributes, + enableAdvancedNetworkCapture, + getSessionId, + recordException, +}; + +export default HyperDXBrowser; diff --git a/packages/app/src/types.ts b/packages/app/src/types.ts index c2ae60dcdf..9355205fac 100644 --- a/packages/app/src/types.ts +++ b/packages/app/src/types.ts @@ -139,6 +139,9 @@ export enum KubePhase { export type NextApiConfigResponseData = { apiKey?: string; collectorUrl: string; + collectorTracesUrl?: string; + collectorMetricsUrl?: string; + collectorLogsUrl?: string; serviceName: string; appVersion?: string; }; diff --git a/packages/app/styles/SearchPage.module.scss b/packages/app/styles/SearchPage.module.scss index 3e74bd07e0..30df4370a2 100644 --- a/packages/app/styles/SearchPage.module.scss +++ b/packages/app/styles/SearchPage.module.scss @@ -14,27 +14,25 @@ overflow: hidden; z-index: 3; // higher z-index to be above other elements - :global { - .mantine-TextInput-wrapper { - background-color: transparent; - } + :global(.mantine-TextInput-wrapper) { + background-color: transparent; + } - .mantine-TextInput-input { - height: 20px; - min-height: 20px; - background-color: transparent; - color: var(--color-text-secondary); - font-size: var(--mantine-font-size-xs); + :global(.mantine-TextInput-input) { + height: 20px; + min-height: 20px; + background-color: transparent; + color: var(--color-text-secondary); + font-size: var(--mantine-font-size-xs); - &::placeholder { - color: var(--color-text-muted); - font-weight: bold; - } + &::placeholder { + color: var(--color-text-muted); + font-weight: bold; + } - &:focus { - border: none; - outline: none; - } + &:focus { + border: none; + outline: none; } } }