Skip to content

Commit

Permalink
feat(utils): Convert Logger class to functions (#4863)
Browse files Browse the repository at this point in the history
Removes the logger class in favour of a function + object. They are functionality identical. Also refactors the `consoleSandbox` function, mainly adjusting the types.
  • Loading branch information
AbhiPrasad committed Apr 6, 2022
1 parent 329e033 commit b1f4fbc
Show file tree
Hide file tree
Showing 2 changed files with 58 additions and 77 deletions.
4 changes: 2 additions & 2 deletions packages/integrations/src/captureconsole.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,12 @@ export class CaptureConsole implements Integration {
/**
* @inheritDoc
*/
private readonly _levels: string[] = CONSOLE_LEVELS;
private readonly _levels: typeof CONSOLE_LEVELS = CONSOLE_LEVELS;

This comment has been minimized.

Copy link
@peternedap

peternedap Apr 7, 2022

This type should be typeof CONSOLE_LEVELS[number][] instead, otherwise _levels can only be the const CONSOLE_LEVELS

This comment has been minimized.

Copy link
@peternedap

peternedap Apr 7, 2022

Playground link with example


/**
* @inheritDoc
*/
public constructor(options: { levels?: string[] } = {}) {
public constructor(options: { levels?: typeof CONSOLE_LEVELS } = {}) {
if (options.levels) {
this._levels = options.levels;
}
Expand Down
131 changes: 56 additions & 75 deletions packages/utils/src/logger.ts
Original file line number Diff line number Diff line change
@@ -1,118 +1,99 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { WrappedFunction } from '@sentry/types';

import { IS_DEBUG_BUILD } from './flags';
import { getGlobalObject } from './global';
import { getGlobalObject, getGlobalSingleton } from './global';

// TODO: Implement different loggers for different environments
const global = getGlobalObject<Window | NodeJS.Global>();

/** Prefix for logging strings */
const PREFIX = 'Sentry Logger ';

export const CONSOLE_LEVELS = ['debug', 'info', 'warn', 'error', 'log', 'assert'];
export const CONSOLE_LEVELS = ['debug', 'info', 'warn', 'error', 'log', 'assert'] as const;

type LoggerMethod = (...args: unknown[]) => void;
type LoggerConsoleMethods = Record<typeof CONSOLE_LEVELS[number], LoggerMethod>;

/** JSDoc */
interface ExtensibleConsole extends Console {
[key: string]: any;
interface Logger extends LoggerConsoleMethods {
disable(): void;
enable(): void;
}

/**
* Temporarily unwrap `console.log` and friends in order to perform the given callback using the original methods.
* Restores wrapping after the callback completes.
* Temporarily disable sentry console instrumentations.
*
* @param callback The function to run against the original `console` messages
* @returns The results of the callback
*/
export function consoleSandbox(callback: () => any): any {
export function consoleSandbox<T>(callback: () => T): T {
const global = getGlobalObject<Window>();

if (!('console' in global)) {
return callback();
}

// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
const originalConsole = (global as any).console as ExtensibleConsole;
const wrappedLevels: { [key: string]: any } = {};
const originalConsole = global.console as Console & Record<string, unknown>;
const wrappedLevels: Partial<LoggerConsoleMethods> = {};

// Restore all wrapped console methods
CONSOLE_LEVELS.forEach(level => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
if (level in (global as any).console && (originalConsole[level] as WrappedFunction).__sentry_original__) {
wrappedLevels[level] = originalConsole[level] as WrappedFunction;
originalConsole[level] = (originalConsole[level] as WrappedFunction).__sentry_original__;
// TODO(v7): Remove this check as it's only needed for Node 6
const originalWrappedFunc =
originalConsole[level] && (originalConsole[level] as WrappedFunction).__sentry_original__;
if (level in global.console && originalWrappedFunc) {
wrappedLevels[level] = originalConsole[level] as LoggerConsoleMethods[typeof level];
originalConsole[level] = originalWrappedFunc as Console[typeof level];
}
});

// Perform callback manipulations
const result = callback();

// Revert restoration to wrapped state
Object.keys(wrappedLevels).forEach(level => {
originalConsole[level] = wrappedLevels[level];
});

return result;
}

/** JSDoc */
class Logger {
/** JSDoc */
private _enabled: boolean;

/** JSDoc */
public constructor() {
this._enabled = false;
}

/** JSDoc */
public disable(): void {
this._enabled = false;
}

/** JSDoc */
public enable(): void {
this._enabled = true;
}

/** JSDoc */
public log(...args: any[]): void {
if (!this._enabled) {
return;
}
consoleSandbox(() => {
global.console.log(`${PREFIX}[Log]:`, ...args);
try {
return callback();
} finally {
// Revert restoration to wrapped state
Object.keys(wrappedLevels).forEach(level => {
originalConsole[level] = wrappedLevels[level as typeof CONSOLE_LEVELS[number]];
});
}
}

/** JSDoc */
public warn(...args: any[]): void {
if (!this._enabled) {
return;
}
consoleSandbox(() => {
global.console.warn(`${PREFIX}[Warn]:`, ...args);
function makeLogger(): Logger {
let enabled = false;
const logger: Partial<Logger> = {
enable: () => {
enabled = true;
},
disable: () => {
enabled = false;
},
};

if (IS_DEBUG_BUILD) {
CONSOLE_LEVELS.forEach(name => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
logger[name] = (...args: any[]) => {
if (enabled) {
consoleSandbox(() => {
global.console[name](`${PREFIX}[${name}]:`, ...args);
});
}
};
});
}

/** JSDoc */
public error(...args: any[]): void {
if (!this._enabled) {
return;
}
consoleSandbox(() => {
global.console.error(`${PREFIX}[Error]:`, ...args);
} else {
CONSOLE_LEVELS.forEach(name => {
logger[name] = () => undefined;
});
}
}

const sentryGlobal = global.__SENTRY__ || {};
const logger = (sentryGlobal.logger as Logger) || new Logger();
return logger as Logger;
}

// Ensure we only have a single logger instance, even if multiple versions of @sentry/utils are being used
let logger: Logger;
if (IS_DEBUG_BUILD) {
// Ensure we only have a single logger instance, even if multiple versions of @sentry/utils are being used
sentryGlobal.logger = logger;
global.__SENTRY__ = sentryGlobal;
logger = getGlobalSingleton('logger', makeLogger);
} else {
logger = makeLogger();
}

export { logger };

0 comments on commit b1f4fbc

Please sign in to comment.