logixlysia 2.3.0 → 2.3.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
+ ## [2.3.1](https://github.com/PunGrumpy/logixlysia/compare/v2.3.0...v2.3.1) (2024-04-07)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * **index:** remove duplicate logging when options IP enabled ([2e99bd6](https://github.com/PunGrumpy/logixlysia/commit/2e99bd628b7160fb43161ca08e49f711b74e8666))
9
+
3
10
  ## [2.3.0](https://github.com/PunGrumpy/logixlysia/compare/v2.2.3...v2.3.0) (2024-04-06)
4
11
 
5
12
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "logixlysia",
3
- "version": "2.3.0",
3
+ "version": "2.3.1",
4
4
  "description": "🦊 Logixlysia is a logger for Elysia",
5
5
  "type": "module",
6
6
  "module": "src/index.ts",
@@ -21,7 +21,7 @@
21
21
  "test:ci": "bun test --timeout 5000 --coverage",
22
22
  "publish": "npm publish",
23
23
  "dev": "bun run --watch example/basic.ts",
24
- "prepare": "husky install",
24
+ "prepare": "husky",
25
25
  "lint:staged": "lint-staged",
26
26
  "prettier": "prettier --write ."
27
27
  },
@@ -77,6 +77,8 @@
77
77
  "devDependencies": {
78
78
  "@elysiajs/eden": "^1.0.4",
79
79
  "bun-types": "^1.0.22",
80
+ "commitizen": "^4.3.0",
81
+ "cz-conventional-changelog": "^3.3.0",
80
82
  "husky": "^9.0.7",
81
83
  "lint-staged": "^15.2.0",
82
84
  "prettier": "^3.1.1"
package/src/index.ts CHANGED
@@ -2,7 +2,7 @@ import Elysia from 'elysia'
2
2
  import { createLogger, handleHttpError } from './logger'
3
3
  import startString from './utils/start'
4
4
  import { Server } from 'bun'
5
- import { Options } from './options'
5
+ import { Options } from './types'
6
6
 
7
7
  /**
8
8
  * Creates a logger.
@@ -20,7 +20,7 @@ import { Options } from './options'
20
20
  * @returns {Elysia} The logger.
21
21
  */
22
22
  export const logger = (options?: Options): Elysia => {
23
- const log = createLogger()
23
+ const log = createLogger(options)
24
24
 
25
25
  const elysia = new Elysia({
26
26
  name: 'Logixlysia'
@@ -32,26 +32,9 @@ export const logger = (options?: Options): Elysia => {
32
32
  ctx.store = { beforeTime: process.hrtime.bigint() }
33
33
  })
34
34
  .onAfterHandle({ as: 'global' }, ({ request, store }) => {
35
- const logStr: string[] = []
36
-
37
- if (options?.ip) {
38
- const forwardedFor = request.headers.get('x-forwarded-for')
39
- if (forwardedFor) {
40
- logStr.push(`IP: ${forwardedFor}`)
41
- }
42
- }
43
-
44
- log.log(
45
- 'INFO',
46
- request,
47
- {
48
- message: logStr.join(' ')
49
- },
50
- store as { beforeTime: bigint }
51
- )
35
+ log.log('INFO', request, { status: 200 }, store as { beforeTime: bigint })
52
36
  })
53
37
  .onError({ as: 'global' }, ({ request, error, store }) => {
54
- // log.log('ERROR', request, error, store as { beforeTime: bigint })
55
38
  handleHttpError(request, error, store as { beforeTime: bigint })
56
39
  })
57
40
 
package/src/logger.ts CHANGED
@@ -4,51 +4,26 @@ import methodString from './utils/method'
4
4
  import logString from './utils/log'
5
5
  import pathString from './utils/path'
6
6
  import statusString from './utils/status'
7
- import { RequestInfo } from './types/RequestInfo'
8
- import { LogData, LogLevel, Logger } from './types/Logger'
9
- import { StoreData } from './types/StoreData'
10
- import { HttpError } from './types/HttpError'
7
+ import { HttpError, RequestInfo } from './types'
8
+ import { LogLevel, LogData, Logger, StoreData, Options } from './types'
11
9
 
12
- /**
13
- * Asynchronously logs a message constructed from various log components.
14
- *
15
- * @async
16
- * @param {LogLevel} level - The log level.
17
- * @param {RequestInfo} request - The request information.
18
- * @param {LogData} data - The log data.
19
- * @param {StoreData} store - The store data.
20
- *
21
- * @returns {Promise<void>}
22
- */
23
10
  async function log(
24
11
  level: LogLevel,
25
12
  request: RequestInfo,
26
13
  data: LogData,
27
- store: StoreData
14
+ store: StoreData,
15
+ options?: Options
28
16
  ): Promise<void> {
29
- const logMessage = buildLogMessage(level, request, data, store)
30
- try {
31
- await writeToLogAsync(logMessage)
32
- } catch (error) {
33
- console.error('Error logging message:', error)
34
- }
17
+ const logMessage = buildLogMessage(level, request, data, store, options)
18
+ console.log(logMessage)
35
19
  }
36
20
 
37
- /**
38
- * Builds the log message string from given parameters.
39
- *
40
- * @param {LogLevel} level - The log level.
41
- * @param {RequestInfo} request - The request information.
42
- * @param {LogData} data - The log data.
43
- * @param {StoreData} store - The store data.
44
- *
45
- * @returns {string} - The constructed log message.
46
- */
47
21
  function buildLogMessage(
48
22
  level: LogLevel,
49
23
  request: RequestInfo,
50
24
  data: LogData,
51
- store: StoreData
25
+ store: StoreData,
26
+ options?: Options
52
27
  ): string {
53
28
  const nowStr = chalk.bgYellow(chalk.black(new Date().toLocaleString()))
54
29
  const levelStr = logString(level)
@@ -58,56 +33,36 @@ function buildLogMessage(
58
33
  const statusStr = statusString(data.status || 200)
59
34
  const messageStr = data.message || ''
60
35
 
61
- return `🦊 ${nowStr} ${levelStr} ${durationStr} ${methodStr} ${pathnameStr} ${statusStr} ${messageStr}`
62
- }
36
+ let logMessage = `🦊 ${nowStr} ${levelStr} ${durationStr} ${methodStr} ${pathnameStr} ${statusStr} ${messageStr}`
63
37
 
64
- /**
65
- * Writes a log message to the console asynchronously.
66
- *
67
- * @async
68
- * @param {string} message - The message to log.
69
- *
70
- * @returns {Promise<void>}
71
- * @throws {Error} - If the timeout is reached.
72
- */
73
- function writeToLogAsync(message: string): Promise<void> {
74
- return new Promise((resolve, reject) => {
75
- console.log(message)
76
- resolve()
38
+ if (options?.ip) {
39
+ const forwardedFor = request.headers.get('x-forwarded-for')
40
+ if (forwardedFor) {
41
+ logMessage += ` IP: ${forwardedFor}`
42
+ }
43
+ }
77
44
 
78
- setTimeout(() => {
79
- reject(new Error('Timed out while writing to log.'))
80
- })
81
- })
45
+ return logMessage
82
46
  }
83
47
 
84
- /**
85
- * Creates a logger instance with an asynchronous log method.
86
- *
87
- * @returns {Logger} - The logger instance.
88
- */
89
- export const createLogger = (): Logger => ({
90
- log: (level, request, data, store) => log(level, request, data, store)
48
+ export const createLogger = (options?: Options): Logger => ({
49
+ log: (level, request, data, store) =>
50
+ log(level, request, data, store, options)
91
51
  })
92
52
 
93
- /**
94
- * Handle HTTP errors and log them with the appropriate status code.
95
- *
96
- * @param {RequestInfo} request - The request information.
97
- * @param {Error} error - The error object.
98
- * @param {StoreData} store - The store data.
99
- */
100
53
  export const handleHttpError = (
101
54
  request: RequestInfo,
102
55
  error: Error,
103
- store: StoreData
56
+ store: StoreData,
57
+ options?: Options
104
58
  ): void => {
105
59
  const statusCode = error instanceof HttpError ? error.status : 500
106
60
  const logMessage = buildLogMessage(
107
61
  'ERROR',
108
62
  request,
109
63
  { status: statusCode },
110
- store
64
+ store,
65
+ options
111
66
  )
112
67
  console.error(logMessage)
113
68
  }
package/src/types.ts ADDED
@@ -0,0 +1,60 @@
1
+ interface RequestInfo {
2
+ headers: { get: (key: string) => any }
3
+ method: string
4
+ url: string
5
+ }
6
+
7
+ interface Server {
8
+ hostname?: string
9
+ port?: number
10
+ protocol?: string
11
+ }
12
+
13
+ interface ColorMap {
14
+ [key: string]: (str: string) => string
15
+ }
16
+
17
+ type LogLevel = 'INFO' | 'WARNING' | 'ERROR' | string
18
+
19
+ interface LogData {
20
+ status?: number
21
+ message?: string
22
+ }
23
+
24
+ interface Logger {
25
+ log(
26
+ level: LogLevel,
27
+ request: RequestInfo,
28
+ data: LogData,
29
+ store: StoreData
30
+ ): void
31
+ }
32
+
33
+ interface StoreData {
34
+ beforeTime: bigint
35
+ }
36
+
37
+ class HttpError extends Error {
38
+ status: number
39
+
40
+ constructor(status: number, message: string) {
41
+ super(message)
42
+ this.status = status
43
+ }
44
+ }
45
+
46
+ interface Options {
47
+ ip?: boolean
48
+ }
49
+
50
+ export {
51
+ RequestInfo,
52
+ Server,
53
+ ColorMap,
54
+ LogLevel,
55
+ LogData,
56
+ Logger,
57
+ StoreData,
58
+ HttpError,
59
+ Options
60
+ }
@@ -1,5 +1,5 @@
1
1
  import chalk from 'chalk'
2
- import { ColorMap } from '~/types/ColorMap'
2
+ import { ColorMap } from '~/types'
3
3
 
4
4
  /**
5
5
  * The color map for the log levels.
package/src/utils/path.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { RequestInfo } from '~/types/RequestInfo'
1
+ import { RequestInfo } from '~/types'
2
2
 
3
3
  /**
4
4
  * Returns the path string.
@@ -1,4 +1,4 @@
1
- import { Server } from '~/types/Server'
1
+ import { Server } from '~/types'
2
2
 
3
3
  /**
4
4
  * Creates a box text.
@@ -3,51 +3,106 @@ import { edenTreaty } from '@elysiajs/eden'
3
3
  import { describe, it, expect, beforeAll, beforeEach } from 'bun:test'
4
4
  import { logger } from '../src'
5
5
 
6
- describe('Logixlysia', () => {
6
+ describe('Logixlysia with IP logging enabled', () => {
7
7
  let server: Elysia
8
8
  let app: any
9
9
  let logs: string[] = []
10
10
 
11
- describe('IP logging disabled', () => {
12
- beforeAll(() => {
13
- server = new Elysia()
14
- .use(logger({ ip: false }))
15
- .get('/', () => '🦊 Logixlysia Getting')
16
- .post('logixlysia', () => '🦊 Logixlysia Posting')
17
- .listen(3000)
11
+ beforeAll(() => {
12
+ server = new Elysia()
13
+ .use(logger({ ip: true }))
14
+ .get('/', ctx => {
15
+ const ipAddress = ctx.request.headers.get('x-forwarded-for') || 'null'
16
+ return '🦊 Logixlysia Getting'
17
+ })
18
+ .post('logixlysia', () => '🦊 Logixlysia Posting')
19
+ .listen(3000)
20
+
21
+ app = edenTreaty<typeof server>('http://127.0.0.1:3000')
22
+ })
23
+
24
+ beforeEach(() => {
25
+ logs = []
26
+ })
27
+
28
+ it("Logs incoming IP address for GET '/' requests when X-Forwarded-For header is present", async () => {
29
+ const requestCount = 5
30
+
31
+ for (let i = 0; i < requestCount; i++) {
32
+ await app.get('/', {
33
+ headers: { 'X-Forwarded-For': '192.168.1.1' }
34
+ })
35
+ }
18
36
 
19
- app = edenTreaty<typeof server>('http://127.0.0.1:3000')
37
+ logs.forEach(log => {
38
+ expect(log).toMatch(/^IP: \d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/)
20
39
  })
40
+ })
41
+
42
+ it("Logs 'null' for GET '/' requests when X-Forwarded-For header is not present", async () => {
43
+ const requestCount = 5
44
+
45
+ for (let i = 0; i < requestCount; i++) {
46
+ const response = await app.get('/')
47
+ }
21
48
 
22
- beforeEach(() => {
23
- logs = []
49
+ logs.forEach(log => {
50
+ expect(log).toBe('IP: null')
24
51
  })
52
+ })
53
+ })
54
+
55
+ describe('Logixlysia with IP logging disabled', () => {
56
+ let server: Elysia
57
+ let app: any
58
+ let logs: string[] = []
25
59
 
26
- it("Responds correctly to GET '/' requests", async () => {
27
- const requestCount = 5
60
+ beforeAll(() => {
61
+ server = new Elysia()
62
+ .use(logger({ ip: false }))
63
+ .get('/', () => '🦊 Logixlysia Getting')
64
+ .post('logixlysia', () => '🦊 Logixlysia Posting')
65
+ .listen(3000)
28
66
 
29
- for (let i = 0; i < requestCount; i++) {
30
- logs.push((await app.get('/')).data)
31
- }
67
+ app = edenTreaty<typeof server>('http://127.0.0.1:3000')
68
+ })
32
69
 
33
- logs.forEach(log => {
34
- expect(log).toBe('🦊 Logixlysia Getting')
35
- })
70
+ beforeEach(() => {
71
+ logs = []
72
+ })
73
+
74
+ it("Responds correctly to GET '/' requests", async () => {
75
+ const requestCount = 5
76
+
77
+ for (let i = 0; i < requestCount; i++) {
78
+ logs.push((await app.get('/')).data)
79
+ }
80
+
81
+ logs.forEach(log => {
82
+ expect(log).toBe('🦊 Logixlysia Getting')
36
83
  })
84
+ })
37
85
 
38
- it("Responds correctly to POST '/logixlysia' requests", async () => {
39
- const requestCount = 5
86
+ it("Responds correctly to POST '/logixlysia' requests", async () => {
87
+ const requestCount = 5
40
88
 
41
- for (let i = 0; i < requestCount; i++) {
42
- const postResponse = await app.logixlysia.post({})
43
- logs.push(
44
- postResponse.status === 200 ? postResponse.data : postResponse.error
45
- )
46
- }
89
+ for (let i = 0; i < requestCount; i++) {
90
+ const postResponse = await app.logixlysia.post({})
91
+ logs.push(
92
+ postResponse.status === 200 ? postResponse.data : postResponse.error
93
+ )
94
+ }
47
95
 
48
- logs.forEach(log => {
49
- expect(log).toBe('🦊 Logixlysia Posting')
50
- })
96
+ logs.forEach(log => {
97
+ expect(log).toBe('🦊 Logixlysia Posting')
51
98
  })
52
99
  })
100
+
101
+ it('Throws an error when attempting to post to an undefined route', async () => {
102
+ const response = await app.undefinedRoute.post({})
103
+ const error = response.error
104
+
105
+ expect(response.status).toBe(404)
106
+ expect(error).toBeInstanceOf(Error)
107
+ })
53
108
  })
@@ -1,5 +1,5 @@
1
1
  import { describe, expect, it } from 'bun:test'
2
- import { ColorMap } from '~/types/ColorMap'
2
+ import { ColorMap } from '~/types'
3
3
 
4
4
  describe('Color Mapping Interface', () => {
5
5
  it('Defines an object with string keys mapping to functions', () => {
@@ -0,0 +1,15 @@
1
+ import { describe, expect, it } from 'bun:test'
2
+ import { HttpError } from '~/types'
3
+
4
+ describe('HttpError', () => {
5
+ it('Should create an instance with correct status and message', () => {
6
+ const status = 404
7
+ const message = 'Not Found'
8
+ const error = new HttpError(status, message)
9
+
10
+ expect(error).toBeInstanceOf(Error)
11
+ expect(error).toBeInstanceOf(HttpError)
12
+ expect(error.status).toBe(status)
13
+ expect(error.message).toBe(message)
14
+ })
15
+ })
@@ -1,7 +1,5 @@
1
1
  import { beforeEach, describe, expect, it, jest } from 'bun:test'
2
- import { LogData } from '~/types/Logger'
3
- import { RequestInfo } from '~/types/RequestInfo'
4
- import { StoreData } from '~/types/StoreData'
2
+ import { RequestInfo, LogData, StoreData } from '~/types'
5
3
 
6
4
  interface Logger {
7
5
  info(request: RequestInfo, data: LogData, store: StoreData): void
@@ -1,5 +1,5 @@
1
1
  import { describe, expect, it } from 'bun:test'
2
- import { RequestInfo } from '~/types/RequestInfo'
2
+ import { RequestInfo } from '~/types'
3
3
 
4
4
  describe('Request Infomation interface', () => {
5
5
  it('Defines the RequestInfo interface correctly', () => {
@@ -1,5 +1,5 @@
1
1
  import { describe, expect, it } from 'bun:test'
2
- import { Server } from '~/types/Server'
2
+ import { Server } from '~/types'
3
3
 
4
4
  describe('Server interface', () => {
5
5
  it('Defines the Server interface correctly', () => {
@@ -1,5 +1,5 @@
1
1
  import { describe, expect, it } from 'bun:test'
2
- import { StoreData } from '~/types/StoreData'
2
+ import { StoreData } from '~/types'
3
3
 
4
4
  describe('Store Data interface', () => {
5
5
  it('Defines the StoreData interface correctly', () => {
@@ -1,5 +1,5 @@
1
1
  import { describe, expect, it } from 'bun:test'
2
- import { RequestInfo } from '~/types/RequestInfo'
2
+ import { RequestInfo } from '~/types'
3
3
  import pathString from '~/utils/path'
4
4
 
5
5
  describe('Path String', () => {
@@ -1,5 +1,5 @@
1
1
  import { afterEach, beforeEach, describe, expect, it, jest } from 'bun:test'
2
- import { Server } from '~/types/Server'
2
+ import { Server } from '~/types'
3
3
  import startString from '~/utils/start'
4
4
 
5
5
  describe('Start String', () => {
package/.czrc DELETED
@@ -1,3 +0,0 @@
1
- {
2
- "path": "cz-conventional-changelog"
3
- }
package/src/options.ts DELETED
@@ -1,11 +0,0 @@
1
- /**
2
- * Options for the logger.
3
- */
4
- interface Options {
5
- /**
6
- * Whether to log the IP address of the client.
7
- */
8
- ip?: boolean
9
- }
10
-
11
- export { Options }
@@ -1,12 +0,0 @@
1
- /**
2
- * The color map interface.
3
- *
4
- * @interface ColorMap
5
- *
6
- * @property {string} key The color function.
7
- */
8
- interface ColorMap {
9
- [key: string]: (str: string) => string
10
- }
11
-
12
- export { ColorMap }
@@ -1,11 +0,0 @@
1
- /**
2
- * Custom Error class for HTTP errors.
3
- */
4
- export class HttpError extends Error {
5
- status: number
6
-
7
- constructor(status: number, message: string) {
8
- super(message)
9
- this.status = status
10
- }
11
- }
@@ -1,40 +0,0 @@
1
- import { RequestInfo } from './RequestInfo'
2
- import { StoreData } from './StoreData'
3
-
4
- /**
5
- * The log level, including standard and custom levels.
6
- *
7
- * @type {string}
8
- */
9
- type LogLevel = 'INFO' | 'WARNING' | 'ERROR' | string
10
-
11
- /**
12
- * The log data interface.
13
- *
14
- * @interface LogData
15
- *
16
- * @property {number} status - The status code.
17
- * @property {string} message - The message.
18
- */
19
- interface LogData {
20
- status?: number
21
- message?: string
22
- }
23
-
24
- /**
25
- * The logger interface.
26
- *
27
- * @interface Logger
28
- *
29
- * @property {Function} log - Logs a message with a given log level.
30
- */
31
- interface Logger {
32
- log(
33
- level: LogLevel,
34
- request: RequestInfo,
35
- data: LogData,
36
- store: StoreData
37
- ): void
38
- }
39
-
40
- export { LogLevel, LogData, Logger }
@@ -1,16 +0,0 @@
1
- /**
2
- * The request info.
3
- *
4
- * @interface RequestInfo
5
- *
6
- * @property {Object} headers The request headers.
7
- * @property {string} method The request method.
8
- * @property {string} url The request URL.
9
- */
10
- interface RequestInfo {
11
- headers: { get: (key: string) => any }
12
- method: string
13
- url: string
14
- }
15
-
16
- export { RequestInfo }
@@ -1,16 +0,0 @@
1
- /**
2
- * The server information.
3
- *
4
- * @interface Server
5
- *
6
- * @property {string} hostname The server hostname.
7
- * @property {number} port The server port.
8
- * @property {string} protocol The server protocol.
9
- */
10
- interface Server {
11
- hostname?: string
12
- port?: number
13
- protocol?: string
14
- }
15
-
16
- export { Server }
@@ -1,12 +0,0 @@
1
- /**
2
- * The store data interface.
3
- *
4
- * @interface StoreData
5
- *
6
- * @property {bigint} beforeTime The time before the request.
7
- */
8
- interface StoreData {
9
- beforeTime: bigint
10
- }
11
-
12
- export { StoreData }