From 00a6a4addc8c59c8cf3d5be6bb5ee78ebea55c6e Mon Sep 17 00:00:00 2001 From: Ben White Date: Wed, 11 Oct 2023 11:00:19 +0200 Subject: [PATCH] feat: Move all logs everything over to logger (#830) --- .eslintrc.js | 3 +- react/src/context/PostHogProvider.tsx | 7 +- src/__tests__/decide.js | 6 +- src/__tests__/featureflags.js | 3 + src/__tests__/gdpr-utils.js | 134 ------------------ src/__tests__/posthog-core.identify.js | 6 +- src/__tests__/posthog-core.js | 9 +- src/__tests__/sessionid.js | 1 + src/__tests__/setup.js | 8 +- src/autocapture-utils.ts | 4 +- src/autocapture.ts | 2 +- src/decide.ts | 12 +- .../exceptions/exception-autocapture.ts | 8 +- src/extensions/segment-integration.ts | 3 +- src/extensions/sessionrecording.ts | 2 +- src/extensions/web-performance.ts | 8 +- src/gdpr-utils.ts | 42 +----- src/posthog-core.ts | 38 ++--- src/posthog-featureflags.ts | 6 +- src/rate-limiter.ts | 2 +- src/retry-queue.ts | 11 +- src/sessionid.ts | 11 +- src/storage.ts | 4 +- src/utils.ts | 89 +++++------- 24 files changed, 116 insertions(+), 303 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index f901c4877..96b381459 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -9,6 +9,7 @@ const rules = { '@typescript-eslint/no-unused-vars': ['error'], 'no-prototype-builtins': 'off', 'no-empty': 'off', + 'no-console': 'error', } const extend = [ @@ -51,7 +52,7 @@ module.exports = { // but excluding the 'plugin:compat/recommended' rule // we don't mind using the latest features in our tests extends: extend.filter((s) => s !== 'plugin:compat/recommended'), - rules, + rules: rules.filter((s) => s !== 'no-console'), }, ], root: true, diff --git a/react/src/context/PostHogProvider.tsx b/react/src/context/PostHogProvider.tsx index ca9d9c24d..9fb95b78c 100644 --- a/react/src/context/PostHogProvider.tsx +++ b/react/src/context/PostHogProvider.tsx @@ -1,3 +1,4 @@ +/* eslint-disable no-console */ import posthogJs, { PostHogConfig } from 'posthog-js' import React, { useMemo } from 'react' @@ -17,13 +18,13 @@ export function PostHogProvider({ const posthog = useMemo(() => { if (client && apiKey) { console.warn( - 'You have provided both a client and an apiKey to PostHogProvider. The apiKey will be ignored in favour of the client.' + '[PostHog.js] You have provided both a client and an apiKey to PostHogProvider. The apiKey will be ignored in favour of the client.' ) } if (client && options) { console.warn( - 'You have provided both a client and options to PostHogProvider. The options will be ignored in favour of the client.' + '[PostHog.js] You have provided both a client and options to PostHogProvider. The options will be ignored in favour of the client.' ) } @@ -33,7 +34,7 @@ export function PostHogProvider({ if (apiKey) { if (posthogJs.__loaded) { - console.warn('posthog was already loaded elsewhere. This may cause issues.') + console.warn('[PostHog.js] was already loaded elsewhere. This may cause issues.') } posthogJs.init(apiKey, options) } diff --git a/src/__tests__/decide.js b/src/__tests__/decide.js index ca88dfa4f..234f9601e 100644 --- a/src/__tests__/decide.js +++ b/src/__tests__/decide.js @@ -183,12 +183,13 @@ describe('Decide', () => { it('Make sure receivedFeatureFlags is not called if the decide response fails', () => { given('decideResponse', () => ({ status: 0 })) + window.POSTHOG_DEBUG = true console.error = jest.fn() given.subject() expect(given.posthog.featureFlags.receivedFeatureFlags).not.toHaveBeenCalled() - expect(console.error).toHaveBeenCalledWith('Failed to fetch feature flags from PostHog.') + expect(console.error).toHaveBeenCalledWith('[PostHog.js]', 'Failed to fetch feature flags from PostHog.') }) it('Make sure receivedFeatureFlags is called with empty if advanced_disable_feature_flags_on_first_load is set', () => { @@ -221,13 +222,14 @@ describe('Decide', () => { }) it('does not run site apps code if not opted in', () => { + window.POSTHOG_DEBUG = true given('config', () => ({ api_host: 'https://test.com', opt_in_site_apps: false, persistence: 'memory' })) given('decideResponse', () => ({ siteApps: [{ id: 1, url: '/site_app/1/tokentoken/hash/' }] })) expect(() => { given.subject() }).toThrow( // throwing only in tests, just an error in production - 'Unexpected console.error: PostHog site apps are disabled. Enable the "opt_in_site_apps" config to proceed.' + 'Unexpected console.error: [PostHog.js],PostHog site apps are disabled. Enable the "opt_in_site_apps" config to proceed.' ) }) }) diff --git a/src/__tests__/featureflags.js b/src/__tests__/featureflags.js index 26f6ec5e8..5e9f41d7a 100644 --- a/src/__tests__/featureflags.js +++ b/src/__tests__/featureflags.js @@ -67,6 +67,7 @@ describe('featureflags', () => { }) it('should warn if decide endpoint was not hit and no flags exist', () => { + window.POSTHOG_DEBUG = true given.featureFlags.instance.decideEndpointWasHit = false given.instance.persistence.unregister('$enabled_feature_flags') given.instance.persistence.unregister('$active_feature_flags') @@ -74,6 +75,7 @@ describe('featureflags', () => { expect(given.featureFlags.getFlags()).toEqual([]) expect(given.featureFlags.isFeatureEnabled('beta-feature')).toEqual(undefined) expect(window.console.warn).toHaveBeenCalledWith( + '[PostHog.js]', 'isFeatureEnabled for key "beta-feature" failed. Feature flags didn\'t load in time.' ) @@ -81,6 +83,7 @@ describe('featureflags', () => { expect(given.featureFlags.getFeatureFlag('beta-feature')).toEqual(undefined) expect(window.console.warn).toHaveBeenCalledWith( + '[PostHog.js]', 'getFeatureFlag for key "beta-feature" failed. Feature flags didn\'t load in time.' ) }) diff --git a/src/__tests__/gdpr-utils.js b/src/__tests__/gdpr-utils.js index df419bd17..27f7a709d 100644 --- a/src/__tests__/gdpr-utils.js +++ b/src/__tests__/gdpr-utils.js @@ -469,138 +469,4 @@ describe(`GDPR utils`, () => { }) }) }) - - describe(`addOptOutCheckPostHogLib`, () => { - const captureEventName = `єνєηт` - const captureProperties = { '𝖕𝖗𝖔𝖕𝖊𝖗𝖙𝖞': `𝓿𝓪𝓵𝓾𝓮` } - let capture, postHogLib - - function setupMocks(config, silenceErrors = false) { - capture = sinon.spy() - postHogLib = { - config, - capture: undefined, - } - postHogLib.capture = gdpr.addOptOutCheck(postHogLib, capture, silenceErrors) - } - - forPersistenceTypes(function (persistenceType) { - it(`should call the wrapped method if the user is neither opted in or opted out`, () => { - TOKENS.forEach((token) => { - setupMocks({ token, opt_out_capturing_persistence_type: persistenceType }) - - postHogLib.capture(captureEventName, captureProperties) - - expect(capture.calledOnceWith(captureEventName, captureProperties)).toBe(true) - }) - }) - - it(`should call the wrapped method if the user is opted in`, () => { - TOKENS.forEach((token) => { - setupMocks({ token, opt_out_capturing_persistence_type: persistenceType }) - - gdpr.optIn(token, { persistenceType }) - postHogLib.capture(captureEventName, captureProperties) - - expect(capture.calledOnceWith(captureEventName, captureProperties)).toBe(true) - }) - }) - - it(`should not call the wrapped method if the user is opted out`, () => { - TOKENS.forEach((token) => { - setupMocks({ token, opt_out_capturing_persistence_type: persistenceType }) - - gdpr.optOut(token, { persistenceType }) - postHogLib.capture(captureEventName, captureProperties) - - expect(capture.notCalled).toBe(true) - }) - }) - - it(`should not invoke the callback directly if the user is neither opted in or opted out`, () => { - TOKENS.forEach((token) => { - setupMocks({ token, opt_out_capturing_persistence_type: persistenceType }) - const callback = sinon.spy() - - postHogLib.capture(captureEventName, captureProperties, callback) - - expect(callback.notCalled).toBe(true) - }) - }) - - it(`should not invoke the callback directly if the user is opted in`, () => { - TOKENS.forEach((token) => { - setupMocks({ token, opt_out_capturing_persistence_type: persistenceType }) - const callback = sinon.spy() - - gdpr.optIn(token, { persistenceType }) - postHogLib.capture(captureEventName, captureProperties, callback) - - expect(callback.notCalled).toBe(true) - }) - }) - - it(`should invoke the callback directly if the user is opted out`, () => { - TOKENS.forEach((token) => { - setupMocks({ token, opt_out_capturing_persistence_type: persistenceType }) - const callback = sinon.spy() - - gdpr.optOut(token, { persistenceType }) - postHogLib.capture(captureEventName, captureProperties, callback) - - expect(callback.calledOnceWith(0)).toBe(true) - }) - }) - - it(`should call the wrapped method if there is no token available`, () => { - TOKENS.forEach((token) => { - setupMocks({ token: null, opt_out_capturing_persistence_type: persistenceType }) - - gdpr.optIn(token, { persistenceType }) - postHogLib.capture(captureEventName, captureProperties) - - expect(capture.calledOnceWith(captureEventName, captureProperties)).toBe(true) - }) - }) - - it(`should call the wrapped method if config is undefined`, () => { - TOKENS.forEach((token) => { - setupMocks(undefined, false) - console.error = jest.fn() - - gdpr.optIn(token, { persistenceType }) - postHogLib.capture(captureEventName, captureProperties) - - expect(capture.calledOnceWith(captureEventName, captureProperties)).toBe(true) - // :KLUDGE: Exact error message may vary between runtimes - expect(console.error).toHaveBeenCalled() - }) - }) - - it(`should allow use of a custom "persistence prefix" string`, () => { - TOKENS.forEach((token) => { - setupMocks({ - token, - opt_out_capturing_persistence_type: persistenceType, - opt_out_capturing_cookie_prefix: CUSTOM_PERSISTENCE_PREFIX, - }) - - gdpr.optOut(token, { persistenceType, persistencePrefix: CUSTOM_PERSISTENCE_PREFIX }) - postHogLib.capture(captureEventName, captureProperties) - - expect(capture.notCalled).toBe(true) - - gdpr.optIn(token, { persistenceType }) - postHogLib.capture(captureEventName, captureProperties) - - expect(capture.notCalled).toBe(true) - - gdpr.optIn(token, { persistenceType, persistencePrefix: CUSTOM_PERSISTENCE_PREFIX }) - postHogLib.capture(captureEventName, captureProperties) - - expect(capture.calledOnceWith(captureEventName, captureProperties)).toBe(true) - }) - }) - }) - }) }) diff --git a/src/__tests__/posthog-core.identify.js b/src/__tests__/posthog-core.identify.js index ee6b51c1c..6d7d62b42 100644 --- a/src/__tests__/posthog-core.identify.js +++ b/src/__tests__/posthog-core.identify.js @@ -225,11 +225,15 @@ describe('identify()', () => { it('does not update user', () => { console.error = jest.fn() + given.lib.debug() given.subject() expect(given.overrides.capture).not.toHaveBeenCalled() expect(given.overrides.register).not.toHaveBeenCalled() - expect(console.error).toHaveBeenCalledWith('Unique user id has not been set in posthog.identify') + expect(console.error).toHaveBeenCalledWith( + '[PostHog.js]', + 'Unique user id has not been set in posthog.identify' + ) }) }) diff --git a/src/__tests__/posthog-core.js b/src/__tests__/posthog-core.js index 3afe3a190..f92215eea 100644 --- a/src/__tests__/posthog-core.js +++ b/src/__tests__/posthog-core.js @@ -19,6 +19,7 @@ jest.mock('../utils', () => ({ given('lib', () => { const posthog = new PostHog() posthog._init('testtoken', given.config, 'testhog') + posthog.debug() return Object.assign(posthog, given.overrides) }) @@ -111,7 +112,7 @@ describe('capture()', () => { expect(() => given.subject()).not.toThrow() expect(hook).not.toHaveBeenCalled() - expect(console.error).toHaveBeenCalledWith('No event name provided to posthog.capture') + expect(console.error).toHaveBeenCalledWith('[PostHog.js]', 'No event name provided to posthog.capture') }) it('errors with object event name', () => { @@ -123,7 +124,7 @@ describe('capture()', () => { expect(() => given.subject()).not.toThrow() expect(hook).not.toHaveBeenCalled() - expect(console.error).toHaveBeenCalledWith('No event name provided to posthog.capture') + expect(console.error).toHaveBeenCalledWith('[PostHog.js]', 'No event name provided to posthog.capture') }) it('truncates long properties', () => { @@ -509,6 +510,7 @@ describe('bootstrapping feature flags', () => { expect(given.lib.get_distinct_id()).not.toEqual(undefined) expect(given.lib.getFeatureFlag('multivariant')).toBe(undefined) expect(console.warn).toHaveBeenCalledWith( + '[PostHog.js]', expect.stringContaining('getFeatureFlag for key "multivariant" failed') ) expect(given.lib.getFeatureFlag('disabled')).toBe(undefined) @@ -861,6 +863,7 @@ describe('group()', () => { it('handles blank keys being passed', () => { window.console.error = jest.fn() + window.console.warn = jest.fn() given.lib.group(null, 'foo') given.lib.group('organization', null) @@ -931,7 +934,7 @@ describe('_loaded()', () => { given.subject() - expect(console.error).toHaveBeenCalledWith('`loaded` function failed', expect.anything()) + expect(console.error).toHaveBeenCalledWith('[PostHog.js]', '`loaded` function failed', expect.anything()) }) describe('/decide', () => { diff --git a/src/__tests__/sessionid.js b/src/__tests__/sessionid.js index 20ce54d6d..54bcd0496 100644 --- a/src/__tests__/sessionid.js +++ b/src/__tests__/sessionid.js @@ -272,6 +272,7 @@ describe('Session ID manager', () => { }) it('uses the custom session_idle_timeout_seconds if within bounds', () => { + window.POSTHOG_DEBUG = true expect(mockSessionManager(61)._sessionTimeoutMs).toEqual(61 * 1000) expect(console.warn).toBeCalledTimes(0) expect(mockSessionManager(59)._sessionTimeoutMs).toEqual(60 * 1000) diff --git a/src/__tests__/setup.js b/src/__tests__/setup.js index ed39d10cf..68e665da6 100644 --- a/src/__tests__/setup.js +++ b/src/__tests__/setup.js @@ -1,8 +1,8 @@ beforeEach(() => { - console.error = (message) => { - throw new Error(`Unexpected console.error: ${message}`) + console.error = (...args) => { + throw new Error(`Unexpected console.error: ${args}`) } - console.warn = (message) => { - throw new Error(`Unexpected console.warn: ${message}`) + console.warn = (...args) => { + throw new Error(`Unexpected console.warn: ${args}`) } }) diff --git a/src/autocapture-utils.ts b/src/autocapture-utils.ts index 8a6f219a7..a1267bd0b 100644 --- a/src/autocapture-utils.ts +++ b/src/autocapture-utils.ts @@ -4,7 +4,7 @@ * @returns {string} the element's class */ import { AutocaptureConfig } from 'types' -import { _each, _includes, _isUndefined, _trim } from './utils' +import { _each, _includes, _isUndefined, _trim, logger } from './utils' export function getClassName(el: Element): string { switch (typeof el.className) { @@ -335,7 +335,7 @@ export function getNestedSpanText(target: Element): string { text = `${text} ${getNestedSpanText(child)}`.trim() } } catch (e) { - console.error(e) + logger.error(e) } } }) diff --git a/src/autocapture.ts b/src/autocapture.ts index 09941d3ce..3feb9b70f 100644 --- a/src/autocapture.ts +++ b/src/autocapture.ts @@ -311,7 +311,7 @@ const autocapture = { afterDecideResponse: function (response: DecideResponse, instance: PostHog): void { const token = instance.config.token if (this._initializedTokens.indexOf(token) > -1) { - logger.log('autocapture already initialized for token "' + token + '"') + logger.info('autocapture already initialized for token "' + token + '"') return } diff --git a/src/decide.ts b/src/decide.ts index 3362a2124..e17d6016d 100644 --- a/src/decide.ts +++ b/src/decide.ts @@ -1,5 +1,5 @@ import { autocapture } from './autocapture' -import { _base64Encode, loadScript } from './utils' +import { _base64Encode, loadScript, logger } from './utils' import { PostHog } from './posthog-core' import { Compression, DecideResponse } from './types' import { STORED_GROUP_PROPERTIES_KEY, STORED_PERSON_PROPERTIES_KEY } from './constants' @@ -44,11 +44,11 @@ export class Decide { this.instance.featureFlags._startReloadTimer() if (response?.status === 0) { - console.error('Failed to fetch feature flags from PostHog.') + logger.error('Failed to fetch feature flags from PostHog.') return } if (!(document && document.body)) { - console.log('document not ready yet, trying again in 500 milliseconds...') + logger.info('document not ready yet, trying again in 500 milliseconds...') setTimeout(() => { this.parseDecideResponse(response) }, 500) @@ -82,7 +82,7 @@ export class Decide { if (response['surveys'] && !surveysGenerator) { loadScript(this.instance.config.api_host + `/static/surveys.js`, (err) => { if (err) { - return console.error(`Could not load surveys script`, err) + return logger.error(`Could not load surveys script`, err) } // eslint-disable-next-line @typescript-eslint/ban-ts-comment @@ -104,12 +104,12 @@ export class Decide { loadScript(scriptUrl, (err) => { if (err) { - console.error(`Error while initializing PostHog app with config id ${id}`, err) + logger.error(`Error while initializing PostHog app with config id ${id}`, err) } }) } } else if (response['siteApps'].length > 0) { - console.error('PostHog site apps are disabled. Enable the "opt_in_site_apps" config to proceed.') + logger.error('PostHog site apps are disabled. Enable the "opt_in_site_apps" config to proceed.') } } } diff --git a/src/extensions/exceptions/exception-autocapture.ts b/src/extensions/exceptions/exception-autocapture.ts index 18ac96b90..47d8b0192 100644 --- a/src/extensions/exceptions/exception-autocapture.ts +++ b/src/extensions/exceptions/exception-autocapture.ts @@ -1,4 +1,4 @@ -import { window } from '../../utils' +import { logger, window } from '../../utils' import { PostHog } from '../../posthog-core' import { DecideResponse, Properties } from '../../types' import { ErrorEventArgs, ErrorProperties, errorToProperties, unhandledRejectionToProperties } from './error-conversion' @@ -19,9 +19,7 @@ export class ExceptionObserver { } private debugLog(...args: any[]) { - if (this.instance.config.debug) { - console.log('PostHog.js [PostHog.ExceptionObserver]', ...args) - } + logger.info('[ExceptionObserver]', ...args) } startCapturing() { @@ -62,7 +60,7 @@ export class ExceptionObserver { }.bind(this) ;(window.onunhandledrejection as any).__POSTHOG_INSTRUMENTED__ = true } catch (e) { - console.error('PostHog failed to start exception autocapture', e) + logger.error('PostHog failed to start exception autocapture', e) this.stopCapturing() } } diff --git a/src/extensions/segment-integration.ts b/src/extensions/segment-integration.ts index 9dd85dd3b..53502a9db 100644 --- a/src/extensions/segment-integration.ts +++ b/src/extensions/segment-integration.ts @@ -16,6 +16,7 @@ * }) * ``` */ +import { logger } from '../utils' import { PostHog } from '../posthog-core' // Loosely based on https://github.com/segmentio/analytics-next/blob/master/packages/core/src/plugins/index.ts @@ -46,7 +47,7 @@ interface SegmentPlugin { export const createSegmentIntegration = (posthog: PostHog): SegmentPlugin => { if (!Promise || !Promise.resolve) { - console.warn('This browser does not have Promise support, and can not use the segment integration') + logger.warn('This browser does not have Promise support, and can not use the segment integration') } const enrichEvent = (ctx: SegmentPluginContext, eventName: string) => { diff --git a/src/extensions/sessionrecording.ts b/src/extensions/sessionrecording.ts index 2b02b4fdd..6c68f38a0 100644 --- a/src/extensions/sessionrecording.ts +++ b/src/extensions/sessionrecording.ts @@ -348,7 +348,7 @@ export class SessionRecording { new MutationRateLimiter(this.rrwebRecord, { onBlockedNode: (id, node) => { const message = `Too many mutations on node '${id}'. Rate limiting. This could be due to SVG animations or something similar` - logger.log(message, { + logger.info(message, { node: node, }) diff --git a/src/extensions/web-performance.ts b/src/extensions/web-performance.ts index dab51fa6b..8734b5122 100644 --- a/src/extensions/web-performance.ts +++ b/src/extensions/web-performance.ts @@ -102,14 +102,14 @@ export class WebPerformanceObserver { } if (window?.PerformanceObserver?.supportedEntryTypes === undefined) { - logger.log( - 'PostHog Performance observer not started because PerformanceObserver is not supported by this browser.' + logger.info( + '[PerformanceObserver] not started because PerformanceObserver is not supported by this browser.' ) return } if (isLocalhost() && !this._forceAllowLocalhost) { - logger.log('PostHog Peformance observer not started because we are on localhost.') + logger.info('[PerformanceObserver] not started because we are on localhost.') return } @@ -130,7 +130,7 @@ export class WebPerformanceObserver { this.observer?.observe({ type: entryType, buffered: true }) }) } catch (e) { - console.error('PostHog failed to start performance observer', e) + logger.error('PostHog failed to start performance observer', e) this.stopObserving() } } diff --git a/src/gdpr-utils.ts b/src/gdpr-utils.ts index 6d40f0a56..33604fd0f 100644 --- a/src/gdpr-utils.ts +++ b/src/gdpr-utils.ts @@ -11,7 +11,7 @@ * These functions are used internally by the SDK and are not intended to be publicly exposed. */ -import { _each, _includes, _isNumber, _isString, window } from './utils' +import { _each, _includes, _isNumber, _isString, logger, window } from './utils' import { cookieStore, localStore, localPlusCookieStore } from './storage' import { GDPROptions, PersistentStore } from './types' import { PostHog } from './posthog-core' @@ -188,7 +188,7 @@ function _hasDoNotTrackFlagOn(options: GDPROptions) { */ function _optInOut(optValue: boolean, token: string, options: GDPROptions) { if (!_isString(token) || !token.length) { - console.error('gdpr.' + (optValue ? 'optIn' : 'optOut') + ' called with an invalid token') + logger.error('gdpr.' + (optValue ? 'optIn' : 'optOut') + ' called with an invalid token') return } @@ -210,7 +210,7 @@ function _optInOut(optValue: boolean, token: string, options: GDPROptions) { } } -export function userOptedOut(posthog: PostHog, silenceErrors: boolean | undefined) { +export function userOptedOut(posthog: PostHog) { let optedOut = false try { @@ -230,41 +230,7 @@ export function userOptedOut(posthog: PostHog, silenceErrors: boolean | undefine }) } } catch (err) { - if (!silenceErrors) { - console.error('Unexpected error when checking capturing opt-out status: ' + err) - } + logger.error('Unexpected error when checking capturing opt-out status: ' + err) } return optedOut } - -/** - * Wrap a method with a check for whether the user is opted out of data capturing and cookies/localstorage for the given token - * If the user has opted out, return early instead of executing the method. - * If a callback argument was provided, execute it passing the 0 error code. - * @param {PostHog} posthog - the posthog instance - * @param {function} method - wrapped method to be executed if the user has not opted out - * @param silenceErrors - * @returns {*} the result of executing method OR undefined if the user has opted out - */ -export function addOptOutCheck any = (...args: any[]) => any>( - posthog: PostHog, - method: M, - silenceErrors?: boolean -): M { - return function (...args) { - const optedOut = userOptedOut(posthog, silenceErrors) - - if (!optedOut) { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - return method.apply(this, args) - } - - const callback = args[args.length - 1] - if (typeof callback === 'function') { - callback(0) - } - - return - } as M -} diff --git a/src/posthog-core.ts b/src/posthog-core.ts index 9f8dbf11d..5cc9d94f7 100644 --- a/src/posthog-core.ts +++ b/src/posthog-core.ts @@ -13,9 +13,9 @@ import { _register_event, _safewrap_class, document, - logger, userAgent, window, + logger, } from './utils' import { autocapture } from './autocapture' import { PostHogFeatureFlags } from './posthog-featureflags' @@ -152,7 +152,7 @@ const defaultConfig = (): PostHogConfig => ({ advanced_disable_toolbar_metrics: false, on_xhr_error: (req) => { const error = 'Bad HTTP status: ' + req.status + ' ' + req.statusText - console.error(error) + logger.error(error) }, get_device_id: (uuid) => uuid, // Used for internal testing @@ -199,7 +199,7 @@ const create_phlib = function ( instance = target as any } else { if (target && !_isArray(target)) { - console.error('You have already initialized ' + name) + logger.error('You have already initialized ' + name) // TODO: throw something instead? // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore @@ -231,10 +231,10 @@ const create_phlib = function ( const num_enabled_buckets = 100 if (!autocapture.enabledForProject(instance.config.token, num_buckets, num_enabled_buckets)) { instance.__autocapture = false - logger.log('Not in active bucket: disabling Automatic Event Collection.') + logger.info('Not in active bucket: disabling Automatic Event Collection.') } else if (!autocapture.isBrowserSupported()) { instance.__autocapture = false - logger.log('Disabling Automatic Event Collection because this browser is not supported') + logger.info('Disabling Automatic Event Collection because this browser is not supported') } else { autocapture.init(instance) } @@ -354,11 +354,13 @@ export class PostHog { */ init(token: string, config?: Partial, name?: string): PostHog | void { if (_isUndefined(name)) { - console.error('You must name your new library: init(token, config, name)') + logger.critical('You must name your new library: init(token, config, name)') return } if (name === PRIMARY_INSTANCE_NAME) { - console.error('You must initialize the main posthog object right after you include the PostHog js snippet') + logger.critical( + 'You must initialize the main posthog object right after you include the PostHog js snippet' + ) return } @@ -533,7 +535,7 @@ export class PostHog { try { this.config.loaded(this) } catch (err) { - console.error('`loaded` function failed', err) + logger.critical('`loaded` function failed', err) } this._start_queue_if_opted_in() @@ -693,7 +695,7 @@ export class PostHog { onResponse: this.rateLimiter.checkForLimiting, }) } catch (e) { - console.error(e) + logger.error(e) } } else { const script = document.createElement('script') @@ -838,7 +840,7 @@ export class PostHog { return logger.unintializedWarning('posthog.capture') } - if (userOptedOut(this, false)) { + if (userOptedOut(this)) { return } @@ -850,7 +852,7 @@ export class PostHog { // typing doesn't prevent interesting data if (_isUndefined(event_name) || typeof event_name !== 'string') { - console.error('No event name provided to posthog.capture') + logger.error('No event name provided to posthog.capture') return } @@ -889,9 +891,7 @@ export class PostHog { this.setPersonPropertiesForFlags(finalSet) } - if (this.config.debug) { - logger.log('PostHog.js send', data) - } + logger.info('send', data) const jsonData = JSON.stringify(data) const url = this.config.api_host + (options.endpoint || '/e/') @@ -989,7 +989,7 @@ export class PostHog { delete properties[blacklisted_prop] }) } else { - console.error('Invalid value for property_blacklist config: ' + property_blacklist) + logger.error('Invalid value for property_blacklist config: ' + property_blacklist) } const sanitize_properties = this.config.sanitize_properties @@ -1260,7 +1260,7 @@ export class PostHog { } //if the new_distinct_id has not been set ignore the identify event if (!new_distinct_id) { - console.error('Unique user id has not been set in posthog.identify') + logger.error('Unique user id has not been set in posthog.identify') return } @@ -1350,7 +1350,7 @@ export class PostHog { */ group(groupType: string, groupKey: string, groupPropertiesToSet?: Properties): void { if (!groupType || !groupKey) { - console.error('posthog.group requires a group type and group key') + logger.error('posthog.group requires a group type and group key') return } @@ -1543,7 +1543,7 @@ export class PostHog { this._register_single(ALIAS_ID_KEY, alias) return this.capture('$create_alias', { alias: alias, distinct_id: original }) } else { - console.error('alias matches current distinct_id - skipping api call.') + logger.warn('alias matches current distinct_id - skipping api call.') this.identify(alias) return -1 } @@ -2150,7 +2150,7 @@ export function init_from_snippet(): void { if (posthog_master['__loaded'] || (posthog_master['config'] && posthog_master['persistence'])) { // lib has already been loaded at least once; we don't want to override the global object this time so bomb early - console.error('PostHog library has already been downloaded at least once.') + logger.critical('PostHog library has already been downloaded at least once.') return } diff --git a/src/posthog-featureflags.ts b/src/posthog-featureflags.ts index 4b9e3f873..b51cd7570 100644 --- a/src/posthog-featureflags.ts +++ b/src/posthog-featureflags.ts @@ -112,7 +112,7 @@ export class PostHogFeatureFlags { } } if (!this._override_warning) { - console.warn('[PostHog] Overriding feature flags!', { + logger.warn(' Overriding feature flags!', { enabledFlags, overriddenFlags, finalFlags, @@ -211,7 +211,7 @@ export class PostHogFeatureFlags { */ getFeatureFlag(key: string, options: { send_event?: boolean } = {}): boolean | string | undefined { if (!this.instance.decideEndpointWasHit && !(this.getFlags() && this.getFlags().length > 0)) { - console.warn('getFeatureFlag for key "' + key + '" failed. Feature flags didn\'t load in time.') + logger.warn('getFeatureFlag for key "' + key + '" failed. Feature flags didn\'t load in time.') return undefined } const flagValue = this.getFlagVariants()[key] @@ -250,7 +250,7 @@ export class PostHogFeatureFlags { */ isFeatureEnabled(key: string, options: { send_event?: boolean } = {}): boolean | undefined { if (!this.instance.decideEndpointWasHit && !(this.getFlags() && this.getFlags().length > 0)) { - console.warn('isFeatureEnabled for key "' + key + '" failed. Feature flags didn\'t load in time.') + logger.warn('isFeatureEnabled for key "' + key + '" failed. Feature flags didn\'t load in time.') return undefined } return !!this.getFeatureFlag(key, options) diff --git a/src/rate-limiter.ts b/src/rate-limiter.ts index b5d039640..653f28aaf 100644 --- a/src/rate-limiter.ts +++ b/src/rate-limiter.ts @@ -23,7 +23,7 @@ export class RateLimiter { const response: CaptureResponse = JSON.parse(xmlHttpRequest.responseText) const quotaLimitedProducts = response.quota_limited || [] quotaLimitedProducts.forEach((batchKey) => { - logger.log(`[PostHog RateLimiter] ${batchKey || 'events'} is quota limited.`) + logger.info(`[RateLimiter] ${batchKey || 'events'} is quota limited.`) this.limits[batchKey] = new Date().getTime() + oneMinuteInMilliseconds }) } catch (e) { diff --git a/src/retry-queue.ts b/src/retry-queue.ts index 0fda82069..743de34e2 100644 --- a/src/retry-queue.ts +++ b/src/retry-queue.ts @@ -1,6 +1,7 @@ import { RequestQueueScaffold } from './base-request-queue' import { encodePostData, xhr } from './send-request' import { QueuedRequestData, RetryQueueElement } from './types' +import { logger } from './utils' import Config from './config' import { RateLimiter } from './rate-limiter' @@ -60,7 +61,7 @@ export class RetryQueue extends RequestQueueScaffold { const retryAt = new Date(Date.now() + msToNextRetry) this.queue.push({ retryAt, requestData }) - console.warn(`Enqueued failed request for retry in ${msToNextRetry}`) + logger.warn(`Enqueued failed request for retry in ${msToNextRetry}`) if (!this.isPolling) { this.isPolling = true this.poll() @@ -99,9 +100,7 @@ export class RetryQueue extends RequestQueueScaffold { const { url, data, options } = requestData if (this.rateLimiter.isRateLimited(options._batchKey)) { - if (Config.DEBUG) { - console.warn('[PostHog RetryQueue] is quota limited. Dropping request.') - } + logger.warn('[RetryQueue] is quota limited. Dropping request.') continue } @@ -112,9 +111,7 @@ export class RetryQueue extends RequestQueueScaffold { } catch (e) { // Note sendBeacon automatically retries, and after the first retry it will lose reference to contextual `this`. // This means in some cases `this.getConfig` will be undefined. - if (Config.DEBUG) { - console.error(e) - } + logger.error(e) } } this.queue = [] diff --git a/src/sessionid.ts b/src/sessionid.ts index 93bcb12b6..2f4ae5203 100644 --- a/src/sessionid.ts +++ b/src/sessionid.ts @@ -3,6 +3,7 @@ import { SESSION_ID } from './constants' import { sessionStore } from './storage' import { PostHogConfig, SessionIdChangedCallback } from './types' import { uuidv7 } from './uuidv7' +import { logger } from './utils' const MAX_SESSION_IDLE_TIMEOUT = 30 * 60 // 30 mins const MIN_SESSION_IDLE_TIMEOUT = 60 // 1 mins @@ -32,16 +33,12 @@ export class SessionIdManager { let desiredTimeout = config['session_idle_timeout_seconds'] || MAX_SESSION_IDLE_TIMEOUT if (typeof desiredTimeout !== 'number') { - console.warn('[PostHog] session_idle_timeout_seconds must be a number. Defaulting to 30 minutes.') + logger.warn('session_idle_timeout_seconds must be a number. Defaulting to 30 minutes.') desiredTimeout = MAX_SESSION_IDLE_TIMEOUT } else if (desiredTimeout > MAX_SESSION_IDLE_TIMEOUT) { - console.warn( - '[PostHog] session_idle_timeout_seconds cannot be greater than 30 minutes. Using 30 minutes instead.' - ) + logger.warn('session_idle_timeout_seconds cannot be greater than 30 minutes. Using 30 minutes instead.') } else if (desiredTimeout < MIN_SESSION_IDLE_TIMEOUT) { - console.warn( - '[PostHog] session_idle_timeout_seconds cannot be less than 60 seconds. Using 60 seconds instead.' - ) + logger.warn('session_idle_timeout_seconds cannot be less than 60 seconds. Using 60 seconds instead.') } this._sessionTimeoutMs = diff --git a/src/storage.ts b/src/storage.ts index 1b7d511b1..86d4ac0ca 100644 --- a/src/storage.ts +++ b/src/storage.ts @@ -269,9 +269,7 @@ export const sessionStore: PersistentStore = { }, error: function (msg) { - if (Config.DEBUG) { - logger.error('sessionStorage error: ', msg) - } + logger.error('sessionStorage error: ', msg) }, get: function (name) { diff --git a/src/utils.ts b/src/utils.ts index 88b2b78b8..8884e0eee 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -21,66 +21,41 @@ const nativeForEach = ArrayProto.forEach, nativeIsArray = Array.isArray, breaker: Breaker = {} -// Console override -const logger = { - /** @type {function(...*)} */ - log: function (...args: any[]) { - if (Config.DEBUG && !_isUndefined(window.console) && window.console) { - // Don't log PostHog debug messages in rrweb - const log = - '__rrweb_original__' in window.console.log - ? (window.console.log as any)['__rrweb_original__'] - : window.console.log - - try { - log.apply(window.console, args) - } catch (err) { - _eachArray(args, function (arg) { - log(arg) - }) - } +const LOGGER_PREFIX = '[PostHog.js]' + +export const logger = { + _log: (level: 'log' | 'warn' | 'error', ...args: any[]) => { + if ((Config.DEBUG || (window as any).POSTHOG_DEBUG) && !_isUndefined(window.console) && window.console) { + const consoleLog = + '__rrweb_original__' in window.console[level] + ? (window.console[level] as any)['__rrweb_original__'] + : window.console[level] + + // eslint-disable-next-line no-console + consoleLog(LOGGER_PREFIX, ...args) } }, - /** @type {function(...*)} */ - error: function (..._args: any[]) { - if (Config.DEBUG && !_isUndefined(window.console) && window.console) { - const args = ['PostHog error:', ..._args] - // Don't log PostHog debug messages in rrweb - const error = - '__rrweb_original__' in window.console.error - ? (window.console.error as any)['__rrweb_original__'] - : window.console.error - try { - error.apply(window.console, args) - } catch (err) { - _eachArray(args, function (arg) { - error(arg) - }) - } - } + + info: (...args: any[]) => { + logger._log('log', ...args) }, - /** @type {function(...*)} */ - critical: function (..._args: any[]) { - if (!_isUndefined(window.console) && window.console) { - const args = ['PostHog error:', ..._args] - // Don't log PostHog debug messages in rrweb - const error = - '__rrweb_original__' in window.console.error - ? (window.console.error as any)['__rrweb_original__'] - : window.console.error - try { - error.apply(window.console, args) - } catch (err) { - _eachArray(args, function (arg) { - error(arg) - }) - } - } + + warn: (...args: any[]) => { + logger._log('warn', ...args) }, - unintializedWarning: function (methodName: string): void { - if (Config.DEBUG && !_isUndefined(window.console) && window.console) { - logger.error(`[PostHog] You must initialize PostHog before calling ${methodName}`) - } + + error: (...args: any[]) => { + logger._log('error', ...args) + }, + + critical: (...args: any[]) => { + // Critical errors are always logged to the console + // eslint-disable-next-line no-console + console.error(LOGGER_PREFIX, ...args) + }, + + unintializedWarning: (methodName: string) => { + logger.error(`You must initialize PostHog before calling ${methodName}`) }, } @@ -975,4 +950,4 @@ export const _info = { }, } -export { win as window, userAgent, logger, document } +export { win as window, userAgent, document }