diff --git a/.changeset/light-regions-brake.md b/.changeset/light-regions-brake.md new file mode 100644 index 000000000..7799471d0 --- /dev/null +++ b/.changeset/light-regions-brake.md @@ -0,0 +1,8 @@ +--- +'highlight.run': patch +'@launchdarkly/observability': patch +'@launchdarkly/session-replay': patch +--- + +remove verbosity of user instrumentation events by default. +only reports click, input, and submit window events as spans unless `otel.eventNames` is provided. diff --git a/e2e/angular/tsconfig.json b/e2e/angular/tsconfig.json index b4aef0e64..0471b0f12 100644 --- a/e2e/angular/tsconfig.json +++ b/e2e/angular/tsconfig.json @@ -2,6 +2,7 @@ { "compileOnSave": false, "compilerOptions": { + "skipLibCheck": true, "outDir": "./dist/out-tsc", "forceConsistentCasingInFileNames": true, "strict": true, diff --git a/e2e/react-router/package.json b/e2e/react-router/package.json index 53fef08a2..f300059ce 100644 --- a/e2e/react-router/package.json +++ b/e2e/react-router/package.json @@ -15,6 +15,7 @@ "@launchdarkly/observability": "workspace:*", "@launchdarkly/session-replay": "workspace:*", "launchdarkly-js-client-sdk": "^3.7.0", + "launchdarkly-react-client-sdk": "^3.7.0", "localforage": "^1.10.0", "match-sorter": "^6.3.1", "react": "^19.0.0", diff --git a/e2e/react-router/src/ldclient.tsx b/e2e/react-router/src/ldclient.tsx new file mode 100644 index 000000000..f24fa38c3 --- /dev/null +++ b/e2e/react-router/src/ldclient.tsx @@ -0,0 +1,33 @@ +// import { initialize as init } from '@launchdarkly/js-client-sdk' +import { initialize as init } from 'launchdarkly-js-client-sdk' +import Observability from '@launchdarkly/observability' +import SessionReplay from '@launchdarkly/session-replay' +// import { LD } from '@launchdarkly/browser' + +export const client = init( + '66d9d3c255856f0fa8fd62d0', + { key: 'unknown' }, + { + // Not including plugins at all would be equivalent to the current LaunchDarkly SDK. + plugins: [ + new Observability('1', { + networkRecording: { + enabled: true, + recordHeadersAndBody: true, + }, + serviceName: 'ryan-test', + backendUrl: 'https://pub.observability.ld-stg.launchdarkly.com', + otel: { + otlpEndpoint: + 'https://otel.observability.ld-stg.launchdarkly.com', + }, + }), + new SessionReplay('1', { + debug: { clientInteractions: true, domRecording: true }, + privacySetting: 'none', + serviceName: 'ryan-test', + backendUrl: 'https://pub.observability.ld-stg.launchdarkly.com', + }), // Could be omitted for customers who cannot use session replay. + ], + }, +) diff --git a/e2e/react-router/src/main.tsx b/e2e/react-router/src/main.tsx index 4f19bc16d..7f720f911 100644 --- a/e2e/react-router/src/main.tsx +++ b/e2e/react-router/src/main.tsx @@ -9,6 +9,7 @@ import { useRouteError, } from 'react-router-dom' import Root from './routes/root' +import Welcome from './routes/welcome' function rootAction() { const contact = { name: 'hello' } @@ -41,17 +42,20 @@ export function ErrorPage() { const router = createBrowserRouter( createRoutesFromElements( - } - loader={rootLoader} - action={rootAction} - ErrorBoundary={ErrorPage} - > - - } /> + <> + } + loader={rootLoader} + action={rootAction} + ErrorBoundary={ErrorPage} + > + + } /> + - , + } /> + , ), ) diff --git a/e2e/react-router/src/routes/root.tsx b/e2e/react-router/src/routes/root.tsx index 9e8b2479f..2d178c859 100644 --- a/e2e/react-router/src/routes/root.tsx +++ b/e2e/react-router/src/routes/root.tsx @@ -1,37 +1,7 @@ -// import { initialize as init } from '@launchdarkly/js-client-sdk' -import { initialize as init } from 'launchdarkly-js-client-sdk' -import Observability, { LDObserve } from '@launchdarkly/observability' -import SessionReplay, { LDRecord } from '@launchdarkly/session-replay' +import { LDObserve } from '@launchdarkly/observability' +import { LDRecord } from '@launchdarkly/session-replay' import { useEffect, useRef, useState } from 'react' -// import { LD } from '@launchdarkly/browser' - -const client = init( - '66d9d3c255856f0fa8fd62d0', - { key: 'unknown' }, - { - // Not including plugins at all would be equivalent to the current LaunchDarkly SDK. - plugins: [ - new Observability('1', { - networkRecording: { - enabled: true, - recordHeadersAndBody: true, - }, - serviceName: 'ryan-test', - backendUrl: 'https://pub.observability.ld-stg.launchdarkly.com', - otel: { - otlpEndpoint: - 'https://otel.observability.ld-stg.launchdarkly.com', - }, - }), - new SessionReplay('1', { - debug: { clientInteractions: true, domRecording: true }, - privacySetting: 'none', - serviceName: 'ryan-test', - backendUrl: 'https://pub.observability.ld-stg.launchdarkly.com', - }), // Could be omitted for customers who cannot use session replay. - ], - }, -) +import { client } from '../ldclient' export default function Root() { const fillColor = 'lightblue' diff --git a/e2e/react-router/src/routes/welcome.tsx b/e2e/react-router/src/routes/welcome.tsx new file mode 100644 index 000000000..4fac00271 --- /dev/null +++ b/e2e/react-router/src/routes/welcome.tsx @@ -0,0 +1,34 @@ +import { useEffect } from 'react' +import { useFlags, useLDClient } from 'launchdarkly-react-client-sdk' +import Observability from '@launchdarkly/observability' +import SessionReplay from '@launchdarkly/session-replay' + +function Welcome() { + const { devTestFlag, observabilityEnabled } = useFlags() + const client = useLDClient() + + useEffect(() => { + if (observabilityEnabled && client) { + const plugins = [ + new Observability(''), + new SessionReplay(''), + ] + plugins.forEach((plugin) => + plugin.register(client, { + sdk: { name: '', version: '' }, + clientSideId: '', + }), + ) + } + }, [observabilityEnabled, client]) + + return ( +

+ welcome to this page! +
+ {devTestFlag ? Flag on : Flag off} +

+ ) +} + +export default Welcome diff --git a/sdk/highlight-run/src/client/index.tsx b/sdk/highlight-run/src/client/index.tsx index fac6242ed..fcc4e8955 100644 --- a/sdk/highlight-run/src/client/index.tsx +++ b/sdk/highlight-run/src/client/index.tsx @@ -601,6 +601,7 @@ export class Highlight { serviceName: this.options?.serviceName ?? 'highlight-browser', instrumentations: this.options?.otel?.instrumentations, + eventNames: this.options?.otel?.eventNames, getIntegrations: () => [...this._integrations], }, sampler, diff --git a/sdk/highlight-run/src/client/otel/index.ts b/sdk/highlight-run/src/client/otel/index.ts index fc80cb08f..bad82edf8 100644 --- a/sdk/highlight-run/src/client/otel/index.ts +++ b/sdk/highlight-run/src/client/otel/index.ts @@ -57,6 +57,7 @@ import { LD_METRIC_NAME_DOCUMENT_LOAD } from '../../integrations/launchdarkly' import { ExportSampler } from './sampling/ExportSampler' import { getPersistentSessionSecureID } from '../utils/sessionStorage/highlightSession' +import type { EventName } from '@opentelemetry/instrumentation-user-interaction' export type Callback = (span?: Span) => any export type BrowserTracingConfig = { @@ -69,6 +70,7 @@ export type BrowserTracingConfig = { serviceName?: string tracingOrigins?: boolean | (string | RegExp)[] urlBlocklist?: string[] + eventNames?: EventName[] instrumentations?: OtelInstrumentatonOptions getIntegrations?: () => IntegrationClient[] } @@ -184,7 +186,11 @@ export const setupBrowserTracing = ( '@opentelemetry/instrumentation-user-interaction' ] if (userInteractionConfig !== false) { - instrumentations.push(new UserInteractionInstrumentation()) + instrumentations.push( + new UserInteractionInstrumentation({ + eventNames: config.eventNames, + }), + ) } if (config.networkRecordingOptions?.enabled) { diff --git a/sdk/highlight-run/src/client/otel/user-interaction.ts b/sdk/highlight-run/src/client/otel/user-interaction.ts index 93f95905c..f6a5514b6 100644 --- a/sdk/highlight-run/src/client/otel/user-interaction.ts +++ b/sdk/highlight-run/src/client/otel/user-interaction.ts @@ -15,6 +15,7 @@ import { AsyncTask } from '@opentelemetry/instrumentation-user-interaction/build const ZONE_CONTEXT_KEY = 'OT_ZONE_CONTEXT' const EVENT_NAVIGATION_NAME = 'Navigation:' +const DEFAULT_EVENT_NAMES = ['click', 'input', 'submit'] as const function defaultShouldPreventSpanCreation() { return false @@ -40,6 +41,7 @@ export class UserInteractionInstrumentation extends InstrumentationBase { Event, api.Span >() + private _eventNames: Set private _shouldPreventSpanCreation: ShouldPreventSpanCreation constructor(config: UserInteractionInstrumentationConfig = {}) { @@ -48,6 +50,9 @@ export class UserInteractionInstrumentation extends InstrumentationBase { UserInteractionInstrumentation.version, config, ) + this._eventNames = new Set( + (config?.eventNames ?? DEFAULT_EVENT_NAMES) as EventName[], + ) this._shouldPreventSpanCreation = typeof config?.shouldPreventSpanCreation === 'function' ? config.shouldPreventSpanCreation @@ -81,8 +86,8 @@ export class UserInteractionInstrumentation extends InstrumentationBase { /** * Controls whether or not to create a span, based on the event type. */ - protected _allowEventName(_: EventName): boolean { - return true + protected _allowEventName(eventName: EventName): boolean { + return this._eventNames.has(eventName) } /** diff --git a/sdk/highlight-run/src/client/types/client.ts b/sdk/highlight-run/src/client/types/client.ts index 4790c1449..4096b6b1c 100644 --- a/sdk/highlight-run/src/client/types/client.ts +++ b/sdk/highlight-run/src/client/types/client.ts @@ -1,4 +1,5 @@ import { RequestResponsePair } from '../listeners/network-listener/utils/models' +import type { EventName } from '@opentelemetry/instrumentation-user-interaction' export const ALL_CONSOLE_METHODS = [ 'assert', @@ -140,6 +141,7 @@ export type NetworkRecordingOptions = { export type OtelOptions = { instrumentations?: OtelInstrumentatonOptions + eventNames?: EventName[] } export type OtelInstrumentatonOptions = { diff --git a/sdk/highlight-run/src/client/types/observe.ts b/sdk/highlight-run/src/client/types/observe.ts index 99c823101..f5702f417 100644 --- a/sdk/highlight-run/src/client/types/observe.ts +++ b/sdk/highlight-run/src/client/types/observe.ts @@ -4,6 +4,7 @@ import type { OtelOptions, } from './client' import type { CommonOptions } from './types' +import type { EventName } from '@opentelemetry/instrumentation-user-interaction' export type ObserveOptions = CommonOptions & { /** @@ -60,5 +61,10 @@ export type ObserveOptions = CommonOptions & { * OTLP HTTP endpoint for OpenTelemetry tracing. */ otlpEndpoint?: string + /** + * User interaction instrumentation event names to record. + * Defaults to 'click', 'input', 'submit' window events. + */ + eventNames?: EventName[] } } diff --git a/sdk/highlight-run/src/plugins/observe.ts b/sdk/highlight-run/src/plugins/observe.ts index 1b03fb287..ea2a1bf4d 100644 --- a/sdk/highlight-run/src/plugins/observe.ts +++ b/sdk/highlight-run/src/plugins/observe.ts @@ -72,6 +72,7 @@ export class Observe extends Plugin implements LDPlugin { tracingOrigins: options?.tracingOrigins, serviceName: options?.serviceName ?? 'browser', instrumentations: options?.otel?.instrumentations, + eventNames: options?.otel?.eventNames, } this.observe = new ObserveSDK(clientOptions) LDObserve.load(this.observe) diff --git a/yarn.lock b/yarn.lock index 7433c32a2..8a845691d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -25426,7 +25426,7 @@ __metadata: languageName: unknown linkType: soft -"hoist-non-react-statics@npm:^3.3.0, hoist-non-react-statics@npm:^3.3.1": +"hoist-non-react-statics@npm:^3.3.0, hoist-non-react-statics@npm:^3.3.1, hoist-non-react-statics@npm:^3.3.2": version: 3.3.2 resolution: "hoist-non-react-statics@npm:3.3.2" dependencies: @@ -29024,6 +29024,20 @@ __metadata: languageName: node linkType: hard +"launchdarkly-react-client-sdk@npm:^3.7.0": + version: 3.7.0 + resolution: "launchdarkly-react-client-sdk@npm:3.7.0" + dependencies: + hoist-non-react-statics: "npm:^3.3.2" + launchdarkly-js-client-sdk: "npm:^3.7.0" + lodash.camelcase: "npm:^4.3.0" + peerDependencies: + react: ^16.6.3 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.4 || ^17.0.0 || ^18.0.0 || ^19.0.0 + checksum: 10/bff2dea3e49d7e99062f605727f991445a7e321debd6a52a567b5935c0eeb18de8511914547f0b49178a6d032424e5614b74c361c134e64cfc50a354dd0c26c7 + languageName: node + linkType: hard + "less-loader@npm:12.2.0": version: 12.2.0 resolution: "less-loader@npm:12.2.0" @@ -35611,6 +35625,7 @@ __metadata: eslint-plugin-react-hooks: "npm:^4.6.0" eslint-plugin-react-refresh: "npm:^0.4.1" launchdarkly-js-client-sdk: "npm:^3.7.0" + launchdarkly-react-client-sdk: "npm:^3.7.0" localforage: "npm:^1.10.0" match-sorter: "npm:^6.3.1" react: "npm:^19.0.0"