Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: User-defined logger config #6409

Merged
merged 18 commits into from
Aug 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions packages/react-native-reanimated/jest-setup.js
Original file line number Diff line number Diff line change
@@ -1,2 +1,19 @@
delete global.MessageChannel;
require('./src/jestUtils').setUpTests();

global.__reanimatedLoggerConfig = {
logFunction: (data) => {
switch (data.level) {
case 'warn':
console.warn(data.message.content);
break;
case 'error':
case 'fatal':
case 'syntax':
console.error(data.message.content);
break;
}
},
level: 'warn',
strict: false,
};
2 changes: 1 addition & 1 deletion packages/react-native-reanimated/plugin/index.js

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions packages/react-native-reanimated/plugin/src/globals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ const notCapturedIdentifiers = [
// Reanimated
'_WORKLET',
'ReanimatedError',
'__reanimatedLoggerConfig',
];

/**
Expand Down
11 changes: 10 additions & 1 deletion packages/react-native-reanimated/src/ConfigHelper.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
'use strict';
import { PropsAllowlists } from './propsAllowlists';
import { jsiConfigureProps } from './core';
import { executeOnUIRuntimeSync, jsiConfigureProps } from './core';
import { ReanimatedError } from './errors';
import { updateLoggerConfig } from './logger';
import type { LoggerConfig } from './logger';

function assertNoOverlapInLists() {
for (const key in PropsAllowlists.NATIVE_THREAD_PROPS_WHITELIST) {
Expand Down Expand Up @@ -52,6 +54,13 @@ export function addWhitelistedUIProps(props: Record<string, boolean>): void {
}
}

export function configureReanimatedLogger(config: LoggerConfig) {
// Update the configuration object in the React runtime
updateLoggerConfig(config);
// Register the updated configuration in the UI runtime
executeOnUIRuntimeSync(updateLoggerConfig)(config);
}

const PROCESSED_VIEW_NAMES = new Set();

export interface ViewConfig {
Expand Down
7 changes: 0 additions & 7 deletions packages/react-native-reanimated/src/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,6 @@ export const isReanimated3 = () => true;
*/
export const isConfigured = isReanimated3;

// this is for web implementation
if (SHOULD_BE_USE_WEB) {
global._WORKLET = false;
global._log = console.log;
global._getAnimationTimestamp = () => performance.now();
}

