@likec4/log 1.21.1 → 1.22.1

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.1",
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.1",
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,201 @@
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 wrapErrorMessage from 'wrap-error-message'
16
+ import { ident, parseStack } from './utils'
17
+
18
+ // export function formatProperties(properties: Record<string, unknown>): {
19
+ // properties: Record<string, unknown>
20
+ // output?: string
21
+ // error?: never
22
+ // } | {
23
+ // properties: Record<string, unknown>
24
+ // output: string
25
+ // error?: {
26
+ // message: string
27
+ // name: string
28
+ // stack?: string
29
+ // }
30
+ // } {
31
+ // let error: {
32
+ // message: string
33
+ // name: string
34
+ // stack?: string
35
+ // } | undefined
36
+ // let totalProps = 0
37
+ // const formattedProperties = {} as Record<string, unknown>
38
+ // for (const [key, value] of Object.entries(properties)) {
39
+ // if (value instanceof Error) {
40
+ // const mergedErr = mergeErrorCause(value)
41
+ // const stack = mergedErr.stack ? parseStack(mergedErr.stack) : undefined
42
+ // const formattedError = {
43
+ // message: mergedErr.message,
44
+ // name: mergedErr.name,
45
+ // ...stack && { stack: stack.join('\n') },
46
+ // }
47
+ // error ??= formattedError
48
+ // formattedProperties[key] = formattedError
49
+ // } else {
50
+ // formattedProperties[key] = value
51
+ // }
52
+ // totalProps++
53
+ // }
54
+ // if (totalProps === 0) {
55
+ // return {
56
+ // properties: formattedProperties,
57
+ // }
58
+ // }
59
+ // if (error) {
60
+ // return {
61
+ // error: error,
62
+ // output: error.stack ? error.message + '\n' + ident(error.stack.slice(1)) : error.message,
63
+ // properties: formattedProperties,
64
+ // }
65
+ // }
66
+ // return {
67
+ // ...(error && { error: error }),
68
+ // output: safeStringify(formattedProperties, { indentation: '\t' }),
69
+ // properties: formattedProperties,
70
+ // }
71
+ // }
72
+
73
+ function gerErrorFromLogRecord(record: LogRecord): Error | null {
74
+ const errors = Object
75
+ .values(record.properties)
76
+ .filter((v) => v instanceof Error)
77
+ .map(err => {
78
+ const mergedErr = mergeErrorCause(err)
79
+ if (mergedErr.stack) {
80
+ mergedErr.stack = parseStack(mergedErr.stack).join('\n')
81
+ }
82
+ return mergedErr
83
+ })
84
+ if (errors.length === 0) {
85
+ return null
86
+ }
87
+ return errors.length === 1 ? errors[0]! : new AggregateError(errors)
88
+ }
89
+
90
+ export function errorFromLogRecord(record: LogRecord): Error | null {
91
+ const error = gerErrorFromLogRecord(record)
92
+ if (error && typeof record.rawMessage === 'string') {
93
+ return wrapErrorMessage(error, record.rawMessage + '\n')
94
+ }
95
+ return error
96
+ }
97
+
98
+ export function appendErrorToMessage(values: FormattedValues, color?: boolean): FormattedValues {
99
+ const error = gerErrorFromLogRecord(values.record)
100
+ if (error) {
101
+ let errorMessge = error.message
102
+ if (error.stack) {
103
+ errorMessge = errorMessge + '\n' + ident(error.stack.split('\n').slice(1))
104
+ }
105
+ if (color) {
106
+ errorMessge = `${ansiColors.red}${errorMessge}${RESET}`
107
+ }
108
+ return {
109
+ ...values,
110
+ message: values.message + '\n' + ident(errorMessge),
111
+ }
112
+ }
113
+ return values
114
+ }
115
+
116
+ // export function formatRecord(values: FormattedValues): FormattedValues {
117
+ // const props = formatProperties(values.record.properties)
118
+ // if (props.output) {
119
+ // props.output = ' \n' + ident(props.output)
120
+ // return {
121
+ // ...values,
122
+ // record: {
123
+ // ...values.record,
124
+ // properties: props.properties,
125
+ // },
126
+ // message: `${values.message}${props.output}`,
127
+ // }
128
+ // }
129
+ // return values
130
+ // }
131
+
132
+ const levelAbbreviations: Record<LogLevel, string> = {
133
+ 'debug': 'DEBUG',
134
+ 'info': 'INFO ',
135
+ 'warning': 'WARN ',
136
+ 'error': 'ERROR',
137
+ 'fatal': 'FATAL',
138
+ }
139
+
140
+ export function getMessageOnlyFormatter(): TextFormatter {
141
+ return getTextFormatter({
142
+ format: ({ message }): string => {
143
+ return message
144
+ },
145
+ })
146
+ }
147
+
148
+ const level = (l: LogLevel): string => levelAbbreviations[l]
149
+
150
+ export function getTextFormatter(options?: TextFormatterOptions): TextFormatter {
151
+ const _format = options?.format ?? (({ timestamp, level, category, message }: FormattedValues): string => {
152
+ return `${timestamp} ${level} ${category} ${message}`
153
+ })
154
+ // const format = options?.format
155
+ return getLogtapeTextFormatter({
156
+ timestamp: 'time',
157
+ level,
158
+ category: '.',
159
+ ...options,
160
+ format: (values) => {
161
+ return _format(appendErrorToMessage(values))
162
+ },
163
+ })
164
+ }
165
+
166
+ const RESET = '\x1b[0m'
167
+
168
+ const ansiColors = {
169
+ // black: "\x1b[30m",
170
+ red: '\x1b[31m',
171
+ // green: "\x1b[32m",
172
+ // yellow: "\x1b[33m",
173
+ // blue: "\x1b[34m",
174
+ // magenta: "\x1b[35m",
175
+ // cyan: "\x1b[36m",
176
+ // white: "\x1b[37m",
177
+ } as const
178
+
179
+ export function getAnsiColorFormatter(options?: AnsiColorFormatterOptions): TextFormatter {
180
+ const _format = options?.format ?? (({ timestamp, level, category, message }: FormattedValues): string => {
181
+ return `${timestamp} ${level} ${category} ${message}`
182
+ })
183
+ return getLogtapeAnsiColorFormatter({
184
+ timestamp: 'time',
185
+ level,
186
+ categoryStyle: 'bold',
187
+ categoryColor: 'cyan',
188
+ category: '.',
189
+ ...options,
190
+ format: (values) => {
191
+ return _format(appendErrorToMessage(values, true))
192
+ },
193
+ })
194
+ }
195
+
196
+ export function getConsoleSink(options?: ConsoleSinkOptions): Sink {
197
+ return getLogtapeConsoleSink({
198
+ formatter: getAnsiColorFormatter(),
199
+ ...options,
200
+ })
201
+ }
package/src/index.ts CHANGED
@@ -1,36 +1,88 @@
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
+ withFilter,
29
+ } from '@logtape/logtape'
30
+
31
+ export {
32
+ logger as consola,
33
+ logger as rootLogger,
34
+ }
35
+
36
+ export {
37
+ loggable,
38
+ } from './utils'
39
+
40
+ export const logger = getLogger('likec4')
41
+
42
+ /**
43
+ * Get a child logger with the given subcategory.
44
+ *
45
+ * @param subcategory The subcategory.
46
+ * @returns The child logger.
47
+ */
48
+ export function createLogger(subcategory: string | readonly [string] | readonly [string, ...string[]]) {
49
+ return logger.getChild(subcategory)
14
50
  }
