diff --git a/src/context.tsx b/src/context.tsx index fcafd54..8b73c99 100644 --- a/src/context.tsx +++ b/src/context.tsx @@ -63,12 +63,7 @@ const StoreManagerProvider: FC = ({ return; } - storeManager - .init() - .then(() => setInit(true)) - .catch((e: Error) => { - console.error('Failed initialized store manager: ', e); - }); + void storeManager.init().then(() => setInit(true)); }, [shouldInit, storeManager]); return ( diff --git a/src/index.ts b/src/index.ts index 2f748aa..0546a42 100644 --- a/src/index.ts +++ b/src/index.ts @@ -13,3 +13,5 @@ export { default as withStores } from './with-stores'; export * from './make-exported'; export { default as Events } from './events'; + +export { default as Logger } from './logger'; diff --git a/src/logger.ts b/src/logger.ts new file mode 100644 index 0000000..2d39e3f --- /dev/null +++ b/src/logger.ts @@ -0,0 +1,96 @@ +import type Manager from './manager'; + +export interface ILoggerOpts { + /** + * 0 - disabled + * 1 - error + * 2 - warning + * 3 - info + * 4 - debug + */ + level: number; + manager: Manager; +} + +export interface ILoggerLogOpts { + level: ILoggerOpts['level']; + err?: Error; + payload?: Record; +} + +class Logger { + /** + * Logger options + */ + protected options: ILoggerOpts; + + /** + * @constructor + */ + constructor(opts: ILoggerOpts) { + this.options = opts; + } + + /** + * Log message + */ + public log(msg: string, { level, err, payload }: ILoggerLogOpts): void { + if (this.options.level < level) { + return; + } + + let type = 'log'; + + switch (level) { + case 1: + type = 'error'; + break; + + case 2: + type = 'warn'; + break; + + case 3: + type = 'info'; + break; + } + + console[type](...[msg, err, payload].filter(Boolean)); + } + + /** + * Log error message + */ + public err(msg: string, err?: unknown, payload?: Record): void { + this.log(msg, { err: err as Error, level: 1, payload }); + } + + /** + * Log warning message + */ + public warn(msg: string, payload?: Record): void { + this.log(msg, { level: 2, payload }); + } + + /** + * Log info message + */ + public info(msg: string, payload?: Record): void { + this.log(msg, { level: 3, payload }); + } + + /** + * Log debug message + */ + public debug(msg: string, payload: Record = {}, hasSnapshot = false): void { + if (hasSnapshot) { + payload.additional = { + relations: Object.fromEntries(this.options.manager.getStoresRelations().entries()), + }; + } + + this.log(`DEBUG: ${msg}`, { level: 4, payload: { ...payload } }); + } +} + +export default Logger; diff --git a/src/manager.ts b/src/manager.ts index 6ec87fa..3fb874c 100644 --- a/src/manager.ts +++ b/src/manager.ts @@ -3,6 +3,7 @@ import { isObservableProp, toJS } from 'mobx'; import { ROOT_CONTEXT_ID } from './constants'; import deepMerge from './deep-merge'; import Events from './events'; +import Logger from './logger'; import { isPropExcludedFromExport, isPropObservableExported, @@ -82,12 +83,21 @@ class Manager { */ protected suspenseRelations: Map> = new Map(); + /** + * Mobx manager logger + */ + protected readonly logger: Logger; + /** * @constructor */ - public constructor({ initState, storesParams, storage, options }: IManagerParams = {}) { + public constructor({ initState, storesParams, storage, options, logger }: IManagerParams = {}) { this.initState = initState || {}; this.storesParams = storesParams || {}; + this.logger = + logger && 'log' in logger + ? logger + : new Logger({ level: 3, ...(logger ?? {}), manager: this }); this.storage = storage instanceof CombinedStorage ? storage @@ -113,8 +123,12 @@ class Manager { * Init store manager */ public async init(): Promise { - if (this.storage) { - await this.storage.get(); + try { + if (this.storage) { + await this.storage.get(); + } + } catch (e) { + this.logger.err('Failed initialized store manager: ', e); } return this; @@ -171,7 +185,6 @@ class Manager { /** * Get store identity - * @protected */ protected getStoreId( store: IConstructableStore | TInitStore, @@ -241,7 +254,7 @@ class Manager { if (matchedIds.length === 1) { return this.stores.get(matchedIds[0]); } else if (matchedIds.length > 1) { - console.error( + this.logger.err( 'Parent context has multiple stores with the same id, please pass key to getStore function.', ); @@ -338,6 +351,15 @@ class Manager { : this.getStoreId(s, { key, contextId })); if (!storeId) { + const msg = `Cannot find or create store '${key}': '${this.getStoreId(s)}'`; + + this.logger.warn(msg); + this.logger.debug( + msg, + { contextId, parentId, suspenseId, componentName, isParent }, + true, + ); + return res; } @@ -610,7 +632,7 @@ class Manager { return true; } catch (e) { - console.error('Failed to persist stores: ', e); + this.logger.err('Failed to persist stores: ', e); } return false; diff --git a/src/types.ts b/src/types.ts index 8408c6b..a354574 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,4 +1,6 @@ import type Events from './events'; +import type { ILoggerOpts } from './logger'; +import type Logger from './logger'; import type Manager from './manager'; import type CombinedStorage from './storages/combined-storage'; import type StoreStatus from './store-status'; @@ -67,6 +69,7 @@ export interface IManagerParams { storage?: IStorage | CombinedStorage; options?: IManagerOptions; initState?: Record; + logger?: Logger | Omit; } export type TWakeup = (state: {