@likec4/log 1.21.1 → 1.22.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@likec4/log",
3
3
  "license": "MIT",
4
- "version": "1.21.1",
4
+ "version": "1.22.0",
5
5
  "bugs": "https://github.com/likec4/likec4/issues",
6
6
  "homepage": "https://likec4.dev",
7
7
  "author": "Denis Davydkov <denis@davydkov.com>",
@@ -18,23 +18,10 @@
18
18
  "exports": {
19
19
  ".": {
20
20
  "sources": "./src/index.ts",
21
- "node": {
21
+ "default": {
22
22
  "types": "./dist/index.d.ts",
23
23
  "import": "./dist/index.mjs",
24
24
  "require": "./dist/index.cjs"
25
- },
26
- "default": {
27
- "types": "./dist/index.d.ts",
28
- "import": "./dist/browser.mjs",
29
- "require": "./dist/browser.cjs"
30
- }
31
- },
32
- "./browser": {
33
- "sources": "./src/browser.ts",
34
- "default": {
35
- "types": "./dist/index.d.ts",
36
- "import": "./dist/browser.mjs",
37
- "require": "./dist/browser.cjs"
38
25
  }
39
26
  }
40
27
  },
@@ -46,9 +33,12 @@
46
33
  "typecheck": "tsc --noEmit",
47
34
  "build": "unbuild"
48
35
  },
