logixlysia 3.6.0 โ†’ 3.6.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/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## [3.6.1](https://github.com/PunGrumpy/logixlysia/compare/v3.6.0...v3.6.1) (2024-08-21)
4
+
5
+
6
+ ### Performance Improvements
7
+
8
+ * **logger:** improve performance and fix TypeScript errors ([d3ed751](https://github.com/PunGrumpy/logixlysia/commit/d3ed751041443b9bd2ce53350994e1443df40971))
9
+
3
10
  ## [3.6.0](https://github.com/PunGrumpy/logixlysia/compare/v3.5.0...v3.6.0) (2024-07-24)
4
11
 
5
12
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "logixlysia",
3
- "version": "3.6.0",
3
+ "version": "3.6.1",
4
4
  "description": "๐ŸฆŠ Logixlysia is a logger for Elysia",
5
5
  "type": "module",
6
6
  "module": "src/index.ts",
@@ -15,10 +15,10 @@
15
15
  },
16
16
  "license": "MIT",
17
17
  "scripts": {
18
- "lint": "eslint ..",
19
- "lint:fix": "eslint .. --fix",
18
+ "lint": "eslint .",
19
+ "lint:fix": "eslint . --fix",
20
20
  "test": "bun test --timeout 5000 --coverage",
21
- "test:ci": "bun test --timeout 5000 --coverage",
21
+ "test:ci": "bun test --timeout 5000 --coverage --coverage-reporter=lcov",
22
22
  "publish": "npm publish",
23
23
  "dev": "bun run --watch example/basic.ts",
24
24
  "prepare": "husky",
@@ -75,19 +75,20 @@
75
75
  },
76
76
  "devDependencies": {
77
77
  "@elysiajs/eden": "^1.1.1",
78
- "@eslint/js": "^9.7.0",
78
+ "@eslint/js": "^9.8.0",
79
79
  "@trunkio/launcher": "^1.3.1",
80
- "@typescript-eslint/eslint-plugin": "^7.17.0",
81
- "@typescript-eslint/parser": "^7.17.0",
80
+ "@typescript-eslint/eslint-plugin": "^8.0.1",
81
+ "@typescript-eslint/parser": "^8.0.1",
82
82
  "bun-types": "^1.1.20",
83
83
  "commitizen": "^4.3.0",
84
84
  "cz-conventional-changelog": "^3.3.0",
85
- "eslint": "^9.7.0",
85
+ "eslint": "9.x",
86
86
  "eslint-plugin-simple-import-sort": "^12.1.1",
87
+ "globals": "^15.9.0",
87
88
  "husky": "^9.1.1",
88
89
  "lint-staged": "^15.2.7",
89
90
  "prettier": "^3.3.3",
90
- "typescript-eslint": "^7.17.0"
91
+ "typescript-eslint": "^8.0.1"
91
92
  },
92
93
  "peerDependencies": {
93
94
  "typescript": "^5.2.2"
package/src/index.ts CHANGED
@@ -1,9 +1,9 @@
1
1
  import { Server } from 'bun'
2
2
  import Elysia from 'elysia'
3
3
 
4
- import { createLogger, handleHttpError } from './logger'
5
- import { HttpError, Options } from './types'
6
- import startServer from './utils/start'
4
+ import { createLogger, handleHttpError } from '~/logger'
5
+ import { HttpError, Options } from '~/types'
6
+ import startServer from '~/utils/start'
7
7
 
8
8
  /**
9
9
  * Creates a logger plugin for ElysiaJS.
@@ -41,3 +41,6 @@ export default function logixlysia(options?: Options): Elysia {
41
41
  )
42
42
  })
43
43
  }
44
+
45
+ export { createLogger } from '~/logger'
46
+ export { handleHttpError } from '~/logger'
@@ -1,12 +1,22 @@
1
1
  import chalk from 'chalk'
2
2
 
3
- import { LogData, LogLevel, Options, RequestInfo, StoreData } from '~/types'
3
+ import {
4
+ LogComponents,
5
+ LogData,
6
+ LogLevel,
7
+ Options,
8
+ RequestInfo,
9
+ StoreData
10
+ } from '~/types'
4
11
  import durationString from '~/utils/duration'
5
12
  import logString from '~/utils/log'
6
13
  import methodString from '~/utils/method'
7
14
  import pathString from '~/utils/path'
8
15
  import statusString from '~/utils/status'
9
16
 
17
+ const defaultLogFormat =
18
+ '๐ŸฆŠ {now} {level} {duration} {method} {pathname} {status} {message} {ip}'
19
+
10
20
  /**
11
21
  * Builds a log message.
12
22
  *
@@ -27,33 +37,29 @@ export function buildLogMessage(
27
37
  useColors: boolean = true
28
38
  ): string {
29
39
  const now = new Date()
30
- const nowStr = useColors
31
- ? chalk.bgYellow(chalk.black(now.toLocaleString()))
32
- : now.toLocaleString()
33
- const epochStr = Math.floor(now.getTime() / 1000).toString()
34
- const levelStr = logString(level, useColors)
35
- const durationStr = durationString(store.beforeTime, useColors)
36
- const methodStr = methodString(request.method, useColors)
37
- const pathnameStr = pathString(request)
38
- const statusStr = statusString(data.status || 200, useColors)
39
- const messageStr = data.message || ''
40
- const ipStr =
41
- options?.config?.ip && request.headers.get('x-forwarded-for')
42
- ? `IP: ${request.headers.get('x-forwarded-for')}`
43
- : ''
40
+ const components: LogComponents = {
41
+ now: useColors
42
+ ? chalk.bgYellow(chalk.black(now.toLocaleString()))
43
+ : now.toLocaleString(),
44
+ epoch: Math.floor(now.getTime() / 1000).toString(),
45
+ level: logString(level, useColors),
46
+ duration: durationString(store.beforeTime, useColors),
47
+ method: methodString(request.method, useColors),
48
+ pathname: pathString(request),
49
+ status: statusString(data.status || 200, useColors),
50
+ message: data.message || '',
51
+ ip:
52
+ options?.config?.ip && request.headers.get('x-forwarded-for')
53
+ ? `IP: ${request.headers.get('x-forwarded-for')}`
54
+ : ''
55
+ }
44
56
 
45
- const logFormat =
46
- options?.config?.customLogFormat ||
47
- '๐ŸฆŠ {now} {level} {duration} {method} {pathname} {status} {message} {ip}'
57
+ const logFormat = options?.config?.customLogFormat || defaultLogFormat
48
58
 
49
- return logFormat
50
- .replace('{now}', nowStr)
51
- .replace('{epoch}', epochStr)
52
- .replace('{level}', levelStr)
53
- .replace('{duration}', durationStr)
54
- .replace('{method}', methodStr)
55
- .replace('{pathname}', pathnameStr || '')
56
- .replace('{status}', statusStr)
57
- .replace('{message}', messageStr)
58
- .replace('{ip}', ipStr || '')
59
+ return logFormat.replace(/{(\w+)}/g, (_, key: string) => {
60
+ if (key in components) {
61
+ return components[key as keyof LogComponents] || ''
62
+ }
63
+ return ''
64
+ })
59
65
  }
@@ -1,3 +1,6 @@
1
+ import { buildLogMessage } from '~/logger/buildLogMessage'
2
+ import { filterLog } from '~/logger/filter'
3
+ import { logToFile } from '~/logger/logToFile'
1
4
  import { logToTransports } from '~/transports'
2
5
  import {
3
6
  LogData,
@@ -8,10 +11,6 @@ import {
8
11
  StoreData
9
12
  } from '~/types'
10
13
 
11
- import { buildLogMessage } from './buildLogMessage'
12
- import { filterLog } from './filter'
13
- import { logToFile } from './logToFile'
14
-
15
14
  /**
16
15
  * Logs a message to the console and optionally to a file.
17
16
  *
@@ -33,18 +32,26 @@ async function log(
33
32
  const logMessage = buildLogMessage(level, request, data, store, options, true)
34
33
  console.log(logMessage)
35
34
 
35
+ const promises = []
36
+
36
37
  if (options?.config?.logFilePath) {
37
- await logToFile(
38
- options.config.logFilePath,
39
- level,
40
- request,
41
- data,
42
- store,
43
- options
38
+ promises.push(
39
+ logToFile(
40
+ options.config.logFilePath,
41
+ level,
42
+ request,
43
+ data,
44
+ store,
45
+ options
46
+ )
44
47
  )
45
48
  }
46
49
 
47
- await logToTransports(level, request, data, store, options)
50
+ if (options?.config?.transports?.length) {
51
+ promises.push(logToTransports(level, request, data, store, options))
52
+ }
53
+
54
+ await Promise.all(promises)
48
55
  }
49
56
 
50
57
  /**
@@ -55,7 +62,7 @@ async function log(
55
62
  */
56
63
  export function createLogger(options?: Options): Logger {
57
64
  return {
58
- log: async (level, request, data, store) =>
65
+ log: (level, request, data, store) =>
59
66
  log(level, request, data, store, options),
60
67
  customLogFormat: options?.config?.customLogFormat
61
68
  }
@@ -1,5 +1,10 @@
1
1
  import { LogLevel, Options } from '~/types'
2
2
 
3
+ const checkFilter = (filterValue: any, value: any) =>
4
+ Array.isArray(filterValue)
5
+ ? filterValue.includes(value)
6
+ : filterValue === value
7
+
3
8
  /**
4
9
  * Filters log messages.
5
10
  *
@@ -18,11 +23,6 @@ export function filterLog(
18
23
  const filter = options?.config?.logFilter
19
24
  if (!filter) return true
20
25
 
21
- const checkFilter = (filterValue: any, value: any) =>
22
- Array.isArray(filterValue)
23
- ? filterValue.includes(value)
24
- : filterValue === value
25
-
26
26
  return (
27
27
  (!filter.level || checkFilter(filter.level, logLevel)) &&
28
28
  (!filter.status || checkFilter(filter.status, status)) &&
@@ -1,7 +1,6 @@
1
+ import { buildLogMessage } from '~/logger/buildLogMessage'
1
2
  import { HttpError, Options, RequestInfo, StoreData } from '~/types'
2
3
 
3
- import { buildLogMessage } from './buildLogMessage'
4
-
5
4
  /**
6
5
  * Handles an HTTP error and logs it.
7
6
  *
@@ -10,19 +9,14 @@ import { buildLogMessage } from './buildLogMessage'
10
9
  * @param {StoreData} store The store data.
11
10
  * @param {Options} options The logger options.
12
11
  */
13
- export async function handleHttpError(
12
+ export function handleHttpError(
14
13
  request: RequestInfo,
15
14
  error: HttpError,
16
15
  store: StoreData,
17
16
  options?: Options
18
- ): Promise<void> {
17
+ ): void {
19
18
  const statusCode = error.status || 500
20
- const logMessage = buildLogMessage(
21
- 'ERROR',
22
- request,
23
- { status: statusCode },
24
- store,
25
- options
19
+ console.error(
20
+ buildLogMessage('ERROR', request, { status: statusCode }, store, options)
26
21
  )
27
- console.error(logMessage)
28
22
  }
@@ -1,2 +1,2 @@
1
- export { createLogger } from './createLogger'
2
- export { handleHttpError } from './handleHttpError'
1
+ export { createLogger } from '~/logger/createLogger'
2
+ export { handleHttpError } from '~/logger/handleHttpError'
@@ -1,9 +1,10 @@
1
1
  import { promises as fs } from 'fs'
2
2
  import { dirname } from 'path'
3
3
 
4
+ import { buildLogMessage } from '~/logger/buildLogMessage'
4
5
  import { LogData, LogLevel, Options, RequestInfo, StoreData } from '~/types'
5
6
 
6
- import { buildLogMessage } from './buildLogMessage'
7
+ const dirCache = new Set<string>()
7
8
 
8
9
  /**
9
10
  * Ensures that the directory exists. If not, it creates the directory.
@@ -12,7 +13,10 @@ import { buildLogMessage } from './buildLogMessage'
12
13
  */
13
14
  async function ensureDirectoryExists(filePath: string): Promise<void> {
14
15
  const dir = dirname(filePath)
15
- await fs.mkdir(dir, { recursive: true })
16
+ if (!dirCache.has(dir)) {
17
+ await fs.mkdir(dir, { recursive: true })
18
+ dirCache.add(dir)
19
+ }
16
20
  }
17
21
 
18
22
  /**
@@ -36,5 +40,5 @@ export async function logToFile(
36
40
  await ensureDirectoryExists(filePath)
37
41
  const logMessage =
38
42
  buildLogMessage(level, request, data, store, options, false) + '\n'
39
- await fs.appendFile(filePath, logMessage)
43
+ await fs.appendFile(filePath, logMessage, { flag: 'a' })
40
44
  }
package/src/types.ts CHANGED
@@ -31,6 +31,18 @@ interface Logger {
31
31
  customLogFormat?: string
32
32
  }
33
33
 
34
+ interface LogComponents {
35
+ now: string
36
+ epoch: string
37
+ level: string
38
+ duration: string
39
+ method: string
40
+ pathname: string | undefined
41
+ status: string
42
+ message: string
43
+ ip: string
44
+ }
45
+
34
46
  interface StoreData {
35
47
  beforeTime: bigint
36
48
  }
@@ -78,6 +90,7 @@ interface Options {
78
90
  export {
79
91
  ColorMap,
80
92
  HttpError,
93
+ LogComponents,
81
94
  LogData,
82
95
  Logger,
83
96
  LogLevel,
@@ -1,5 +1,12 @@
1
1
  import chalk from 'chalk'
2
2
 
3
+ const timeUnits = [
4
+ { unit: 's', threshold: 1e9, decimalPlaces: 2 },
5
+ { unit: 'ms', threshold: 1e6, decimalPlaces: 0 },
6
+ { unit: 'ยตs', threshold: 1e3, decimalPlaces: 0 },
7
+ { unit: 'ns', threshold: 1, decimalPlaces: 0 }
8
+ ]
9
+
3
10
  /**
4
11
  * Converts a time difference into a formatted string with the most appropriate time unit.
5
12
  *
@@ -8,38 +15,19 @@ import chalk from 'chalk'
8
15
  * @returns {string} A formatted duration string including the time unit.
9
16
  */
10
17
  function durationString(beforeTime: bigint, useColors: boolean): string {
11
- const currentTime = process.hrtime.bigint()
12
- const nanoseconds = Number(currentTime - beforeTime)
13
-
14
- const timeUnits = [
15
- { unit: 's', threshold: 1e9, decimalPlaces: 2 },
16
- { unit: 'ms', threshold: 1e6, decimalPlaces: 0 },
17
- { unit: 'ยตs', threshold: 1e3, decimalPlaces: 0 }
18
- ]
18
+ const nanoseconds = Number(process.hrtime.bigint() - beforeTime)
19
19
 
20
20
  for (const { unit, threshold, decimalPlaces } of timeUnits) {
21
21
  if (nanoseconds >= threshold) {
22
22
  const value = (nanoseconds / threshold).toFixed(decimalPlaces)
23
- return formatTime(value, unit, useColors)
23
+ const timeStr = `${value}${unit}`.padStart(8).padEnd(16)
24
+ return useColors ? chalk.gray(timeStr) : timeStr
24
25
  }
25
26
  }
26
27
 
27
- return formatTime(nanoseconds.toString(), 'ns', useColors)
28
- }
29
-
30
- /**
31
- * Formats the time value with the given unit and applies chalk styling.
32
- *
33
- * @param {string} value - The time value.
34
- * @param {string} unit - The time unit.
35
- * @param {boolean} useColors - Whether to apply colors to the output.
36
- * @returns {string} Styled time string.
37
- */
38
- function formatTime(value: string, unit: string, useColors: boolean): string {
39
- const timeStr = `${value}${unit}`
40
28
  return useColors
41
- ? chalk.gray(timeStr).padStart(8).padEnd(16)
42
- : timeStr.padStart(8).padEnd(16)
29
+ ? chalk.gray('0ns'.padStart(8).padEnd(16))
30
+ : '0ns'.padStart(8).padEnd(16)
43
31
  }
44
32
 
45
33
  export default durationString
package/src/utils/log.ts CHANGED
@@ -1,6 +1,5 @@
1
1
  import { LogLevel } from '~/types'
2
-
3
- import { LogLevelColorMap } from './colorMapping'
2
+ import { LogLevelColorMap } from '~/utils/colorMapping'
4
3
 
5
4
  /**
6
5
  * Converts the log level to a string.
@@ -9,7 +8,7 @@ import { LogLevelColorMap } from './colorMapping'
9
8
  * @param {boolean} useColors - Whether to apply colors to the output.
10
9
  * @returns {string} The log level as a string.
11
10
  */
12
- function logString(level: LogLevel, useColors: boolean): string {
11
+ const logString = (level: LogLevel, useColors: boolean): string => {
13
12
  const levelStr = level.toUpperCase()
14
13
  return useColors
15
14
  ? LogLevelColorMap[levelStr]?.(levelStr.padEnd(7)) || levelStr
@@ -1,4 +1,4 @@
1
- import { HttpMethodColorMap } from './colorMapping'
1
+ import { HttpMethodColorMap } from '~/utils/colorMapping'
2
2
 
3
3
  /**
4
4
  * Converts an HTTP request method to a colored string representation.
package/src/utils/path.ts CHANGED
@@ -6,7 +6,7 @@ import { RequestInfo } from '~/types'
6
6
  * @param {RequestInfo} requestInfo The request info.
7
7
  * @returns {string | undefined} The path string.
8
8
  */
9
- function pathString(requestInfo: RequestInfo): string | undefined {
9
+ const pathString = (requestInfo: RequestInfo): string | undefined => {
10
10
  try {
11
11
  return new URL(requestInfo.url).pathname
12
12
  } catch {
@@ -7,7 +7,7 @@ import { Options, Server } from '~/types'
7
7
  * @param {number} width The box width.
8
8
  * @returns {string} The box text.
9
9
  */
10
- function createBoxText(text: string, width: number): string {
10
+ const createBoxText = (text: string, width: number): string => {
11
11
  const paddingLength = Math.max(0, (width - text.length) / 2)
12
12
  const padding = ' '.repeat(paddingLength)
13
13
  return `${padding}${text}${padding}`.padEnd(width)
@@ -29,14 +29,15 @@ function startServer(config: Server, options?: Options): void {
29
29
  const message = `๐ŸฆŠ Elysia is running at ${protocol}://${hostname}:${port}`
30
30
  const boxWidth = Math.max(title.length, message.length) + 4
31
31
  const border = 'โ”€'.repeat(boxWidth)
32
+ const emptyLine = createBoxText('', boxWidth)
32
33
 
33
34
  console.log(`
34
35
  โ”Œ${border}โ”
35
- โ”‚${createBoxText('', boxWidth)}โ”‚
36
+ โ”‚${emptyLine}โ”‚
36
37
  โ”‚${createBoxText(title, boxWidth)}โ”‚
37
- โ”‚${createBoxText('', boxWidth)}โ”‚
38
+ โ”‚${emptyLine}โ”‚
38
39
  โ”‚${createBoxText(message, boxWidth)}โ”‚
39
- โ”‚${createBoxText('', boxWidth)}โ”‚
40
+ โ”‚${emptyLine}โ”‚
40
41
  โ””${border}โ”˜
41
42
  `)
42
43
  } else {
@@ -7,7 +7,7 @@ import chalk from 'chalk'
7
7
  * @param {boolean} useColors - Whether to apply colors to the output.
8
8
  * @returns {string} The status code as a string.
9
9
  */
10
- function statusString(status: number, useColors: boolean): string {
10
+ const statusString = (status: number, useColors: boolean): string => {
11
11
  const color =
12
12
  status >= 500
13
13
  ? 'red'