From ccc97f1b61f90c6e07154e205d79952fc579fae1 Mon Sep 17 00:00:00 2001 From: Seth Silesky <5115498+silesky@users.noreply.github.com> Date: Thu, 7 Nov 2024 17:31:08 -0600 Subject: [PATCH] Log signals if debug mode is on (#1184) --- .changeset/silly-papayas-buy.md | 5 ++ .../signals-example/src/lib/analytics.ts | 2 +- packages/signals/signals/README.md | 9 ++- .../signals/src/core/debug-mode/index.ts | 21 +++++- .../signals/signals/src/core/emitter/index.ts | 29 ++++++-- .../signals/src/core/signals/settings.ts | 74 +++++++------------ .../signals/src/core/signals/signals.ts | 14 +++- .../signals/signals/src/lib/logger/index.ts | 30 ++++++-- .../signals/src/lib/storage/debug-storage.ts | 29 ++++++++ .../signals/src/plugin/signals-plugin.ts | 17 ++++- .../signals/src/types/process-signal.ts | 2 +- 11 files changed, 166 insertions(+), 66 deletions(-) create mode 100644 .changeset/silly-papayas-buy.md create mode 100644 packages/signals/signals/src/lib/storage/debug-storage.ts diff --git a/.changeset/silly-papayas-buy.md b/.changeset/silly-papayas-buy.md new file mode 100644 index 000000000..73d1b1a62 --- /dev/null +++ b/.changeset/silly-papayas-buy.md @@ -0,0 +1,5 @@ +--- +'@segment/analytics-signals': minor +--- + +Update logging diff --git a/packages/signals/signals-example/src/lib/analytics.ts b/packages/signals/signals-example/src/lib/analytics.ts index 57e088043..821965552 100644 --- a/packages/signals/signals-example/src/lib/analytics.ts +++ b/packages/signals/signals-example/src/lib/analytics.ts @@ -33,7 +33,7 @@ const isStage = process.env.STAGE === 'true' const signalsPlugin = new SignalsPlugin({ ...(isStage ? { apiHost: 'signals.segment.build/v1' } : {}), - enableDebugLogging: true, + // enableDebugLogging: true, // processSignal: processSignalExample, }) diff --git a/packages/signals/signals/README.md b/packages/signals/signals/README.md index f63eeb0de..5ae77c2ce 100644 --- a/packages/signals/signals/README.md +++ b/packages/signals/signals/README.md @@ -77,6 +77,14 @@ You can *turn off debugging* by doing: https://my-website.com?segment_signals_debug=false ``` +* This also logs all signals to the js console. + +#### Alternative method of enabling debug mode +In your JS console: +```js +SegmentSignalsPlugin.debug() +``` + ### Advanced #### Listening to signals @@ -108,4 +116,3 @@ Network signals emit when an HTTP Request is made, or an HTTP Response is receiv - Initiated using the `fetch` API - First party domain (e.g if on `foo.com`, then `foo.com/api/products`, but not `bar.com/api/products`) - Contains the content-type: `application/json` - diff --git a/packages/signals/signals/src/core/debug-mode/index.ts b/packages/signals/signals/src/core/debug-mode/index.ts index ba3769efe..e0809d848 100644 --- a/packages/signals/signals/src/core/debug-mode/index.ts +++ b/packages/signals/signals/src/core/debug-mode/index.ts @@ -5,7 +5,26 @@ export const parseDebugModeQueryString = (): boolean | undefined => { const queryParams = new URLSearchParams(window.location.search) - const val = queryParams.get('segment_signals_debug') + const val = + queryParams.get('segment_signals_debug') || + queryParams.get('seg_signals_debug') + if (val === 'true' || val === 'false') { + return val === 'true' + } + return undefined +} + +/** + * This turns on advanced logging for signals! + */ +export const parseSignalsLoggingAdvancedQueryString = (): + | boolean + | undefined => { + const queryParams = new URLSearchParams(window.location.search) + + const val = + queryParams.get('segment_signals_logging_advanced') || + queryParams.get('seg_signals_logging_advanced') if (val === 'true' || val === 'false') { return val === 'true' } diff --git a/packages/signals/signals/src/core/emitter/index.ts b/packages/signals/signals/src/core/emitter/index.ts index 47959bdcc..cd7880639 100644 --- a/packages/signals/signals/src/core/emitter/index.ts +++ b/packages/signals/signals/src/core/emitter/index.ts @@ -6,21 +6,40 @@ export interface EmitSignal { emit: (signal: Signal) => void } +interface SignalEmitterSettings { + shouldLogSignals: () => boolean +} + export class SignalEmitter implements EmitSignal { private emitter = new Emitter<{ add: [Signal] }>() - + private listeners = new Set<(signal: Signal) => void>() + private settings?: SignalEmitterSettings + constructor(settings?: SignalEmitterSettings) { + this.settings = settings + } emit(signal: Signal) { - logger.debug('new signal emitted', signal) + if (this.settings?.shouldLogSignals()) { + logger.log('New signal:', signal.type, signal.data) + } this.emitter.emit('add', signal) } - subscribe(broadcaster: (signal: Signal) => void) { - logger.debug('subscribed') - this.emitter.on('add', broadcaster) + subscribe(listener: (signal: Signal) => void) { + // Prevent duplicate subscriptions + if (!this.listeners.has(listener)) { + logger.debug('subscribed') + this.listeners.add(listener) + } + this.emitter.on('add', listener) } unsubscribe(listener: (signal: Signal) => void) { + this.listeners.delete(listener) logger.debug('unsubscribed') this.emitter.off('add', listener) } + + once(listener: (signal: Signal) => void) { + this.emitter.once('add', listener) + } } diff --git a/packages/signals/signals/src/core/signals/settings.ts b/packages/signals/signals/src/core/signals/settings.ts index 6b6a96e3a..322cd5a02 100644 --- a/packages/signals/signals/src/core/signals/settings.ts +++ b/packages/signals/signals/src/core/signals/settings.ts @@ -5,6 +5,7 @@ import { SignalsIngestSettingsConfig } from '../client' import { SandboxSettingsConfig } from '../processor/sandbox' import { NetworkSettingsConfig } from '../signal-generators/network-gen' import { SignalsPluginSettingsConfig } from '../../types' +import { DebugStorage } from '../../lib/storage/debug-storage' export type SignalsSettingsConfig = Pick< SignalsPluginSettingsConfig, @@ -33,9 +34,9 @@ export class SignalGlobalSettings { signalBuffer: SignalBufferSettingsConfig ingestClient: SignalsIngestSettingsConfig network: NetworkSettingsConfig + signalsDebug: SignalsDebugSettings private sampleSuccess = false - private signalsDebug = new SignalsDebugSettings() constructor(settings: SignalsSettingsConfig) { if (settings.maxBufferSize && settings.signalStorage) { @@ -110,68 +111,49 @@ export class SignalGlobalSettings { } } -class SignalsDebugSettings { +export class SignalsDebugSettings { private static redactionKey = 'segment_signals_debug_redaction_disabled' private static ingestionKey = 'segment_signals_debug_ingestion_enabled' + private static logSignals = 'segment_signals_log_signals_enabled' + storage: DebugStorage + constructor(disableRedaction?: boolean, enableIngestion?: boolean) { + this.storage = new DebugStorage('sessionStorage') if (typeof disableRedaction === 'boolean') { - this.setDebugKey(SignalsDebugSettings.redactionKey, disableRedaction) + this.storage.setDebugKey( + SignalsDebugSettings.redactionKey, + disableRedaction + ) } if (typeof enableIngestion === 'boolean') { - this.setDebugKey(SignalsDebugSettings.ingestionKey, enableIngestion) + this.storage.setDebugKey( + SignalsDebugSettings.ingestionKey, + enableIngestion + ) } - // setting ?segment_signals_debug=true will disable redaction, enable ingestion, and set keys in local storage - // this setting will persist across page loads (even if there is no query string) - // in order to clear the setting, user must set ?segment_signals_debug=false const debugModeInQs = parseDebugModeQueryString() logger.debug('debugMode is set to true via query string') if (typeof debugModeInQs === 'boolean') { - this.setDebugKey(SignalsDebugSettings.redactionKey, debugModeInQs) - this.setDebugKey(SignalsDebugSettings.ingestionKey, debugModeInQs) + this.setAllDebugging(debugModeInQs) } } - setDebugKey(key: string, enable: boolean) { - try { - if (enable) { - window.sessionStorage.setItem(key, 'true') - } else { - logger.debug(`Removing debug key ${key} from storage`) - window.sessionStorage.removeItem(key) - } - } catch (e) { - logger.debug('Storage error', e) - } + setAllDebugging = (boolean: boolean) => { + this.storage.setDebugKey(SignalsDebugSettings.redactionKey, boolean) + this.storage.setDebugKey(SignalsDebugSettings.ingestionKey, boolean) + this.storage.setDebugKey(SignalsDebugSettings.logSignals, boolean) } - getDisableSignalsRedaction() { - try { - const isEnabled = Boolean( - window.sessionStorage.getItem(SignalsDebugSettings.redactionKey) - ) - if (isEnabled) { - logger.debug(`${SignalsDebugSettings.redactionKey}=true (app. storage)`) - return true - } - } catch (e) { - logger.debug('Storage error', e) - } - return false + getDisableSignalsRedaction = (): boolean => { + return this.storage.getDebugKey(SignalsDebugSettings.redactionKey) } - getEnableSignalsIngestion() { - try { - const isEnabled = Boolean( - window.sessionStorage.getItem(SignalsDebugSettings.ingestionKey) - ) - if (isEnabled) { - logger.debug(`${SignalsDebugSettings.ingestionKey}=true (app. storage)`) - return true - } - } catch (e) { - logger.debug('Storage error', e) - } - return false + getEnableSignalsIngestion = (): boolean => { + return this.storage.getDebugKey(SignalsDebugSettings.ingestionKey) + } + + getEnableLogSignals = (): boolean => { + return this.storage.getDebugKey(SignalsDebugSettings.logSignals) } } diff --git a/packages/signals/signals/src/core/signals/signals.ts b/packages/signals/signals/src/core/signals/signals.ts index 207ace283..26bb54274 100644 --- a/packages/signals/signals/src/core/signals/signals.ts +++ b/packages/signals/signals/src/core/signals/signals.ts @@ -38,7 +38,10 @@ export class Signals implements ISignals { private globalSettings: SignalGlobalSettings constructor(settingsConfig: SignalsSettingsConfig = {}) { this.globalSettings = new SignalGlobalSettings(settingsConfig) - this.signalEmitter = new SignalEmitter() + this.signalEmitter = new SignalEmitter({ + shouldLogSignals: () => + this.globalSettings.signalsDebug.getEnableLogSignals(), + }) this.signalsClient = new SignalsIngestClient( this.globalSettings.ingestClient ) @@ -130,7 +133,14 @@ export class Signals implements ISignals { } /** - * Emit custom signals. + * Disable redaction, ingestion of signals, and other debug logging. + */ + debug(boolean = true): void { + this.globalSettings.signalsDebug.setAllDebugging(boolean) + } + + /** + * Register custom signal generators to emit signals. */ async registerGenerator( generators: (SignalGeneratorClass | SignalGenerator)[] diff --git a/packages/signals/signals/src/lib/logger/index.ts b/packages/signals/signals/src/lib/logger/index.ts index a8e0923e8..9bb7c0367 100644 --- a/packages/signals/signals/src/lib/logger/index.ts +++ b/packages/signals/signals/src/lib/logger/index.ts @@ -1,15 +1,31 @@ +import { parseSignalsLoggingAdvancedQueryString } from '../../core/debug-mode' +import { DebugStorage } from '../storage/debug-storage' + class Logger { - globalKey = 'SEGMENT_SIGNALS_DEBUG' - get debugLoggingEnabled() { - return (window as any)[this.globalKey] === true + private static advancedLogging = 'segment_signals_logging_advanced' + + storage = new DebugStorage('sessionStorage') + constructor() { + const val = parseSignalsLoggingAdvancedQueryString() + if (typeof val === 'boolean') { + this.storage.setDebugKey(Logger.advancedLogging, val) + } + } + + private debugLoggingEnabled = (): boolean => { + return this.storage.getDebugKey(Logger.advancedLogging) + } + + enableDebugLogging = (bool = true) => { + this.storage.setDebugKey(Logger.advancedLogging, bool) } - enableDebugLogging() { - ;(window as any)[this.globalKey] = true + log = (...args: any[]): void => { + console.log('[signals log]', ...args) } - debug(...args: any[]): void { - if (this.debugLoggingEnabled) { + debug = (...args: any[]): void => { + if (this.debugLoggingEnabled()) { console.log('[signals debug]', ...args) } } diff --git a/packages/signals/signals/src/lib/storage/debug-storage.ts b/packages/signals/signals/src/lib/storage/debug-storage.ts new file mode 100644 index 000000000..55a88baa7 --- /dev/null +++ b/packages/signals/signals/src/lib/storage/debug-storage.ts @@ -0,0 +1,29 @@ +export class DebugStorage { + private storageType: 'localStorage' | 'sessionStorage' + constructor(type: 'localStorage' | 'sessionStorage') { + this.storageType = type + } + public setDebugKey = (key: string, enable: boolean): void => { + try { + if (enable) { + window[this.storageType].setItem(key, 'true') + } else { + window.sessionStorage.removeItem(key) + } + } catch (e) { + console.warn('Storage error', e) + } + } + + public getDebugKey = (key: string): boolean => { + try { + const isEnabled = Boolean(window[this.storageType].getItem(key)) + if (isEnabled) { + return true + } + } catch (e) { + console.warn('Storage error', e) + } + return false + } +} diff --git a/packages/signals/signals/src/plugin/signals-plugin.ts b/packages/signals/signals/src/plugin/signals-plugin.ts index 4992bd32a..c5f731fde 100644 --- a/packages/signals/signals/src/plugin/signals-plugin.ts +++ b/packages/signals/signals/src/plugin/signals-plugin.ts @@ -26,13 +26,19 @@ export class SignalsPlugin implements Plugin, SignalsAugmentedFunctionality { readonly name = 'SignalsPlugin' readonly version = version public signals: Signals - constructor(settings: SignalsPluginSettingsConfig = {}) { assertBrowserEnv() + + // assign to window for debugging purposes + Object.assign(window, { SegmentSignalsPlugin: this }) + if (settings.enableDebugLogging) { logger.enableDebugLogging() } - logger.debug('SignalsPlugin initializing', { settings }) + + logger.debug(`SignalsPlugin v${version} initializing`, { + settings, + }) this.signals = new Signals({ disableSignalsRedaction: settings.disableSignalsRedaction, @@ -79,4 +85,11 @@ export class SignalsPlugin implements Plugin, SignalsAugmentedFunctionality { this.signals.signalEmitter.emit(signal) return this } + + /** + * Enable redaction and disable ingestion of signals. Also, logs signals to the console. + */ + debug(boolean = true): void { + this.signals.debug(boolean) + } } diff --git a/packages/signals/signals/src/types/process-signal.ts b/packages/signals/signals/src/types/process-signal.ts index 906e17e7b..24d8b8cd4 100644 --- a/packages/signals/signals/src/types/process-signal.ts +++ b/packages/signals/signals/src/types/process-signal.ts @@ -18,7 +18,7 @@ export interface AnalyticsRuntimePublicApi { export type ProcessSignalScope = { analytics: AnalyticsRuntimePublicApi - signals: SignalsRuntime + signals: SignalsRuntime } & typeof WebRuntimeConstants export interface ProcessSignal {