@codeleap/logger 6.3.0 → 7.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,4 @@
1
+ export * from './lib';
2
+ export * from './types';
3
+ export * from './utils';
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,OAAO,CAAA;AACrB,cAAc,SAAS,CAAA;AACvB,cAAc,SAAS,CAAA"}
@@ -0,0 +1,52 @@
1
+ import { LoggerConfig } from '../types';
2
+ import { SlackService } from './Slack';
3
+ import { PerformanceService } from './performance';
4
+ /**
5
+ * Base logger class. Logging methods (`info`, `error`, `warn`, `log`, `debug`, `test`) are stubs
6
+ * that must be overridden by the consuming app — calling them before providing an implementation
7
+ * throws. Use {@link createLogger} to produce a concrete instance, then assign it to `logger`.
8
+ *
9
+ * `initialize` must be called once before `patchConsole` or any Slack/perf functionality works.
10
+ */
11
+ export declare class Logger {
12
+ static initialized: boolean;
13
+ private config;
14
+ slack: SlackService;
15
+ perf: PerformanceService;
16
+ private isIgnored;
17
+ /**
18
+ * Monkey-patches `console.log`, `console.warn`, and `console.error` so that any message
19
+ * matching an entry in `config.Logger.ignoreLogs` is silently dropped.
20
+ *
21
+ * Call this after `initialize`. Patching before initialization has no effect because
22
+ * `isIgnored` returns `false` until the config is loaded.
23
+ */
24
+ patchConsole(): void;
25
+ /**
26
+ * Bootstraps the logger with app-wide config. Safe to call multiple times — subsequent calls
27
+ * are no-ops. Also instantiates `slack` and `perf` sub-services, so they are only available
28
+ * after this method returns.
29
+ */
30
+ initialize<T extends LoggerConfig>(config: T): void;
31
+ /**
32
+ * Stub — must be overridden. Intended for temporary test logs that should never reach
33
+ * production; implementations typically no-op in non-dev environments.
34
+ */
35
+ test(...args: unknown[]): void;
36
+ /** Stub — must be overridden. */
37
+ info(...args: unknown[]): void;
38
+ /** Stub — must be overridden. */
39
+ error(...args: unknown[]): void;
40
+ /** Stub — must be overridden. */
41
+ warn(...args: unknown[]): void;
42
+ /** Stub — must be overridden. */
43
+ log(...args: unknown[]): void;
44
+ /** Stub — must be overridden. */
45
+ debug(...args: unknown[]): void;
46
+ }
47
+ /**
48
+ * Shared `Logger` instance. Logging methods throw until the consuming app replaces them via
49
+ * {@link createLogger} and calls {@link Logger.initialize}.
50
+ */
51
+ export declare const logger: Logger;
52
+ //# sourceMappingURL=Logger.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Logger.d.ts","sourceRoot":"","sources":["../../src/lib/Logger.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAA;AACvC,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AACtC,OAAO,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAA;AAElD;;;;;;GAMG;AACH,qBAAa,MAAM;IACjB,MAAM,CAAC,WAAW,UAAQ;IAE1B,OAAO,CAAC,MAAM,CAAe;IAE7B,KAAK,EAAG,YAAY,CAAA;IAEpB,IAAI,EAAG,kBAAkB,CAAA;IAEzB,OAAO,CAAC,SAAS;IAMjB;;;;;;OAMG;IACH,YAAY;IAWZ;;;;OAIG;IACH,UAAU,CAAC,CAAC,SAAS,YAAY,EAAE,MAAM,EAAE,CAAC;IAa5C;;;OAGG;IACH,IAAI,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE;IAIvB,iCAAiC;IACjC,IAAI,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE;IAIvB,iCAAiC;IACjC,KAAK,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE;IAIxB,iCAAiC;IACjC,IAAI,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE;IAIvB,iCAAiC;IACjC,GAAG,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE;IAItB,iCAAiC;IACjC,KAAK,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE;CAGzB;AAED;;;GAGG;AACH,eAAO,MAAM,MAAM,QAAe,CAAA"}
@@ -0,0 +1,51 @@
1
+ import { LoggerConfig } from '../types';
2
+ type EchoSlack = {
3
+ label: string;
4
+ data: object;
5
+ options?: EchoSlackOptions;
6
+ module?: string;
7
+ };
8
+ type OptionInclude = 'version';
9
+ type SendIn = 'debug' | 'release';
10
+ type EchoSlackOptions = {
11
+ sendIn?: SendIn[];
12
+ include?: OptionInclude[];
13
+ };
14
+ /**
15
+ * Sends structured log messages to a Slack channel via the Slack Web API.
16
+ *
17
+ * The service is inert until {@link setApi} is called with an HTTP client. Messages are
18
+ * silently suppressed when the API client is absent, `echoConfig.enabled` is `false`, or
19
+ * the current environment does not match the caller-supplied `sendIn` filter.
20
+ */
21
+ export declare class SlackService {
22
+ private config;
23
+ private echoConfig;
24
+ private isDev;
25
+ private appName;
26
+ private api;
27
+ constructor(config: LoggerConfig);
28
+ /**
29
+ * Registers the HTTP client used to POST messages to Slack. Must be called before `echo`
30
+ * can send anything; calling `echo` without a registered API is a no-op.
31
+ */
32
+ setApi(fetcher: any): void;
33
+ /**
34
+ * Posts a labelled object to Slack. The message is formatted with `util.inspect` so nested
35
+ * structures are human-readable in the channel.
36
+ *
37
+ * Delivery is conditional on three independent guards:
38
+ * - `options.sendIn` — restricts to `'debug'` (IsDev) or `'release'` (production) builds;
39
+ * omitting `sendIn` sends in both environments.
40
+ * - `echoConfig.enabled` — master switch in the config; defaults to `true` when omitted.
41
+ * - A registered API client (see {@link setApi}).
42
+ *
43
+ * Failures are caught and logged to `console.error` rather than propagated.
44
+ */
45
+ echo(label: EchoSlack['label'], slackData: EchoSlack['data'], moduleName?: EchoSlack['module'], messageOptions?: EchoSlack['options']): Promise<void>;
46
+ private serializers;
47
+ private parseOptions;
48
+ private parseData;
49
+ }
50
+ export {};
51
+ //# sourceMappingURL=Slack.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Slack.d.ts","sourceRoot":"","sources":["../../src/lib/Slack.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAA;AAEvC,KAAK,SAAS,GAAG;IACf,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,CAAC,EAAE,gBAAgB,CAAA;IAC1B,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB,CAAA;AAED,KAAK,aAAa,GAAG,SAAS,CAAA;AAE9B,KAAK,MAAM,GAAG,OAAO,GAAG,SAAS,CAAA;AAEjC,KAAK,gBAAgB,GAAG;IACtB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAA;IACjB,OAAO,CAAC,EAAE,aAAa,EAAE,CAAA;CAC1B,CAAA;AAMD;;;;;;GAMG;AACH,qBAAa,YAAY;IASX,OAAO,CAAC,MAAM;IAR1B,OAAO,CAAC,UAAU,CAA+B;IAEjD,OAAO,CAAC,KAAK,CAAsC;IAEnD,OAAO,CAAC,OAAO,CAAyB;IAExC,OAAO,CAAC,GAAG,CAAK;gBAEI,MAAM,EAAE,YAAY;IAMxC;;;OAGG;IACH,MAAM,CAAC,OAAO,EAAE,GAAG;IAInB;;;;;;;;;;;OAWG;IACG,IAAI,CACR,KAAK,EAAE,SAAS,CAAC,OAAO,CAAC,EACzB,SAAS,EAAE,SAAS,CAAC,MAAM,CAAC,EAC5B,UAAU,GAAE,SAAS,CAAC,QAAQ,CAAa,EAC3C,cAAc,GAAE,SAAS,CAAC,SAAS,CAAM;IA+B3C,OAAO,CAAC,WAAW,CAIlB;IAED,OAAO,CAAC,YAAY;IA+BpB,OAAO,CAAC,SAAS;CA0BlB"}
@@ -0,0 +1,2 @@
1
+ export * from './Logger';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/lib/index.ts"],"names":[],"mappings":"AAAA,cAAc,UAAU,CAAA"}
@@ -0,0 +1,16 @@
1
+ import { InspectRenderOptions } from './types';
2
+ type ErrorArgs = InspectRenderOptions & {
3
+ name: string;
4
+ maxRenders: number;
5
+ };
6
+ type ErrorNames = 'maxRenders';
7
+ /**
8
+ * Thrown by {@link PerformanceService.inspectRender} when a component exceeds its allowed
9
+ * render budget. The error name is always `'Codeleap:Perf'`, making it easy to distinguish
10
+ * from application errors in React error boundaries or crash-reporting tools.
11
+ */
12
+ export declare class PerformanceError extends Error {
13
+ constructor(errorName: ErrorNames, args: Partial<ErrorArgs>);
14
+ }
15
+ export {};
16
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../../src/lib/performance/errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAA;AAE9C,KAAK,SAAS,GAAG,oBAAoB,GAAG;IACtC,IAAI,EAAE,MAAM,CAAA;IACZ,UAAU,EAAE,MAAM,CAAA;CACnB,CAAA;AAED,KAAK,UAAU,GAAG,YAAY,CAAA;AAa9B;;;;GAIG;AACH,qBAAa,gBAAiB,SAAQ,KAAK;gBAC7B,SAAS,EAAE,UAAU,EAAE,IAAI,EAAE,OAAO,CAAC,SAAS,CAAC;CAI5D"}
@@ -0,0 +1,31 @@
1
+ import { InspectRenderOptions } from './types';
2
+ import { LoggerConfig } from '../../types';
3
+ export * from './types';
4
+ /**
5
+ * Dev-only performance utilities. All methods are no-ops in production
6
+ * (`config.Environment.IsDev === false`) or when
7
+ * `config.Logger.performanceInspector.enabled` is `false`.
8
+ */
9
+ export declare class PerformanceService {
10
+ private config;
11
+ renderCounter: Record<string, number>;
12
+ constructor(config: LoggerConfig);
13
+ /**
14
+ * Tracks render frequency for a component and warns when it exceeds the configured threshold.
15
+ *
16
+ * Call this at the top of a function component body (not inside a hook). By default it also
17
+ * registers mount/unmount effects via `useEffect` — pass `noHooks: true` to skip those when
18
+ * the component cannot accept additional hooks.
19
+ *
20
+ * When the accumulated render count within a `throttleInterval` window exceeds `maxRenders`,
21
+ * the counter resets and a {@link PerformanceError} is thrown (surfacing as a React error
22
+ * boundary hit rather than a silent warning).
23
+ *
24
+ * Components whose names start with any entry in
25
+ * `config.Logger.performanceInspector.blacklist` are silently skipped.
26
+ *
27
+ * Has no effect outside a dev environment or when the inspector is disabled in config.
28
+ */
29
+ inspectRender: (name: string, options?: InspectRenderOptions) => void;
30
+ }
31
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/lib/performance/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAA;AAC9C,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAE1C,cAAc,SAAS,CAAA;AAEvB;;;;GAIG;AACH,qBAAa,kBAAkB;IAGjB,OAAO,CAAC,MAAM;IAF1B,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAK;gBAEtB,MAAM,EAAE,YAAY;IAExC;;;;;;;;;;;;;;;OAeG;IACH,aAAa,GACX,MAAM,MAAM,EACZ,UAAS,oBAIR,UAmDF;CACF"}
@@ -0,0 +1,7 @@
1
+ export type InspectRenderOptions = {
2
+ noHooks?: boolean;
3
+ logMode?: 'raw' | 'summarized';
4
+ throttleInterval?: number;
5
+ maxRenders?: number;
6
+ };
7
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/lib/performance/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,oBAAoB,GAAG;IACjC,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,OAAO,CAAC,EAAE,KAAK,GAAG,YAAY,CAAA;IAC9B,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB,CAAA"}
@@ -0,0 +1,25 @@
1
+ export type LoggerConfig = {
2
+ AppName: string;
3
+ Environment: {
4
+ IsDev: boolean;
5
+ };
6
+ Slack: {
7
+ echo: {
8
+ channel?: string;
9
+ icon: string;
10
+ token: string;
11
+ baseURL?: string;
12
+ enabled?: boolean;
13
+ options?: Record<string, any>;
14
+ };
15
+ };
16
+ Logger: {
17
+ ignoreLogs: string[];
18
+ performanceInspector: {
19
+ enabled: boolean;
20
+ maxRenders: number;
21
+ blacklist: string[];
22
+ };
23
+ };
24
+ };
25
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AACA,MAAM,MAAM,YAAY,GAAG;IACzB,OAAO,EAAE,MAAM,CAAA;IAEf,WAAW,EAAE;QACX,KAAK,EAAE,OAAO,CAAA;KACf,CAAA;IAED,KAAK,EAAE;QACL,IAAI,EAAE;YACJ,OAAO,CAAC,EAAE,MAAM,CAAA;YAChB,IAAI,EAAE,MAAM,CAAA;YACZ,KAAK,EAAE,MAAM,CAAA;YACb,OAAO,CAAC,EAAE,MAAM,CAAA;YAChB,OAAO,CAAC,EAAE,OAAO,CAAA;YACjB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;SAC9B,CAAA;KACF,CAAA;IAED,MAAM,EAAE;QACN,UAAU,EAAE,MAAM,EAAE,CAAA;QAEpB,oBAAoB,EAAE;YACpB,OAAO,EAAE,OAAO,CAAA;YAChB,UAAU,EAAE,MAAM,CAAA;YAClB,SAAS,EAAE,MAAM,EAAE,CAAA;SACpB,CAAA;KACF,CAAA;CAEF,CAAA"}
@@ -0,0 +1,40 @@
1
+ type Level = 'info' | 'debug' | 'error' | 'warn' | 'log';
2
+ export type TransportFunction = (level: Level, ...args: unknown[]) => void;
3
+ type LoggerOptions = {
4
+ transport: TransportFunction[];
5
+ };
6
+ /**
7
+ * Concrete logger implementation produced by {@link createLogger}. Each log method writes to
8
+ * the native `console` counterpart **and** fans out to every registered transport, so
9
+ * third-party sinks (e.g. Sentry, Datadog) receive the same payload without extra wiring.
10
+ *
11
+ * `test` is intentionally a no-op — it exists as a scratch channel that leaves no output in
12
+ * any environment.
13
+ *
14
+ * Transport functions are shared as a static property, meaning all `CustomLogger` instances
15
+ * created in the same process share the same transport list. Only one call to
16
+ * {@link createLogger} is expected per app.
17
+ */
18
+ declare class CustomLogger {
19
+ static transport: TransportFunction[];
20
+ static applyTransport(level: Level, ...args: unknown[]): void;
21
+ info(...args: unknown[]): void;
22
+ error(...args: unknown[]): void;
23
+ warn(...args: unknown[]): void;
24
+ /** Intentional no-op. Use as a scratch channel for temporary logs that must not ship. */
25
+ test(...args: unknown[]): void;
26
+ log(...args: unknown[]): void;
27
+ debug(...args: unknown[]): void;
28
+ }
29
+ /**
30
+ * Wires up the transport list and returns a ready-to-use {@link CustomLogger} instance.
31
+ *
32
+ * The returned instance satisfies the logging method contract expected by {@link Logger},
33
+ * so it should be used to replace the stub methods on the shared `logger` singleton after
34
+ * calling `logger.initialize(config)`.
35
+ *
36
+ * Calling this more than once replaces the shared transport list for all existing instances.
37
+ */
38
+ export declare const createLogger: (options: LoggerOptions) => CustomLogger;
39
+ export {};
40
+ //# sourceMappingURL=createLogger.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"createLogger.d.ts","sourceRoot":"","sources":["../../src/utils/createLogger.ts"],"names":[],"mappings":"AACA,KAAK,KAAK,GAAG,MAAM,GAAG,OAAO,GAAG,OAAO,GAAG,MAAM,GAAG,KAAK,CAAA;AAExD,MAAM,MAAM,iBAAiB,GAAG,CAAC,KAAK,EAAE,KAAK,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,CAAA;AAE1E,KAAK,aAAa,GAAG;IACnB,SAAS,EAAE,iBAAiB,EAAE,CAAA;CAC/B,CAAA;AAED;;;;;;;;;;;GAWG;AACH,cAAM,YAAY;IAChB,MAAM,CAAC,SAAS,EAAE,iBAAiB,EAAE,CAAA;IAErC,MAAM,CAAC,cAAc,CAAC,KAAK,EAAE,KAAK,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE;IAMtD,IAAI,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE;IAKvB,KAAK,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE;IAKxB,IAAI,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE;IAKvB,yFAAyF;IACzF,IAAI,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE;IAIvB,GAAG,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE;IAKtB,KAAK,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE;CAIzB;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,YAAY,GAAI,SAAS,aAAa,iBAIlD,CAAA"}
@@ -0,0 +1,2 @@
1
+ export * from './createLogger';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAA"}
package/package.json CHANGED
@@ -1,7 +1,20 @@
1
1
  {
2
2
  "name": "@codeleap/logger",
3
- "version": "6.3.0",
3
+ "version": "7.0.0",
4
4
  "main": "src/index.ts",
5
+ "types": "dist/index.d.ts",
6
+ "exports": {
7
+ ".": {
8
+ "source": "./src/index.ts",
9
+ "types": "./dist/index.d.ts",
10
+ "import": "./dist/index.js",
11
+ "default": "./dist/index.js"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist",
16
+ "src"
17
+ ],
5
18
  "license": "UNLICENSED",
6
19
  "repository": {
7
20
  "url": "https://github.com/codeleap-uk/internal-libs-monorepo.git",
@@ -9,23 +22,24 @@
9
22
  "directory": "packages/logger"
10
23
  },
11
24
  "devDependencies": {
12
- "@codeleap/types": "6.3.0",
13
- "@codeleap/utils": "6.3.0",
14
- "@codeleap/config": "6.3.0",
25
+ "@codeleap/types": "7.0.0",
26
+ "@codeleap/utils": "7.0.0",
27
+ "@codeleap/config": "7.0.0",
15
28
  "ts-node-dev": "1.1.8",
16
29
  "@sentry/types": "8.40.0"
17
30
  },
18
31
  "scripts": {
19
- "build": "echo 'No build needed'"
32
+ "build": "tsc --build tsconfig.build.json",
33
+ "typecheck": "bun tsc --noEmit -p ./tsconfig.json"
20
34
  },
21
35
  "peerDependencies": {
22
- "@codeleap/types": "6.3.0",
23
- "@codeleap/utils": "6.3.0",
24
- "typescript": "5.5.2",
36
+ "@codeleap/types": "7.0.0",
37
+ "@codeleap/utils": "7.0.0",
38
+ "typescript": "6.0.3",
25
39
  "react": "19.1.0"
26
40
  },
27
41
  "dependencies": {
28
42
  "util": "0.12.5",
29
43
  "url-parse": "^1.5.10"
30
44
  }
31
- }
45
+ }
package/src/lib/Logger.ts CHANGED
@@ -1,45 +1,57 @@
1
1
  import { LoggerConfig } from '../types'
