Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b475f1f67a | |||
| 54caa7ae8c | |||
| d2f38be0af | |||
| 9a61a3a9af | |||
| dbcfeba16d | |||
| d70954f3ef |
Vendored
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"json.schemas": [
|
"json.schemas": [
|
||||||
{
|
{
|
||||||
"fileMatch": ["/npmextra.json"],
|
"fileMatch": ["/.smartconfig.json"],
|
||||||
"schema": {
|
"schema": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|||||||
@@ -1,5 +1,29 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 2026-04-07 - 3.2.2 - fix(build)
|
||||||
|
migrate project config to .smartconfig.json and replace smartfile usage with native fs
|
||||||
|
|
||||||
|
- rename npmextra.json to .smartconfig.json for updated tooling compatibility
|
||||||
|
- remove the @push.rocks/smartfile dependency by using native fs for file destination setup
|
||||||
|
- update tsconfig for newer tsbuild and TypeScript versions by explicitly including node types
|
||||||
|
- fix edge cases in buffer timestamp filtering and initialize internal fields for stricter TypeScript compatibility
|
||||||
|
|
||||||
|
## 2026-02-20 - 3.2.1 - fix(destination-buffer)
|
||||||
|
return entries in chronological order (oldest-first) and adjust pagination semantics
|
||||||
|
|
||||||
|
- Change getEntries to return the most recent entries in chronological (oldest-first) order instead of newest-first
|
||||||
|
- Adjust pagination to compute slice indices from the newest end (start = max(0, len - limit - offset), end = len - offset)
|
||||||
|
- Update tests to expect chronological ordering and clarified pagination examples
|
||||||
|
- Modified files: ts_destination_buffer/classes.destinationbuffer.ts, test/test.destination-buffer.node.ts
|
||||||
|
|
||||||
|
## 2026-02-19 - 3.2.0 - feat(destination-buffer)
|
||||||
|
add SmartlogDestinationBuffer in-memory circular buffer destination with query/filter/pagination and tests
|
||||||
|
|
||||||
|
- Introduce SmartlogDestinationBuffer: an in-memory circular buffer implementing ILogDestination with configurable maxEntries (default 2000).
|
||||||
|
- Expose APIs: handleLog, getEntries (supports level filtering, search, since timestamp, limit/offset pagination, newest-first ordering), getEntryCount, and clear.
|
||||||
|
- Add package export './destination-buffer' and module entry points (index and plugins) to expose the new destination.
|
||||||
|
- Add tests covering storage, filtering by level and search, pagination, eviction when maxEntries exceeded, clearing, and default maxEntries behavior.
|
||||||
|
|
||||||
## 2026-02-14 - 3.1.11 - fix(destination-receiver)
|
## 2026-02-14 - 3.1.11 - fix(destination-receiver)
|
||||||
return full webrequest response from SmartlogDestinationReceiver and migrate to WebrequestClient; update tests, dependencies, docs, and npmextra metadata
|
return full webrequest response from SmartlogDestinationReceiver and migrate to WebrequestClient; update tests, dependencies, docs, and npmextra metadata
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
Copyright (c) 2018 Lossless GmbH (hello@lossless.com)
|
Copyright (c) 2018 Task Venture Capital GmbH (hello@task.vc)
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|||||||
+13
-13
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@push.rocks/smartlog",
|
"name": "@push.rocks/smartlog",
|
||||||
"version": "3.1.11",
|
"version": "3.2.2",
|
||||||
"private": false,
|
"private": false,
|
||||||
"description": "A minimalistic, distributed, and extensible logging tool supporting centralized log management.",
|
"description": "A minimalistic, distributed, and extensible logging tool supporting centralized log management.",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
@@ -30,10 +30,11 @@
|
|||||||
"./destination-devtools": "./dist_ts_destination_devtools/index.js",
|
"./destination-devtools": "./dist_ts_destination_devtools/index.js",
|
||||||
"./destination-file": "./dist_ts_destination_file/index.js",
|
"./destination-file": "./dist_ts_destination_file/index.js",
|
||||||
"./destination-local": "./dist_ts_destination_local/index.js",
|
"./destination-local": "./dist_ts_destination_local/index.js",
|
||||||
|
"./destination-buffer": "./dist_ts_destination_buffer/index.js",
|
||||||
"./destination-receiver": "./dist_ts_destination_receiver/index.js",
|
"./destination-receiver": "./dist_ts_destination_receiver/index.js",
|
||||||
"./receiver": "./dist_ts_receiver/index.js"
|
"./receiver": "./dist_ts_receiver/index.js"
|
||||||
},
|
},
|
||||||
"author": "Lossless GmbH",
|
"author": "Task Venture Capital GmbH",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "(tstest test/**/*.ts --verbose)",
|
"test": "(tstest test/**/*.ts --verbose)",
|
||||||
@@ -42,23 +43,22 @@
|
|||||||
"buildDocs": "tsdoc"
|
"buildDocs": "tsdoc"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@git.zone/tsbuild": "^4.1.2",
|
"@git.zone/tsbuild": "^4.4.0",
|
||||||
"@git.zone/tsbundle": "^2.8.3",
|
"@git.zone/tsbundle": "^2.10.0",
|
||||||
"@git.zone/tsrun": "^2.0.1",
|
"@git.zone/tsrun": "^2.0.2",
|
||||||
"@git.zone/tstest": "^3.1.8",
|
"@git.zone/tstest": "^3.6.3",
|
||||||
"@types/node": "^22.15.20"
|
"@types/node": "^25.5.2"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@api.global/typedrequest-interfaces": "^3.0.19",
|
"@api.global/typedrequest-interfaces": "^3.0.19",
|
||||||
"@push.rocks/consolecolor": "^2.0.3",
|
"@push.rocks/consolecolor": "^2.0.3",
|
||||||
"@push.rocks/isounique": "^1.0.5",
|
"@push.rocks/isounique": "^1.0.5",
|
||||||
"@push.rocks/smartclickhouse": "^2.0.17",
|
"@push.rocks/smartclickhouse": "^2.2.0",
|
||||||
"@push.rocks/smartfile": "^11.2.7",
|
|
||||||
"@push.rocks/smarthash": "^3.2.6",
|
"@push.rocks/smarthash": "^3.2.6",
|
||||||
"@push.rocks/smartpromise": "^4.2.3",
|
"@push.rocks/smartpromise": "^4.2.3",
|
||||||
"@push.rocks/smarttime": "^4.1.1",
|
"@push.rocks/smarttime": "^4.2.3",
|
||||||
"@push.rocks/webrequest": "^4.0.1",
|
"@push.rocks/webrequest": "^4.0.5",
|
||||||
"@tsclass/tsclass": "^9.3.0"
|
"@tsclass/tsclass": "^9.5.0"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"ts/**/*",
|
"ts/**/*",
|
||||||
@@ -69,7 +69,7 @@
|
|||||||
"dist_ts_web/**/*",
|
"dist_ts_web/**/*",
|
||||||
"assets/**/*",
|
"assets/**/*",
|
||||||
"cli.js",
|
"cli.js",
|
||||||
"npmextra.json",
|
".smartconfig.json",
|
||||||
"readme.md"
|
"readme.md"
|
||||||
],
|
],
|
||||||
"browserslist": [
|
"browserslist": [
|
||||||
|
|||||||
Generated
+1958
-3276
File diff suppressed because it is too large
Load Diff
+9
-2
@@ -33,11 +33,18 @@ The library uses feature detection to adapt to different environments:
|
|||||||
## Available Destinations
|
## Available Destinations
|
||||||
|
|
||||||
- Console (built-in)
|
- Console (built-in)
|
||||||
- File (ts_destination_file)
|
- File (ts_destination_file) — uses native Node `fs` (no longer depends on @push.rocks/smartfile)
|
||||||
- Local (ts_destination_local)
|
- Local (ts_destination_local)
|
||||||
- Clickhouse (ts_destination_clickhouse)
|
- Clickhouse (ts_destination_clickhouse)
|
||||||
- Developer Tools (ts_destination_devtools)
|
- Developer Tools (ts_destination_devtools)
|
||||||
- Receiver (ts_destination_receiver)
|
- Receiver (ts_destination_receiver)
|
||||||
|
- Buffer (ts_destination_buffer) — in-memory circular buffer with query/filter/pagination
|
||||||
|
|
||||||
|
## Build & TS Config Notes
|
||||||
|
|
||||||
|
- `tsconfig.json` requires `"types": ["node"]` for Node globals (Buffer, process, NodeJS namespace) to be picked up by tsbuild 4.4+ / TypeScript 6.x. Without it, transitive deps like @tsclass/tsclass and @push.rocks/smartrx fail to compile because they reference Node types in their public `.d.ts`.
|
||||||
|
- Do NOT add `baseUrl` or `paths: {}` — `baseUrl` is deprecated in TS 6.x and triggers TS5101.
|
||||||
|
- Configuration is now in `.smartconfig.json` (renamed from `npmextra.json` in tsbuild 4.4).
|
||||||
|
|
||||||
## Advanced Features
|
## Advanced Features
|
||||||
|
|
||||||
|
|||||||
@@ -254,6 +254,48 @@ logger.addLogDestination(receiver);
|
|||||||
|
|
||||||
Logs are sent as authenticated JSON payloads with SHA-256 hashed passphrases.
|
Logs are sent as authenticated JSON payloads with SHA-256 hashed passphrases.
|
||||||
|
|
||||||
|
#### 🧠 In-Memory Buffer
|
||||||
|
|
||||||
|
Keep a rolling, queryable window of recent log entries in memory — perfect for diagnostics dashboards, post-mortems, or exposing recent logs through a debug endpoint without writing them to disk:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { SmartlogDestinationBuffer } from '@push.rocks/smartlog/destination-buffer';
|
||||||
|
|
||||||
|
// Default buffer holds 2000 entries (circular: oldest entries are evicted)
|
||||||
|
const buffer = new SmartlogDestinationBuffer({ maxEntries: 5000 });
|
||||||
|
logger.addLogDestination(buffer);
|
||||||
|
|
||||||
|
// Query recent entries (chronological order, oldest-first)
|
||||||
|
const recent = buffer.getEntries({ limit: 100 });
|
||||||
|
|
||||||
|
// Filter by level (single or multiple)
|
||||||
|
const errorsOnly = buffer.getEntries({ level: 'error' });
|
||||||
|
const warningsAndErrors = buffer.getEntries({ level: ['warn', 'error'] });
|
||||||
|
|
||||||
|
// Filter by message content (case-insensitive search)
|
||||||
|
const dbErrors = buffer.getEntries({ search: 'database' });
|
||||||
|
|
||||||
|
// Filter by timestamp (entries since a given moment)
|
||||||
|
const sinceLastMinute = buffer.getEntries({ since: Date.now() - 60_000 });
|
||||||
|
|
||||||
|
// Pagination — limit + offset (offset counts back from the newest entry)
|
||||||
|
const page = buffer.getEntries({ limit: 50, offset: 100 });
|
||||||
|
|
||||||
|
// Inspect or clear the buffer
|
||||||
|
console.log(buffer.getEntryCount()); // current number of stored entries
|
||||||
|
buffer.clear();
|
||||||
|
```
|
||||||
|
|
||||||
|
Use it together with an HTTP endpoint to surface live, in-process logs for debugging:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
app.get('/_debug/logs', (req, res) => {
|
||||||
|
const level = req.query.level as any;
|
||||||
|
const search = req.query.search as string;
|
||||||
|
res.json(buffer.getEntries({ level, search, limit: 200 }));
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
### 🛠️ Custom Destinations
|
### 🛠️ Custom Destinations
|
||||||
|
|
||||||
Build your own destination for any logging backend by implementing the `ILogDestination` interface:
|
Build your own destination for any logging backend by implementing the `ILogDestination` interface:
|
||||||
@@ -684,7 +726,7 @@ interface ILogPackage<T = unknown> {
|
|||||||
|
|
||||||
## License and Legal Information
|
## License and Legal Information
|
||||||
|
|
||||||
This repository contains open-source code licensed under the MIT License. A copy of the license can be found in the [LICENSE](./LICENSE) file.
|
This repository contains open-source code licensed under the MIT License. A copy of the license can be found in the [license](./license) file.
|
||||||
|
|
||||||
**Please note:** The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.
|
**Please note:** The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,120 @@
|
|||||||
|
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||||
|
import { SmartlogDestinationBuffer } from '../ts_destination_buffer/index.js';
|
||||||
|
import type { ILogPackage, TLogLevel } from '../ts_interfaces/index.js';
|
||||||
|
|
||||||
|
const createMockLogPackage = (level: TLogLevel, message: string): ILogPackage => {
|
||||||
|
return {
|
||||||
|
timestamp: Date.now(),
|
||||||
|
type: 'log',
|
||||||
|
level,
|
||||||
|
message,
|
||||||
|
context: {
|
||||||
|
environment: 'test',
|
||||||
|
runtime: 'node',
|
||||||
|
zone: 'test-zone',
|
||||||
|
},
|
||||||
|
correlation: {
|
||||||
|
id: '123',
|
||||||
|
type: 'none',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
let buffer: SmartlogDestinationBuffer;
|
||||||
|
|
||||||
|
tap.test('should create a buffer destination instance', async () => {
|
||||||
|
buffer = new SmartlogDestinationBuffer({ maxEntries: 100 });
|
||||||
|
expect(buffer).toBeTruthy();
|
||||||
|
expect(buffer.getEntryCount()).toEqual(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('should store log entries via handleLog', async () => {
|
||||||
|
await buffer.handleLog(createMockLogPackage('info', 'Hello world'));
|
||||||
|
await buffer.handleLog(createMockLogPackage('error', 'Something failed'));
|
||||||
|
await buffer.handleLog(createMockLogPackage('warn', 'Watch out'));
|
||||||
|
|
||||||
|
expect(buffer.getEntryCount()).toEqual(3);
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('should retrieve entries in chronological order (oldest-first)', async () => {
|
||||||
|
const entries = buffer.getEntries();
|
||||||
|
expect(entries.length).toEqual(3);
|
||||||
|
expect(entries[0].message).toEqual('Hello world');
|
||||||
|
expect(entries[2].message).toEqual('Watch out');
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('should filter entries by level', async () => {
|
||||||
|
const errorEntries = buffer.getEntries({ level: 'error' });
|
||||||
|
expect(errorEntries.length).toEqual(1);
|
||||||
|
expect(errorEntries[0].message).toEqual('Something failed');
|
||||||
|
|
||||||
|
const multiLevel = buffer.getEntries({ level: ['info', 'warn'] });
|
||||||
|
expect(multiLevel.length).toEqual(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('should filter entries by search string', async () => {
|
||||||
|
const results = buffer.getEntries({ search: 'hello' });
|
||||||
|
expect(results.length).toEqual(1);
|
||||||
|
expect(results[0].message).toEqual('Hello world');
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('should support limit and offset pagination', async () => {
|
||||||
|
// limit=2, offset=0 → last 2 entries in chronological order
|
||||||
|
const page1 = buffer.getEntries({ limit: 2, offset: 0 });
|
||||||
|
expect(page1.length).toEqual(2);
|
||||||
|
expect(page1[0].message).toEqual('Something failed');
|
||||||
|
expect(page1[1].message).toEqual('Watch out');
|
||||||
|
|
||||||
|
// limit=2, offset=2 → skip 2 from end, return up to 2
|
||||||
|
const page2 = buffer.getEntries({ limit: 2, offset: 2 });
|
||||||
|
expect(page2.length).toEqual(1);
|
||||||
|
expect(page2[0].message).toEqual('Hello world');
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('should filter by since timestamp', async () => {
|
||||||
|
const now = Date.now();
|
||||||
|
const freshBuffer = new SmartlogDestinationBuffer();
|
||||||
|
|
||||||
|
const oldPkg = createMockLogPackage('info', 'Old message');
|
||||||
|
oldPkg.timestamp = now - 60000;
|
||||||
|
await freshBuffer.handleLog(oldPkg);
|
||||||
|
|
||||||
|
const newPkg = createMockLogPackage('info', 'New message');
|
||||||
|
newPkg.timestamp = now;
|
||||||
|
await freshBuffer.handleLog(newPkg);
|
||||||
|
|
||||||
|
const results = freshBuffer.getEntries({ since: now - 1000 });
|
||||||
|
expect(results.length).toEqual(1);
|
||||||
|
expect(results[0].message).toEqual('New message');
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('should enforce circular buffer max entries', async () => {
|
||||||
|
const smallBuffer = new SmartlogDestinationBuffer({ maxEntries: 5 });
|
||||||
|
|
||||||
|
for (let i = 0; i < 10; i++) {
|
||||||
|
await smallBuffer.handleLog(createMockLogPackage('info', `Message ${i}`));
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(smallBuffer.getEntryCount()).toEqual(5);
|
||||||
|
|
||||||
|
// Should have kept the latest 5 (messages 5-9), returned chronologically
|
||||||
|
const entries = smallBuffer.getEntries({ limit: 10 });
|
||||||
|
expect(entries[0].message).toEqual('Message 5');
|
||||||
|
expect(entries[4].message).toEqual('Message 9');
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('should clear all entries', async () => {
|
||||||
|
expect(buffer.getEntryCount()).toBeGreaterThan(0);
|
||||||
|
buffer.clear();
|
||||||
|
expect(buffer.getEntryCount()).toEqual(0);
|
||||||
|
expect(buffer.getEntries().length).toEqual(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('should use default maxEntries of 2000', async () => {
|
||||||
|
const defaultBuffer = new SmartlogDestinationBuffer();
|
||||||
|
// Just verify it was created without error
|
||||||
|
expect(defaultBuffer).toBeTruthy();
|
||||||
|
expect(defaultBuffer.getEntryCount()).toEqual(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
export default tap.start();
|
||||||
@@ -3,6 +3,6 @@
|
|||||||
*/
|
*/
|
||||||
export const commitinfo = {
|
export const commitinfo = {
|
||||||
name: '@push.rocks/smartlog',
|
name: '@push.rocks/smartlog',
|
||||||
version: '3.1.11',
|
version: '3.2.2',
|
||||||
description: 'A minimalistic, distributed, and extensible logging tool supporting centralized log management.'
|
description: 'A minimalistic, distributed, and extensible logging tool supporting centralized log management.'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ export class Smartlog implements plugins.smartlogInterfaces.ILogDestination {
|
|||||||
|
|
||||||
public uniInstanceId: string = plugins.isounique.uni();
|
public uniInstanceId: string = plugins.isounique.uni();
|
||||||
|
|
||||||
private consoleEnabled: boolean;
|
private consoleEnabled: boolean = false;
|
||||||
|
|
||||||
private logRouter = new LogRouter();
|
private logRouter = new LogRouter();
|
||||||
|
|
||||||
@@ -52,11 +52,11 @@ export class Smartlog implements plugins.smartlogInterfaces.ILogDestination {
|
|||||||
const originalStdoutWrite = process.stdout.write.bind(process.stdout);
|
const originalStdoutWrite = process.stdout.write.bind(process.stdout);
|
||||||
const originalStderrWrite = process.stderr.write.bind(process.stderr);
|
const originalStderrWrite = process.stderr.write.bind(process.stderr);
|
||||||
|
|
||||||
process.stdout.write = (...args: any) => {
|
process.stdout.write = ((...args: any[]) => {
|
||||||
const logString: string = args[0];
|
const logString: string = args[0];
|
||||||
if (!logString || typeof logString !== 'string') {
|
if (!logString || typeof logString !== 'string') {
|
||||||
// continue as planned
|
// continue as planned
|
||||||
return originalStdoutWrite(...args);
|
return (originalStdoutWrite as any)(...args);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!logString.startsWith('LOG')) {
|
if (!logString.startsWith('LOG')) {
|
||||||
@@ -68,17 +68,17 @@ export class Smartlog implements plugins.smartlogInterfaces.ILogDestination {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return originalStdoutWrite(...args);
|
return (originalStdoutWrite as any)(...args);
|
||||||
};
|
}) as typeof process.stdout.write;
|
||||||
|
|
||||||
process.stderr.write = (...args: any) => {
|
process.stderr.write = ((...args: any[]) => {
|
||||||
const logString: string = args[0];
|
const logString: string = args[0];
|
||||||
if (!logString || typeof logString !== 'string' || !logString.startsWith('LOG')) {
|
if (!logString || typeof logString !== 'string' || !logString.startsWith('LOG')) {
|
||||||
this.log('error', logString);
|
this.log('error', logString);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return originalStderrWrite(...args);
|
return (originalStderrWrite as any)(...args);
|
||||||
};
|
}) as typeof process.stderr.write;
|
||||||
}
|
}
|
||||||
this.consoleEnabled = true;
|
this.consoleEnabled = true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,68 @@
|
|||||||
|
import type { ILogDestination, ILogPackage, TLogLevel } from '../dist_ts_interfaces/index.js';
|
||||||
|
|
||||||
|
export interface IDestinationBufferOptions {
|
||||||
|
maxEntries?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IBufferQueryOptions {
|
||||||
|
level?: TLogLevel | TLogLevel[];
|
||||||
|
search?: string;
|
||||||
|
limit?: number;
|
||||||
|
offset?: number;
|
||||||
|
since?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class SmartlogDestinationBuffer implements ILogDestination {
|
||||||
|
private logPackages: ILogPackage[] = [];
|
||||||
|
private maxEntries: number;
|
||||||
|
|
||||||
|
constructor(options?: IDestinationBufferOptions) {
|
||||||
|
this.maxEntries = options?.maxEntries ?? 2000;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async handleLog(logPackage: ILogPackage): Promise<void> {
|
||||||
|
this.logPackages.push(logPackage);
|
||||||
|
if (this.logPackages.length > this.maxEntries) {
|
||||||
|
this.logPackages.shift();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public getEntries(options?: IBufferQueryOptions): ILogPackage[] {
|
||||||
|
const limit = options?.limit ?? 100;
|
||||||
|
const offset = options?.offset ?? 0;
|
||||||
|
|
||||||
|
let results = this.logPackages;
|
||||||
|
|
||||||
|
// Filter by level
|
||||||
|
if (options?.level) {
|
||||||
|
const levels = Array.isArray(options.level) ? options.level : [options.level];
|
||||||
|
results = results.filter((pkg) => levels.includes(pkg.level));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter by search (message content)
|
||||||
|
if (options?.search) {
|
||||||
|
const searchLower = options.search.toLowerCase();
|
||||||
|
results = results.filter((pkg) => pkg.message.toLowerCase().includes(searchLower));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter by timestamp
|
||||||
|
if (options?.since !== undefined) {
|
||||||
|
const since = options.since;
|
||||||
|
results = results.filter((pkg) => pkg.timestamp >= since);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return most recent `limit` entries in chronological order (oldest-first)
|
||||||
|
// offset skips from the newest end
|
||||||
|
const start = Math.max(0, results.length - limit - offset);
|
||||||
|
const end = results.length - offset;
|
||||||
|
return results.slice(Math.max(0, start), Math.max(0, end));
|
||||||
|
}
|
||||||
|
|
||||||
|
public getEntryCount(): number {
|
||||||
|
return this.logPackages.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
public clear(): void {
|
||||||
|
this.logPackages = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { SmartlogDestinationBuffer, type IDestinationBufferOptions, type IBufferQueryOptions } from './classes.destinationbuffer.js';
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
import * as smartlogInterfaces from '../dist_ts_interfaces/index.js';
|
||||||
|
|
||||||
|
export { smartlogInterfaces };
|
||||||
@@ -12,7 +12,7 @@ export class SmartlogDestinationClickhouse implements plugins.smartlogInterfaces
|
|||||||
|
|
||||||
// INSTANCE
|
// INSTANCE
|
||||||
private smartclickhouseDb: plugins.smartclickhouse.SmartClickHouseDb;
|
private smartclickhouseDb: plugins.smartclickhouse.SmartClickHouseDb;
|
||||||
private logTable: plugins.smartclickhouse.TimeDataTable;
|
private logTable!: plugins.smartclickhouse.TimeDataTable;
|
||||||
constructor(options: plugins.smartclickhouse.IClickhouseConstructorOptions) {
|
constructor(options: plugins.smartclickhouse.IClickhouseConstructorOptions) {
|
||||||
this.smartclickhouseDb = new plugins.smartclickhouse.SmartClickHouseDb(options);
|
this.smartclickhouseDb = new plugins.smartclickhouse.SmartClickHouseDb(options);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import * as plugins from './smartfile-destination-file.plugins.js';
|
import * as plugins from './plugins.js';
|
||||||
|
|
||||||
export class SmartlogDestinationFile implements plugins.smartlogInterfaces.ILogDestination {
|
export class SmartlogDestinationFile implements plugins.smartlogInterfaces.ILogDestination {
|
||||||
public fileWriteStream: plugins.fs.WriteStream;
|
public fileWriteStream: plugins.fs.WriteStream;
|
||||||
@@ -12,7 +12,10 @@ export class SmartlogDestinationFile implements plugins.smartlogInterfaces.ILogD
|
|||||||
if (!plugins.path.isAbsolute(filePathArg)) {
|
if (!plugins.path.isAbsolute(filePathArg)) {
|
||||||
throw new Error(`filePath needs to be absolute but is not: "${filePathArg}"`);
|
throw new Error(`filePath needs to be absolute but is not: "${filePathArg}"`);
|
||||||
}
|
}
|
||||||
plugins.smartfile.fs.ensureFileSync(filePathArg, `# Smartlogfile. Created at ${extendedDate.toISOString()}\n`);
|
if (!plugins.fs.existsSync(filePathArg)) {
|
||||||
|
plugins.fs.mkdirSync(plugins.path.dirname(filePathArg), { recursive: true });
|
||||||
|
plugins.fs.writeFileSync(filePathArg, `# Smartlogfile. Created at ${extendedDate.toISOString()}\n`);
|
||||||
|
}
|
||||||
this.fileWriteStream = plugins.fs.createWriteStream(
|
this.fileWriteStream = plugins.fs.createWriteStream(
|
||||||
filePathArg,
|
filePathArg,
|
||||||
{
|
{
|
||||||
|
|||||||
-2
@@ -8,12 +8,10 @@ export {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// pushrocks scope
|
// pushrocks scope
|
||||||
import * as smartfile from '@push.rocks/smartfile';
|
|
||||||
import * as smartlogInterfaces from '../dist_ts_interfaces/index.js';
|
import * as smartlogInterfaces from '../dist_ts_interfaces/index.js';
|
||||||
import * as smarttime from '@push.rocks/smarttime';
|
import * as smarttime from '@push.rocks/smarttime';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
smartfile,
|
|
||||||
smartlogInterfaces,
|
smartlogInterfaces,
|
||||||
smarttime
|
smarttime
|
||||||
};
|
};
|
||||||
+1
-2
@@ -7,8 +7,7 @@
|
|||||||
"moduleResolution": "NodeNext",
|
"moduleResolution": "NodeNext",
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"verbatimModuleSyntax": true,
|
"verbatimModuleSyntax": true,
|
||||||
"baseUrl": ".",
|
"types": ["node"]
|
||||||
"paths": {}
|
|
||||||
},
|
},
|
||||||
"exclude": [
|
"exclude": [
|
||||||
"dist_*/**/*.d.ts"
|
"dist_*/**/*.d.ts"
|
||||||
|
|||||||
Reference in New Issue
Block a user