logixlysia 3.6.1 → 3.7.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 +14 -0
- package/README.md +10 -8
- package/package.json +11 -14
- package/src/{logger → core}/buildLogMessage.ts +21 -22
- package/src/{logger → core}/createLogger.ts +8 -20
- package/src/{logger → core}/filter.ts +1 -10
- package/src/core/handleHttpError.ts +30 -0
- package/src/core/index.ts +2 -0
- package/src/index.ts +13 -25
- package/src/plugins/index.ts +1 -0
- package/src/{utils/start.ts → plugins/startServer.ts} +3 -18
- package/src/transports/console.ts +22 -0
- package/src/{logger/logToFile.ts → transports/file.ts} +2 -17
- package/src/transports/index.ts +2 -22
- package/src/{types.ts → types/index.ts} +20 -28
- package/src/utils/colorMapping.ts +3 -15
- package/src/utils/duration.ts +4 -10
- package/src/utils/index.ts +6 -0
- package/src/utils/log.ts +3 -12
- package/src/utils/method.ts +5 -11
- package/src/utils/path.ts +4 -10
- package/src/utils/status.ts +4 -10
- package/tests/__mocks__/fs.ts +9 -0
- package/tests/core/buildLogMessage.test.ts +45 -0
- package/tests/core/filter.test.ts +35 -0
- package/tests/helpers.ts +19 -0
- package/tests/logixlysia.test.ts +8 -0
- package/tests/transports/logToTransports.test.ts +29 -0
- package/tsconfig.json +0 -3
- package/src/logger/handleHttpError.ts +0 -22
- package/src/logger/index.ts +0 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [3.7.0](https://github.com/PunGrumpy/logixlysia/compare/v3.6.2...v3.7.0) (2024-09-26)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* **optional:** add startup message config ([c7a76db](https://github.com/PunGrumpy/logixlysia/commit/c7a76dbc0f30f4c9c607b52dedc49979e26b67ec))
|
|
9
|
+
|
|
10
|
+
## [3.6.2](https://github.com/PunGrumpy/logixlysia/compare/v3.6.1...v3.6.2) (2024-09-18)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Bug Fixes
|
|
14
|
+
|
|
15
|
+
* **logtofile:** store error to log file ([1bb2d07](https://github.com/PunGrumpy/logixlysia/commit/1bb2d07ae0c815742a9eb7c930ac8487ac287b2c)), closes [#63](https://github.com/PunGrumpy/logixlysia/issues/63)
|
|
16
|
+
|
|
3
17
|
## [3.6.1](https://github.com/PunGrumpy/logixlysia/compare/v3.6.0...v3.6.1) (2024-08-21)
|
|
4
18
|
|
|
5
19
|
|
package/README.md
CHANGED
|
@@ -21,7 +21,8 @@ const app = new Elysia({
|
|
|
21
21
|
}).use(
|
|
22
22
|
logixlysia({
|
|
23
23
|
config: {
|
|
24
|
-
|
|
24
|
+
showStartupMessage: true,
|
|
25
|
+
startupMessageFormat: 'simple',
|
|
25
26
|
ip: true,
|
|
26
27
|
logFilePath: './logs/example.log',
|
|
27
28
|
customLogFormat:
|
|
@@ -45,13 +46,14 @@ app.listen(3000)
|
|
|
45
46
|
|
|
46
47
|
### Options
|
|
47
48
|
|
|
48
|
-
| Option
|
|
49
|
-
|
|
|
50
|
-
| `
|
|
51
|
-
| `
|
|
52
|
-
| `
|
|
53
|
-
| `
|
|
54
|
-
| `
|
|
49
|
+
| Option | Type | Description | Default |
|
|
50
|
+
| ---------------------- | ------------------------ | --------------------------------------------------------------------- | ------------------------------------------------------------------------- |
|
|
51
|
+
| `showStartupMessage` | `boolean` | Display the startup message | `true` |
|
|
52
|
+
| `startupMessageFormat` | `"banner"` \| `"simple"` | Choose the startup message format | `"banner"` |
|
|
53
|
+
| `ip` | `boolean` | Display the incoming IP address based on the `X-Forwarded-For` header | `false` |
|
|
54
|
+
| `customLogMessage` | `string` | Custom log message to display | `🦊 {now} {level} {duration} {method} {pathname} {status} {message} {ip}` |
|
|
55
|
+
| `logFilter` | `object` | Filter the logs based on the level, method, and status | `null` |
|
|
56
|
+
| `logFilePath` | `string` | Path to the log file | `./logs/elysia.log` |
|
|
55
57
|
|
|
56
58
|
### Custom Log Message
|
|
57
59
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "logixlysia",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.7.0",
|
|
4
4
|
"description": "🦊 Logixlysia is a logger for Elysia",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"module": "src/index.ts",
|
|
@@ -71,24 +71,21 @@
|
|
|
71
71
|
],
|
|
72
72
|
"dependencies": {
|
|
73
73
|
"chalk": "^5.3.0",
|
|
74
|
-
"elysia": "^1.1.
|
|
74
|
+
"elysia": "^1.1.16"
|
|
75
75
|
},
|
|
76
76
|
"devDependencies": {
|
|
77
|
-
"@elysiajs/eden": "^1.1.
|
|
78
|
-
"@eslint/js": "^9.
|
|
79
|
-
"@trunkio/launcher": "^1.3.
|
|
80
|
-
"@typescript-eslint/eslint-plugin": "^8.0
|
|
81
|
-
"@typescript-eslint/parser": "^8.0
|
|
82
|
-
"bun-types": "^1.1.
|
|
83
|
-
"commitizen": "^4.3.0",
|
|
84
|
-
"cz-conventional-changelog": "^3.3.0",
|
|
85
|
-
"eslint": "9.x",
|
|
77
|
+
"@elysiajs/eden": "^1.1.3",
|
|
78
|
+
"@eslint/js": "^9.11.1",
|
|
79
|
+
"@trunkio/launcher": "^1.3.2",
|
|
80
|
+
"@typescript-eslint/eslint-plugin": "^8.7.0",
|
|
81
|
+
"@typescript-eslint/parser": "^8.7.0",
|
|
82
|
+
"bun-types": "^1.1.29",
|
|
86
83
|
"eslint-plugin-simple-import-sort": "^12.1.1",
|
|
87
84
|
"globals": "^15.9.0",
|
|
88
|
-
"husky": "^9.1.
|
|
89
|
-
"lint-staged": "^15.2.
|
|
85
|
+
"husky": "^9.1.6",
|
|
86
|
+
"lint-staged": "^15.2.10",
|
|
90
87
|
"prettier": "^3.3.3",
|
|
91
|
-
"typescript-eslint": "^8.0
|
|
88
|
+
"typescript-eslint": "^8.7.0"
|
|
92
89
|
},
|
|
93
90
|
"peerDependencies": {
|
|
94
91
|
"typescript": "^5.2.2"
|
|
@@ -7,27 +7,25 @@ import {
|
|
|
7
7
|
Options,
|
|
8
8
|
RequestInfo,
|
|
9
9
|
StoreData
|
|
10
|
-
} from '
|
|
11
|
-
import
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
10
|
+
} from '../types'
|
|
11
|
+
import {
|
|
12
|
+
durationString,
|
|
13
|
+
logString,
|
|
14
|
+
methodString,
|
|
15
|
+
pathString,
|
|
16
|
+
statusString
|
|
17
|
+
} from '../utils'
|
|
16
18
|
|
|
17
19
|
const defaultLogFormat =
|
|
18
20
|
'🦊 {now} {level} {duration} {method} {pathname} {status} {message} {ip}'
|
|
19
21
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
* @param {Options} options The logger options.
|
|
28
|
-
* @param {boolean} useColors Whether to apply colors to the log message.
|
|
29
|
-
* @returns {string} The formatted log message.
|
|
30
|
-
*/
|
|
22
|
+
function shouldUseColors(useColors: boolean, options?: Options): boolean {
|
|
23
|
+
if (options?.config?.useColors !== undefined) {
|
|
24
|
+
return options.config.useColors && process.env.NO_COLOR === undefined
|
|
25
|
+
}
|
|
26
|
+
return useColors && process.env.NO_COLOR === undefined
|
|
27
|
+
}
|
|
28
|
+
|
|
31
29
|
export function buildLogMessage(
|
|
32
30
|
level: LogLevel,
|
|
33
31
|
request: RequestInfo,
|
|
@@ -36,17 +34,18 @@ export function buildLogMessage(
|
|
|
36
34
|
options?: Options,
|
|
37
35
|
useColors: boolean = true
|
|
38
36
|
): string {
|
|
37
|
+
const actuallyUseColors = shouldUseColors(useColors, options)
|
|
39
38
|
const now = new Date()
|
|
40
39
|
const components: LogComponents = {
|
|
41
|
-
now:
|
|
40
|
+
now: actuallyUseColors
|
|
42
41
|
? chalk.bgYellow(chalk.black(now.toLocaleString()))
|
|
43
42
|
: now.toLocaleString(),
|
|
44
43
|
epoch: Math.floor(now.getTime() / 1000).toString(),
|
|
45
|
-
level: logString(level,
|
|
46
|
-
duration: durationString(store.beforeTime,
|
|
47
|
-
method: methodString(request.method,
|
|
44
|
+
level: logString(level, actuallyUseColors),
|
|
45
|
+
duration: durationString(store.beforeTime, actuallyUseColors),
|
|
46
|
+
method: methodString(request.method, actuallyUseColors),
|
|
48
47
|
pathname: pathString(request),
|
|
49
|
-
status: statusString(data.status || 200,
|
|
48
|
+
status: statusString(data.status || 200, actuallyUseColors),
|
|
50
49
|
message: data.message || '',
|
|
51
50
|
ip:
|
|
52
51
|
options?.config?.ip && request.headers.get('x-forwarded-for')
|
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { logToFile } from '~/logger/logToFile'
|
|
4
|
-
import { logToTransports } from '~/transports'
|
|
1
|
+
import { logToTransports } from '../transports'
|
|
2
|
+
import { logToFile } from '../transports'
|
|
5
3
|
import {
|
|
6
4
|
LogData,
|
|
7
5
|
Logger,
|
|
@@ -9,17 +7,11 @@ import {
|
|
|
9
7
|
Options,
|
|
10
8
|
RequestInfo,
|
|
11
9
|
StoreData
|
|
12
|
-
} from '
|
|
10
|
+
} from '../types'
|
|
11
|
+
import { buildLogMessage } from './buildLogMessage'
|
|
12
|
+
import { filterLog } from './filter'
|
|
13
|
+
import { handleHttpError } from './handleHttpError'
|
|
13
14
|
|
|
14
|
-
/**
|
|
15
|
-
* Logs a message to the console and optionally to a file.
|
|
16
|
-
*
|
|
17
|
-
* @param {LogLevel} level The log level.
|
|
18
|
-
* @param {RequestInfo} request The request information.
|
|
19
|
-
* @param {LogData} data The log data.
|
|
20
|
-
* @param {StoreData} store The store data.
|
|
21
|
-
* @param {Options} options The logger options.
|
|
22
|
-
*/
|
|
23
15
|
async function log(
|
|
24
16
|
level: LogLevel,
|
|
25
17
|
request: RequestInfo,
|
|
@@ -54,16 +46,12 @@ async function log(
|
|
|
54
46
|
await Promise.all(promises)
|
|
55
47
|
}
|
|
56
48
|
|
|
57
|
-
/**
|
|
58
|
-
* Creates a logger instance.
|
|
59
|
-
*
|
|
60
|
-
* @param {Options} options The logger options.
|
|
61
|
-
* @returns {Logger} The logger instance.
|
|
62
|
-
*/
|
|
63
49
|
export function createLogger(options?: Options): Logger {
|
|
64
50
|
return {
|
|
65
51
|
log: (level, request, data, store) =>
|
|
66
52
|
log(level, request, data, store, options),
|
|
53
|
+
handleHttpError: (request, error, store) =>
|
|
54
|
+
handleHttpError(request, error, store, options),
|
|
67
55
|
customLogFormat: options?.config?.customLogFormat
|
|
68
56
|
}
|
|
69
57
|
}
|
|
@@ -1,19 +1,10 @@
|
|
|
1
|
-
import { LogLevel, Options } from '
|
|
1
|
+
import { LogLevel, Options } from '../types'
|
|
2
2
|
|
|
3
3
|
const checkFilter = (filterValue: any, value: any) =>
|
|
4
4
|
Array.isArray(filterValue)
|
|
5
5
|
? filterValue.includes(value)
|
|
6
6
|
: filterValue === value
|
|
7
7
|
|
|
8
|
-
/**
|
|
9
|
-
* Filters log messages.
|
|
10
|
-
*
|
|
11
|
-
* @param {LogLevel} logLevel The log level.
|
|
12
|
-
* @param {number} status The status code.
|
|
13
|
-
* @param {string} method The method.
|
|
14
|
-
* @param {Options} options The options.
|
|
15
|
-
* @returns {boolean} `true` if the log message should be logged, otherwise `false`.
|
|
16
|
-
*/
|
|
17
8
|
export function filterLog(
|
|
18
9
|
logLevel: LogLevel,
|
|
19
10
|
status: number,
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { logToFile } from '../transports'
|
|
2
|
+
import { HttpError, Options, RequestInfo, StoreData } from '../types'
|
|
3
|
+
import { buildLogMessage } from './buildLogMessage'
|
|
4
|
+
|
|
5
|
+
export function handleHttpError(
|
|
6
|
+
request: RequestInfo,
|
|
7
|
+
error: HttpError,
|
|
8
|
+
store: StoreData,
|
|
9
|
+
options?: Options
|
|
10
|
+
): void {
|
|
11
|
+
const statusCode = error.status || 500
|
|
12
|
+
console.error(
|
|
13
|
+
buildLogMessage('ERROR', request, { status: statusCode }, store, options)
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
const promises = []
|
|
17
|
+
|
|
18
|
+
if (options?.config?.logFilePath) {
|
|
19
|
+
promises.push(
|
|
20
|
+
logToFile(
|
|
21
|
+
options.config.logFilePath,
|
|
22
|
+
'ERROR',
|
|
23
|
+
request,
|
|
24
|
+
{ status: statusCode },
|
|
25
|
+
store,
|
|
26
|
+
options
|
|
27
|
+
)
|
|
28
|
+
)
|
|
29
|
+
}
|
|
30
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,31 +1,19 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import Elysia from 'elysia'
|
|
1
|
+
import { Elysia } from 'elysia'
|
|
3
2
|
|
|
4
|
-
import { createLogger
|
|
5
|
-
import {
|
|
6
|
-
import
|
|
3
|
+
import { createLogger } from './core'
|
|
4
|
+
import { startServer } from './plugins'
|
|
5
|
+
import { HttpError, Options, Server } from './types'
|
|
7
6
|
|
|
8
|
-
/**
|
|
9
|
-
* Creates a logger plugin for ElysiaJS.
|
|
10
|
-
*
|
|
11
|
-
* @export
|
|
12
|
-
* @module logger
|
|
13
|
-
* @category Logger
|
|
14
|
-
* @subcategory Functions
|
|
15
|
-
*
|
|
16
|
-
* @name Logixlysia
|
|
17
|
-
* @description Logixlysia is a logger plugin for ElysiaJS.
|
|
18
|
-
* @param {Options} [options] Configuration options for the logger.
|
|
19
|
-
*
|
|
20
|
-
* @returns {Elysia} The logger plugin for ElysiaJS.
|
|
21
|
-
*/
|
|
22
7
|
export default function logixlysia(options?: Options): Elysia {
|
|
23
8
|
const log = createLogger(options)
|
|
24
9
|
|
|
25
10
|
return new Elysia({
|
|
26
11
|
name: 'Logixlysia'
|
|
27
12
|
})
|
|
28
|
-
.onStart(ctx =>
|
|
13
|
+
.onStart(ctx => {
|
|
14
|
+
const showStartupMessage = options?.config?.showStartupMessage ?? true
|
|
15
|
+
if (showStartupMessage) startServer(ctx.server as Server, options)}
|
|
16
|
+
)
|
|
29
17
|
.onRequest(ctx => {
|
|
30
18
|
ctx.store = { beforeTime: process.hrtime.bigint() }
|
|
31
19
|
})
|
|
@@ -33,14 +21,14 @@ export default function logixlysia(options?: Options): Elysia {
|
|
|
33
21
|
log.log('INFO', request, { status: 200 }, store as { beforeTime: bigint })
|
|
34
22
|
})
|
|
35
23
|
.onError({ as: 'global' }, ({ request, error, store }) => {
|
|
36
|
-
handleHttpError(
|
|
24
|
+
log.handleHttpError(
|
|
37
25
|
request,
|
|
38
26
|
error as HttpError,
|
|
39
|
-
store as { beforeTime: bigint }
|
|
40
|
-
options
|
|
27
|
+
store as { beforeTime: bigint }
|
|
41
28
|
)
|
|
42
29
|
})
|
|
43
30
|
}
|
|
44
31
|
|
|
45
|
-
export { createLogger } from '
|
|
46
|
-
export {
|
|
32
|
+
export { createLogger, handleHttpError } from './core'
|
|
33
|
+
export { logToTransports } from './transports'
|
|
34
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as startServer } from './startServer'
|
|
@@ -1,27 +1,14 @@
|
|
|
1
|
-
import { Options, Server } from '
|
|
1
|
+
import { Options, Server } from '../types'
|
|
2
2
|
|
|
3
|
-
/**
|
|
4
|
-
* Creates a box text.
|
|
5
|
-
*
|
|
6
|
-
* @param {string} text The text.
|
|
7
|
-
* @param {number} width The box width.
|
|
8
|
-
* @returns {string} The box text.
|
|
9
|
-
*/
|
|
10
3
|
const createBoxText = (text: string, width: number): string => {
|
|
11
4
|
const paddingLength = Math.max(0, (width - text.length) / 2)
|
|
12
5
|
const padding = ' '.repeat(paddingLength)
|
|
13
6
|
return `${padding}${text}${padding}`.padEnd(width)
|
|
14
7
|
}
|
|
15
8
|
|
|
16
|
-
|
|
17
|
-
* Starts the server string.
|
|
18
|
-
*
|
|
19
|
-
* @param {Server} config The server configuration.
|
|
20
|
-
* @returns {void} The server string.
|
|
21
|
-
*/
|
|
22
|
-
function startServer(config: Server, options?: Options): void {
|
|
9
|
+
export default function startServer(config: Server, options?: Options): void {
|
|
23
10
|
const { hostname, port, protocol } = config
|
|
24
|
-
const showBanner = options?.config?.
|
|
11
|
+
const showBanner = options?.config?.startupMessageFormat !== 'simple'
|
|
25
12
|
|
|
26
13
|
if (showBanner) {
|
|
27
14
|
const ELYSIA_VERSION = import.meta.require('elysia/package.json').version
|
|
@@ -44,5 +31,3 @@ function startServer(config: Server, options?: Options): void {
|
|
|
44
31
|
console.log(`🦊 Elysia is running at ${protocol}://${hostname}:${port}`)
|
|
45
32
|
}
|
|
46
33
|
}
|
|
47
|
-
|
|
48
|
-
export default startServer
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { buildLogMessage } from '../core/buildLogMessage'
|
|
2
|
+
import { LogData, LogLevel, Options, RequestInfo, StoreData } from '../types'
|
|
3
|
+
|
|
4
|
+
export async function logToTransports(
|
|
5
|
+
level: LogLevel,
|
|
6
|
+
request: RequestInfo,
|
|
7
|
+
data: LogData,
|
|
8
|
+
store: StoreData,
|
|
9
|
+
options?: Options
|
|
10
|
+
): Promise<void> {
|
|
11
|
+
if (!options?.config?.transports || options.config.transports.length === 0) {
|
|
12
|
+
return
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const message = buildLogMessage(level, request, data, store, options, false)
|
|
16
|
+
|
|
17
|
+
const promises = options.config.transports.map(transport =>
|
|
18
|
+
transport.log(level, message, { request, data, store })
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
await Promise.all(promises)
|
|
22
|
+
}
|
|
@@ -1,16 +1,11 @@
|
|
|
1
1
|
import { promises as fs } from 'fs'
|
|
2
2
|
import { dirname } from 'path'
|
|
3
3
|
|
|
4
|
-
import { buildLogMessage } from '
|
|
5
|
-
import { LogData, LogLevel, Options, RequestInfo, StoreData } from '
|
|
4
|
+
import { buildLogMessage } from '../core/buildLogMessage'
|
|
5
|
+
import { LogData, LogLevel, Options, RequestInfo, StoreData } from '../types'
|
|
6
6
|
|
|
7
7
|
const dirCache = new Set<string>()
|
|
8
8
|
|
|
9
|
-
/**
|
|
10
|
-
* Ensures that the directory exists. If not, it creates the directory.
|
|
11
|
-
*
|
|
12
|
-
* @param {string} filePath The path to the log file.
|
|
13
|
-
*/
|
|
14
9
|
async function ensureDirectoryExists(filePath: string): Promise<void> {
|
|
15
10
|
const dir = dirname(filePath)
|
|
16
11
|
if (!dirCache.has(dir)) {
|
|
@@ -19,16 +14,6 @@ async function ensureDirectoryExists(filePath: string): Promise<void> {
|
|
|
19
14
|
}
|
|
20
15
|
}
|
|
21
16
|
|
|
22
|
-
/**
|
|
23
|
-
* Logs a message to a file.
|
|
24
|
-
*
|
|
25
|
-
* @param {string} filePath The path to the log file.
|
|
26
|
-
* @param {LogLevel} level The log level.
|
|
27
|
-
* @param {RequestInfo} request The request information.
|
|
28
|
-
* @param {LogData} data The log data.
|
|
29
|
-
* @param {StoreData} store The store data.
|
|
30
|
-
* @param {Options} options The logger options.
|
|
31
|
-
*/
|
|
32
17
|
export async function logToFile(
|
|
33
18
|
filePath: string,
|
|
34
19
|
level: LogLevel,
|
package/src/transports/index.ts
CHANGED
|
@@ -1,22 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
export async function logToTransports(
|
|
5
|
-
level: LogLevel,
|
|
6
|
-
request: RequestInfo,
|
|
7
|
-
data: LogData,
|
|
8
|
-
store: StoreData,
|
|
9
|
-
options?: Options
|
|
10
|
-
): Promise<void> {
|
|
11
|
-
if (!options?.config?.transports || options.config.transports.length === 0) {
|
|
12
|
-
return
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
const message = buildLogMessage(level, request, data, store, options, false)
|
|
16
|
-
|
|
17
|
-
const promises = options.config.transports.map(transport =>
|
|
18
|
-
transport.log(level, message, { request, data, store })
|
|
19
|
-
)
|
|
20
|
-
|
|
21
|
-
await Promise.all(promises)
|
|
22
|
-
}
|
|
1
|
+
export { logToTransports } from './console'
|
|
2
|
+
export { logToFile } from './file'
|
|
@@ -1,37 +1,42 @@
|
|
|
1
|
-
interface RequestInfo {
|
|
1
|
+
export interface RequestInfo {
|
|
2
2
|
headers: { get: (key: string) => string | null }
|
|
3
3
|
method: string
|
|
4
4
|
url: string
|
|
5
5
|
}
|
|
6
6
|
|
|
7
|
-
interface Server {
|
|
7
|
+
export interface Server {
|
|
8
8
|
hostname?: string
|
|
9
9
|
port?: number
|
|
10
10
|
protocol?: string
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
interface ColorMap {
|
|
13
|
+
export interface ColorMap {
|
|
14
14
|
[key: string]: (str: string) => string
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
type LogLevel = 'INFO' | 'WARNING' | 'ERROR' | string
|
|
17
|
+
export type LogLevel = 'INFO' | 'WARNING' | 'ERROR' | string
|
|
18
18
|
|
|
19
|
-
interface LogData {
|
|
19
|
+
export interface LogData {
|
|
20
20
|
status?: number
|
|
21
21
|
message?: string
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
interface Logger {
|
|
24
|
+
export interface Logger {
|
|
25
25
|
log(
|
|
26
26
|
level: LogLevel,
|
|
27
27
|
request: RequestInfo,
|
|
28
28
|
data: LogData,
|
|
29
29
|
store: StoreData
|
|
30
30
|
): void
|
|
31
|
+
handleHttpError(
|
|
32
|
+
request: RequestInfo,
|
|
33
|
+
error: HttpError,
|
|
34
|
+
store: StoreData
|
|
35
|
+
): void
|
|
31
36
|
customLogFormat?: string
|
|
32
37
|
}
|
|
33
38
|
|
|
34
|
-
interface LogComponents {
|
|
39
|
+
export interface LogComponents {
|
|
35
40
|
now: string
|
|
36
41
|
epoch: string
|
|
37
42
|
level: string
|
|
@@ -43,11 +48,11 @@ interface LogComponents {
|
|
|
43
48
|
ip: string
|
|
44
49
|
}
|
|
45
50
|
|
|
46
|
-
interface StoreData {
|
|
51
|
+
export interface StoreData {
|
|
47
52
|
beforeTime: bigint
|
|
48
53
|
}
|
|
49
54
|
|
|
50
|
-
class HttpError extends Error {
|
|
55
|
+
export class HttpError extends Error {
|
|
51
56
|
status: number
|
|
52
57
|
|
|
53
58
|
constructor(status: number, message: string) {
|
|
@@ -56,7 +61,7 @@ class HttpError extends Error {
|
|
|
56
61
|
}
|
|
57
62
|
}
|
|
58
63
|
|
|
59
|
-
interface TransportFunction {
|
|
64
|
+
export interface TransportFunction {
|
|
60
65
|
(
|
|
61
66
|
level: LogLevel,
|
|
62
67
|
message: string,
|
|
@@ -68,11 +73,11 @@ interface TransportFunction {
|
|
|
68
73
|
): Promise<void> | void
|
|
69
74
|
}
|
|
70
75
|
|
|
71
|
-
interface Transport {
|
|
76
|
+
export interface Transport {
|
|
72
77
|
log: TransportFunction
|
|
73
78
|
}
|
|
74
79
|
|
|
75
|
-
interface Options {
|
|
80
|
+
export interface Options {
|
|
76
81
|
config?: {
|
|
77
82
|
customLogFormat?: string
|
|
78
83
|
logFilePath?: string
|
|
@@ -82,22 +87,9 @@ interface Options {
|
|
|
82
87
|
status?: number | number[]
|
|
83
88
|
} | null
|
|
84
89
|
ip?: boolean
|
|
85
|
-
|
|
90
|
+
useColors?: boolean
|
|
91
|
+
showStartupMessage?: boolean
|
|
92
|
+
startupMessageFormat?: 'banner' | 'simple'
|
|
86
93
|
transports?: Transport[]
|
|
87
94
|
}
|
|
88
95
|
}
|
|
89
|
-
|
|
90
|
-
export {
|
|
91
|
-
ColorMap,
|
|
92
|
-
HttpError,
|
|
93
|
-
LogComponents,
|
|
94
|
-
LogData,
|
|
95
|
-
Logger,
|
|
96
|
-
LogLevel,
|
|
97
|
-
Options,
|
|
98
|
-
RequestInfo,
|
|
99
|
-
Server,
|
|
100
|
-
StoreData,
|
|
101
|
-
Transport,
|
|
102
|
-
TransportFunction
|
|
103
|
-
}
|
|
@@ -1,24 +1,14 @@
|
|
|
1
1
|
import chalk from 'chalk'
|
|
2
2
|
|
|
3
|
-
import { ColorMap } from '
|
|
3
|
+
import { ColorMap } from '../types'
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
* The color map for the log levels.
|
|
7
|
-
*
|
|
8
|
-
* @type {ColorMap}
|
|
9
|
-
*/
|
|
10
|
-
const LogLevelColorMap: ColorMap = {
|
|
5
|
+
export const LogLevelColorMap: ColorMap = {
|
|
11
6
|
INFO: chalk.bgGreen.black,
|
|
12
7
|
WARNING: chalk.bgYellow.black,
|
|
13
8
|
ERROR: chalk.bgRed.black
|
|
14
9
|
}
|
|
15
10
|
|
|
16
|
-
|
|
17
|
-
* The color map for the HTTP methods.
|
|
18
|
-
*
|
|
19
|
-
* @type {ColorMap}
|
|
20
|
-
*/
|
|
21
|
-
const HttpMethodColorMap: ColorMap = {
|
|
11
|
+
export const HttpMethodColorMap: ColorMap = {
|
|
22
12
|
GET: chalk.green,
|
|
23
13
|
POST: chalk.yellow,
|
|
24
14
|
PUT: chalk.blue,
|
|
@@ -27,5 +17,3 @@ const HttpMethodColorMap: ColorMap = {
|
|
|
27
17
|
HEAD: chalk.cyan,
|
|
28
18
|
OPTIONS: chalk.magenta
|
|
29
19
|
}
|
|
30
|
-
|
|
31
|
-
export { HttpMethodColorMap, LogLevelColorMap }
|
package/src/utils/duration.ts
CHANGED
|
@@ -7,14 +7,10 @@ const timeUnits = [
|
|
|
7
7
|
{ unit: 'ns', threshold: 1, decimalPlaces: 0 }
|
|
8
8
|
]
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
* @param {boolean} useColors - Whether to apply colors to the output.
|
|
15
|
-
* @returns {string} A formatted duration string including the time unit.
|
|
16
|
-
*/
|
|
17
|
-
function durationString(beforeTime: bigint, useColors: boolean): string {
|
|
10
|
+
export default function durationString(
|
|
11
|
+
beforeTime: bigint,
|
|
12
|
+
useColors: boolean
|
|
13
|
+
): string {
|
|
18
14
|
const nanoseconds = Number(process.hrtime.bigint() - beforeTime)
|
|
19
15
|
|
|
20
16
|
for (const { unit, threshold, decimalPlaces } of timeUnits) {
|
|
@@ -29,5 +25,3 @@ function durationString(beforeTime: bigint, useColors: boolean): string {
|
|
|
29
25
|
? chalk.gray('0ns'.padStart(8).padEnd(16))
|
|
30
26
|
: '0ns'.padStart(8).padEnd(16)
|
|
31
27
|
}
|
|
32
|
-
|
|
33
|
-
export default durationString
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { HttpMethodColorMap, LogLevelColorMap } from './colorMapping'
|
|
2
|
+
export { default as durationString } from './duration'
|
|
3
|
+
export { default as logString } from './log'
|
|
4
|
+
export { default as methodString } from './method'
|
|
5
|
+
export { default as pathString } from './path'
|
|
6
|
+
export { default as statusString } from './status'
|
package/src/utils/log.ts
CHANGED
|
@@ -1,18 +1,9 @@
|
|
|
1
|
-
import { LogLevel } from '
|
|
2
|
-
import { LogLevelColorMap } from '
|
|
1
|
+
import { LogLevel } from '../types'
|
|
2
|
+
import { LogLevelColorMap } from './colorMapping'
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
* Converts the log level to a string.
|
|
6
|
-
*
|
|
7
|
-
* @param {LogLevel} level The log level.
|
|
8
|
-
* @param {boolean} useColors - Whether to apply colors to the output.
|
|
9
|
-
* @returns {string} The log level as a string.
|
|
10
|
-
*/
|
|
11
|
-
const logString = (level: LogLevel, useColors: boolean): string => {
|
|
4
|
+
export default function logString(level: LogLevel, useColors: boolean): string {
|
|
12
5
|
const levelStr = level.toUpperCase()
|
|
13
6
|
return useColors
|
|
14
7
|
? LogLevelColorMap[levelStr]?.(levelStr.padEnd(7)) || levelStr
|
|
15
8
|
: levelStr.padEnd(7)
|
|
16
9
|
}
|
|
17
|
-
|
|
18
|
-
export default logString
|
package/src/utils/method.ts
CHANGED
|
@@ -1,17 +1,11 @@
|
|
|
1
|
-
import { HttpMethodColorMap } from '
|
|
1
|
+
import { HttpMethodColorMap } from './colorMapping'
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
* @param {boolean} useColors - Whether to apply colors to the output.
|
|
8
|
-
* @returns {string} A string representing the method.
|
|
9
|
-
*/
|
|
10
|
-
function methodString(method: string, useColors: boolean): string {
|
|
3
|
+
export default function methodString(
|
|
4
|
+
method: string,
|
|
5
|
+
useColors: boolean
|
|
6
|
+
): string {
|
|
11
7
|
const colorFunction = HttpMethodColorMap[method]
|
|
12
8
|
return useColors && colorFunction
|
|
13
9
|
? colorFunction(method.padEnd(7))
|
|
14
10
|
: method.padEnd(7)
|
|
15
11
|
}
|
|
16
|
-
|
|
17
|
-
export default methodString
|
package/src/utils/path.ts
CHANGED
|
@@ -1,17 +1,11 @@
|
|
|
1
|
-
import { RequestInfo } from '
|
|
1
|
+
import { RequestInfo } from '../types'
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
* @param {RequestInfo} requestInfo The request info.
|
|
7
|
-
* @returns {string | undefined} The path string.
|
|
8
|
-
*/
|
|
9
|
-
const pathString = (requestInfo: RequestInfo): string | undefined => {
|
|
3
|
+
export default function pathString(
|
|
4
|
+
requestInfo: RequestInfo
|
|
5
|
+
): string | undefined {
|
|
10
6
|
try {
|
|
11
7
|
return new URL(requestInfo.url).pathname
|
|
12
8
|
} catch {
|
|
13
9
|
return undefined
|
|
14
10
|
}
|
|
15
11
|
}
|
|
16
|
-
|
|
17
|
-
export default pathString
|
package/src/utils/status.ts
CHANGED
|
@@ -1,13 +1,9 @@
|
|
|
1
1
|
import chalk from 'chalk'
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
* @param {boolean} useColors - Whether to apply colors to the output.
|
|
8
|
-
* @returns {string} The status code as a string.
|
|
9
|
-
*/
|
|
10
|
-
const statusString = (status: number, useColors: boolean): string => {
|
|
3
|
+
export default function statusString(
|
|
4
|
+
status: number,
|
|
5
|
+
useColors: boolean
|
|
6
|
+
): string {
|
|
11
7
|
const color =
|
|
12
8
|
status >= 500
|
|
13
9
|
? 'red'
|
|
@@ -20,5 +16,3 @@ const statusString = (status: number, useColors: boolean): string => {
|
|
|
20
16
|
: 'white'
|
|
21
17
|
return useColors ? chalk[color](status.toString()) : status.toString()
|
|
22
18
|
}
|
|
23
|
-
|
|
24
|
-
export default statusString
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { expect, test } from 'bun:test'
|
|
2
|
+
|
|
3
|
+
import { buildLogMessage } from '../../src/core/buildLogMessage'
|
|
4
|
+
import { LogData, LogLevel, Options, StoreData } from '../../src/types'
|
|
5
|
+
import { createMockRequest } from '../helpers'
|
|
6
|
+
|
|
7
|
+
test('buildLogMessage', () => {
|
|
8
|
+
const level: LogLevel = 'INFO'
|
|
9
|
+
const request = createMockRequest()
|
|
10
|
+
const data: LogData = { status: 200, message: 'Test message' }
|
|
11
|
+
const store: StoreData = { beforeTime: BigInt(0) }
|
|
12
|
+
const options: Options = {
|
|
13
|
+
config: {
|
|
14
|
+
ip: true,
|
|
15
|
+
customLogFormat: '{level} {message} {ip}'
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const message = buildLogMessage(level, request, data, store, options, false)
|
|
20
|
+
expect(message).toContain('INFO')
|
|
21
|
+
expect(message).toContain('Test message')
|
|
22
|
+
expect(message).toContain('127.0.0.1')
|
|
23
|
+
|
|
24
|
+
const colorMessage = buildLogMessage(
|
|
25
|
+
level,
|
|
26
|
+
request,
|
|
27
|
+
data,
|
|
28
|
+
store,
|
|
29
|
+
options,
|
|
30
|
+
true
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
expect(colorMessage).toContain('INFO')
|
|
34
|
+
expect(colorMessage).toContain('Test message')
|
|
35
|
+
expect(colorMessage).toContain('127.0.0.1')
|
|
36
|
+
|
|
37
|
+
const hasAnsiCodes = /\\x1B\[[0-9;]*m/.test(colorMessage)
|
|
38
|
+
if (hasAnsiCodes) {
|
|
39
|
+
expect(colorMessage).not.toBe(message)
|
|
40
|
+
} else {
|
|
41
|
+
console.warn(
|
|
42
|
+
'No ANSI color codes detected. Colors might be disabled in this environment.'
|
|
43
|
+
)
|
|
44
|
+
}
|
|
45
|
+
})
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { expect, test } from 'bun:test'
|
|
2
|
+
|
|
3
|
+
import { filterLog } from '../../src/core/filter'
|
|
4
|
+
import { Options } from '../../src/types'
|
|
5
|
+
|
|
6
|
+
test('filterLog', () => {
|
|
7
|
+
const options: Options = {
|
|
8
|
+
config: {
|
|
9
|
+
logFilter: {
|
|
10
|
+
level: 'ERROR',
|
|
11
|
+
method: 'POST',
|
|
12
|
+
status: 500
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
expect(filterLog('ERROR', 500, 'POST', options)).toBe(true)
|
|
18
|
+
expect(filterLog('INFO', 200, 'GET', options)).toBe(false)
|
|
19
|
+
expect(filterLog('WARNING', 400, 'PUT', options)).toBe(false)
|
|
20
|
+
|
|
21
|
+
// Test with array filters
|
|
22
|
+
const arrayOptions: Options = {
|
|
23
|
+
config: {
|
|
24
|
+
logFilter: {
|
|
25
|
+
level: ['ERROR', 'WARNING'],
|
|
26
|
+
method: ['POST', 'PUT'],
|
|
27
|
+
status: [500, 400]
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
expect(filterLog('ERROR', 500, 'POST', arrayOptions)).toBe(true)
|
|
33
|
+
expect(filterLog('WARNING', 400, 'PUT', arrayOptions)).toBe(true)
|
|
34
|
+
expect(filterLog('INFO', 200, 'GET', arrayOptions)).toBe(false)
|
|
35
|
+
})
|
package/tests/helpers.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { mock } from 'bun:test'
|
|
2
|
+
|
|
3
|
+
import { RequestInfo } from '../src/types'
|
|
4
|
+
|
|
5
|
+
export function createMockRequest(
|
|
6
|
+
method: string = 'GET',
|
|
7
|
+
url: string = 'http://localhost:3000/'
|
|
8
|
+
): RequestInfo {
|
|
9
|
+
return {
|
|
10
|
+
headers: {
|
|
11
|
+
get: (key: string) => (key === 'x-forwarded-for' ? '127.0.0.1' : null)
|
|
12
|
+
},
|
|
13
|
+
method,
|
|
14
|
+
url
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const mockConsoleLog = mock(() => {})
|
|
19
|
+
export const mockConsoleError = mock(() => {})
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { expect, mock, test } from 'bun:test'
|
|
2
|
+
|
|
3
|
+
import { logToTransports } from '../../src/transports'
|
|
4
|
+
import { LogData, LogLevel, Options, StoreData } from '../../src/types'
|
|
5
|
+
import { createMockRequest } from '../helpers'
|
|
6
|
+
|
|
7
|
+
test('logToTransports', async () => {
|
|
8
|
+
const level: LogLevel = 'INFO'
|
|
9
|
+
const request = createMockRequest()
|
|
10
|
+
const data: LogData = { status: 200, message: 'Test message' }
|
|
11
|
+
const store: StoreData = { beforeTime: BigInt(0) }
|
|
12
|
+
|
|
13
|
+
const mockTransport = {
|
|
14
|
+
log: mock(() => Promise.resolve())
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const options: Options = {
|
|
18
|
+
config: {
|
|
19
|
+
transports: [mockTransport]
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
await logToTransports(level, request, data, store, options)
|
|
24
|
+
expect(mockTransport.log).toHaveBeenCalled()
|
|
25
|
+
|
|
26
|
+
// Test with no transports
|
|
27
|
+
await logToTransports(level, request, data, store, {})
|
|
28
|
+
expect(mockTransport.log).toHaveBeenCalledTimes(1) // Should not be called again
|
|
29
|
+
})
|
package/tsconfig.json
CHANGED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import { buildLogMessage } from '~/logger/buildLogMessage'
|
|
2
|
-
import { HttpError, Options, RequestInfo, StoreData } from '~/types'
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Handles an HTTP error and logs it.
|
|
6
|
-
*
|
|
7
|
-
* @param {RequestInfo} request The request information.
|
|
8
|
-
* @param {HttpError} error The HTTP error.
|
|
9
|
-
* @param {StoreData} store The store data.
|
|
10
|
-
* @param {Options} options The logger options.
|
|
11
|
-
*/
|
|
12
|
-
export function handleHttpError(
|
|
13
|
-
request: RequestInfo,
|
|
14
|
-
error: HttpError,
|
|
15
|
-
store: StoreData,
|
|
16
|
-
options?: Options
|
|
17
|
-
): void {
|
|
18
|
-
const statusCode = error.status || 500
|
|
19
|
-
console.error(
|
|
20
|
-
buildLogMessage('ERROR', request, { status: statusCode }, store, options)
|
|
21
|
-
)
|
|
22
|
-
}
|
package/src/logger/index.ts
DELETED