diff --git a/.changeset/three-lines-relate.md b/.changeset/three-lines-relate.md new file mode 100644 index 000000000..476ff2989 --- /dev/null +++ b/.changeset/three-lines-relate.md @@ -0,0 +1,8 @@ +--- +'highlight.run': minor +--- + +Set up new data emission in highlight SDK from launchdarkly SDK. +* adds H.log function to report custom logs from the highlight SDK +* use traces instead of track events from LD track calls +* record logs for identify integration from LD identify calls diff --git a/sdk/highlight-run/src/client/index.tsx b/sdk/highlight-run/src/client/index.tsx index 303f4496d..97bb743db 100644 --- a/sdk/highlight-run/src/client/index.tsx +++ b/sdk/highlight-run/src/client/index.tsx @@ -107,10 +107,17 @@ import type { HighlightClientRequestWorker } from './workers/highlight-client-wo import HighlightClientWorker from './workers/highlight-client-worker?worker&inline' import { MessageType, PropertyType } from './workers/types' import { parseError } from './utils/errors' -import { Counter, Gauge, Histogram, UpDownCounter } from '@opentelemetry/api' +import { + Attributes, + Counter, + Gauge, + Histogram, + UpDownCounter, +} from '@opentelemetry/api' import { IntegrationClient } from '../integrations' import { LaunchDarklyIntegration } from '../integrations/launchdarkly' import { LDClientMin } from '../integrations/launchdarkly/types/LDClient' +import { createLog, defaultLogOptions } from './listeners/console-listener' export const HighlightWarning = (context: string, msg: any) => { console.warn(`Highlight Warning: (${context}): `, { output: msg }) @@ -476,6 +483,12 @@ export class Highlight { } } + log(message: any, level: string, metadata?: Attributes) { + this._firstLoadListeners.messages.push( + createLog(level, defaultLogOptions, message, metadata), + ) + } + pushCustomError(message: string, payload?: string) { return this.consumeCustomError(new Error(message), undefined, payload) } diff --git a/sdk/highlight-run/src/client/listeners/console-listener.tsx b/sdk/highlight-run/src/client/listeners/console-listener.tsx index 92ed3eb0a..d9d0de9f5 100644 --- a/sdk/highlight-run/src/client/listeners/console-listener.tsx +++ b/sdk/highlight-run/src/client/listeners/console-listener.tsx @@ -1,5 +1,5 @@ import type { StackFrame } from 'error-stack-parser' -import { ConsoleMethods } from '../types/client' +import { ALL_CONSOLE_METHODS, ConsoleMethods } from '../types/client' import { ConsoleMessage } from '../types/shared-types' import { patch, stringify } from '../utils/utils' import { parseError } from '../utils/errors' @@ -51,6 +51,16 @@ export type Logger = { warn?: typeof console.warn } +export const defaultLogOptions: LogRecordOptions = { + level: [...ALL_CONSOLE_METHODS], + logger: 'console', + stringifyOptions: { + depthOfLimit: 10, + numOfKeysLimit: 100, + stringLengthLimit: 1000, + }, +} + export function ConsoleListener( callback: (c: ConsoleMessage) => void, logOptions: LogRecordOptions, @@ -115,28 +125,7 @@ export function ConsoleListener( // @ts-expect-error original.apply(this, data) try { - const trace = parseError(new Error()) - const message = logOptions.serializeConsoleAttributes - ? data.map((o) => - typeof o === 'object' - ? stringify(o, logOptions.stringifyOptions) - : o, - ) - : data - .filter((o) => typeof o !== 'object') - .map((o) => `${o}`) - callback({ - type: level, - trace: trace.slice(1), - value: message, - attributes: stringify( - data - .filter((d) => typeof d === 'object') - .reduce((a, b) => ({ ...a, ...b }), {}), - logOptions.stringifyOptions, - ), - time: Date.now(), - }) + callback(createLog(level, logOptions, data)) } catch (error) { original('highlight logger error:', error, ...data) } @@ -144,3 +133,30 @@ export function ConsoleListener( }) } } + +export function createLog( + level: string, + logOptions: LogRecordOptions, + ...data: Array +) { + const trace = parseError(new Error()) + const message = logOptions.serializeConsoleAttributes + ? data.map((o) => + typeof o === 'object' + ? stringify(o, logOptions.stringifyOptions) + : o, + ) + : data.filter((o) => typeof o !== 'object').map((o) => `${o}`) + return { + type: level, + trace: trace.slice(1), + value: message, + attributes: stringify( + data + .filter((d) => typeof d === 'object') + .reduce((a, b) => ({ ...a, ...b }), {}), + logOptions.stringifyOptions, + ), + time: Date.now(), + } +} diff --git a/sdk/highlight-run/src/client/types/types.ts b/sdk/highlight-run/src/client/types/types.ts index 9397bb159..0b781f73c 100644 --- a/sdk/highlight-run/src/client/types/types.ts +++ b/sdk/highlight-run/src/client/types/types.ts @@ -1,4 +1,4 @@ -import type { Context, Span, SpanOptions } from '@opentelemetry/api' +import { Attributes, Context, Span, SpanOptions } from '@opentelemetry/api' import { ConsoleMethods, DebugOptions, @@ -288,6 +288,7 @@ export declare interface HighlightPublicInterface { * @param metadata Additional details you want to associate to the event. */ track: (event: string, metadata?: Metadata) => void + log: (message: any, level: string, metadata?: Attributes) => void /** * @deprecated with replacement by `consumeError` for an in-app stacktrace. */ diff --git a/sdk/highlight-run/src/index.tsx b/sdk/highlight-run/src/index.tsx index a8017c7a0..f2d9b5aaf 100644 --- a/sdk/highlight-run/src/index.tsx +++ b/sdk/highlight-run/src/index.tsx @@ -28,7 +28,13 @@ import { loadCookieSessionData, } from './client/utils/sessionStorage/highlightSession.js' import { setCookieWriteEnabled } from './client/utils/storage' -import { Context, Span, SpanOptions, Tracer } from '@opentelemetry/api' +import { + Attributes, + Context, + Span, + SpanOptions, + Tracer, +} from '@opentelemetry/api' import firstloadVersion from './__generated/version.js' import { listenToChromeExtensionMessage } from './browserExtension/extensionListener.js' import configureElectronHighlight from './environments/electron.js' @@ -263,9 +269,13 @@ const H: HighlightPublicInterface = { }, track: (event: string, metadata: Metadata = {}) => { try { - H.onHighlightReady(() => - highlight_obj.addProperties({ ...metadata, event: event }), - ) + H.onHighlightReady(() => { + highlight_obj.addProperties({ ...metadata, event: event }) + highlight_obj.log('H.track', 'INFO', { + ...(metadata ?? {}), + event, + }) + }) const highlightUrl = highlight_obj?.getCurrentSessionURL() if (!H.options?.integrations?.mixpanel?.disabled) { @@ -295,6 +305,15 @@ const H: HighlightPublicInterface = { HighlightWarning('track', e) } }, + log: (message: any, level: string, metadata?: Attributes) => { + try { + H.onHighlightReady(() => + highlight_obj.log(message, level, metadata ?? {}), + ) + } catch (e) { + HighlightWarning('log', e) + } + }, start: (options) => { if (highlight_obj?.state === 'Recording' && !options?.forceNew) { if (!options?.silent) { diff --git a/sdk/highlight-run/src/integrations/launchdarkly/index.ts b/sdk/highlight-run/src/integrations/launchdarkly/index.ts index 57e7478cc..7412a66b3 100644 --- a/sdk/highlight-run/src/integrations/launchdarkly/index.ts +++ b/sdk/highlight-run/src/integrations/launchdarkly/index.ts @@ -6,7 +6,6 @@ import { IdentifySeriesData, IdentifySeriesResult, } from './types/Hooks' -import { trace } from '@opentelemetry/api' import { type HighlightPublicInterface, MetricCategory } from '../../client' import type { ErrorMessage, Source } from '../../client/types/shared-types' import type { IntegrationClient } from '../index' @@ -66,9 +65,16 @@ export function setupLaunchDarklyIntegration( data: IdentifySeriesData, _result: IdentifySeriesResult, ) => { + hClient.log('LD.identify', 'INFO', { + key: getCanonicalKey(hookContext.context), + timeout: hookContext.timeout, + }) hClient.identify( getCanonicalKey(hookContext.context), - hookContext.context, + { + key: getCanonicalKey(hookContext.context), + timeout: hookContext.timeout, + }, 'LaunchDarkly', ) return data @@ -87,18 +93,11 @@ export function setupLaunchDarklyIntegration( getCanonicalKey(hookContext.context) } - let span = trace.getActiveSpan() - if (span) { - span.addEvent(FEATURE_FLAG_SCOPE, eventAttributes) - } else { - hClient.startSpan(FEATURE_FLAG_SPAN_NAME, (s) => { - if (s) { - s.addEvent(FEATURE_FLAG_SCOPE, eventAttributes) - } - }) - } - - hClient.track(FEATURE_FLAG_SPAN_NAME, eventAttributes) + hClient.startSpan(FEATURE_FLAG_SPAN_NAME, (s) => { + if (s) { + s.addEvent(FEATURE_FLAG_SCOPE, eventAttributes) + } + }) return data }, @@ -154,10 +153,6 @@ export class LaunchDarklyIntegration implements IntegrationClient { track(sessionSecureID: string, metadata: object) { const event = (metadata as unknown as { event?: string }).event - // skip integration hClient.track() calls - if (event === FEATURE_FLAG_SPAN_NAME) { - return - } this.client.track( event ? `${LD_TRACK_EVENT}:${event}` : LD_TRACK_EVENT, {