2
- import { SentryService } from './Sentry'
3
2
  import { SlackService } from './Slack'
4
3
  import { PerformanceService } from './performance'
5
4
 
5
+ /**
6
+ * Base logger class. Logging methods (`info`, `error`, `warn`, `log`, `debug`, `test`) are stubs
7
+ * that must be overridden by the consuming app — calling them before providing an implementation
8
+ * throws. Use {@link createLogger} to produce a concrete instance, then assign it to `logger`.
9
+ *
10
+ * `initialize` must be called once before `patchConsole` or any Slack/perf functionality works.
11
+ */
6
12
  export class Logger {
7
13
  static initialized = false
8
14
 
9
- private config: LoggerConfig
15
+ private config!: LoggerConfig
10
16
 
11
- slack: SlackService
17
+ slack!: SlackService
12
18
 
13
- sentry: SentryService
14
-
15
- perf: PerformanceService
16
-
17
- private overrideConsoleMethod(args: unknown[], originalConsole: Console['log']) {
18
- if (!Logger.initialized) return
19
+ perf!: PerformanceService
19
20
 
21
+ private isIgnored(args: unknown[]): boolean {
22
+ if (!Logger.initialized) return false
20
23
  const ignoreLogs = this.config.Logger.ignoreLogs
21
- const shouldIgnore = typeof args[0] === 'string' && ignoreLogs.some(ignoredWarning => args.join(' ').includes(ignoredWarning))
22
-
23
- if (shouldIgnore) return
24
-
25
- return originalConsole.apply(console, args)
24
+ return typeof args[0] === 'string' && ignoreLogs.some(w => args.join(' ').includes(w))
26
25
  }
27
26
 
28
- constructor() {
29
- const consoles = ['log', 'warn', 'error']
30
-
27
+ /**
28
+ * Monkey-patches `console.log`, `console.warn`, and `console.error` so that any message
29
+ * matching an entry in `config.Logger.ignoreLogs` is silently dropped.
30
+ *
31
+ * Call this after `initialize`. Patching before initialization has no effect because
32
+ * `isIgnored` returns `false` until the config is loaded.
33
+ */
34
+ patchConsole() {
35
+ const consoles = ['log', 'warn', 'error'] as const
31
36
  consoles.forEach(level => {
32
- const consoleRef = console[level]
33
- console[level] = (...args: unknown[]) => this.overrideConsoleMethod(args, consoleRef)
37
+ const consoleAny = console as unknown as Record<string, (...args: unknown[]) => void>
38
+ const consoleRef = consoleAny[level].bind(console)
39
+ consoleAny[level] = (...args: unknown[]) => {
40
+ if (!this.isIgnored(args)) consoleRef(...args)
41
+ }
34
42
  })
35
43
  }
36
44
 
45
+ /**
46
+ * Bootstraps the logger with app-wide config. Safe to call multiple times — subsequent calls
47
+ * are no-ops. Also instantiates `slack` and `perf` sub-services, so they are only available
48
+ * after this method returns.
49
+ */
37
50
  initialize<T extends LoggerConfig>(config: T) {
38
51
  if (Logger.initialized) return
39
52
 
40
53
  this.config = config
41
54
 
42
- this.sentry = new SentryService(config)
43
55
 
44
56
  this.slack = new SlackService(config)
45
57
 
@@ -48,25 +60,42 @@ export class Logger {
48
60
  Logger.initialized = true
49
61
  }
50
62
 
63
+ /**
64
+ * Stub — must be overridden. Intended for temporary test logs that should never reach
65
+ * production; implementations typically no-op in non-dev environments.
66
+ */
67
+ test(...args: unknown[]){
68
+ throw new Error('Logger: implement the method "test"')
69
+ }
70
+
71
+ /** Stub — must be overridden. */
51
72
  info(...args: unknown[]) {
52
73
  throw new Error('Logger: implement the method "info"')
53
74
  }
54
75
 
76
+ /** Stub — must be overridden. */
55
77
  error(...args: unknown[]) {
56
78
  throw new Error('Logger: implement the method "error"')
57
79
  }
58
80
 
81
+ /** Stub — must be overridden. */
59
82
  warn(...args: unknown[]) {
60
83
  throw new Error('Logger: implement the method "warn"')
61
84
  }
62
85
 
86
+ /** Stub — must be overridden. */
63
87
  log(...args: unknown[]) {
64
88
  throw new Error('Logger: implement the method "log"')
65
89
  }
66
90
 
91
+ /** Stub — must be overridden. */
67
92
  debug(...args: unknown[]) {
68
93
  throw new Error('Logger: implement the method "debug"')
69
94
  }
70
95
  }
71
96
 
97
+ /**
98
+ * Shared `Logger` instance. Logging methods throw until the consuming app replaces them via
99
+ * {@link createLogger} and calls {@link Logger.initialize}.
100
+ */
72
101
  export const logger = new Logger()
package/src/lib/Slack.ts CHANGED
@@ -1,3 +1,4 @@
1
+ /// <reference types="node" />
1
2
  import { inspect } from 'util'
2
3
  import { TypeGuards } from '@codeleap/types'
3
4
  import { LoggerConfig } from '../types'
@@ -22,6 +23,13 @@ const DEFAULT_CHANNEL = '#_dev_logs'
22
23
 
23
24
  const DEFAULT_BASE_URL = 'https://slack.com/api/chat.postMessage'
24
25
 
26
+ /**
27
+ * Sends structured log messages to a Slack channel via the Slack Web API.
28
+ *
29
+ * The service is inert until {@link setApi} is called with an HTTP client. Messages are
30
+ * silently suppressed when the API client is absent, `echoConfig.enabled` is `false`, or
31
+ * the current environment does not match the caller-supplied `sendIn` filter.
32
+ */
25
33
  export class SlackService {
26
34
  private echoConfig: LoggerConfig['Slack']['echo']
27
35
 
@@ -29,7 +37,7 @@ export class SlackService {
29
37
 
30
38
  private appName: LoggerConfig['AppName']
31
39
 
32
- private api
40
+ private api: any
33
41
 
34
42
  constructor(private config: LoggerConfig) {
35
43
  this.echoConfig = config.Slack.echo
@@ -37,14 +45,30 @@ export class SlackService {
37
45
  this.appName = config.AppName
38
46
  }
39
47
 
48
+ /**
49
+ * Registers the HTTP client used to POST messages to Slack. Must be called before `echo`
50
+ * can send anything; calling `echo` without a registered API is a no-op.
51
+ */
40
52
  setApi(fetcher: any) {
41
53
  this.api = fetcher
42
54
  }
43
55
 
56
+ /**
57
+ * Posts a labelled object to Slack. The message is formatted with `util.inspect` so nested
58
+ * structures are human-readable in the channel.
59
+ *
60
+ * Delivery is conditional on three independent guards:
61
+ * - `options.sendIn` — restricts to `'debug'` (IsDev) or `'release'` (production) builds;
62
+ * omitting `sendIn` sends in both environments.
63
+ * - `echoConfig.enabled` — master switch in the config; defaults to `true` when omitted.
64
+ * - A registered API client (see {@link setApi}).
65
+ *
66
+ * Failures are caught and logged to `console.error` rather than propagated.
67
+ */
44
68
  async echo(
45
69
  label: EchoSlack['label'],
46
70
  slackData: EchoSlack['data'],
47
- moduleName: EchoSlack['module'] = null,
71
+ moduleName: EchoSlack['module'] = undefined,
48
72
  messageOptions: EchoSlack['options'] = {}
49
73
  ) {
50
74
  const options = this.parseOptions(messageOptions)
@@ -11,13 +11,18 @@ const defineError = (errorName: ErrorNames, args: Partial<ErrorArgs>) => {
11
11
  switch (errorName) {
12
12
  case 'maxRenders':
13
13
  return `${args.name} is rendering more than ${args.maxRenders}time per ${
14
- args.throttleInterval / 1000
14
+ (args.throttleInterval ?? 0) / 1000
15
15
  }second!
16
16
  If you aware of this, you can disable it in the Settings.ts > Performancer.
17
17
  `
18
18
  }
19
19
  }
20
20
 
21
+ /**
22
+ * Thrown by {@link PerformanceService.inspectRender} when a component exceeds its allowed
23
+ * render budget. The error name is always `'Codeleap:Perf'`, making it easy to distinguish
24
+ * from application errors in React error boundaries or crash-reporting tools.
25
+ */
21
26
  export class PerformanceError extends Error {
22
27
  constructor(errorName: ErrorNames, args: Partial<ErrorArgs>) {
23
28
  super(defineError(errorName, args))
@@ -6,18 +6,32 @@ import { LoggerConfig } from '../../types'
6
6
 
7
7
  export * from './types'
8
8
 
9
+ /**
10
+ * Dev-only performance utilities. All methods are no-ops in production
11
+ * (`config.Environment.IsDev === false`) or when
12
+ * `config.Logger.performanceInspector.enabled` is `false`.
13
+ */
9
14
  export class PerformanceService {
10
15
  renderCounter: Record<string, number> = {}
11
16
 
12
17
  constructor(private config: LoggerConfig) { }
13
18
 
14
19
  /**
15
- * inspectRender monitors how much time a component render per second.
16
- * Use logger.perf.inspectRender('ComponentName') inside a component to monitor it.
17
- * @param {string} name - Component name
18
- * @param {PerformanceInspector} options - Some options for the inspector
19
- * @returns
20
- */
20
+ * Tracks render frequency for a component and warns when it exceeds the configured threshold.
21
+ *
22
+ * Call this at the top of a function component body (not inside a hook). By default it also
23
+ * registers mount/unmount effects via `useEffect` pass `noHooks: true` to skip those when
24
+ * the component cannot accept additional hooks.
25
+ *
26
+ * When the accumulated render count within a `throttleInterval` window exceeds `maxRenders`,
27
+ * the counter resets and a {@link PerformanceError} is thrown (surfacing as a React error
28
+ * boundary hit rather than a silent warning).
29
+ *
30
+ * Components whose names start with any entry in
31
+ * `config.Logger.performanceInspector.blacklist` are silently skipped.
32
+ *
33
+ * Has no effect outside a dev environment or when the inspector is disabled in config.
34
+ */
21
35
  inspectRender = (
22
36
  name: string,
23
37
  options: InspectRenderOptions = {
@@ -67,13 +81,13 @@ export class PerformanceService {
67
81
  return
68
82
  }
69
83
 
70
- function logSummary() {
84
+ function logSummary(this: PerformanceService) {
71
85
  if (renders <= 0) return
72
86
 
73
87
  console.log(`[PerformanceInspector] Render summary -> ${name}: ${renders}`)
74
88
  this.renderCounter[name] = 0
75
89
  }
76
-
90
+ // @ts-ignore
77
91
  throttle(logSummary, name, throttleInterval)
78
92
  }
79
93
  }
package/src/types.ts CHANGED
@@ -27,12 +27,4 @@ export type LoggerConfig = {
27
27
  }
28
28
  }
29
29
 
30
- Sentry: {
31
- enabled: boolean
32
- dsn: string
33
- provider: any
34
- debug?: boolean
35
- initArgs?: any
36
- beforeBreadcrumb?: any
37
- }
38
30
  }
@@ -7,6 +7,18 @@ type LoggerOptions = {
7
7
  transport: TransportFunction[]
8
8
  }
9
9
 
10
+ /**
11
+ * Concrete logger implementation produced by {@link createLogger}. Each log method writes to
12
+ * the native `console` counterpart **and** fans out to every registered transport, so
13
+ * third-party sinks (e.g. Sentry, Datadog) receive the same payload without extra wiring.
14
+ *
15
+ * `test` is intentionally a no-op — it exists as a scratch channel that leaves no output in
16
+ * any environment.
17
+ *
18
+ * Transport functions are shared as a static property, meaning all `CustomLogger` instances
19
+ * created in the same process share the same transport list. Only one call to
20
+ * {@link createLogger} is expected per app.
21
+ */
10
22
  class CustomLogger {
11
23
  static transport: TransportFunction[]
12
24
 
@@ -31,6 +43,11 @@ class CustomLogger {
31
43
  CustomLogger.applyTransport('warn', ...args)
32
44
  }
33
45
 
46
+ /** Intentional no-op. Use as a scratch channel for temporary logs that must not ship. */
47
+ test(...args: unknown[]) {
48
+
49
+ }
50
+
34
51
  log(...args: unknown[]) {
35
52
  console.log(...args)
36
53
  CustomLogger.applyTransport('log', ...args)
@@ -42,6 +59,15 @@ class CustomLogger {
42
59
  }
43
60
  }
44
61
 
62
+ /**
63
+ * Wires up the transport list and returns a ready-to-use {@link CustomLogger} instance.
64
+ *
65
+ * The returned instance satisfies the logging method contract expected by {@link Logger},
66
+ * so it should be used to replace the stub methods on the shared `logger` singleton after
67
+ * calling `logger.initialize(config)`.
68
+ *
69
+ * Calling this more than once replaces the shared transport list for all existing instances.
70
+ */
45
71
  export const createLogger = (options: LoggerOptions) => {
46
72
  CustomLogger.transport = options.transport
47
73
 
package/package.json.bak DELETED
@@ -1,31 +0,0 @@
1
- {
2
- "name": "@codeleap/logger",
3
- "version": "6.3.0",
4
- "main": "src/index.ts",
5
- "license": "UNLICENSED",
6
- "repository": {
7
- "url": "https://github.com/codeleap-uk/internal-libs-monorepo.git",
8
- "type": "git",
9
- "directory": "packages/logger"
10
- },
11
- "devDependencies": {
12
- "@codeleap/types": "workspace:*",
13
- "@codeleap/utils": "workspace:*",
14
- "@codeleap/config": "workspace:*",
15
- "ts-node-dev": "1.1.8",
16
- "@sentry/types": "8.40.0"
17
- },
18
- "scripts": {
19
- "build": "echo 'No build needed'"
20
- },
21
- "peerDependencies": {
22
- "@codeleap/types": "workspace:*",
23
- "@codeleap/utils": "workspace:*",
24
- "typescript": "5.5.2",
25
- "react": "19.1.0"
26
- },
27
- "dependencies": {
28
- "util": "0.12.5",
29
- "url-parse": "^1.5.10"
30
- }
31
- }
package/src/lib/Sentry.ts DELETED
@@ -1,62 +0,0 @@
1
- import type { Breadcrumb, ClientOptions, SeverityLevel, Client } from '@sentry/types'
2
- import { LoggerConfig } from '../types'
3
-
4
- const SentrySeverityMap: Record<string, SeverityLevel> = {
5
- debug: 'debug',
6
- error: 'error',
7
- info: 'info',
8
- log: 'log',
9
- warn: 'warning',
10
- silent: 'log',
11
- }
12
-
13
- type SentryProvider = {
14
- addBreadcrumb: (args: Breadcrumb) => void
15
- init(options: ClientOptions): Client
16
- captureException(err: any): void
17
- }
18
-
19
- export class SentryService {
20
- get provider(): SentryProvider {
21
- return this.config.Sentry.provider
22
- }
23
-
24
- private get enabled() {
25
- return this.config.Sentry.enabled
26
- }
27
-
28
- constructor(private config: LoggerConfig) {
29
- if (config.Sentry.enabled) {
30
- const initOptions: ClientOptions = {
31
- dsn: config.Sentry.dsn,
32
- debug: config.Sentry.debug,
33
- beforeBreadcrumb: config.Sentry.beforeBreadcrumb,
34
- integrations: [],
35
- enabled: this.enabled,
36
- ...config.Sentry.initArgs,
37
- }
38
-
39
- this.provider?.init?.(initOptions)
40
- }
41
- }
42
-
43
- captureBreadcrumb(type: string, msg: string, data: any, category = `logger:${type}`) {
44
- if (!this.enabled) return
45
-
46
- const sentryArgs: Breadcrumb = {
47
- message: msg,
48
- data,
49
- category,
50
- level: SentrySeverityMap[type],
51
- type: '',
52
- }
53
-
54
- this.provider.addBreadcrumb(sentryArgs)
55
- }
56
-
57
- captureException(err: any) {
58
- if (!this.enabled) return
59
-
60
- this.provider.captureException(err)
61
- }
62
- }