-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Martin Hrášek
committed
Dec 25, 2024
1 parent
102dba9
commit ac62e06
Showing
8 changed files
with
1,759 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,154 @@ | ||
# logz | ||
Node.js logging package following SysLog protocol - RFC 5424 written in Typescipt. | ||
# mrlm-net/logz | ||
|
||
Logging package following SysLog protocol - [RFC 5424](https://datatracker.ietf.org/doc/html/rfc5424) written in Typescipt. | ||
|
||
| Package | `mrlm-net/logz` | | ||
| :-- | :-- | | ||
| NPM name | `@mrlm/logz` | | ||
| NPM version | ![NPM Version](https://img.shields.io/npm/v/@mrlm/logz) | | ||
| Latest version | ![GitHub Release](https://img.shields.io/github/v/release/mrlm-net/logz) | | ||
| License | ![GitHub License](https://img.shields.io/github/license/mrlm-net/logz) | | ||
|
||
## Table of contents | ||
|
||
- [Installation](#installation) | ||
- [Usage](#usage) | ||
- [Advanced Usage](#advanced-usage) | ||
- [Interfaces](#interfaces) | ||
- [Contributing](#contributing) | ||
|
||
## Installation | ||
|
||
> I'm using `YARN` so examples will be using it, you can install this package via any Node Package Manager. | ||
```shell | ||
$ yarn add @mrlm/logz | ||
``` | ||
|
||
## Usage | ||
|
||
```typescript | ||
import { Logger, LogLevel, LogOutput } from '@mrlm/logz'; | ||
|
||
const logger = new Logger({ | ||
level: LogLevel.DEBUG, | ||
format: LogOutput.STRING, | ||
prefix: 'my-app' | ||
}); | ||
|
||
logger.info('This is an info message'); | ||
logger.error('This is an error message', { errorCode: 123 }); | ||
``` | ||
|
||
Output: | ||
``` | ||
[my-app] [2023-10-05T14:48:00.000Z] [INFO] This is an info message | ||
[my-app] [2023-10-05T14:48:00.000Z] [ERROR] ["123"] This is an error message | ||
``` | ||
|
||
## Advanced Usage | ||
|
||
### Using Outputs | ||
|
||
You can customize the logger to output logs to different destinations. For example, you can use the `Console` object to stream logs to a file. | ||
|
||
```typescript | ||
import { Logger, LogLevel, LogOutput } from '@mrlm/logz'; | ||
import { Console } from 'console'; | ||
import { createWriteStream } from 'fs'; | ||
|
||
const output = createWriteStream('./stdout.log'); | ||
const errorOutput = createWriteStream('./stderr.log'); | ||
const loggerConsole = new Console({ stdout: output, stderr: errorOutput }); | ||
|
||
const logger = new Logger({ | ||
level: LogLevel.DEBUG, | ||
format: LogOutput.STRING, | ||
outputs: [ | ||
(level, message) => { | ||
if (level <= LogLevel.ERROR) { | ||
loggerConsole.error(message); | ||
} else { | ||
loggerConsole.log(message); | ||
} | ||
} | ||
], | ||
prefix: 'my-app' | ||
}); | ||
|
||
logger.info('This is an info message'); | ||
logger.error('This is an error message', { errorCode: 123 }); | ||
``` | ||
|
||
Output in `stdout.log`: | ||
``` | ||
[my-app] [2023-10-05T14:48:00.000Z] [INFO] This is an info message | ||
``` | ||
|
||
Output in `stderr.log`: | ||
``` | ||
[my-app] [2023-10-05T14:48:00.000Z] [ERROR] ["123"] This is an error message | ||
``` | ||
|
||
## Interfaces | ||
|
||
### LogLevel | ||
|
||
```typescript | ||
export enum LogLevel { | ||
EMERGENCY = 0, | ||
ALERT = 1, | ||
CRITICAL = 2, | ||
ERROR = 3, | ||
WARNING = 4, | ||
NOTICE = 5, | ||
INFO = 6, | ||
DEBUG = 7 | ||
} | ||
``` | ||
|
||
### LogOutput | ||
|
||
```typescript | ||
export enum LogOutput { | ||
STRING = "string", | ||
JSON = "json" | ||
} | ||
``` | ||
|
||
### LogOptions | ||
|
||
```typescript | ||
export interface LogOptions { | ||
level?: LogLevel; | ||
format?: LogOutput; | ||
formatCallback?: (level: LogLevel, message: string, additionalInfo?: object) => string; | ||
outputs?: Array<(level: LogLevel, message: string) => void>; | ||
prefix?: string; | ||
} | ||
``` | ||
|
||
### ILogger | ||
|
||
```typescript | ||
export interface ILogger { | ||
log(level: LogLevel, message: string, additionalInfo?: object): void; | ||
emergency(message: string, additionalInfo?: object): void; | ||
alert(message: string, additionalInfo?: object): void; | ||
critical(message: string, additionalInfo?: object): void; | ||
error(message: string, additionalInfo?: object): void; | ||
warning(message: string, additionalInfo?: object): void; | ||
notice(message: string, additionalInfo?: object): void; | ||
info(message: string, additionalInfo?: object): void; | ||
debug(message: string, additionalInfo?: object): void; | ||
} | ||
``` | ||
|
||
## Contributing | ||
|
||
_Contributions are welcomed and must follow [Code of Conduct](https://github.com/mrlm-net/logz?tab=coc-ov-file) and common [Contributions guidelines](https://github.com/mrlm-net/.github/blob/main/docs/CONTRIBUTING.md)._ | ||
|
||
> If you'd like to report security issue please follow [security guidelines](https://github.com/mrlm-net/logz?tab=security-ov-file). | ||
--- | ||
<sup><sub>_All rights reserved © Martin Hrášek [<@marley-ma>](https://github.com/marley-ma) and WANTED.solutions s.r.o. [<@wanted-solutions>](https://github.com/wanted-solutions)_</sub></sup> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
{ | ||
"name": "@mrlm/logz", | ||
"version": "0.1.0", | ||
"description": "Typescript RFC5424 logging package.", | ||
"type": "module", | ||
"main": "./dist/index.js", | ||
"types": "./dist/index.d.ts", | ||
"exports": { | ||
".": "./dist/index.js", | ||
"./types": "./dist/index.d.ts" | ||
}, | ||
"files": [ | ||
"dist", | ||
"docs" | ||
], | ||
"author": { | ||
"name": "Martin Hrášek - MRLM.NET", | ||
"email": "martin@hrasek.email" | ||
}, | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/mrlm-net/logz.git" | ||
}, | ||
"scripts": { | ||
"build": "npx vite build", | ||
"package:publish": "npm publish --access public", | ||
"test": "vitest" | ||
}, | ||
"devDependencies": { | ||
"@types/node": "^22.10.2", | ||
"vite": "^6.0.5", | ||
"vite-plugin-dts": "^4.4.0", | ||
"vitest": "^2.1.8" | ||
}, | ||
"packageManager": "yarn@1.22.22+sha1.ac34549e6aa8e7ead463a7407e1c7390f61a6610" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
import { describe, it, expect, vi } from 'vitest'; | ||
import { Logger } from './index'; | ||
import { LogLevel, LogOutput } from './types'; | ||
|
||
describe('Logger', () => { | ||
it('should log messages at or above the set log level', () => { | ||
const output = vi.fn(); | ||
const logger = new Logger({ level: LogLevel.INFO, outputs: [output] }); | ||
|
||
logger.info('Info message'); | ||
logger.debug('Debug message'); | ||
|
||
expect(output).toHaveBeenCalledWith(LogLevel.INFO, expect.stringContaining('Info message')); | ||
expect(output).not.toHaveBeenCalledWith(LogLevel.DEBUG, expect.stringContaining('Debug message')); | ||
}); | ||
|
||
it('should format messages correctly', () => { | ||
const output = vi.fn(); | ||
const logger = new Logger({ level: LogLevel.INFO, outputs: [output] }); | ||
|
||
logger.info('Test message'); | ||
|
||
expect(output).toHaveBeenCalledWith(LogLevel.INFO, expect.stringContaining('Test message')); | ||
}); | ||
|
||
it('should use custom format callback if provided', () => { | ||
const output = vi.fn(); | ||
const formatCallback = vi.fn((level, message) => `Custom format: ${message}`); | ||
const logger = new Logger({ level: LogLevel.INFO, outputs: [output], formatCallback }); | ||
|
||
logger.info('Test message'); | ||
|
||
expect(formatCallback).toHaveBeenCalledWith(LogLevel.INFO, 'Test message', undefined); | ||
expect(output).toHaveBeenCalledWith(LogLevel.INFO, 'Custom format: Test message'); | ||
}); | ||
|
||
it('should log messages in JSON format if specified', () => { | ||
const output = vi.fn(); | ||
const logger = new Logger({ level: LogLevel.INFO, outputs: [output], format: LogOutput.JSON }); | ||
|
||
logger.info('Test message'); | ||
|
||
expect(output).toHaveBeenCalledWith(LogLevel.INFO, expect.stringContaining('"message":"Test message"')); | ||
}); | ||
|
||
it('should include additional info in the log message if provided', () => { | ||
const output = vi.fn(); | ||
const logger = new Logger({ level: LogLevel.INFO, outputs: [output] }); | ||
|
||
logger.info('Test message {userId}', { userId: 123 }); | ||
|
||
expect(output).toHaveBeenCalledWith(LogLevel.INFO, expect.stringContaining('123')); | ||
}); | ||
|
||
it('should not log messages below the set log level', () => { | ||
const output = vi.fn(); | ||
const logger = new Logger({ level: LogLevel.WARNING, outputs: [output] }); | ||
|
||
logger.info('Info message'); | ||
logger.debug('Debug message'); | ||
|
||
expect(output).not.toHaveBeenCalledWith(LogLevel.INFO, expect.stringContaining('Info message')); | ||
expect(output).not.toHaveBeenCalledWith(LogLevel.DEBUG, expect.stringContaining('Debug message')); | ||
}); | ||
|
||
it('should not use custom format callback if not provided', () => { | ||
const output = vi.fn(); | ||
const logger = new Logger({ level: LogLevel.INFO, outputs: [output] }); | ||
|
||
logger.info('Test message'); | ||
|
||
expect(output).toHaveBeenCalledWith(LogLevel.INFO, expect.stringContaining('Test message')); | ||
}); | ||
|
||
it('should not log messages in JSON format if not specified', () => { | ||
const output = vi.fn(); | ||
const logger = new Logger({ level: LogLevel.INFO, outputs: [output] }); | ||
|
||
logger.info('Test message'); | ||
|
||
expect(output).toHaveBeenCalledWith(LogLevel.INFO, expect.not.stringContaining('"message":"Test message"')); | ||
}); | ||
|
||
it('should not include additional info in the log message if not provided', () => { | ||
const output = vi.fn(); | ||
const logger = new Logger({ level: LogLevel.INFO, outputs: [output] }); | ||
|
||
logger.info('Test message', { userId: 123 }); | ||
|
||
expect(output).toHaveBeenCalledWith(LogLevel.INFO, expect.not.stringContaining('"userId":123')); | ||
}); | ||
|
||
it('should include prefix in the log message if provided', () => { | ||
const output = vi.fn(); | ||
const logger = new Logger({ level: LogLevel.INFO, outputs: [output], prefix: 'PREFIX' }); | ||
|
||
logger.info('Test message'); | ||
|
||
expect(output).toHaveBeenCalledWith(LogLevel.INFO, expect.stringContaining('[PREFIX]')); | ||
}); | ||
|
||
it('should not include prefix in the log message if not provided', () => { | ||
const output = vi.fn(); | ||
const logger = new Logger({ level: LogLevel.INFO, outputs: [output] }); | ||
|
||
logger.info('Test message'); | ||
|
||
expect(output).toHaveBeenCalledWith(LogLevel.INFO, expect.not.stringContaining('[PREFIX]')); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
import { | ||
ILogger, | ||
LogLevel, | ||
LogOptions, | ||
LogOutput | ||
} from "./types"; | ||
|
||
export class Logger implements ILogger { | ||
private level: LogLevel; | ||
private format: LogOutput; | ||
private formatCallback?: (level: LogLevel, message: string, additionalInfo?: object) => string; | ||
private outputs: Array<(level: LogLevel, message: string) => void>; | ||
private prefix?: string; | ||
|
||
constructor(options: LogOptions) { | ||
this.level = options.level || LogLevel.INFO; | ||
this.format = options.format || LogOutput.STRING; | ||
this.formatCallback = options.formatCallback; | ||
this.outputs = options.outputs || [ | ||
(level, message) => console.log(level, message) | ||
]; | ||
this.prefix = options.prefix; | ||
} | ||
|
||
private formatMessage(level: LogLevel, message: string, additionalInfo?: object): string { | ||
if (this.formatCallback) { | ||
return this.formatCallback(level, message, additionalInfo); | ||
} | ||
|
||
const timestamp = new Date().toISOString(); | ||
const logLevel = LogLevel[level]; | ||
|
||
let baseMessage = (this.prefix) | ||
? `[${this.prefix}] [${timestamp}] [${logLevel}]` | ||
: `[${timestamp}] [${logLevel}]`; | ||
|
||
if (this.format === LogOutput.JSON) { | ||
return JSON.stringify({ timestamp, level: logLevel, message, ...additionalInfo }); | ||
} | ||
|
||
if (additionalInfo) { | ||
baseMessage += ` ${JSON.stringify(Object.values(additionalInfo))}`; | ||
} | ||
|
||
return `${baseMessage} ${message}`; | ||
} | ||
|
||
private shouldLog(level: LogLevel): boolean { | ||
return level <= this.level; | ||
} | ||
|
||
private outputMessage(level: LogLevel, message: string) { | ||
this.outputs.forEach(output => output(level, message)); | ||
} | ||
|
||
log(level: LogLevel, message: string, additionalInfo?: object) { | ||
if (this.shouldLog(level)) { | ||
this.outputMessage(level, this.formatMessage(level, message, additionalInfo)); | ||
} | ||
} | ||
|
||
emergency(message: string, additionalInfo?: object) { | ||
this.log(LogLevel.EMERGENCY, message, additionalInfo); | ||
} | ||
|
||
alert(message: string, additionalInfo?: object) { | ||
this.log(LogLevel.ALERT, message, additionalInfo); | ||
} | ||
|
||
critical(message: string, additionalInfo?: object) { | ||
this.log(LogLevel.CRITICAL, message, additionalInfo); | ||
} | ||
|
||
error(message: string, additionalInfo?: object) { | ||
this.log(LogLevel.ERROR, message, additionalInfo); | ||
} | ||
|
||
warning(message: string, additionalInfo?: object) { | ||
this.log(LogLevel.WARNING, message, additionalInfo); | ||
} | ||
|
||
notice(message: string, additionalInfo?: object) { | ||
this.log(LogLevel.NOTICE, message, additionalInfo); | ||
} | ||
|
||
info(message: string, additionalInfo?: object) { | ||
this.log(LogLevel.INFO, message, additionalInfo); | ||
} | ||
|
||
debug(message: string, additionalInfo?: object) { | ||
this.log(LogLevel.DEBUG, message, additionalInfo); | ||
} | ||
} |
Oops, something went wrong.