Skip to content

Commit

Permalink
0.1.0
Browse files Browse the repository at this point in the history
  • Loading branch information
Martin Hrášek committed Dec 25, 2024
1 parent 102dba9 commit ac62e06
Show file tree
Hide file tree
Showing 8 changed files with 1,759 additions and 2 deletions.
156 changes: 154 additions & 2 deletions README.md
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 &copy; 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>
36 changes: 36 additions & 0 deletions package.json
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"
}
110 changes: 110 additions & 0 deletions src/index.spec.ts
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]'));
});
});
93 changes: 93 additions & 0 deletions src/index.ts
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);
}
}
Loading

0 comments on commit ac62e06

Please sign in to comment.