15
51
 
16
- export function formatLogObj(logObj: LogObject): FormattedLogObject {
17
- return formattedLogObj(logObj, parseStack)
52
+ let configureWasCalled = false
53
+
54
+ export async function configureLogger<TSinkId extends string, TFilterId extends string>(
55
+ config?: Config<TSinkId, TFilterId>,
56
+ ) {
57
+ try {
58
+ configureWasCalled = true
59
+ const sinks = config?.sinks ?? {}
60
+ await configureLogtape<any, any>({
61
+ ...config,
62
+ sinks: {
63
+ ...sinks,
64
+ // @ts-expect-error console is not a valid sink id
65
+ console: sinks['console'] ?? getConsoleSink(),
66
+ },
67
+ loggers: [
68
+ { category: ['logtape', 'meta'], sinks: ['console' as any], lowestLevel: 'warning' },
69
+ ...(config?.loggers ?? [
70
+ {
71
+ category: 'likec4',
72
+ sinks: ['console' as any],
73
+ lowestLevel: 'debug',
74
+ },
75
+ ]),
76
+ ],
77
+ })
78
+ } catch (e) {
79
+ console.error(e)
80
+ }
18
81
  }
19
82
 
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 }
83
+ export function ensureLoggerIsConfigured() {
84
+ if (!configureWasCalled) {
85
+ configureLogger()
86
+ console.warn('logger automatically configured with default settings')
87
+ }
88
+ }
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
+ }