logixlysia 2.2.3 → 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.
@@ -0,0 +1,16 @@
1
+ # Required, otherwise ggshield considers the file to use the deprecated v1 format
2
+ version: 2
3
+
4
+ # Set to true if the desired exit code for the CLI is always 0, otherwise the
5
+ # exit code will be 1 if incidents are found.
6
+ exit-zero: false
7
+
8
+ verbose: false
9
+
10
+ instance: https://dashboard.gitguardian.com
11
+
12
+ # Maximum commits to scan in a hook.
13
+ max-commits-for-hook: 50
14
+
15
+ # Accept self-signed certificates for the API.
16
+ allow-self-signed: false
package/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
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
+
10
+ ## [2.3.0](https://github.com/PunGrumpy/logixlysia/compare/v2.2.3...v2.3.0) (2024-04-06)
11
+
12
+
13
+ ### Features
14
+
15
+ * **logger:** handle HTTP errors and log with appropriate status code ([276b3c4](https://github.com/PunGrumpy/logixlysia/commit/276b3c41334bcf1cefd032ae11f71fb8cda2bc9a)), closes [#24](https://github.com/PunGrumpy/logixlysia/issues/24)
16
+
3
17
  ## [2.2.3](https://github.com/PunGrumpy/logixlysia/compare/v2.2.2...v2.2.3) (2024-04-06)
4
18
 
5
19
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "logixlysia",
3
- "version": "2.2.3",
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
@@ -1,8 +1,8 @@
1
1
  import Elysia from 'elysia'
2
- import { createLogger } from './logger'
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,10 @@ 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 })
38
+ handleHttpError(request, error, store as { beforeTime: bigint })
55
39
  })
56
40
 
57
41
  return elysia
package/src/logger.ts CHANGED
@@ -4,50 +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'
7
+ import { HttpError, RequestInfo } from './types'
8
+ import { LogLevel, LogData, Logger, StoreData, Options } from './types'
10
9
 
11
- /**
12
- * Asynchronously logs a message constructed from various log components.
13
- *
14
- * @async
15
- * @param {LogLevel} level - The log level.
16
- * @param {RequestInfo} request - The request information.
17
- * @param {LogData} data - The log data.
18
- * @param {StoreData} store - The store data.
19
- *
20
- * @returns {Promise<void>}
21
- */
22
10
  async function log(
23
11
  level: LogLevel,
24
12
  request: RequestInfo,
25
13
  data: LogData,
26
- store: StoreData
14
+ store: StoreData,
15
+ options?: Options
27
16
  ): Promise<void> {
28
- const logMessage = buildLogMessage(level, request, data, store)
29
- try {
30
- await writeToLogAsync(logMessage)
31
- } catch (error) {
32
- console.error('Error logging message:', error)
33
- }
17
+ const logMessage = buildLogMessage(level, request, data, store, options)
18
+ console.log(logMessage)
34
19
  }
35
20
 
36
- /**
37
- * Builds the log message string from given parameters.
38
- *
39
- * @param {LogLevel} level - The log level.
40
- * @param {RequestInfo} request - The request information.
41
- * @param {LogData} data - The log data.
42
- * @param {StoreData} store - The store data.
43
- *
44
- * @returns {string} - The constructed log message.
45
- */
46
21
  function buildLogMessage(
47
22
  level: LogLevel,
48
23
  request: RequestInfo,
49
24
  data: LogData,
50
- store: StoreData
25
+ store: StoreData,
26
+ options?: Options
51
27
  ): string {
52
28
  const nowStr = chalk.bgYellow(chalk.black(new Date().toLocaleString()))
53
29
  const levelStr = logString(level)
@@ -57,34 +33,36 @@ function buildLogMessage(
57
33
  const statusStr = statusString(data.status || 200)
58
34
  const messageStr = data.message || ''
59
35
 
60
- return `🦊 ${nowStr} ${levelStr} ${durationStr} ${methodStr} ${pathnameStr} ${statusStr} ${messageStr}`
61
- }
36
+ let logMessage = `🦊 ${nowStr} ${levelStr} ${durationStr} ${methodStr} ${pathnameStr} ${statusStr} ${messageStr}`
62
37
 
63
- /**
64
- * Writes a log message to the console asynchronously.
65
- *
66
- * @async
67
- * @param {string} message - The message to log.
68
- *
69
- * @returns {Promise<void>}
70
- * @throws {Error} - If the timeout is reached.
71
- */
72
- function writeToLogAsync(message: string): Promise<void> {
73
- return new Promise((resolve, reject) => {
74
- console.log(message)
75
- resolve()
38
+ if (options?.ip) {
39
+ const forwardedFor = request.headers.get('x-forwarded-for')
40
+ if (forwardedFor) {
41
+ logMessage += ` IP: ${forwardedFor}`
42
+ }
43
+ }
76
44
 
77
- setTimeout(() => {
78
- reject(new Error('Timed out while writing to log.'))
79
- })
80
- })
45
+ return logMessage
81
46
  }
82
47
 
83
- /**
84
- * Creates a logger instance with an asynchronous log method.
85
- *
86
- * @returns {Logger} - The logger instance.
87
- */
88
- export const createLogger = (): Logger => ({
89
- 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)
90
51
  })
52
+
53
+ export const handleHttpError = (
54
+ request: RequestInfo,
55
+ error: Error,
56
+ store: StoreData,
57
+ options?: Options
58
+ ): void => {
59
+ const statusCode = error instanceof HttpError ? error.status : 500
60
+ const logMessage = buildLogMessage(
61
+ 'ERROR',
62
+ request,
63
+ { status: statusCode },
64
+ store,
65
+ options
66
+ )
67
+ console.error(logMessage)
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,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 }