Skip to content

Commit

Permalink
Log events from signals (#1186)
Browse files Browse the repository at this point in the history
  • Loading branch information
silesky authored Nov 12, 2024
1 parent 8616fc8 commit bf85047
Show file tree
Hide file tree
Showing 12 changed files with 177 additions and 96 deletions.
5 changes: 5 additions & 0 deletions .changeset/shiny-boats-warn.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@segment/analytics-signals': patch
---

Add signals logging for events
17 changes: 10 additions & 7 deletions packages/signals/signals/src/core/debug-mode/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,19 @@ export const parseDebugModeQueryString = (): boolean | undefined => {
/**
* This turns on advanced logging for signals!
*/
export const parseSignalsLoggingAdvancedQueryString = ():
| boolean
| undefined => {
export type LogLevelOptions = 'info' | 'debug' | 'off'
export const parseSignalsLogLevel = (): LogLevelOptions | 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'
queryParams.get('segment_signals_log_level') ||
queryParams.get('seg_signals_log_level')
if (val === 'info' || val === 'debug' || val === 'off') {
return val
} else if (typeof val === 'string') {
console.error(
`Invalid signals_log_level: "${val}". Valid options are: info, debug, off`
)
}
return undefined
}
13 changes: 2 additions & 11 deletions packages/signals/signals/src/core/emitter/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,12 @@ 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) {
if (this.settings?.shouldLogSignals()) {
logger.log('New signal:', signal.type, signal.data)
}
logger.info('New signal:', signal.type, signal.data)
this.emitter.emit('add', signal)
}

Expand Down
3 changes: 2 additions & 1 deletion packages/signals/signals/src/core/processor/processor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ export class SignalEventProcessor {
const name = methodName as MethodName
const eventsCollection = analyticsMethodCalls[name]
eventsCollection.forEach((args) => {
logger.debug(`analytics.${name}(...) called with args`, args)
logger.info('New method call:', `analytics.${name}()`, args)

// @ts-ignore
this.analytics[name](...args)
})
Expand Down
33 changes: 12 additions & 21 deletions packages/signals/signals/src/core/signals/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +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'
import { WebStorage } from '../../lib/storage/web-storage'

export type SignalsSettingsConfig = Pick<
SignalsPluginSettingsConfig,
Expand Down Expand Up @@ -114,22 +114,14 @@ export class SignalGlobalSettings {
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
private storage = new WebStorage(window.sessionStorage)

constructor(disableRedaction?: boolean, enableIngestion?: boolean) {
this.storage = new DebugStorage('sessionStorage')
if (typeof disableRedaction === 'boolean') {
this.storage.setDebugKey(
SignalsDebugSettings.redactionKey,
disableRedaction
)
this.storage.setItem(SignalsDebugSettings.redactionKey, disableRedaction)
}
if (typeof enableIngestion === 'boolean') {
this.storage.setDebugKey(
SignalsDebugSettings.ingestionKey,
enableIngestion
)
this.storage.setItem(SignalsDebugSettings.ingestionKey, enableIngestion)
}

const debugModeInQs = parseDebugModeQueryString()
Expand All @@ -140,20 +132,19 @@ export class SignalsDebugSettings {
}

setAllDebugging = (boolean: boolean) => {
this.storage.setDebugKey(SignalsDebugSettings.redactionKey, boolean)
this.storage.setDebugKey(SignalsDebugSettings.ingestionKey, boolean)
this.storage.setDebugKey(SignalsDebugSettings.logSignals, boolean)
this.storage.setItem(SignalsDebugSettings.redactionKey, boolean)
this.storage.setItem(SignalsDebugSettings.ingestionKey, boolean)
}

getDisableSignalsRedaction = (): boolean => {
return this.storage.getDebugKey(SignalsDebugSettings.redactionKey)
return (
this.storage.getItem<boolean>(SignalsDebugSettings.redactionKey) ?? false
)
}

getEnableSignalsIngestion = (): boolean => {
return this.storage.getDebugKey(SignalsDebugSettings.ingestionKey)
}

getEnableLogSignals = (): boolean => {
return this.storage.getDebugKey(SignalsDebugSettings.logSignals)
return (
this.storage.getItem<boolean>(SignalsDebugSettings.ingestionKey) ?? false
)
}
}
11 changes: 5 additions & 6 deletions packages/signals/signals/src/core/signals/signals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { SignalEventProcessor } from '../processor/processor'
import { Sandbox, SandboxSettings } from '../processor/sandbox'
import { SignalGlobalSettings, SignalsSettingsConfig } from './settings'
import { logger } from '../../lib/logger'
import { LogLevelOptions } from '../debug-mode'

interface ISignals {
start(analytics: AnyAnalytics): Promise<void>
Expand All @@ -38,10 +39,7 @@ export class Signals implements ISignals {
private globalSettings: SignalGlobalSettings
constructor(settingsConfig: SignalsSettingsConfig = {}) {
this.globalSettings = new SignalGlobalSettings(settingsConfig)
this.signalEmitter = new SignalEmitter({
shouldLogSignals: () =>
this.globalSettings.signalsDebug.getEnableLogSignals(),
})
this.signalEmitter = new SignalEmitter()
this.signalsClient = new SignalsIngestClient(
this.globalSettings.ingestClient
)
Expand Down Expand Up @@ -136,10 +134,11 @@ export class Signals implements ISignals {
}

/**
* Disable redaction, ingestion of signals, and other debug logging.
* Disable redaction, ingestion of signals, and other logging.
*/
debug(boolean = true): void {
debug(boolean = true, logLevel?: LogLevelOptions): void {
this.globalSettings.signalsDebug.setAllDebugging(boolean)
logger.enableLogging(logLevel ?? 'info')
}

/**
Expand Down
49 changes: 34 additions & 15 deletions packages/signals/signals/src/lib/logger/index.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,51 @@
import { parseSignalsLoggingAdvancedQueryString } from '../../core/debug-mode'
import { DebugStorage } from '../storage/debug-storage'
import {
LogLevelOptions,
parseDebugModeQueryString,
parseSignalsLogLevel,
} from '../../core/debug-mode'
import { WebStorage } from '../storage/web-storage'

class Logger {
private static advancedLogging = 'segment_signals_logging_advanced'
private logLevelKey = 'segment_signals_log_level'
private storage = new WebStorage(window.sessionStorage)
get logLevel(): LogLevelOptions {
return this.storage.getItem(this.logLevelKey) ?? 'off'
}

storage = new DebugStorage('sessionStorage')
constructor() {
const val = parseSignalsLoggingAdvancedQueryString()
if (typeof val === 'boolean') {
this.storage.setDebugKey(Logger.advancedLogging, val)
// if log level is set in query string, use that, otherwise if debug mode is set, set log level to info
const logLevel = parseSignalsLogLevel()
if (logLevel !== undefined) {
logLevel === 'off' ? this.disableLogging() : this.enableLogging(logLevel)
} else {
const debugMode = parseDebugModeQueryString()
if (debugMode === true) {
this.enableLogging('info')
}
}
}

private debugLoggingEnabled = (): boolean => {
return this.storage.getDebugKey(Logger.advancedLogging)
enableLogging = (type: LogLevelOptions) => {
this.storage.setItem(this.logLevelKey, type)
}

disableLogging = () => {
this.storage.setItem(this.logLevelKey, 'off')
}

enableDebugLogging = (bool = true) => {
this.storage.setDebugKey(Logger.advancedLogging, bool)
private log = (level: 'info' | 'debug', ...args: any[]): void => {
console.log(`[signals:${level}]`, ...args)
}

log = (...args: any[]): void => {
console.log('[signals log]', ...args)
info = (...args: any[]): void => {
if (this.logLevel === 'info' || this.logLevel === 'debug') {
this.log('info', ...args)
}
}

debug = (...args: any[]): void => {
if (this.debugLoggingEnabled()) {
console.log('[signals debug]', ...args)
if (this.logLevel === 'debug') {
this.log('debug', ...args)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { setLocation } from '../../test-helpers/set-location'
import { normalizeUrl } from '../normalize-url'
import { setLocation } from '../../../test-helpers/set-location'
import { normalizeUrl } from '..'

describe('normalizeUrl', () => {
beforeEach(() => {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { WebStorage } from '../web-storage'

describe('WebStorage', () => {
let webStorage: WebStorage

beforeEach(() => {
webStorage = new WebStorage(sessionStorage)
})
afterEach(() => {
sessionStorage.clear()
})
describe('getItem, setItem', () => {
it('should retrieve and parse a stored value from storage', () => {
const key = 'testKey'
const value = { foo: 'bar' }

webStorage.setItem(key, value)

const result = webStorage.getItem<typeof value>(key)

expect(result).toEqual(value)
})

it('should return undefined if the key does not exist in storage', () => {
const key = 'nonexistentKey'

const result = webStorage.getItem(key)
expect(result).toBeUndefined()
})

it('should handle JSON serializing errors gracefully when setting', () => {
const key = 'testKey'
const consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation()

webStorage.setItem(
key,
// @ts-ignore - intentational non-serializable value
BigInt(1)
)

expect(consoleWarnSpy).toHaveBeenCalledWith(
'Storage set error',
expect.any(Object),
expect.any(Error)
)
})

it('should handle JSON parsing errors gracefully when retrieving', () => {
const key = 'testKey'
const consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation()

// if somehow invalid JSON is stored in the storage
sessionStorage.setItem(key, 'invalid JSON')

const result = webStorage.getItem(key)

expect(result).toBeUndefined()
expect(consoleWarnSpy).toHaveBeenCalledWith(
'Storage retrieval error',
expect.any(Object),
expect.any(Error)
)
})
})
})
29 changes: 0 additions & 29 deletions packages/signals/signals/src/lib/storage/debug-storage.ts

This file was deleted.

37 changes: 37 additions & 0 deletions packages/signals/signals/src/lib/storage/web-storage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
export class WebStorage {
private storage: Storage
constructor(storage: Storage) {
this.storage = storage
}

/**
* Set a json-parsable item in storage
*/
public setItem = <T extends string | number | boolean | object>(
key: string,
value: T
): void => {
try {
const item = JSON.stringify(value)
this.storage.setItem(key, item)
} catch (e) {
console.warn('Storage set error', { key, value }, e)
}
}

/**
* Get a json-parsed item from storage
*/
public getItem = <T>(key: string): T | undefined => {
try {
const item = this.storage.getItem(key)
if (item === null) {
return undefined
}
return JSON.parse(item) as T
} catch (e) {
console.warn('Storage retrieval error', { key }, e)
}
return undefined
}
}
7 changes: 3 additions & 4 deletions packages/signals/signals/src/plugin/signals-plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,11 @@ export class SignalsPlugin implements Plugin, SignalsAugmentedFunctionality {
public signals: Signals
constructor(settings: SignalsPluginSettingsConfig = {}) {
assertBrowserEnv()

// assign to window for debugging purposes
Object.assign(window, { SegmentSignalsPlugin: this })

if (settings.enableDebugLogging) {
logger.enableDebugLogging()
logger.enableLogging('debug')
}

logger.debug(`SignalsPlugin v${version} initializing`, {
Expand Down Expand Up @@ -89,7 +88,7 @@ export class SignalsPlugin implements Plugin, SignalsAugmentedFunctionality {
/**
* Enable redaction and disable ingestion of signals. Also, logs signals to the console.
*/
debug(boolean = true): void {
this.signals.debug(boolean)
debug(...args: Parameters<typeof this.signals['debug']>): void {
this.signals.debug(...args)
}
}

0 comments on commit bf85047

Please sign in to comment.