logixlysia 3.7.0 → 4.1.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/CHANGELOG.md +19 -1
- package/README.md +7 -0
- package/package.json +15 -8
- package/src/core/buildLogMessage.ts +9 -6
- package/src/index.ts +18 -8
- package/src/types/index.ts +5 -0
- package/src/utils/index.ts +1 -0
- package/src/utils/status.ts +14 -11
- package/src/utils/timestamp.ts +48 -0
- package/tests/utils/status.test.ts +46 -0
- package/tests/utils/timestamp.test.ts +35 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,11 +1,29 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [4.1.0](https://github.com/PunGrumpy/logixlysia/compare/v4.0.0...v4.1.0) (2025-01-31)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* **logging:** add status string parsing and error handling ([32800ac](https://github.com/PunGrumpy/logixlysia/commit/32800acb0fbfdf957e2975b5773b0a350e3af0d8)), closes [#71](https://github.com/PunGrumpy/logixlysia/issues/71)
|
|
9
|
+
|
|
10
|
+
## [4.0.0](https://github.com/PunGrumpy/logixlysia/compare/v3.7.0...v4.0.0) (2024-10-28)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### ⚠ BREAKING CHANGES
|
|
14
|
+
|
|
15
|
+
* **timestamp:** The timestamp format in logs will now respect the new timestamp configuration options when provided
|
|
16
|
+
|
|
17
|
+
### Features
|
|
18
|
+
|
|
19
|
+
* **timestamp:** add customize timestamp formatting ([5a4e2a5](https://github.com/PunGrumpy/logixlysia/commit/5a4e2a5230443761d8c2ca99d9ebc586ff26a8c0)), closes [#69](https://github.com/PunGrumpy/logixlysia/issues/69)
|
|
20
|
+
|
|
3
21
|
## [3.7.0](https://github.com/PunGrumpy/logixlysia/compare/v3.6.2...v3.7.0) (2024-09-26)
|
|
4
22
|
|
|
5
23
|
|
|
6
24
|
### Features
|
|
7
25
|
|
|
8
|
-
* **optional:** add startup message config ([c7a76db](https://github.com/PunGrumpy/logixlysia/commit/c7a76dbc0f30f4c9c607b52dedc49979e26b67ec))
|
|
26
|
+
* **optional:** add startup message config ([#66](https://github.com/PunGrumpy/logixlysia/pull/66)) ([c7a76db](https://github.com/PunGrumpy/logixlysia/commit/c7a76dbc0f30f4c9c607b52dedc49979e26b67ec)) by [@n0ky4](https://github.com/n0ky4)
|
|
9
27
|
|
|
10
28
|
## [3.6.2](https://github.com/PunGrumpy/logixlysia/compare/v3.6.1...v3.6.2) (2024-09-18)
|
|
11
29
|
|
package/README.md
CHANGED
|
@@ -23,6 +23,9 @@ const app = new Elysia({
|
|
|
23
23
|
config: {
|
|
24
24
|
showStartupMessage: true,
|
|
25
25
|
startupMessageFormat: 'simple',
|
|
26
|
+
timestamp: {
|
|
27
|
+
translateTime: 'yyyy-mm-dd HH:MM:ss'
|
|
28
|
+
},
|
|
26
29
|
ip: true,
|
|
27
30
|
logFilePath: './logs/example.log',
|
|
28
31
|
customLogFormat:
|
|
@@ -42,6 +45,9 @@ app.listen(3000)
|
|
|
42
45
|
> [!NOTE]
|
|
43
46
|
> You can discover more about example in the [example](example) directory.
|
|
44
47
|
|
|
48
|
+
> [!TIP]
|
|
49
|
+
> Also, you can play my example with Swagger UI on `http://localhost:3000/swagger`.
|
|
50
|
+
|
|
45
51
|
## `📚` Documentation
|
|
46
52
|
|
|
47
53
|
### Options
|
|
@@ -50,6 +56,7 @@ app.listen(3000)
|
|
|
50
56
|
| ---------------------- | ------------------------ | --------------------------------------------------------------------- | ------------------------------------------------------------------------- |
|
|
51
57
|
| `showStartupMessage` | `boolean` | Display the startup message | `true` |
|
|
52
58
|
| `startupMessageFormat` | `"banner"` \| `"simple"` | Choose the startup message format | `"banner"` |
|
|
59
|
+
| `timestamp` | `object` | Display the timestamp in the logs | `{ translateTime: 'yyyy-mm-dd HH:MM:ss' }` |
|
|
53
60
|
| `ip` | `boolean` | Display the incoming IP address based on the `X-Forwarded-For` header | `false` |
|
|
54
61
|
| `customLogMessage` | `string` | Custom log message to display | `🦊 {now} {level} {duration} {method} {pathname} {status} {message} {ip}` |
|
|
55
62
|
| `logFilter` | `object` | Filter the logs based on the level, method, and status | `null` |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "logixlysia",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.1.0",
|
|
4
4
|
"description": "🦊 Logixlysia is a logger for Elysia",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"module": "src/index.ts",
|
|
@@ -10,6 +10,12 @@
|
|
|
10
10
|
"maintainers": [
|
|
11
11
|
"PunGrumpy"
|
|
12
12
|
],
|
|
13
|
+
"contributors": [
|
|
14
|
+
{
|
|
15
|
+
"name": "n0ky4",
|
|
16
|
+
"url": "https://github.com/n0ky4"
|
|
17
|
+
}
|
|
18
|
+
],
|
|
13
19
|
"publishConfig": {
|
|
14
20
|
"access": "public"
|
|
15
21
|
},
|
|
@@ -70,22 +76,23 @@
|
|
|
70
76
|
"middleware"
|
|
71
77
|
],
|
|
72
78
|
"dependencies": {
|
|
79
|
+
"@elysiajs/swagger": "^1.2.0",
|
|
73
80
|
"chalk": "^5.3.0",
|
|
74
|
-
"elysia": "^1.1.
|
|
81
|
+
"elysia": "^1.1.23"
|
|
75
82
|
},
|
|
76
83
|
"devDependencies": {
|
|
77
84
|
"@elysiajs/eden": "^1.1.3",
|
|
78
|
-
"@eslint/js": "^9.
|
|
85
|
+
"@eslint/js": "^9.13.0",
|
|
79
86
|
"@trunkio/launcher": "^1.3.2",
|
|
80
|
-
"@typescript-eslint/eslint-plugin": "^8.
|
|
81
|
-
"@typescript-eslint/parser": "^8.
|
|
82
|
-
"bun-types": "^1.1.
|
|
87
|
+
"@typescript-eslint/eslint-plugin": "^8.11.0",
|
|
88
|
+
"@typescript-eslint/parser": "^8.11.0",
|
|
89
|
+
"bun-types": "^1.1.33",
|
|
83
90
|
"eslint-plugin-simple-import-sort": "^12.1.1",
|
|
84
|
-
"globals": "^15.
|
|
91
|
+
"globals": "^15.11.0",
|
|
85
92
|
"husky": "^9.1.6",
|
|
86
93
|
"lint-staged": "^15.2.10",
|
|
87
94
|
"prettier": "^3.3.3",
|
|
88
|
-
"typescript-eslint": "^8.
|
|
95
|
+
"typescript-eslint": "^8.11.0"
|
|
89
96
|
},
|
|
90
97
|
"peerDependencies": {
|
|
91
98
|
"typescript": "^5.2.2"
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
} from '../types'
|
|
11
11
|
import {
|
|
12
12
|
durationString,
|
|
13
|
+
formatTimestamp,
|
|
13
14
|
logString,
|
|
14
15
|
methodString,
|
|
15
16
|
pathString,
|
|
@@ -38,14 +39,16 @@ export function buildLogMessage(
|
|
|
38
39
|
const now = new Date()
|
|
39
40
|
const components: LogComponents = {
|
|
40
41
|
now: actuallyUseColors
|
|
41
|
-
? chalk.bgYellow(
|
|
42
|
-
|
|
42
|
+
? chalk.bgYellow(
|
|
43
|
+
chalk.black(formatTimestamp(now, options?.config?.timestamp))
|
|
44
|
+
)
|
|
45
|
+
: formatTimestamp(now, options?.config?.timestamp),
|
|
43
46
|
epoch: Math.floor(now.getTime() / 1000).toString(),
|
|
44
|
-
level: logString(level,
|
|
45
|
-
duration: durationString(store.beforeTime,
|
|
46
|
-
method: methodString(request.method,
|
|
47
|
+
level: logString(level, useColors),
|
|
48
|
+
duration: durationString(store.beforeTime, useColors),
|
|
49
|
+
method: methodString(request.method, useColors),
|
|
47
50
|
pathname: pathString(request),
|
|
48
|
-
status: statusString(data.status || 200,
|
|
51
|
+
status: statusString(data.status || 200, useColors),
|
|
49
52
|
message: data.message || '',
|
|
50
53
|
ip:
|
|
51
54
|
options?.config?.ip && request.headers.get('x-forwarded-for')
|
package/src/index.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { Elysia } from 'elysia'
|
|
|
3
3
|
import { createLogger } from './core'
|
|
4
4
|
import { startServer } from './plugins'
|
|
5
5
|
import { HttpError, Options, Server } from './types'
|
|
6
|
+
import { getStatusCode } from './utils/status'
|
|
6
7
|
|
|
7
8
|
export default function logixlysia(options?: Options): Elysia {
|
|
8
9
|
const log = createLogger(options)
|
|
@@ -11,19 +12,29 @@ export default function logixlysia(options?: Options): Elysia {
|
|
|
11
12
|
name: 'Logixlysia'
|
|
12
13
|
})
|
|
13
14
|
.onStart(ctx => {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
)
|
|
15
|
+
const showStartupMessage = options?.config?.showStartupMessage ?? true
|
|
16
|
+
if (showStartupMessage) startServer(ctx.server as Server, options)
|
|
17
|
+
})
|
|
17
18
|
.onRequest(ctx => {
|
|
18
19
|
ctx.store = { beforeTime: process.hrtime.bigint() }
|
|
19
20
|
})
|
|
20
|
-
.onAfterHandle({ as: 'global' }, ({ request, store }) => {
|
|
21
|
-
|
|
21
|
+
.onAfterHandle({ as: 'global' }, ({ request, set, store }) => {
|
|
22
|
+
const status = getStatusCode(set.status || 200)
|
|
23
|
+
log.log(
|
|
24
|
+
'INFO',
|
|
25
|
+
request,
|
|
26
|
+
{
|
|
27
|
+
status,
|
|
28
|
+
message: set.headers?.['x-message'] || ''
|
|
29
|
+
},
|
|
30
|
+
store as { beforeTime: bigint }
|
|
31
|
+
)
|
|
22
32
|
})
|
|
23
|
-
.onError({ as: 'global' }, ({ request, error, store }) => {
|
|
33
|
+
.onError({ as: 'global' }, ({ request, error, set, store }) => {
|
|
34
|
+
const status = getStatusCode(set.status || 500)
|
|
24
35
|
log.handleHttpError(
|
|
25
36
|
request,
|
|
26
|
-
error as HttpError,
|
|
37
|
+
{ ...error, status } as HttpError,
|
|
27
38
|
store as { beforeTime: bigint }
|
|
28
39
|
)
|
|
29
40
|
})
|
|
@@ -31,4 +42,3 @@ export default function logixlysia(options?: Options): Elysia {
|
|
|
31
42
|
|
|
32
43
|
export { createLogger, handleHttpError } from './core'
|
|
33
44
|
export { logToTransports } from './transports'
|
|
34
|
-
|
package/src/types/index.ts
CHANGED
|
@@ -77,6 +77,10 @@ export interface Transport {
|
|
|
77
77
|
log: TransportFunction
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
+
export interface TimestampConfig {
|
|
81
|
+
translateTime?: boolean | string
|
|
82
|
+
}
|
|
83
|
+
|
|
80
84
|
export interface Options {
|
|
81
85
|
config?: {
|
|
82
86
|
customLogFormat?: string
|
|
@@ -91,5 +95,6 @@ export interface Options {
|
|
|
91
95
|
showStartupMessage?: boolean
|
|
92
96
|
startupMessageFormat?: 'banner' | 'simple'
|
|
93
97
|
transports?: Transport[]
|
|
98
|
+
timestamp?: TimestampConfig // Add this new option
|
|
94
99
|
}
|
|
95
100
|
}
|
package/src/utils/index.ts
CHANGED
package/src/utils/status.ts
CHANGED
|
@@ -1,18 +1,21 @@
|
|
|
1
1
|
import chalk from 'chalk'
|
|
2
|
+
import { StatusMap } from 'elysia'
|
|
3
|
+
|
|
4
|
+
export function getStatusCode(status: string | number): number {
|
|
5
|
+
if (typeof status === 'number') return status
|
|
6
|
+
return (StatusMap as Record<string, number>)[status] || 500
|
|
7
|
+
}
|
|
2
8
|
|
|
3
9
|
export default function statusString(
|
|
4
10
|
status: number,
|
|
5
11
|
useColors: boolean
|
|
6
12
|
): string {
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
? 'green'
|
|
16
|
-
: 'white'
|
|
17
|
-
return useColors ? chalk[color](status.toString()) : status.toString()
|
|
13
|
+
const statusStr = status.toString()
|
|
14
|
+
if (!useColors) return statusStr
|
|
15
|
+
|
|
16
|
+
if (status >= 500) return chalk.red(statusStr)
|
|
17
|
+
if (status >= 400) return chalk.yellow(statusStr)
|
|
18
|
+
if (status >= 300) return chalk.cyan(statusStr)
|
|
19
|
+
if (status >= 200) return chalk.green(statusStr)
|
|
20
|
+
return chalk.white(statusStr)
|
|
18
21
|
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { TimestampConfig } from '../types'
|
|
2
|
+
|
|
3
|
+
// const DEFAULT_TIMESTAMP_FORMAT = 'yyyy-mm-dd HH:MM:ss'
|
|
4
|
+
const SYS_TIME = 'SYS:STANDARD'
|
|
5
|
+
|
|
6
|
+
const pad = (n: number): string => n.toString().padStart(2, '0')
|
|
7
|
+
|
|
8
|
+
function formatSystemTime(date: Date): string {
|
|
9
|
+
const year = date.getFullYear()
|
|
10
|
+
const month = pad(date.getMonth() + 1)
|
|
11
|
+
const day = pad(date.getDate())
|
|
12
|
+
const hours = pad(date.getHours())
|
|
13
|
+
const minutes = pad(date.getMinutes())
|
|
14
|
+
const seconds = pad(date.getSeconds())
|
|
15
|
+
const ms = date.getMilliseconds().toString().padStart(3, '0')
|
|
16
|
+
|
|
17
|
+
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}.${ms}`
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function formatCustomTime(date: Date, format: string): string {
|
|
21
|
+
const tokens: { [key: string]: string | number } = {
|
|
22
|
+
yyyy: date.getFullYear(),
|
|
23
|
+
yy: date.getFullYear().toString().slice(-2),
|
|
24
|
+
mm: pad(date.getMonth() + 1),
|
|
25
|
+
dd: pad(date.getDate()),
|
|
26
|
+
HH: pad(date.getHours()),
|
|
27
|
+
MM: pad(date.getMinutes()),
|
|
28
|
+
ss: pad(date.getSeconds()),
|
|
29
|
+
SSS: pad(date.getMilliseconds()),
|
|
30
|
+
Z: -date.getTimezoneOffset() / 60
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return format.replace(/yyyy|yy|mm|dd|HH|MM|ss|SSS|Z/g, match =>
|
|
34
|
+
(tokens[match] ?? '').toString()
|
|
35
|
+
)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function formatTimestamp(date: Date, config?: TimestampConfig): string {
|
|
39
|
+
if (!config || !config.translateTime) {
|
|
40
|
+
return date.toISOString()
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (config.translateTime === true || config.translateTime === SYS_TIME) {
|
|
44
|
+
return formatSystemTime(date)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return formatCustomTime(date, config.translateTime)
|
|
48
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { expect, test } from 'bun:test'
|
|
2
|
+
import { Elysia } from 'elysia'
|
|
3
|
+
|
|
4
|
+
import logixlysia from '../../src/index'
|
|
5
|
+
|
|
6
|
+
test('handles numeric status codes', async () => {
|
|
7
|
+
const app = new Elysia().use(logixlysia()).get('/rate-limited', ({ set }) => {
|
|
8
|
+
set.status = 429
|
|
9
|
+
return 'Rate Limited'
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
const res = await app.handle(new Request('http://localhost/rate-limited'))
|
|
13
|
+
expect(res.status).toBe(429)
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
test('handles string status codes', async () => {
|
|
17
|
+
const app = new Elysia().use(logixlysia()).get('/not-found', ({ set }) => {
|
|
18
|
+
set.status = 'Not Found'
|
|
19
|
+
return 'Resource not found'
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
const res = await app.handle(new Request('http://localhost/not-found'))
|
|
23
|
+
expect(res.status).toBe(404)
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
test('handles custom error status', async () => {
|
|
27
|
+
const app = new Elysia().use(logixlysia()).get('/error', ({ set }) => {
|
|
28
|
+
set.status = 418
|
|
29
|
+
const error = new Error('Custom error')
|
|
30
|
+
throw error
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
const res = await app.handle(new Request('http://localhost/error'))
|
|
34
|
+
expect(res.status).toBe(418)
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
test('handles custom error status with message', async () => {
|
|
38
|
+
const app = new Elysia().use(logixlysia()).get('/error', ({ set }) => {
|
|
39
|
+
set.status = "I'm a teapot"
|
|
40
|
+
const error = new Error('Custom error')
|
|
41
|
+
throw error
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
const res = await app.handle(new Request('http://localhost/error'))
|
|
45
|
+
expect(res.status).toBe(418)
|
|
46
|
+
})
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { expect, test } from 'bun:test'
|
|
2
|
+
|
|
3
|
+
import { formatTimestamp } from '../../src/utils/timestamp'
|
|
4
|
+
|
|
5
|
+
test('formatTimestamp with different configurations', () => {
|
|
6
|
+
const testDate = new Date('2024-01-15T14:30:45.123Z')
|
|
7
|
+
|
|
8
|
+
// Test default format
|
|
9
|
+
expect(formatTimestamp(testDate)).toBe(testDate.toISOString())
|
|
10
|
+
|
|
11
|
+
// Test system time format
|
|
12
|
+
expect(formatTimestamp(testDate, { translateTime: true })).toMatch(
|
|
13
|
+
/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{3}$/
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
// Test custom format
|
|
17
|
+
expect(
|
|
18
|
+
formatTimestamp(testDate, {
|
|
19
|
+
translateTime: 'yyyy-mm-dd HH:MM:ss.SSS'
|
|
20
|
+
})
|
|
21
|
+
).toMatch(/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{3}$/)
|
|
22
|
+
|
|
23
|
+
// Test different custom formats
|
|
24
|
+
expect(
|
|
25
|
+
formatTimestamp(testDate, {
|
|
26
|
+
translateTime: 'HH:MM:ss'
|
|
27
|
+
})
|
|
28
|
+
).toMatch(/^\d{2}:\d{2}:\d{2}$/)
|
|
29
|
+
|
|
30
|
+
expect(
|
|
31
|
+
formatTimestamp(testDate, {
|
|
32
|
+
translateTime: 'yyyy/mm/dd'
|
|
33
|
+
})
|
|
34
|
+
).toMatch(/^\d{4}\/\d{2}\/\d{2}$/)
|
|
35
|
+
})
|