@codeleap/logger 4.3.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.
package/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "@codeleap/logger",
3
+ "version": "4.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": "4.3.0",
13
+ "@codeleap/utils": "4.3.0",
14
+ "@codeleap/config": "4.3.0",
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": "4.3.0",
23
+ "@codeleap/utils": "4.3.0",
24
+ "typescript": "5.0.4",
25
+ "react": "18.1.0"
26
+ },
27
+ "dependencies": {
28
+ "util": "0.12.5",
29
+ "url-parse": "^1.5.10"
30
+ }
31
+ }
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "@codeleap/logger",
3
+ "version": "4.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.0.4",
25
+ "react": "18.1.0"
26
+ },
27
+ "dependencies": {
28
+ "util": "0.12.5",
29
+ "url-parse": "^1.5.10"
30
+ }
31
+ }
@@ -0,0 +1,67 @@
1
+ import { obfuscate } from './obfuscate'
2
+ import { FunctionType, AnyFunction, AppSettings } from '@codeleap/types'
3
+
4
+ export type AnalyticsObject = {
5
+ name: string
6
+ type: 'interaction' | 'event'
7
+ data: any
8
+ }
9
+
10
+ type IAnalyticsArgs = {
11
+ init(): any
12
+ prepareData: () => any
13
+ error?: (err: any) => any
14
+ } & Record<`on${Capitalize<AnalyticsObject['type']>}`, FunctionType<[AnalyticsObject], void>>
15
+
16
+ export class Analytics {
17
+
18
+ constructor(private callers: IAnalyticsArgs, private settings: AppSettings) {
19
+ this.callers.init()
20
+
21
+ }
22
+
23
+ private prepare() {
24
+ const data = this.callers.prepareData()
25
+
26
+ return data
27
+ }
28
+
29
+ obfuscate(data) {
30
+ return obfuscate({
31
+ object: data,
32
+ keys: this?.settings?.Logger?.Obfuscate?.keys || [],
33
+ values: this?.settings?.Logger?.Obfuscate?.values || [],
34
+ })
35
+ }
36
+
37
+ event(name: string, data = {}) {
38
+ this.handle(name, data, 'event', this.callers.onEvent)
39
+ }
40
+
41
+ interaction(name: string, data = {}) {
42
+ this.handle(name, data, 'interaction', this.callers.onInteraction)
43
+
44
+ }
45
+
46
+ onError(cb) {
47
+ this.callers.error = cb
48
+ }
49
+
50
+ private handle(name: string, data: any, type: AnalyticsObject['type'], fn: AnyFunction) {
51
+ try {
52
+
53
+ const obfuscated = this.obfuscate({
54
+ ...data,
55
+ ...this.prepare(),
56
+ })
57
+
58
+ fn({
59
+ name,
60
+ type,
61
+ data: obfuscated,
62
+ })
63
+ } catch (e) {
64
+ this.callers.error(e)
65
+ }
66
+ }
67
+ }
package/src/Logger.ts ADDED
@@ -0,0 +1,223 @@
1
+ import { inspect } from 'util'
2
+ import { TypeGuards, LogType, AppSettings } from '@codeleap/types'
3
+ import { Analytics } from './Analytics'
4
+ import { SentryService } from './Sentry'
5
+ import { SlackService } from './Slack'
6
+ import { LogToTerminal, LogFunctionArgs, LogToTerminalArgs, LoggerMiddleware } from './types'
7
+
8
+ const logLevels: LogType[] = ['debug', 'info', 'log', 'warn', 'error']
9
+
10
+ const emptyFunction = () => { }
11
+
12
+ const hollowAnalytics = new Analytics({
13
+ init: emptyFunction,
14
+ onEvent: emptyFunction,
15
+ onInteraction: emptyFunction,
16
+ prepareData: () => ({}),
17
+ }, {})
18
+
19
+ export class Logger {
20
+ static settings: AppSettings
21
+
22
+ settings: AppSettings
23
+
24
+ sentry: SentryService
25
+
26
+ slack: SlackService
27
+
28
+ middleware: LoggerMiddleware[] = []
29
+
30
+ constructor(settings: AppSettings, middleware?: LoggerMiddleware[], public analytics?: Analytics) {
31
+ this.settings = settings
32
+ this.middleware = middleware || []
33
+ if (settings.Logger.isMain) {
34
+ Logger.settings = settings
35
+ }
36
+
37
+ if (settings?.Logger?.IgnoreWarnings?.length) {
38
+ const newConsole = (args, oldConsole) => {
39
+ const shouldIgnore = typeof args[0] === 'string' &&
40
+ settings.Logger.IgnoreWarnings.some(ignoredWarning => args.join(' ').includes(ignoredWarning))
41
+ if (shouldIgnore) return
42
+ else return oldConsole.apply(console, args)
43
+ }
44
+ const consoles = ['log', 'warn', 'error']
45
+
46
+ consoles.forEach(t => {
47
+ const tmp = console[t]
48
+ console[t] = (...args) => newConsole(args, tmp)
49
+ })
50
+ }
51
+
52
+ this.sentry = new SentryService(settings)
53
+
54
+ this.slack = new SlackService(settings)
55
+
56
+ if (!analytics) {
57
+ this.analytics = hollowAnalytics
58
+ }
59
+
60
+ this.analytics.onError((err) => {
61
+ this.logToTerminal({
62
+ logType: 'error',
63
+ args: ['Error on analytics event', err, 'Internal'],
64
+ })
65
+ })
66
+
67
+ }
68
+
69
+ static formatContent(logArgs: LogToTerminalArgs) {
70
+ const { logType, args: content, deviceIdentifier: deviceId, stringify, logKeys = true } = logArgs
71
+
72
+ const [descriptionOrValue, value, category] = content
73
+
74
+ const nArgs = content.length
75
+ let logContent = content
76
+
77
+ const logValue = nArgs === 1 ? descriptionOrValue : value
78
+
79
+ const shouldStringify = stringify && !!logValue && TypeGuards.isObject(logValue) && !(logValue instanceof Error)
80
+ const inspectOptions = Logger?.settings?.Logger?.inspect || {}
81
+
82
+ const displayValue = shouldStringify ? inspect(logValue, {
83
+ depth: 5,
84
+ showHidden: true,
85
+ ...inspectOptions,
86
+ }) : logValue
87
+
88
+ if (nArgs === 3) {
89
+ logContent = [
90
+ `(${category}) ${descriptionOrValue}${displayValue ? ' ->' : ''}`,
91
+ displayValue,
92
+ ]
93
+ }
94
+
95
+ if (nArgs === 2) {
96
+ logContent = [
97
+ `${descriptionOrValue}${displayValue ? ' ->' : ''}`,
98
+ displayValue,
99
+ ]
100
+ }
101
+
102
+ if (nArgs === 1) {
103
+ const isObj = typeof descriptionOrValue === 'object' && !(descriptionOrValue instanceof Error)
104
+ const keys = isObj ? Object.keys(descriptionOrValue) : null
105
+ const title = isObj && keys.length && logKeys ?
106
+ `${keys.filter(i => !!i).slice(0, 3).join(', ')}${keys.length > 3 ? '...' : ''} ->`
107
+ : null
108
+
109
+ if (title) {
110
+ logContent = [
111
+ title,
112
+ displayValue,
113
+ ]
114
+ } else {
115
+ logContent = [
116
+ displayValue,
117
+ ]
118
+ }
119
+ }
120
+
121
+ return logContent
122
+ }
123
+
124
+ static coloredLog: LogToTerminal = (logArgs) => {
125
+ const { logType, args: content, deviceIdentifier: deviceId, stringify } = logArgs
126
+
127
+ const logContent = Logger.formatContent(logArgs)
128
+
129
+ const displayLog = logType === 'error' ? 'warn' : logType
130
+
131
+ console[displayLog](deviceId, ...logContent)
132
+
133
+ return logContent
134
+ }
135
+
136
+ private logToTerminal: LogToTerminal = (logArgs) => {
137
+ const { logType, args, color } = logArgs
138
+
139
+ if (this.settings.Logger.Level === 'silent') return
140
+
141
+ const shouldLog = TypeGuards.isString(this.settings.Logger.Level) ?
142
+ logLevels.indexOf(logType) >=
143
+ logLevels.indexOf(this.settings.Logger.Level) : this.settings.Logger.Level.includes(logType)
144
+ if (!shouldLog) return
145
+
146
+ const content = Logger.formatContent(logArgs)
147
+
148
+ if (this.settings.Environment.IsDev) {
149
+
150
+ const deviceId = this.settings.Logger?.DeviceIdentifier ?
151
+ `[${this.settings.Logger.DeviceIdentifier}]` : ''
152
+
153
+ const stringify = this.settings.Logger?.StringifyObjects
154
+
155
+ this.middleware.forEach(m => m(logArgs, content))
156
+
157
+ Logger.coloredLog(
158
+ {
159
+ logType: logType as LogType,
160
+ args,
161
+ color,
162
+ deviceIdentifier: deviceId,
163
+ stringify,
164
+ },
165
+ )
166
+
167
+ }
168
+
169
+ if (!this.settings.Environment.IsDev || this.settings.Logger.alwaysSendToSentry) {
170
+ try {
171
+
172
+ this.middleware.forEach(m => m(logArgs, content))
173
+ if (['info', 'log'].includes(logType)) {
174
+
175
+ this.sentry.captureBreadcrumb(
176
+ logType,
177
+ content,
178
+ )
179
+ }
180
+ if (['error'].includes(logType)) {
181
+ this.sentry.sendLog(args?.[1] || args?.[0])
182
+ }
183
+ } catch (e) {
184
+ // Nothing
185
+ }
186
+ }
187
+ }
188
+
189
+ info(...args: LogFunctionArgs) {
190
+ this.logToTerminal({
191
+ args,
192
+ logType: 'info',
193
+ })
194
+ }
195
+
196
+ error(...args: LogFunctionArgs) {
197
+ this.logToTerminal({
198
+ args,
199
+ logType: 'error',
200
+ })
201
+ }
202
+
203
+ warn(...args: LogFunctionArgs) {
204
+ this.logToTerminal({
205
+ args,
206
+ logType: 'warn',
207
+ })
208
+ }
209
+
210
+ log(...args: LogFunctionArgs) {
211
+ this.logToTerminal({
212
+ args,
213
+ logType: 'log',
214
+ })
215
+ }
216
+
217
+ debug(...args: LogFunctionArgs) {
218
+ this.logToTerminal({
219
+ args,
220
+ logType: 'debug',
221
+ })
222
+ }
223
+ }
package/src/Sentry.ts ADDED
@@ -0,0 +1,54 @@
1
+ import type { Breadcrumb, ClientOptions } from '@sentry/types'
2
+ import { AppSettings } from '@codeleap/types'
3
+ import {
4
+ LogFunctionArgs,
5
+ LogType,
6
+ SentrySeverityMap,
7
+ SentryProvider,
8
+ } from './types'
9
+
10
+ export class SentryService {
11
+ private sentry: SentryProvider
12
+
13
+ private use: boolean
14
+
15
+ constructor(settings: AppSettings) {
16
+ this.use = settings?.Sentry?.enable
17
+ this.sentry = settings?.Sentry?.provider as SentryProvider
18
+ if (this.use) {
19
+ const isDebug = settings?.Sentry?.debug || false
20
+ if (isDebug) console.log('> > > Initializing Sentry', settings.Sentry)
21
+ const initObj:ClientOptions = {
22
+ dsn: settings.Sentry.dsn,
23
+ debug: isDebug,
24
+ integrations: [],
25
+ ...settings?.Sentry?.initArgs,
26
+ }
27
+ if (settings?.Sentry?.beforeBreadcrumb) {
28
+ initObj.beforeBreadcrumb = settings?.Sentry?.beforeBreadcrumb
29
+ }
30
+ this.sentry?.init?.(initObj)
31
+ }
32
+ }
33
+
34
+ captureBreadcrumb(type: LogType, content: LogFunctionArgs) {
35
+ if (!this.use) return
36
+
37
+ const [message, data, category] = content
38
+
39
+ const sentryArgs: Breadcrumb = {
40
+ message,
41
+ data,
42
+ category,
43
+ level: SentrySeverityMap[type],
44
+ type: '',
45
+ }
46
+
47
+ this.sentry.addBreadcrumb(sentryArgs)
48
+ }
49
+
50
+ sendLog(err?: any) {
51
+ if (!this.use) return
52
+ this.sentry.captureException(err)
53
+ }
54
+ }
package/src/Slack.ts ADDED
@@ -0,0 +1,138 @@
1
+ import { inspect } from 'util'
2
+ import { TypeGuards, AppSettings } from '@codeleap/types'
3
+
4
+ type EchoSlackConfig = AppSettings['Slack']['echo']
5
+
6
+ type EchoSlack = {
7
+ label: string
8
+ data: object
9
+ options?: EchoSlackOptions
10
+ module?: string
11
+ }
12
+
13
+ type OptionInclude = 'version'
14
+
15
+ type SendIn = 'debug' | 'release'
16
+
17
+ type EchoSlackOptions = {
18
+ sendIn?: SendIn[]
19
+ include?: OptionInclude[]
20
+ }
21
+
22
+ const DEFAULT_CHANNEL = '#_dev_logs'
23
+ const DEFAULT_BASE_URL = 'https://slack.com/api/chat.postMessage'
24
+
25
+ export class SlackService {
26
+ private echoConfig: EchoSlackConfig
27
+
28
+ private isDev: boolean
29
+
30
+ private appName: string
31
+
32
+ public api
33
+
34
+ constructor(settings: AppSettings) {
35
+ this.echoConfig = settings?.Slack?.echo as EchoSlackConfig
36
+ this.isDev = settings?.Environment?.IsDev
37
+ this.appName = settings?.AppName
38
+ }
39
+
40
+ async echo(
41
+ label: EchoSlack['label'],
42
+ slackData: EchoSlack['data'],
43
+ moduleName: EchoSlack['module'] = null,
44
+ messageOptions: EchoSlack['options'] = {}
45
+ ) {
46
+ const options = this.parseOptions(messageOptions)
47
+ const slack = this.parseData(label, slackData, options.info, moduleName)
48
+
49
+ const enabled = TypeGuards.isBoolean(this.echoConfig.enabled) ? this.echoConfig.enabled : true
50
+
51
+ if (!options.send || !this.api || !this.echoConfig || !enabled) return
52
+
53
+ const settingsData = this?.echoConfig?.options ?? {}
54
+
55
+ try {
56
+ const data = {
57
+ 'channel': this?.echoConfig?.channel ?? DEFAULT_CHANNEL,
58
+ text: slack,
59
+ 'username': `${this.appName} Log`,
60
+ 'icon_url': this?.echoConfig?.icon,
61
+ ...settingsData,
62
+ }
63
+
64
+ await this.api.post('', data, {
65
+ baseURL: this?.echoConfig?.baseURL ?? DEFAULT_BASE_URL,
66
+ headers: {
67
+ Authorization: `Bearer ${this?.echoConfig?.token}`,
68
+ },
69
+ })
70
+ } catch (err) {
71
+ console.error('Failed to echo', err, 'logger echoSlack')
72
+ }
73
+ }
74
+
75
+ private serializers: Record<OptionInclude, (IsDev: boolean) => string> = {
76
+ version: (IsDev: boolean) => {
77
+ return IsDev ? 'debug' : 'release'
78
+ },
79
+ }
80
+
81
+ private parseOptions(options: EchoSlackOptions) {
82
+ const {
83
+ sendIn = [],
84
+ include = [],
85
+ } = options
86
+
87
+ const hasSendIn = sendIn.length >= 1
88
+ const isDebug = hasSendIn ? sendIn.includes('debug') : true
89
+ const isRelease = hasSendIn ? sendIn.includes('release') : true
90
+
91
+ if (!isDebug && this.isDev || !isRelease && !this.isDev) {
92
+ return {
93
+ info: '',
94
+ send: false,
95
+ }
96
+ }
97
+
98
+ let str = ''
99
+ const separator = ' - '
100
+
101
+ include.forEach(k => {
102
+ const data = this.serializers[k]?.(this.isDev)
103
+ str = `${str}${str.length > 0 ? separator : ''}[${data}]`
104
+ })
105
+
106
+ return {
107
+ info: str,
108
+ send: true,
109
+ }
110
+ }
111
+
112
+ private parseData(label: string, data: object, info: string, module?: string) {
113
+ const obj = !info ? data : {
114
+ ...data,
115
+ info,
116
+ }
117
+
118
+ const args = [`${!module ? '' : `(${module}) `}${label}: `, obj]
119
+
120
+ const slack = args.map(i => {
121
+ if (typeof i === 'object') {
122
+ try {
123
+ return inspect(i, {
124
+ depth: 5,
125
+ compact: false,
126
+ showHidden: true,
127
+ })
128
+ } catch (e) {
129
+ return `${i} (Unserializable value)`
130
+ }
131
+ }
132
+
133
+ return String(i)
134
+ }).join(' ')
135
+
136
+ return slack
137
+ }
138
+ }
@@ -0,0 +1,37 @@
1
+ import { ConsoleColor } from './types'
2
+ import { LogType } from '@codeleap/types'
3
+
4
+ export const foregroundColors = {
5
+ Black: '\x1b[30m',
6
+ Red: '\x1b[31m',
7
+ Green: '\x1b[32m',
8
+ Yellow: '\x1b[33m',
9
+ Blue: '\x1b[34m',
10
+ Magenta: '\x1b[35m',
11
+ Cyan: '\x1b[36m',
12
+ White: '\x1b[37m',
13
+ } as const
14
+
15
+ export const formatColors = {
16
+ Reset: '\x1b[0m',
17
+ Bright: '\x1b[1m',
18
+ Dim: '\x1b[2m',
19
+ Underscore: '\x1b[4m',
20
+ Blink: '\x1b[5m',
21
+ Reverse: '\x1b[7m',
22
+ Hidden: '\x1b[8m',
23
+ } as const
24
+
25
+ export const colors = {
26
+ ...foregroundColors,
27
+ ...formatColors,
28
+ }
29
+
30
+ export const logColors: Record<LogType, ConsoleColor> = {
31
+ error: 'Red',
32
+ info: 'White',
33
+ warn: 'Yellow',
34
+ debug: 'Magenta',
35
+ log: 'White',
36
+ silent: 'Green',
37
+ }
package/src/index.ts ADDED
@@ -0,0 +1,5 @@
1
+ export * as LoggerTypes from './types'
2
+ export * as LoggerAnalytics from './Analytics'
3
+ export * from './Logger'
4
+ export * from './silentLogger'
5
+ export * from './performance'
@@ -0,0 +1,76 @@
1
+ import { cloneDeep } from '@codeleap/utils'
2
+ import { Matcher } from '@codeleap/types'
3
+ import { inspect } from 'util'
4
+ import parse from 'url-parse'
5
+
6
+ type ObfuscateArgs = {
7
+ object: any
8
+ keys: (Matcher<'key'>)[]
9
+ values: (Matcher<'value'>)[]
10
+ }
11
+
12
+ function removeKey(obj, key) {
13
+ if (obj?.hasOwnProperty(key)) {
14
+ obj[key] = '[secret]'
15
+ }
16
+ for (const subObj in obj) {
17
+ if (typeof obj[subObj] == 'object') {
18
+ removeKey(obj[subObj], key)
19
+ }
20
+ }
21
+ }
22
+
23
+ function removeValue(obj, value) {
24
+ for (const subObj in obj) {
25
+ const isString = typeof obj[subObj] == 'string'
26
+ if (isString) {
27
+ const isRegex = value instanceof RegExp
28
+ const match = isRegex ? value.test(obj[subObj]) : obj[subObj].includes(value)
29
+ if (match) {
30
+ if (obj[subObj].startsWith('http')) {
31
+ const url = parse(obj[subObj])
32
+ obj[subObj] = `${url.origin}${url.pathname}/[secret]`
33
+ } else {
34
+ obj[subObj] = '[secret]'
35
+ }
36
+ }
37
+ }
38
+ if (typeof obj[subObj] == 'object') {
39
+ removeValue(obj[subObj], value)
40
+ }
41
+ }
42
+ }
43
+
44
+ export function obfuscate(args: ObfuscateArgs) {
45
+ const { object, keys, values } = args
46
+
47
+ let isCircular = false
48
+ try {
49
+ JSON.stringify(args)
50
+ } catch (e) {
51
+ isCircular = true
52
+ }
53
+
54
+ if (typeof object === 'object' && !isCircular) {
55
+ let cleanData = {}
56
+ try {
57
+ cleanData = cloneDeep(object)
58
+ keys.forEach(fieldName => removeKey(cleanData, fieldName))
59
+ values.forEach(fieldName => removeValue(cleanData, fieldName))
60
+ } catch (err1) {
61
+ try {
62
+ cleanData = inspect(object, { depth: 1 })
63
+ } catch (err2) {
64
+ cleanData = { value: `Couldn't process data` }
65
+ }
66
+ }
67
+ const result = cleanData
68
+ return result
69
+ } else {
70
+ if (isCircular) {
71
+ return { ...args.object, WARNING: 'Circular reference detected' }
72
+ } else {
73
+ return args.object
74
+ }
75
+ }
76
+ }
@@ -0,0 +1,26 @@
1
+ import { InspectRenderOptions } from '.'
2
+
3
+ type ErrorArgs = InspectRenderOptions & {
4
+ name: string
5
+ maxRenders: number
6
+ }
7
+
8
+ type ErrorNames = 'maxRenders'
9
+
10
+ const defineError = (errorName: ErrorNames, args: Partial<ErrorArgs>) => {
11
+ switch (errorName) {
12
+ case 'maxRenders':
13
+ return `${args.name} is rendering more than ${args.maxRenders}time per ${
14
+ args.throttleInterval / 1000
15
+ }second!
16
+ If you aware of this, you can disable it in the Settings.ts > Performancer.
17
+ `
18
+ }
19
+ }
20
+
21
+ export class PerformanceError extends Error {
22
+ constructor(errorName: ErrorNames, args: Partial<ErrorArgs>) {
23
+ super(defineError(errorName, args))
24
+ this.name = 'Codeleap:Perf'
25
+ }
26
+ }
@@ -0,0 +1,86 @@
1
+ import { throttle } from '@codeleap/utils'
2
+ import { useEffect } from 'react'
3
+ import { AppSettings } from '@codeleap/types'
4
+ import { PerformanceError } from './errors'
5
+
6
+ export type InspectRenderOptions = {
7
+ noHooks?: boolean
8
+ logMode?: 'raw' | 'summarized'
9
+ throttleInterval?: number
10
+ maxRenders?: number
11
+ }
12
+
13
+ export type PerformanceInspector = {
14
+ inspectRender: (name: string, options?: InspectRenderOptions) => void
15
+ }
16
+
17
+ const renderCounter: Record<string, number> = {}
18
+
19
+ export function makePerformanceInspector(settings: AppSettings) {
20
+ /**
21
+ * inspectRender monitors how much time a component render per second.
22
+ * Use perf.inspectRender('ComponentName') inside a component to monitor it.
23
+ * @param {string} name - Component name
24
+ * @param {PerformanceInspector} options - Some options for the inspector
25
+ * @returns
26
+ */
27
+ const inspectRender = (
28
+ name: string,
29
+ options: InspectRenderOptions = {
30
+ noHooks: false,
31
+ logMode: 'summarized',
32
+ throttleInterval: 1000,
33
+ maxRenders: settings?.PerformanceInspector.maxRenders,
34
+ },
35
+ ) => {
36
+ const blacklist = settings?.PerformanceInspector.blacklist || []
37
+ if (blacklist.some((item) => name.startsWith(item))) return
38
+
39
+ const { noHooks, logMode, throttleInterval, maxRenders } = options
40
+
41
+ if (
42
+ !settings?.PerformanceInspector.enable ||
43
+ !settings?.Environment.IsDev
44
+ ) {
45
+ return
46
+ }
47
+
48
+ if (!noHooks) {
49
+ useEffect(() => {
50
+ console.log(`[PerformanceInspector] Mounted -> ${name}`)
51
+
52
+ return () => {
53
+ console.log(`[PerformanceInspector] Unmounted -> ${name}`)
54
+ }
55
+ })
56
+ }
57
+
58
+ renderCounter[name] = renderCounter[name] ? renderCounter[name] + 1 : 1
59
+ const renders = renderCounter[name]
60
+
61
+ if (renders > maxRenders) {
62
+ renderCounter[name] = 0
63
+ throw new PerformanceError('maxRenders', {
64
+ name,
65
+ throttleInterval,
66
+ maxRenders,
67
+ })
68
+ }
69
+
70
+ if (logMode === 'raw') {
71
+ console.log(`[PerformanceInspector] Rendered -> ${name}: ${renders}`)
72
+ return
73
+ }
74
+
75
+ function logSummary() {
76
+ if (renders <= 0) return
77
+
78
+ console.log(`[PerformanceInspector] Render summary -> ${name}: ${renders}`)
79
+ renderCounter[name] = 0
80
+ }
81
+
82
+ throttle(logSummary, name, throttleInterval)
83
+ }
84
+
85
+ return { inspectRender }
86
+ }
@@ -0,0 +1,11 @@
1
+ import { Logger } from './Logger'
2
+
3
+ export const silentLogger = new Logger({
4
+ Logger: {
5
+ Level: 'silent',
6
+ IgnoreWarnings: [
7
+ `Require cycle:`,
8
+ `Require cycles are allowed`,
9
+ ],
10
+ },
11
+ })
package/src/types.ts ADDED
@@ -0,0 +1,45 @@
1
+ import { FunctionType } from '@codeleap/types'
2
+ import { colors, foregroundColors } from './constants'
3
+
4
+ import type { SeverityLevel, Client, ClientOptions, Breadcrumb } from '@sentry/types'
5
+
6
+ export type LogType = 'info' | 'debug' | 'warn' | 'error' | 'log' | 'silent'
7
+
8
+ export type LogFunctionArgs = [
9
+ description?: any,
10
+ value?:any,
11
+ category?:string
12
+ ]
13
+ export type ConsoleColor = keyof typeof colors
14
+ export type DebugColors = {
15
+ [Property in keyof typeof foregroundColors as `${Lowercase<
16
+ string & Property
17
+ >}`]: (...args: LogFunctionArgs) => void;
18
+ }
19
+ export type DebugColor = keyof DebugColors
20
+ export type LogToTerminalArgs = {
21
+ logType: LogType
22
+ args: LogFunctionArgs
23
+ color?: keyof DebugColors
24
+ deviceIdentifier?: string
25
+ stringify?: boolean
26
+ logKeys?: boolean
27
+ }
28
+ export type LogToTerminal = FunctionType<[LogToTerminalArgs], void>
29
+
30
+ export const SentrySeverityMap: Record<LogType, SeverityLevel> = {
31
+ debug: 'debug',
32
+ error: 'error',
33
+ info: 'info',
34
+ log: 'log',
35
+ warn: 'warning',
36
+ silent: 'log',
37
+ }
38
+
39
+ export type SentryProvider = {
40
+ addBreadcrumb: FunctionType<[Breadcrumb], void>
41
+ init(options: ClientOptions): Client
42
+ captureException(err: any): void
43
+ }
44
+
45
+ export type LoggerMiddleware = FunctionType<[arguments: LogToTerminalArgs, formattedContent: string[]], any>