36
+ "dependencies": {
37
+ "@logtape/logtape": "^0.8.2"
38
+ },
49
39
  "devDependencies": {
50
- "@likec4/tsconfig": "1.21.1",
51
- "@types/node": "^20.17.7",
40
+ "@likec4/tsconfig": "1.22.0",
41
+ "@types/node": "^20.17.17",
52
42
  "consola": "^3.4.0",
53
43
  "merge-error-cause": "^5.0.0",
54
44
  "safe-stringify": "^1.1.1",
@@ -0,0 +1,194 @@
1
+ import {
2
+ type AnsiColorFormatterOptions,
3
+ type ConsoleSinkOptions,
4
+ type FormattedValues,
5
+ type LogLevel,
6
+ type LogRecord,
7
+ type Sink,
8
+ type TextFormatter,
9
+ type TextFormatterOptions,
10
+ getAnsiColorFormatter as getLogtapeAnsiColorFormatter,
11
+ getConsoleSink as getLogtapeConsoleSink,
12
+ getTextFormatter as getLogtapeTextFormatter,
13
+ } from '@logtape/logtape'
14
+ import mergeErrorCause from 'merge-error-cause'
15
+ import { ident, parseStack } from './utils'
16
+
17
+ // export function formatProperties(properties: Record<string, unknown>): {
18
+ // properties: Record<string, unknown>
19
+ // output?: string
20
+ // error?: never
21
+ // } | {
22
+ // properties: Record<string, unknown>
23
+ // output: string
24
+ // error?: {
25
+ // message: string
26
+ // name: string
27
+ // stack?: string
28
+ // }
29
+ // } {
30
+ // let error: {
31
+ // message: string
32
+ // name: string
33
+ // stack?: string
34
+ // } | undefined
35
+ // let totalProps = 0
36
+ // const formattedProperties = {} as Record<string, unknown>
37
+ // for (const [key, value] of Object.entries(properties)) {
38
+ // if (value instanceof Error) {
39
+ // const mergedErr = mergeErrorCause(value)
40
+ // const stack = mergedErr.stack ? parseStack(mergedErr.stack) : undefined
41
+ // const formattedError = {
42
+ // message: mergedErr.message,
43
+ // name: mergedErr.name,
44
+ // ...stack && { stack: stack.join('\n') },
45
+ // }
46
+ // error ??= formattedError
47
+ // formattedProperties[key] = formattedError
48
+ // } else {
49
+ // formattedProperties[key] = value
50
+ // }
51
+ // totalProps++
52
+ // }
53
+ // if (totalProps === 0) {
54
+ // return {
55
+ // properties: formattedProperties,
56
+ // }
57
+ // }
58
+ // if (error) {
59
+ // return {
60
+ // error: error,
61
+ // output: error.stack ? error.message + '\n' + ident(error.stack.slice(1)) : error.message,
62
+ // properties: formattedProperties,
63
+ // }
64
+ // }
65
+ // return {
66
+ // ...(error && { error: error }),
67
+ // output: safeStringify(formattedProperties, { indentation: '\t' }),
68
+ // properties: formattedProperties,
69
+ // }
70
+ // }
71
+
72
+ export function errorFromLogRecord(record: LogRecord): Error | null {
73
+ const errors = Object
74
+ .values(record.properties)
75
+ .filter((v) => v instanceof Error)
76
+ .map(err => {
77
+ const mergedErr = mergeErrorCause(err)
78
+ if (mergedErr.stack) {
79
+ mergedErr.stack = parseStack(mergedErr.stack).join('\n')
80
+ }
81
+ return mergedErr
82
+ })
83
+ if (errors.length === 0) {
84
+ return null
85
+ }
86
+ return errors.length === 1 ? errors[0]! : new AggregateError(errors)
87
+ // if (typeof record.rawMessage === 'string') {
88
+ // return wrapErrorMessage(error, record.rawMessage + Z'\n')
89
+ // }
90
+ // return error
91
+ }
92
+
93
+ export function appendErrorToMessage(values: FormattedValues, color = false): FormattedValues {
94
+ const error = errorFromLogRecord(values.record)
95
+ if (error) {
96
+ let errorMessge = error.message
97
+ if (error.stack) {
98
+ errorMessge = errorMessge + '\n' + ident(error.stack.split('\n').slice(1))
99
+ }
100
+ if (color) {
101
+ errorMessge = `RED: ${ansiColors.red}${errorMessge}${RESET}`
102
+ }
103
+ return {
104
+ ...values,
105
+ message: values.message + '\n' + ident(errorMessge),
106
+ }
107
+ }
108
+ return values
109
+ }
110
+
111
+ // export function formatRecord(values: FormattedValues): FormattedValues {
112
+ // const props = formatProperties(values.record.properties)
113
+ // if (props.output) {
114
+ // props.output = ' \n' + ident(props.output)
115
+ // return {
116
+ // ...values,
117
+ // record: {
118
+ // ...values.record,
119
+ // properties: props.properties,
120
+ // },
121
+ // message: `${values.message}${props.output}`,
122
+ // }
123
+ // }
124
+ // return values
125
+ // }
126
+
127
+ const levelAbbreviations: Record<LogLevel, string> = {
128
+ 'debug': 'DEBUG',
129
+ 'info': 'INFO ',
130
+ 'warning': 'WARN ',
131
+ 'error': 'ERROR',
132
+ 'fatal': 'FATAL',
133
+ }
134
+
135
+ export function getMessageOnlyFormatter(): TextFormatter {
136
+ return getTextFormatter({
137
+ format: ({ message }): string => {
138
+ return message
139
+ },
140
+ })
141
+ }
142
+
143
+ const level = (l: LogLevel): string => levelAbbreviations[l]
144
+
145
+ export function getTextFormatter(options?: TextFormatterOptions): TextFormatter {
146
+ const format = options?.format ?? (({ timestamp, level, category, message }: FormattedValues): string => {
147
+ return `${timestamp} ${level} ${category} ${message}`
148
+ })
149
+ // const format = options?.format
150
+ return getLogtapeTextFormatter({
151
+ timestamp: 'time',
152
+ level,
153
+ category: '.',
154
+ ...options,
155
+ format: (values) => format(appendErrorToMessage(values)),
156
+ })
157
+ }
158
+
159
+ const RESET = '\x1b[0m'
160
+
161
+ const ansiColors = {
162
+ // black: "\x1b[30m",
163
+ red: '\x1b[31m',
164
+ // green: "\x1b[32m",
165
+ // yellow: "\x1b[33m",
166
+ // blue: "\x1b[34m",
167
+ // magenta: "\x1b[35m",
168
+ // cyan: "\x1b[36m",
169
+ // white: "\x1b[37m",
170
+ } as const
171
+
172
+ export function getAnsiColorFormatter(options?: AnsiColorFormatterOptions): TextFormatter {
173
+ const format = options?.format ?? (({ timestamp, level, category, message }: FormattedValues): string => {
174
+ return `${timestamp} ${level} ${category} ${message}`
175
+ })
176
+ return getLogtapeAnsiColorFormatter({
177
+ timestamp: 'time',
178
+ level,
179
+ categoryStyle: 'bold',
180
+ categoryColor: 'cyan',
181
+ category: '.',
182
+ ...options,
183
+ format: (values) => {
184
+ return format(appendErrorToMessage(values, true))
185
+ },
186
+ })
187
+ }
188
+
189
+ export function getConsoleSink(options?: ConsoleSinkOptions): Sink {
190
+ return getLogtapeConsoleSink({
191
+ formatter: getAnsiColorFormatter(),
192
+ ...options,
193
+ })
194
+ }
package/src/index.ts CHANGED
@@ -1,36 +1,84 @@
1
- import { createConsola } from 'consola'
2
- import { type LogObject, LogLevels } from 'consola/core'
3
- import { sep } from 'node:path'
4
- import { cwd } from 'node:process'
5
- import { type FormattedLogObject, formattedLogObj } from './format'
6
-
7
- export type * from 'consola/core'
8
- export type { FormattedLogObject }
9
-
10
- function parseStack(stack: string): string[] {
11
- const currentDir = cwd() + sep
12
- const lines = stack.split('\n').map((l) => l.trim().replace('file://', '').replace(currentDir, ''))
13
- return lines
1
+ import {
2
+ type Config,
3
+ configure as configureLogtape,
4
+ getLogger,
5
+ } from '@logtape/logtape'
6
+ import { getConsoleSink } from './formatters'
7
+
8
+ export type {
9
+ Filter,
10
+ Logger,
11
+ LogLevel,
12
+ LogRecord,
13
+ Sink,
14
+ TextFormatter,
15
+ } from '@logtape/logtape'
16
+
17
+ export {
18
+ errorFromLogRecord,
19
+ // formatProperties,
20
+ // formatRecord,
21
+ getAnsiColorFormatter,
22
+ getConsoleSink,
23
+ getMessageOnlyFormatter,
24
+ getTextFormatter,
25
+ } from './formatters'
26
+
27
+ export {
28
+ logger as consola,
29
+ logger as rootLogger,
14
30
  }
15
31
 
16
- export function formatLogObj(logObj: LogObject): FormattedLogObject {
17
- return formattedLogObj(logObj, parseStack)
32
+ export {
33
+ loggable,
34
+ } from './utils'
35
+
36
+ export const logger = getLogger('likec4')
37
+
38
+ /**
39
+ * Get a child logger with the given subcategory.
40
+ *
41
+ * @param subcategory The subcategory.
42
+ * @returns The child logger.
43
+ */
44
+ export function createLogger(subcategory: string | readonly [string] | readonly [string, ...string[]]) {
45
+ return logger.getChild(subcategory)
18
46
  }
19
47
 
20
- const level = LogLevels.debug
21
-
22
- const consola = createConsola({
23
- level,
24
- defaults: {
25
- level,
26
- },
27
- throttle: 2,
28
- throttleMin: 500,
29
- formatOptions: {
30
- colors: true,
31
- compact: false,
32
- date: false,
33
- },
34
- })
35
-
36
- export { consola, consola as logger, consola as rootLogger, LogLevels }
48
+ let configureWasCalled = false
49
+
50
+ export async function configureLogger<TSinkId extends string, TFilterId extends string>(
51
+ config?: Config<TSinkId, TFilterId>,
52
+ ) {
53
+ try {
54
+ configureWasCalled = true
55
+ const sinks = config?.sinks ?? {}
56
+ await configureLogtape<any, any>({
57
+ ...config,
58
+ sinks: {
59
+ ...sinks,
60
+ // @ts-expect-error console is not a valid sink id
61
+ console: sinks['console'] ?? getConsoleSink(),
62
+ },
63
+ loggers: [
64
+ { category: ['logtape', 'meta'], sinks: ['console' as any], lowestLevel: 'warning' },
65
+ ...(config?.loggers ?? [
66
+ {
67
+ category: 'likec4',
68
+ sinks: ['console' as any],
69
+ lowestLevel: 'debug',
70
+ },
71
+ ]),
72
+ ],
73
+ })
74
+ } catch (e) {
75
+ console.error(e)
76
+ }
77
+ }
78
+
79
+ export function ensureLoggerIsConfigured() {
80
+ if (!configureWasCalled) {
81
+ configureLogger()
82
+ console.warn('logger automatically configured with default settings')
83
+ }
84
+ }
package/src/utils.ts ADDED
@@ -0,0 +1,37 @@
1
+ import mergeErrorCause from 'merge-error-cause'
2
+ import safeStringify from 'safe-stringify'
3
+
4
+ export const parseStack = (stack: string): string[] => {
5
+ const lines = stack
6
+ .split('\n')
7
+ .map((l) => {
8
+ let replaced = l.trim()
9
+ .replace('file://', '')
10
+ // // Remove c:\Users\<user>... -> @vscode...
11
+ // .replace(/[A-Za-z]:\\Users\\[^\\]+\\/g, '@vscode\\')
12
+ // // Remove /Users/<user>/... -> @vscode/...
13
+ // .replace(/\/Users\/[^/]+\//g, '@vscode/')
14
+ return replaced
15
+ })
16
+ return lines
17
+ }
18
+
19
+ export function ident(value: string | string[], identation = 2): string {
20
+ value = Array.isArray(value) ? value : value.split('\n')
21
+ return value.map((l) => `${' '.repeat(identation)}${l}`).join('\n')
22
+ }
23
+
24
+ export function loggable(error: unknown): string {
25
+ if (typeof error === 'string') {
26
+ return error
27
+ }
28
+ if (error instanceof Error) {
29
+ const mergedErr = mergeErrorCause(error)
30
+ if (mergedErr.stack) {
31
+ const stack = parseStack(mergedErr.stack)
32
+ return mergedErr.message + '\n' + ident(stack.slice(1))
33
+ }
34
+ return mergedErr.message
35
+ }
36
+ return safeStringify(error)
37
+ }