export function getViewProp<T>(
viewTag: number,
propName: string,
Expand Down
2 changes: 2 additions & 0 deletions packages/react-native-reanimated/src/globals.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import type { UpdatePropsManager } from './UpdateProps';
import type { callGuardDEV } from './initializers';
import type { WorkletRuntime } from './runtimes';
import type { RNScreensTurboModuleType } from './screenTransition/commonTypes';
import type { LoggerConfigInternal } from './logger';

declare global {
var _REANIMATED_IS_REDUCED_MOTION: boolean | undefined;
Expand Down Expand Up @@ -112,4 +113,5 @@ declare global {
shadowNodeWrapper: ShadowNodeWrapper,
propName: string
) => string;
var __reanimatedLoggerConfig: LoggerConfigInternal;
}
2 changes: 2 additions & 0 deletions packages/react-native-reanimated/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import * as Animated from './Animated';

export default Animated;

export { configureReanimatedLogger } from './ConfigHelper';
export { LogLevel as ReanimatedLogLevel } from './logger';
export type { WorkletRuntime } from './core';
export {
runOnJS,
Expand Down
42 changes: 27 additions & 15 deletions packages/react-native-reanimated/src/initializers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,34 +9,46 @@ import {
executeOnUIRuntimeSync,
} from './threads';
import { mockedRequestAnimationFrame } from './mockedRequestAnimationFrame';
import type { LogData } from './logger';
import {
logger,
DEFAULT_LOGGER_CONFIG,
logToLogBoxAndConsole,
registerLoggerConfig,
replaceLoggerImplementation,
} from './logger';
import { makeShareableCloneRecursive } from './shareables';
import { shareableMappingCache } from './shareableMappingCache';

const IS_JEST = isJest();
const SHOULD_BE_USE_WEB = shouldBeUseWeb();
const IS_CHROME_DEBUGGER = isChromeDebugger();

// Register ReanimatedError in the UI global scope.
// (we are using `executeOnUIRuntimeSync` here to make sure that the error is
// registered before any async operations are executed on the UI runtime)
if (!shouldBeUseWeb()) {
executeOnUIRuntimeSync(registerReanimatedError)();
}

// Override the logFunction implementation with the one that adds logs
// with better stack traces to the LogBox (need to override it after `runOnJS`
// is defined).
replaceLoggerImplementation((data: LogData) => {
function overrideLogFunctionImplementation() {
'worklet';
runOnJS(logToLogBoxAndConsole)(data);
});
shareableMappingCache.set(logger, makeShareableCloneRecursive(logger));
replaceLoggerImplementation((data) => {
'worklet';
runOnJS(logToLogBoxAndConsole)(data);
});
}

// Register logger config and replace the log function implementation in
// the React runtime global scope
registerLoggerConfig(DEFAULT_LOGGER_CONFIG);
overrideLogFunctionImplementation();

// this is for web implementation
if (SHOULD_BE_USE_WEB) {
global._WORKLET = false;
global._log = console.log;
global._getAnimationTimestamp = () => performance.now();
} else {
// Register ReanimatedError and logger config in the UI runtime global scope.
// (we are using `executeOnUIRuntimeSync` here to make sure that the changes
// are applied before any async operations are executed on the UI runtime)
executeOnUIRuntimeSync(registerReanimatedError)();
executeOnUIRuntimeSync(registerLoggerConfig)(DEFAULT_LOGGER_CONFIG);
executeOnUIRuntimeSync(overrideLogFunctionImplementation)();
}

// callGuard is only used with debug builds
export function callGuardDEV<Args extends unknown[], ReturnValue>(
Expand Down
4 changes: 2 additions & 2 deletions packages/react-native-reanimated/src/logger/LogBox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import type { LogBoxStatic } from 'react-native';
import { LogBox as RNLogBox } from 'react-native';

export type LogLevel = 'warn' | 'error' | 'fatal' | 'syntax';
export type LogBoxLogLevel = 'warn' | 'error' | 'fatal' | 'syntax';

type Message = {
content: string;
Expand All @@ -34,7 +34,7 @@ type ComponentStack = CodeFrame[];
type ComponentStackType = 'legacy' | 'stack';

export type LogData = {
level: LogLevel;
level: LogBoxLogLevel;
message: Message;
category: Category;
componentStack: ComponentStack;
Expand Down
100 changes: 85 additions & 15 deletions packages/react-native-reanimated/src/logger/logger.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,23 @@
'use strict';
import { addLogBoxLog } from './LogBox';
import type { LogLevel, LogData } from './LogBox';
import type { LogData, LogBoxLogLevel } from './LogBox';

type LogFunction = (data: LogData) => void;

export enum LogLevel {
warn = 1,
error = 2,
fatal = 3,
}

export type LoggerConfig = {
level?: LogLevel;
strict?: boolean;
};

export type LoggerConfigInternal = {
logFunction: LogFunction;
} & Required<LoggerConfig>;

function logToConsole(data: LogData) {
'worklet';
Expand All @@ -16,12 +33,18 @@ function logToConsole(data: LogData) {
}
}

export const DEFAULT_LOGGER_CONFIG: LoggerConfigInternal = {
logFunction: logToConsole,
level: LogLevel.warn,
strict: false,
};

function formatMessage(message: string) {
'worklet';
return `[Reanimated] ${message}`;
}

function createLog(level: LogLevel, message: string): LogData {
function createLog(level: LogBoxLogLevel, message: string): LogData {
'worklet';
const formattedMessage = formatMessage(message);

Expand All @@ -38,10 +61,6 @@ function createLog(level: LogLevel, message: string): LogData {
};
}

const loggerImpl = {
logFunction: logToConsole,
};

/**
* Function that logs to LogBox and console.
* Used to replace the default console logging with logging to LogBox
Expand All @@ -54,28 +73,79 @@ export function logToLogBoxAndConsole(data: LogData) {
logToConsole(data);
}

/**
* Registers the logger configuration.
* use it only for Worklet runtimes.
*
* @param config - The config to register.
*/
export function registerLoggerConfig(config: LoggerConfigInternal) {
'worklet';
global.__reanimatedLoggerConfig = config;
}

/**
* Replaces the default log function with a custom implementation.
*
* @param logFunction - The custom log function.
*/
export function replaceLoggerImplementation(
logFunction: (data: LogData) => void
export function replaceLoggerImplementation(logFunction: LogFunction) {
'worklet';
registerLoggerConfig({ ...__reanimatedLoggerConfig, logFunction });
}

/**
* Updates logger configuration.
*
* @param options - The new logger configuration to apply.
* - level: The minimum log level to display.
* - strict: Whether to log warnings and errors that are not strict.
* Defaults to false.
*/
export function updateLoggerConfig(options?: Partial<LoggerConfig>) {
'worklet';
registerLoggerConfig({
...__reanimatedLoggerConfig,
// Don't reuse previous level and strict values from the global config
level: options?.level ?? LogLevel.warn,
strict: options?.strict ?? false,
});
}

type LogOptions = {
strict?: boolean;
};

function handleLog(
level: Exclude<LogBoxLogLevel, 'syntax'>,
message: string,
options: LogOptions
) {
loggerImpl.logFunction = logFunction;
'worklet';
const config = __reanimatedLoggerConfig;
if (
// Don't log if the log is marked as strict-only and the config doesn't
// enable strict logging
(options.strict && !config.strict) ||
// Don't log if the log level is below the minimum configured level
LogLevel[level] < config.level
) {
return;
}
config.logFunction(createLog(level, message));
}

export const logger = {
warn(message: string) {
warn(message: string, options: LogOptions = {}) {
'worklet';
loggerImpl.logFunction(createLog('warn', message));
handleLog('warn', message, options);
tjzel marked this conversation as resolved.
Show resolved Hide resolved
},
error(message: string) {
error(message: string, options: LogOptions = {}) {
'worklet';
loggerImpl.logFunction(createLog('error', message));
handleLog('error', message, options);
},
fatal(message: string) {
fatal(message: string, options: LogOptions = {}) {
'worklet';
loggerImpl.logFunction(createLog('fatal', message));
handleLog('fatal', message, options);
},
};
5 changes: 5 additions & 0 deletions packages/react-native-reanimated/src/runtimes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { isWorkletFunction } from './commonTypes';
import type { WorkletFunction } from './commonTypes';
import { ReanimatedError, registerReanimatedError } from './errors';
import { setupCallGuard, setupConsole } from './initializers';
import { registerLoggerConfig } from './logger';
import NativeReanimatedModule from './NativeReanimated';
import { shouldBeUseWeb } from './PlatformChecker';
import {
Expand Down Expand Up @@ -35,11 +36,15 @@ export function createWorkletRuntime(
name: string,
initializer?: WorkletFunction<[], void>
): WorkletRuntime {
// Assign to a different variable as __reanimatedLoggerConfig is not a captured
// identifier in the Worklet runtime.
const config = __reanimatedLoggerConfig;
return NativeReanimatedModule.createWorkletRuntime(
name,
makeShareableCloneRecursive(() => {
'worklet';
registerReanimatedError();
registerLoggerConfig(config);
MatiPl01 marked this conversation as resolved.
Show resolved Hide resolved
setupCallGuard();
setupConsole();
initializer?.();
Expand Down
Loading