@pori15/logixlysia 6.0.1 → 6.0.2

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.
@@ -1,246 +1,240 @@
1
- import chalk from 'chalk'
2
- import { getStatusCode } from '../helpers/status'
3
- import type {
4
- LogLevel,
5
- Options,
6
- Pino,
7
- StoreData,
8
-
9
- } from '../interfaces'
10
-
11
- const pad2 = (value: number): string => String(value).padStart(2, '0')
12
- const pad3 = (value: number): string => String(value).padStart(3, '0')
13
-
14
- const shouldUseColors = (options: Options): boolean => {
15
- const config = options.config
16
- const enabledByConfig = config?.useColors ?? true
17
-
18
- // Avoid ANSI sequences in non-interactive output (pipes, CI logs, files).
19
- const isTty = typeof process !== 'undefined' && process.stdout?.isTTY === true
20
- return enabledByConfig && isTty
21
- }
22
-
23
- const formatTimestamp = (date: Date, pattern?: string): string => {
24
- if (!pattern) {
25
- return date.toISOString()
26
- }
27
-
28
- const yyyy = String(date.getFullYear())
29
- const mm = pad2(date.getMonth() + 1)
30
- const dd = pad2(date.getDate())
31
- const HH = pad2(date.getHours())
32
- const MM = pad2(date.getMinutes())
33
- const ss = pad2(date.getSeconds())
34
- const SSS = pad3(date.getMilliseconds())
35
-
36
- return pattern
37
- .replaceAll('yyyy', yyyy)
38
- .replaceAll('mm', mm)
39
- .replaceAll('dd', dd)
40
- .replaceAll('HH', HH)
41
- .replaceAll('MM', MM)
42
- .replaceAll('ss', ss)
43
- .replaceAll('SSS', SSS)
44
- }
45
-
46
- const getIp = (request: Request): string => {
47
- const forwarded = request.headers.get('x-forwarded-for')
48
- if (forwarded) {
49
- return forwarded.split(',')[0]?.trim() ?? ''
50
- }
51
- return request.headers.get('x-real-ip') ?? ''
52
- }
53
-
54
- const getColoredLevel = (level: LogLevel, useColors: boolean): string => {
55
- if (!useColors) {
56
- return level
57
- }
58
-
59
- if (level === 'ERROR') {
60
- return chalk.bgRed.black(level)
61
- }
62
- if (level === 'WARNING') {
63
- return chalk.bgYellow.black(level)
64
- }
65
- if (level === 'DEBUG') {
66
- return chalk.bgBlue.black(level)
67
- }
68
-
69
- return chalk.bgGreen.black(level)
70
- }
71
-
72
- const getColoredMethod = (method: string, useColors: boolean): string => {
73
- if (!useColors) {
74
- return method
75
- }
76
-
77
- const upper = method.toUpperCase()
78
- if (upper === 'GET') {
79
- return chalk.green.bold(upper)
80
- }
81
- if (upper === 'POST') {
82
- return chalk.blue.bold(upper)
83
- }
84
- if (upper === 'PUT') {
85
- return chalk.yellow.bold(upper)
86
- }
87
- if (upper === 'PATCH') {
88
- return chalk.yellowBright.bold(upper)
89
- }
90
- if (upper === 'DELETE') {
91
- return chalk.red.bold(upper)
92
- }
93
- if (upper === 'OPTIONS') {
94
- return chalk.cyan.bold(upper)
95
- }
96
- if (upper === 'HEAD') {
97
- return chalk.greenBright.bold(upper)
98
- }
99
- if (upper === 'TRACE') {
100
- return chalk.magenta.bold(upper)
101
- }
102
- if (upper === 'CONNECT') {
103
- return chalk.cyanBright.bold(upper)
104
- }
105
-
106
- return chalk.white.bold(upper)
107
- }
108
-
109
- const getColoredStatus = (status: string, useColors: boolean): string => {
110
- if (!useColors) {
111
- return status
112
- }
113
-
114
- const numeric = Number.parseInt(status, 10)
115
- if (!Number.isFinite(numeric)) {
116
- return status
117
- }
118
-
119
- if (numeric >= 500) {
120
- return chalk.red(status)
121
- }
122
- if (numeric >= 400) {
123
- return chalk.yellow(status)
124
- }
125
- if (numeric >= 300) {
126
- return chalk.cyan(status)
127
- }
128
- if (numeric >= 200) {
129
- return chalk.green(status)
130
- }
131
- return chalk.gray(status)
132
- }
133
-
134
- const getColoredDuration = (duration: string, useColors: boolean): string => {
135
- if (!useColors) {
136
- return duration
137
- }
138
-
139
- return chalk.gray(duration)
140
- }
141
-
142
- const getColoredTimestamp = (timestamp: string, useColors: boolean): string => {
143
- if (!useColors) {
144
- return timestamp
145
- }
146
-
147
- return chalk.bgHex('#FFA500').black(timestamp)
148
- }
149
-
150
- const getColoredPathname = (pathname: string, useColors: boolean): string => {
151
- if (!useColors) {
152
- return pathname
153
- }
154
-
155
- return chalk.whiteBright(pathname)
156
- }
157
-
158
- const getContextString = (value: unknown): string => {
159
- if (typeof value === 'object' && value !== null) {
160
- return JSON.stringify(value)
161
- }
162
-
163
- return ''
164
- }
165
-
166
- export const formatLine = ({
167
- level,
168
- request,
169
- data,
170
- store,
171
- options
172
- }: {
173
- level: LogLevel
174
- request: Request
175
- data: Record<string, unknown>
176
- store: StoreData
177
- options: Options
178
- }): string => {
179
- const config = options.config
180
- const useColors = shouldUseColors(options)
181
- const format =
182
- config?.customLogFormat ??
183
- '🦊 {now} {level} {duration} {method} {pathname} {status} {message} {ip} {context}'
184
-
185
- const now = new Date()
186
- const epoch = String(now.getTime())
187
- const rawTimestamp = formatTimestamp(now, config?.timestamp?.translateTime)
188
- const timestamp = getColoredTimestamp(rawTimestamp, useColors)
189
-
190
- const message = typeof data.message === 'string' ? data.message : ''
191
- const durationMs =
192
- store.beforeTime === BigInt(0)
193
- ? 0
194
- : Number(process.hrtime.bigint() - store.beforeTime) / 1_000_000
195
-
196
- const pathname = new URL(request.url).pathname
197
- const statusValue = data.status
198
- const statusCode =
199
- statusValue === null || statusValue === undefined
200
- ? 200
201
- : getStatusCode(statusValue)
202
- const status = String(statusCode)
203
- const ip = config?.ip === true ? getIp(request) : ''
204
- const ctxString = getContextString(data.context)
205
- const coloredLevel = getColoredLevel(level, useColors)
206
- const coloredMethod = getColoredMethod(request.method, useColors)
207
- const coloredPathname = getColoredPathname(pathname, useColors)
208
- const coloredStatus = getColoredStatus(status, useColors)
209
- const coloredDuration = getColoredDuration(
210
- `${durationMs.toFixed(2)}ms`,
211
- useColors
212
- )
213
-
214
- return format
215
- .replaceAll('{now}', timestamp)
216
- .replaceAll('{epoch}', epoch)
217
- .replaceAll('{level}', coloredLevel)
218
- .replaceAll('{duration}', coloredDuration)
219
- .replaceAll('{method}', coloredMethod)
220
- .replaceAll('{pathname}', coloredPathname)
221
- .replaceAll('{path}', coloredPathname)
222
- .replaceAll('{status}', coloredStatus)
223
- .replaceAll('{message}', message)
224
- .replaceAll('{ip}', ip)
225
- .replaceAll('{context}', ctxString)
226
- }
227
-
228
- export const logWithPino = (
229
- logger: Pino,
230
- level: LogLevel,
231
- data: Record<string, unknown>
232
- ): void => {
233
- if (level === 'ERROR') {
234
- logger.error(data)
235
- return
236
- }
237
- if (level === 'WARNING') {
238
- logger.warn(data)
239
- return
240
- }
241
- if (level === 'DEBUG') {
242
- logger.debug(data)
243
- return
244
- }
245
- logger.info(data)
246
- }
1
+ import chalk from 'chalk'
2
+ import { getStatusCode } from '../helpers/status'
3
+ import type { LogLevel, Options, Pino, StoreData } from '../interfaces'
4
+
5
+ const pad2 = (value: number): string => String(value).padStart(2, '0')
6
+ const pad3 = (value: number): string => String(value).padStart(3, '0')
7
+
8
+ const shouldUseColors = (options: Options): boolean => {
9
+ const config = options.config
10
+ const enabledByConfig = config?.useColors ?? true
11
+
12
+ // Avoid ANSI sequences in non-interactive output (pipes, CI logs, files).
13
+ const isTty = typeof process !== 'undefined' && process.stdout?.isTTY === true
14
+ return enabledByConfig && isTty
15
+ }
16
+
17
+ const formatTimestamp = (date: Date, pattern?: string): string => {
18
+ if (!pattern) {
19
+ return date.toISOString()
20
+ }
21
+
22
+ const yyyy = String(date.getFullYear())
23
+ const mm = pad2(date.getMonth() + 1)
24
+ const dd = pad2(date.getDate())
25
+ const HH = pad2(date.getHours())
26
+ const MM = pad2(date.getMinutes())
27
+ const ss = pad2(date.getSeconds())
28
+ const SSS = pad3(date.getMilliseconds())
29
+
30
+ return pattern
31
+ .replaceAll('yyyy', yyyy)
32
+ .replaceAll('mm', mm)
33
+ .replaceAll('dd', dd)
34
+ .replaceAll('HH', HH)
35
+ .replaceAll('MM', MM)
36
+ .replaceAll('ss', ss)
37
+ .replaceAll('SSS', SSS)
38
+ }
39
+
40
+ const getIp = (request: Request): string => {
41
+ const forwarded = request.headers.get('x-forwarded-for')
42
+ if (forwarded) {
43
+ return forwarded.split(',')[0]?.trim() ?? ''
44
+ }
45
+ return request.headers.get('x-real-ip') ?? ''
46
+ }
47
+
48
+ const getColoredLevel = (level: LogLevel, useColors: boolean): string => {
49
+ if (!useColors) {
50
+ return level
51
+ }
52
+
53
+ if (level === 'ERROR') {
54
+ return chalk.bgRed.black(level)
55
+ }
56
+ if (level === 'WARNING') {
57
+ return chalk.bgYellow.black(level)
58
+ }
59
+ if (level === 'DEBUG') {
60
+ return chalk.bgBlue.black(level)
61
+ }
62
+
63
+ return chalk.bgGreen.black(level)
64
+ }
65
+
66
+ const getColoredMethod = (method: string, useColors: boolean): string => {
67
+ if (!useColors) {
68
+ return method
69
+ }
70
+
71
+ const upper = method.toUpperCase()
72
+ if (upper === 'GET') {
73
+ return chalk.green.bold(upper)
74
+ }
75
+ if (upper === 'POST') {
76
+ return chalk.blue.bold(upper)
77
+ }
78
+ if (upper === 'PUT') {
79
+ return chalk.yellow.bold(upper)
80
+ }
81
+ if (upper === 'PATCH') {
82
+ return chalk.yellowBright.bold(upper)
83
+ }
84
+ if (upper === 'DELETE') {
85
+ return chalk.red.bold(upper)
86
+ }
87
+ if (upper === 'OPTIONS') {
88
+ return chalk.cyan.bold(upper)
89
+ }
90
+ if (upper === 'HEAD') {
91
+ return chalk.greenBright.bold(upper)
92
+ }
93
+ if (upper === 'TRACE') {
94
+ return chalk.magenta.bold(upper)
95
+ }
96
+ if (upper === 'CONNECT') {
97
+ return chalk.cyanBright.bold(upper)
98
+ }
99
+
100
+ return chalk.white.bold(upper)
101
+ }
102
+
103
+ const getColoredStatus = (status: string, useColors: boolean): string => {
104
+ if (!useColors) {
105
+ return status
106
+ }
107
+
108
+ const numeric = Number.parseInt(status, 10)
109
+ if (!Number.isFinite(numeric)) {
110
+ return status
111
+ }
112
+
113
+ if (numeric >= 500) {
114
+ return chalk.red(status)
115
+ }
116
+ if (numeric >= 400) {
117
+ return chalk.yellow(status)
118
+ }
119
+ if (numeric >= 300) {
120
+ return chalk.cyan(status)
121
+ }
122
+ if (numeric >= 200) {
123
+ return chalk.green(status)
124
+ }
125
+ return chalk.gray(status)
126
+ }
127
+
128
+ const getColoredDuration = (duration: string, useColors: boolean): string => {
129
+ if (!useColors) {
130
+ return duration
131
+ }
132
+
133
+ return chalk.gray(duration)
134
+ }
135
+
136
+ const getColoredTimestamp = (timestamp: string, useColors: boolean): string => {
137
+ if (!useColors) {
138
+ return timestamp
139
+ }
140
+
141
+ return chalk.bgHex('#FFA500').black(timestamp)
142
+ }
143
+
144
+ const getColoredPathname = (pathname: string, useColors: boolean): string => {
145
+ if (!useColors) {
146
+ return pathname
147
+ }
148
+
149
+ return chalk.whiteBright(pathname)
150
+ }
151
+
152
+ const getContextString = (value: unknown): string => {
153
+ if (typeof value === 'object' && value !== null) {
154
+ return JSON.stringify(value)
155
+ }
156
+
157
+ return ''
158
+ }
159
+
160
+ export const formatLine = ({
161
+ level,
162
+ request,
163
+ data,
164
+ store,
165
+ options
166
+ }: {
167
+ level: LogLevel
168
+ request: Request
169
+ data: Record<string, unknown>
170
+ store: StoreData
171
+ options: Options
172
+ }): string => {
173
+ const config = options.config
174
+ const useColors = shouldUseColors(options)
175
+ const format =
176
+ config?.customLogFormat ??
177
+ '🦊 {now} {level} {duration} {method} {pathname} {status} {message} {ip} {context}'
178
+
179
+ const now = new Date()
180
+ const epoch = String(now.getTime())
181
+ const rawTimestamp = formatTimestamp(now, config?.timestamp?.translateTime)
182
+ const timestamp = getColoredTimestamp(rawTimestamp, useColors)
183
+
184
+ const message = typeof data.message === 'string' ? data.message : ''
185
+ const durationMs =
186
+ store.beforeTime === BigInt(0)
187
+ ? 0
188
+ : Number(process.hrtime.bigint() - store.beforeTime) / 1_000_000
189
+
190
+ const pathname = new URL(request.url).pathname
191
+ const statusValue = data.status
192
+ const statusCode =
193
+ statusValue === null || statusValue === undefined
194
+ ? 200
195
+ : getStatusCode(statusValue)
196
+ const status = String(statusCode)
197
+ const ip = config?.ip === true ? getIp(request) : ''
198
+ const ctxString = getContextString(data.context)
199
+ const coloredLevel = getColoredLevel(level, useColors)
200
+ const coloredMethod = getColoredMethod(request.method, useColors)
201
+ const coloredPathname = getColoredPathname(pathname, useColors)
202
+ const coloredStatus = getColoredStatus(status, useColors)
203
+ const coloredDuration = getColoredDuration(
204
+ `${durationMs.toFixed(2)}ms`,
205
+ useColors
206
+ )
207
+
208
+ return format
209
+ .replaceAll('{now}', timestamp)
210
+ .replaceAll('{epoch}', epoch)
211
+ .replaceAll('{level}', coloredLevel)
212
+ .replaceAll('{duration}', coloredDuration)
213
+ .replaceAll('{method}', coloredMethod)
214
+ .replaceAll('{pathname}', coloredPathname)
215
+ .replaceAll('{path}', coloredPathname)
216
+ .replaceAll('{status}', coloredStatus)
217
+ .replaceAll('{message}', message)
218
+ .replaceAll('{ip}', ip)
219
+ .replaceAll('{context}', ctxString)
220
+ }
221
+
222
+ export const logWithPino = (
223
+ logger: Pino,
224
+ level: LogLevel,
225
+ data: Record<string, unknown>
226
+ ): void => {
227
+ if (level === 'ERROR') {
228
+ logger.error(data)
229
+ return
230
+ }
231
+ if (level === 'WARNING') {
232
+ logger.warn(data)
233
+ return
234
+ }
235
+ if (level === 'DEBUG') {
236
+ logger.debug(data)
237
+ return
238
+ }
239
+ logger.info(data)
240
+ }
@@ -1,63 +1,60 @@
1
-
2
- import { ProblemError } from '../Error/errors'
3
- import type { LogLevel, Options, StoreData } from '../interfaces'
4
- import { logToTransports } from '../output'
5
- import { logToFile } from '../output/file'
6
-
7
-
8
- export const handleHttpError = (
9
- request: Request,
10
- problem: ProblemError,
11
- store: StoreData,
12
- options: Options
13
- ): void => {
14
- const config = options.config
15
- // 1. 准备日志数据:将 RFC 标准字段与日志元数据合并
16
- const level: LogLevel = 'ERROR';
17
- const rfcData = problem.toJSON();
18
- const data = {
19
- status: problem.status,
20
- message: problem.detail || problem.title,
21
- ...rfcData
22
- };
23
- // 2. 阶段:传输层 (Transports)
24
- logToTransports({ level, request, data, store, options });
25
- // 3. 阶段:持久化 (File Logging)
26
- // 匹配你的接口:useTransportsOnly disableFileLogging 直接在 config
27
- if (!(config?.useTransportsOnly || config?.disableFileLogging)) {
28
- const filePath = config?.logFilePath;
29
- if (filePath) {
30
- logToFile({ filePath, level, request, data, store, options }).catch(() => {});
31
- }
32
- }
33
-
34
- // 4. 阶段:控制台输出 (Console/Internal)
35
- if (config?.useTransportsOnly || config?.disableInternalLogger) return;
36
-
37
-
38
- // 处理时间戳显示逻辑
39
- let timestamp = '';
40
- if (config?.timestamp) {
41
- timestamp = `[${new Date().toISOString()}] `;
42
- }
43
-
44
- // 1. 安全提取 Method Path
45
- const method = typeof request === 'string' ? 'REQ' : request.method;
46
- const urlString = typeof request === 'string' ? request : request.url;
47
-
48
- let path: string;
49
- try {
50
- // 如果是完整 URL 则提取 pathname,如果是相对路径则直接使用
51
- path = urlString.includes('://')
52
- ? new URL(urlString).pathname
53
- : urlString;
54
- } catch {
55
- path = urlString;
56
- }
57
-
58
- // 2. 语义化终端打印
59
- // 现在的代码对 string 和 Request 类型都百分之百安全了
60
- console.error(
61
- `${timestamp}${level} ${method} ${path} ${problem.status} - ${problem.title}`
62
- );
63
- }
1
+ import type { ProblemError } from '../Error/errors'
2
+ import type { LogLevel, Options, StoreData } from '../interfaces'
3
+ import { logToTransports } from '../output'
4
+ import { logToFile } from '../output/file'
5
+
6
+ export const handleHttpError = (
7
+ request: Request,
8
+ problem: ProblemError,
9
+ store: StoreData,
10
+ options: Options
11
+ ): void => {
12
+ const config = options.config
13
+ // 1. 准备日志数据:将 RFC 标准字段与日志元数据合并
14
+ const level: LogLevel = 'ERROR'
15
+ const rfcData = problem.toJSON()
16
+ const data = {
17
+ status: problem.status,
18
+ message: problem.detail || problem.title,
19
+ ...rfcData
20
+ }
21
+ // 2. 阶段:传输层 (Transports)
22
+ logToTransports({ level, request, data, store, options })
23
+ // 3. 阶段:持久化 (File Logging)
24
+ // 匹配你的接口:useTransportsOnly disableFileLogging 直接在 config
25
+ if (!(config?.useTransportsOnly || config?.disableFileLogging)) {
26
+ const filePath = config?.logFilePath
27
+ if (filePath) {
28
+ logToFile({ filePath, level, request, data, store, options }).catch(
29
+ () => {}
30
+ )
31
+ }
32
+ }
33
+
34
+ // 4. 阶段:控制台输出 (Console/Internal)
35
+ if (config?.useTransportsOnly || config?.disableInternalLogger) return
36
+
37
+ // 处理时间戳显示逻辑
38
+ let timestamp = ''
39
+ if (config?.timestamp) {
40
+ timestamp = `[${new Date().toISOString()}] `
41
+ }
42
+
43
+ // 1. 安全提取 Method 和 Path
44
+ const method = typeof request === 'string' ? 'REQ' : request.method
45
+ const urlString = typeof request === 'string' ? request : request.url
46
+
47
+ let path: string
48
+ try {
49
+ // 如果是完整 URL 则提取 pathname,如果是相对路径则直接使用
50
+ path = urlString.includes('://') ? new URL(urlString).pathname : urlString
51
+ } catch {
52
+ path = urlString
53
+ }
54
+
55
+ // 2. 语义化终端打印
56
+ // 现在的代码对 string 和 Request 类型都百分之百安全了
57
+ console.error(
58
+ `${timestamp}${level} ${method} ${path} ${problem.status} - ${problem.title}`
59
+ )
60
+ }