fix(destination-receiver): return full webrequest response from SmartlogDestinationReceiver and migrate to WebrequestClient; update tests, dependencies, docs, and npmextra metadata
This commit is contained in:
10
changelog.md
10
changelog.md
@@ -1,5 +1,15 @@
|
||||
# Changelog
|
||||
|
||||
## 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
|
||||
|
||||
- SmartlogDestinationReceiver now uses plugins.webrequest.WebrequestClient instead of plugins.webrequest.WebRequest and returns the full response object instead of response.body — callers expecting the previous shape will need to adapt (breaking change)
|
||||
- Tests updated to match the new webrequest.postJson return shape
|
||||
- Bumped several devDependencies and dependencies (@git.zone/* toolchain, @push.rocks/* libs) to newer major/minor versions
|
||||
- Removed tsbundle from the build script and adjusted build tooling invocation
|
||||
- Added pnpm.onlyBuiltDependencies and updated npmextra.json namespace keys and release/tsdoc metadata
|
||||
- Documentation (readme.md) updated: examples changed to async/await usage, expanded legal and issue-reporting sections
|
||||
|
||||
## 2025-09-22 - 3.1.10 - fix(tests)
|
||||
Bump dependency versions and adjust test to use enableConsole() default
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"gitzone": {
|
||||
"@git.zone/cli": {
|
||||
"projectType": "npm",
|
||||
"module": {
|
||||
"githost": "code.foss.global",
|
||||
@@ -25,13 +25,19 @@
|
||||
"error tracking",
|
||||
"development tools"
|
||||
]
|
||||
},
|
||||
"release": {
|
||||
"registries": [
|
||||
"https://verdaccio.lossless.digital",
|
||||
"https://registry.npmjs.org"
|
||||
],
|
||||
"accessLevel": "public"
|
||||
}
|
||||
},
|
||||
"npmci": {
|
||||
"npmGlobalTools": [],
|
||||
"npmAccessLevel": "public"
|
||||
},
|
||||
"tsdoc": {
|
||||
"@git.zone/tsdoc": {
|
||||
"legal": "\n## License and Legal Information\n\nThis repository contains open-source code that is licensed under the MIT License. A copy of the MIT License can be found in the [license](license) file within this repository. \n\n**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.\n\n### Trademarks\n\nThis project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH and are not included within the scope of the MIT license granted herein. Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines, and any usage must be approved in writing by Task Venture Capital GmbH.\n\n### Company Information\n\nTask Venture Capital GmbH \nRegistered at District court Bremen HRB 35230 HB, Germany\n\nFor any legal inquiries or if you require further information, please contact us via email at hello@task.vc.\n\nBy using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.\n"
|
||||
},
|
||||
"@ship.zone/szci": {
|
||||
"npmGlobalTools": []
|
||||
}
|
||||
}
|
||||
24
package.json
24
package.json
@@ -37,28 +37,28 @@
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"test": "(tstest test/**/*.ts --verbose)",
|
||||
"build": "(tsbuild tsfolders --allowimplicitany && tsbundle npm)",
|
||||
"build": "(tsbuild tsfolders)",
|
||||
"format": "(gitzone format)",
|
||||
"buildDocs": "tsdoc"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@git.zone/tsbuild": "^2.6.8",
|
||||
"@git.zone/tsbundle": "^2.5.1",
|
||||
"@git.zone/tsrun": "^1.3.3",
|
||||
"@git.zone/tstest": "^2.3.6",
|
||||
"@git.zone/tsbuild": "^4.1.2",
|
||||
"@git.zone/tsbundle": "^2.8.3",
|
||||
"@git.zone/tsrun": "^2.0.1",
|
||||
"@git.zone/tstest": "^3.1.8",
|
||||
"@types/node": "^22.15.20"
|
||||
},
|
||||
"dependencies": {
|
||||
"@api.global/typedrequest-interfaces": "^3.0.19",
|
||||
"@push.rocks/consolecolor": "^2.0.3",
|
||||
"@push.rocks/isounique": "^1.0.4",
|
||||
"@push.rocks/isounique": "^1.0.5",
|
||||
"@push.rocks/smartclickhouse": "^2.0.17",
|
||||
"@push.rocks/smartfile": "^11.2.7",
|
||||
"@push.rocks/smarthash": "^3.2.3",
|
||||
"@push.rocks/smarthash": "^3.2.6",
|
||||
"@push.rocks/smartpromise": "^4.2.3",
|
||||
"@push.rocks/smarttime": "^4.1.1",
|
||||
"@push.rocks/webrequest": "^3.0.37",
|
||||
"@tsclass/tsclass": "^9.2.0"
|
||||
"@push.rocks/webrequest": "^4.0.1",
|
||||
"@tsclass/tsclass": "^9.3.0"
|
||||
},
|
||||
"files": [
|
||||
"ts/**/*",
|
||||
@@ -86,6 +86,10 @@
|
||||
"url": "https://code.foss.global/push.rocks/smartlog/issues"
|
||||
},
|
||||
"pnpm": {
|
||||
"overrides": {}
|
||||
"overrides": {},
|
||||
"onlyBuiltDependencies": [
|
||||
"esbuild",
|
||||
"puppeteer"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
4019
pnpm-lock.yaml
generated
4019
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
628
readme.md
628
readme.md
@@ -6,10 +6,14 @@
|
||||
|
||||
> **smartlog** is a powerful, distributed, and extensible logging system designed for the cloud-native era. Whether you're debugging locally, monitoring production systems, or building complex microservices, smartlog adapts to your needs with style. 🎯
|
||||
|
||||
## Issue Reporting and Security
|
||||
|
||||
For reporting bugs, issues, or security vulnerabilities, please visit [community.foss.global/](https://community.foss.global/). This is the central community hub for all issue reporting. Developers who sign and comply with our contribution agreement and go through identification can also get a [code.foss.global/](https://code.foss.global/) account to submit Pull Requests directly.
|
||||
|
||||
## 🌟 Why smartlog?
|
||||
|
||||
- **🎨 Beautiful Console Output**: Color-coded, formatted logs that are actually readable
|
||||
- **🔌 Extensible Architecture**: Plug in any destination - databases, files, remote servers
|
||||
- **🔌 Extensible Architecture**: Plug in any destination — databases, files, remote servers
|
||||
- **🌍 Distributed by Design**: Built for microservices with correlation and context tracking
|
||||
- **⚡ Zero-Config Start**: Works out of the box, scales when you need it
|
||||
- **🎭 Interactive CLI Tools**: Spinners and progress bars that handle non-TTY environments gracefully
|
||||
@@ -24,9 +28,6 @@ pnpm add @push.rocks/smartlog
|
||||
|
||||
# Using npm
|
||||
npm install @push.rocks/smartlog
|
||||
|
||||
# Using yarn
|
||||
yarn add @push.rocks/smartlog
|
||||
```
|
||||
|
||||
## 🚀 Quick Start
|
||||
@@ -48,25 +49,32 @@ const logger = new Smartlog({
|
||||
}
|
||||
});
|
||||
|
||||
// Enable beautiful console output
|
||||
// Enable console output
|
||||
logger.enableConsole();
|
||||
|
||||
// Start logging!
|
||||
logger.log('info', '🎉 Application started successfully');
|
||||
logger.log('error', '💥 Database connection failed', {
|
||||
await logger.log('info', '🎉 Application started successfully');
|
||||
await logger.log('error', '💥 Database connection failed', {
|
||||
errorCode: 'DB_TIMEOUT',
|
||||
attemptCount: 3
|
||||
attemptCount: 3
|
||||
});
|
||||
```
|
||||
|
||||
### Using the Default Logger
|
||||
### Using `createForCommitinfo`
|
||||
|
||||
For quick prototyping and simple applications:
|
||||
If you're integrating with a build system that provides commit info, you can use the static factory method:
|
||||
|
||||
```typescript
|
||||
import { defaultLogger } from '@push.rocks/smartlog';
|
||||
import { Smartlog } from '@push.rocks/smartlog';
|
||||
|
||||
defaultLogger.log('info', 'This is so easy!');
|
||||
const logger = Smartlog.createForCommitinfo({
|
||||
name: 'my-app',
|
||||
version: '1.0.0',
|
||||
description: 'My application'
|
||||
});
|
||||
|
||||
logger.enableConsole();
|
||||
await logger.log('lifecycle', '🔄 App starting...');
|
||||
```
|
||||
|
||||
## 📚 Core Concepts
|
||||
@@ -77,25 +85,42 @@ smartlog supports semantic log levels for different scenarios:
|
||||
|
||||
```typescript
|
||||
// Lifecycle events
|
||||
logger.log('lifecycle', '🔄 Container starting up...');
|
||||
await logger.log('lifecycle', '🔄 Container starting up...');
|
||||
|
||||
// Success states
|
||||
logger.log('success', '✅ Payment processed');
|
||||
logger.log('ok', '👍 Health check passed');
|
||||
await logger.log('success', '✅ Payment processed');
|
||||
await logger.log('ok', '👍 Health check passed');
|
||||
|
||||
// Information and debugging
|
||||
logger.log('info', '📋 User profile updated');
|
||||
logger.log('note', '📌 Cache invalidated');
|
||||
logger.log('debug', '🔍 Query execution plan', { sql: 'SELECT * FROM users' });
|
||||
await logger.log('info', '📋 User profile updated');
|
||||
await logger.log('note', '📌 Cache invalidated');
|
||||
await logger.log('debug', '🔍 Query execution plan', { sql: 'SELECT * FROM users' });
|
||||
|
||||
// Warnings and errors
|
||||
logger.log('warn', '⚠️ Memory usage above 80%');
|
||||
logger.log('error', '❌ Failed to send email');
|
||||
await logger.log('warn', '⚠️ Memory usage above 80%');
|
||||
await logger.log('error', '❌ Failed to send email');
|
||||
|
||||
// Verbose output
|
||||
logger.log('silly', '🔬 Entering function processPayment()');
|
||||
await logger.log('silly', '🔬 Entering function processPayment()');
|
||||
```
|
||||
|
||||
Available levels (from most to least verbose): `silly`, `debug`, `info`, `note`, `ok`, `success`, `warn`, `error`, `lifecycle`
|
||||
|
||||
### Log Types
|
||||
|
||||
Each log entry has a type that describes what kind of data it represents:
|
||||
|
||||
| Type | Description |
|
||||
|------|-------------|
|
||||
| `log` | Standard log message |
|
||||
| `increment` | Counter/metric increment |
|
||||
| `gauge` | Gauge measurement |
|
||||
| `error` | Error event |
|
||||
| `success` | Success event |
|
||||
| `value` | Value recording |
|
||||
| `finance` | Financial transaction |
|
||||
| `compliance` | Compliance event |
|
||||
|
||||
### Log Groups for Correlation
|
||||
|
||||
Perfect for tracking request flows through your system:
|
||||
@@ -109,36 +134,75 @@ requestGroup.log('debug', 'Validating request body');
|
||||
requestGroup.log('info', 'Creating user in database');
|
||||
requestGroup.log('success', 'User created successfully', { userId: 'usr_123' });
|
||||
|
||||
// All logs in the group share the same correlation ID
|
||||
// All logs in the group share the same group ID and transaction ID
|
||||
```
|
||||
|
||||
### Log Context
|
||||
|
||||
Every log carries contextual information about the environment it was created in:
|
||||
|
||||
```typescript
|
||||
interface ILogContext {
|
||||
commitinfo?: ICommitInfo; // Build/version info
|
||||
company?: string; // Company name
|
||||
companyunit?: string; // Team or department
|
||||
containerName?: string; // Container/service name
|
||||
environment?: 'local' | 'test' | 'staging' | 'production';
|
||||
runtime?: 'node' | 'chrome' | 'rust' | 'deno' | 'cloudflare_workers';
|
||||
zone?: string; // Deployment zone/region
|
||||
}
|
||||
```
|
||||
|
||||
## 🎯 Log Destinations
|
||||
|
||||
smartlog routes log packages to any number of destinations simultaneously. Each destination implements the `ILogDestination` interface with a single `handleLog` method.
|
||||
|
||||
### Built-in Destinations
|
||||
|
||||
#### 🖥️ Local Console (Enhanced)
|
||||
|
||||
Beautiful, colored output for local development:
|
||||
Beautiful, color-coded output for local development:
|
||||
|
||||
```typescript
|
||||
import { DestinationLocal } from '@push.rocks/smartlog/destination-local';
|
||||
|
||||
const localDestination = new DestinationLocal({
|
||||
logLevel: 'debug' // Optional: filter by minimum log level
|
||||
});
|
||||
|
||||
const localDestination = new DestinationLocal();
|
||||
logger.addLogDestination(localDestination);
|
||||
|
||||
// Output is color-coded per log level:
|
||||
// 🔵 info: blue prefix, white text
|
||||
// 🔴 error: red prefix, red text
|
||||
// 🟢 ok/success: green prefix, green text
|
||||
// 🟠 warn: orange prefix, orange text
|
||||
// 🟣 note/debug: pink prefix, pink text
|
||||
// 🔵 silly: blue background, blue text
|
||||
```
|
||||
|
||||
The `DestinationLocal` also supports **reduced logging** — a mode where repeated identical log messages are suppressed:
|
||||
|
||||
```typescript
|
||||
const localDest = new DestinationLocal();
|
||||
|
||||
localDest.logReduced('Waiting for connection...'); // Logged
|
||||
localDest.logReduced('Waiting for connection...'); // Suppressed
|
||||
localDest.logReduced('Waiting for connection...'); // Suppressed
|
||||
localDest.logReduced('Connected!'); // Logged (new message)
|
||||
```
|
||||
|
||||
#### 📁 File Logging
|
||||
|
||||
Persist logs to files with automatic rotation support:
|
||||
Persist logs to files with timestamped entries:
|
||||
|
||||
```typescript
|
||||
import { SmartlogDestinationFile } from '@push.rocks/smartlog/destination-file';
|
||||
|
||||
const fileDestination = new SmartlogDestinationFile('./logs/app.log');
|
||||
// Path MUST be absolute
|
||||
const fileDestination = new SmartlogDestinationFile('/var/log/myapp/app.log');
|
||||
logger.addLogDestination(fileDestination);
|
||||
|
||||
// Log entries are written as timestamped lines:
|
||||
// 2024-01-15T10:30:00.000Z: Application started
|
||||
// 2024-01-15T10:30:01.123Z: Processing request
|
||||
```
|
||||
|
||||
#### 🌐 Browser DevTools
|
||||
@@ -150,60 +214,54 @@ import { SmartlogDestinationDevtools } from '@push.rocks/smartlog/destination-de
|
||||
|
||||
const devtools = new SmartlogDestinationDevtools();
|
||||
logger.addLogDestination(devtools);
|
||||
|
||||
// Uses CSS styling in browser console for beautiful, categorized output
|
||||
// Different colors for error (red), info (pink), ok (green), success (green),
|
||||
// warn (orange), and note (blue) levels
|
||||
```
|
||||
|
||||
#### 📊 ClickHouse Analytics
|
||||
|
||||
Store logs in ClickHouse for powerful analytics:
|
||||
Store logs in ClickHouse for powerful time-series analytics:
|
||||
|
||||
```typescript
|
||||
import { SmartlogDestinationClickhouse } from '@push.rocks/smartlog/destination-clickhouse';
|
||||
|
||||
const clickhouse = await SmartlogDestinationClickhouse.createAndStart({
|
||||
host: 'analytics.example.com',
|
||||
port: 8123,
|
||||
url: 'https://analytics.example.com:8123',
|
||||
database: 'logs',
|
||||
user: 'logger',
|
||||
username: 'logger',
|
||||
password: process.env.CLICKHOUSE_PASSWORD
|
||||
});
|
||||
|
||||
logger.addLogDestination(clickhouse);
|
||||
|
||||
// Query your logs with SQL!
|
||||
// SELECT * FROM logs WHERE level = 'error' AND timestamp > now() - INTERVAL 1 HOUR
|
||||
```
|
||||
|
||||
#### 🔗 Remote Receiver
|
||||
|
||||
Send logs to a centralized logging service:
|
||||
Send logs to a centralized logging service with authenticated transport:
|
||||
|
||||
```typescript
|
||||
import { SmartlogDestinationReceiver } from '@push.rocks/smartlog/destination-receiver';
|
||||
|
||||
const receiver = new SmartlogDestinationReceiver({
|
||||
endpoint: 'https://logs.mycompany.com/ingest',
|
||||
apiKey: process.env.LOG_API_KEY,
|
||||
batchSize: 100, // Send logs in batches
|
||||
flushInterval: 5000 // Flush every 5 seconds
|
||||
passphrase: process.env.LOG_PASSPHRASE,
|
||||
receiverEndpoint: 'https://logs.mycompany.com/ingest'
|
||||
});
|
||||
|
||||
logger.addLogDestination(receiver);
|
||||
```
|
||||
|
||||
Logs are sent as authenticated JSON payloads with SHA-256 hashed passphrases.
|
||||
|
||||
### 🛠️ Custom Destinations
|
||||
|
||||
Build your own destination for any logging backend:
|
||||
Build your own destination for any logging backend by implementing the `ILogDestination` interface:
|
||||
|
||||
```typescript
|
||||
import { ILogDestination, ILogPackage } from '@push.rocks/smartlog/interfaces';
|
||||
import type { ILogDestination, ILogPackage } from '@push.rocks/smartlog/interfaces';
|
||||
|
||||
class ElasticsearchDestination implements ILogDestination {
|
||||
private client: ElasticsearchClient;
|
||||
|
||||
constructor(config: ElasticsearchConfig) {
|
||||
this.client = new ElasticsearchClient(config);
|
||||
}
|
||||
|
||||
async handleLog(logPackage: ILogPackage): Promise<void> {
|
||||
await this.client.index({
|
||||
index: `logs-${new Date().toISOString().split('T')[0]}`,
|
||||
@@ -219,10 +277,7 @@ class ElasticsearchDestination implements ILogDestination {
|
||||
}
|
||||
}
|
||||
|
||||
// Use your custom destination
|
||||
logger.addLogDestination(new ElasticsearchDestination({
|
||||
node: 'https://elasticsearch.example.com'
|
||||
}));
|
||||
logger.addLogDestination(new ElasticsearchDestination());
|
||||
```
|
||||
|
||||
## 🎨 Interactive Console Features
|
||||
@@ -241,19 +296,20 @@ spinner.text('🔄 Fetching data from API...');
|
||||
// ... perform async operation
|
||||
spinner.finishSuccess('✅ Data fetched successfully!');
|
||||
|
||||
// Chained operations
|
||||
spinner
|
||||
.text('📡 Connecting to database')
|
||||
.successAndNext('🔍 Running migrations')
|
||||
.successAndNext('🌱 Seeding data')
|
||||
.finishSuccess('🎉 Database ready!');
|
||||
// Chain success and move to next task
|
||||
spinner.text('📡 Connecting to database');
|
||||
// ... connect
|
||||
spinner.successAndNext('🔍 Running migrations');
|
||||
// ... migrate
|
||||
spinner.finishSuccess('🎉 Database ready!');
|
||||
|
||||
// Customize appearance
|
||||
spinner
|
||||
.setSpinnerStyle('dots') // dots, line, star, simple
|
||||
.setSpinnerStyle('dots') // 'dots' | 'line' | 'star' | 'simple'
|
||||
.setColor('cyan') // any terminal color
|
||||
.setSpeed(80) // animation speed in ms
|
||||
.text('🚀 Deploying application...');
|
||||
.setSpeed(80); // animation speed in ms
|
||||
|
||||
spinner.text('🚀 Deploying application...');
|
||||
|
||||
// Handle failures
|
||||
try {
|
||||
@@ -274,12 +330,12 @@ import { SmartlogProgressBar } from '@push.rocks/smartlog/source-interactive';
|
||||
// Create a progress bar
|
||||
const progress = new SmartlogProgressBar({
|
||||
total: 100,
|
||||
width: 40,
|
||||
complete: '█',
|
||||
incomplete: '░',
|
||||
showEta: true,
|
||||
showPercent: true,
|
||||
showCount: true
|
||||
width: 40, // Bar width in characters
|
||||
complete: '█', // Fill character
|
||||
incomplete: '░', // Empty character
|
||||
showEta: true, // Show estimated time remaining
|
||||
showPercent: true, // Show percentage
|
||||
showCount: true // Show current/total count
|
||||
});
|
||||
|
||||
// Update progress
|
||||
@@ -290,22 +346,21 @@ for (let i = 0; i <= 100; i++) {
|
||||
|
||||
progress.complete();
|
||||
|
||||
// Real-world example: Processing files
|
||||
// Or use increment for simpler tracking
|
||||
const files = await getFiles();
|
||||
const progress = new SmartlogProgressBar({
|
||||
total: files.length,
|
||||
width: 50
|
||||
});
|
||||
const fileProgress = new SmartlogProgressBar({ total: files.length });
|
||||
|
||||
for (const [index, file] of files.entries()) {
|
||||
for (const file of files) {
|
||||
await processFile(file);
|
||||
progress.increment(); // or progress.update(index + 1)
|
||||
fileProgress.increment();
|
||||
}
|
||||
|
||||
fileProgress.complete();
|
||||
```
|
||||
|
||||
### Non-Interactive Fallback
|
||||
|
||||
Both spinners and progress bars automatically detect non-interactive environments (CI/CD, Docker logs, piped output) and fallback to simple text output:
|
||||
Both spinners and progress bars automatically detect non-interactive environments (CI/CD, Docker logs, piped output) and fall back to simple text output:
|
||||
|
||||
```
|
||||
[Loading] Connecting to database
|
||||
@@ -314,86 +369,96 @@ Both spinners and progress bars automatically detect non-interactive environment
|
||||
Progress: 25% (25/100)
|
||||
Progress: 50% (50/100)
|
||||
Progress: 100% (100/100)
|
||||
[Success] Migrations complete
|
||||
Completed: 100% (100/100)
|
||||
```
|
||||
|
||||
Detection checks for: TTY capability, CI environment variables (GitHub Actions, Jenkins, GitLab CI, Travis, CircleCI), and `TERM=dumb`.
|
||||
|
||||
### Backward Compatibility
|
||||
|
||||
The `SmartlogSourceOra` class extends `SmartlogSourceInteractive` and provides a compatibility layer for code that previously used the `ora` npm package:
|
||||
|
||||
```typescript
|
||||
import { SmartlogSourceOra } from '@push.rocks/smartlog/source-interactive';
|
||||
|
||||
const ora = new SmartlogSourceOra();
|
||||
ora.oraInstance.start();
|
||||
ora.oraInstance.succeed('Done!');
|
||||
```
|
||||
|
||||
## 🔧 Advanced Features
|
||||
|
||||
### Context Management
|
||||
### Minimum Log Level
|
||||
|
||||
Add context that flows through your entire application:
|
||||
|
||||
```typescript
|
||||
// Set global context
|
||||
logger.addLogContext({
|
||||
requestId: 'req-123',
|
||||
userId: 'user-456',
|
||||
feature: 'payment-processing'
|
||||
});
|
||||
|
||||
// All subsequent logs include this context
|
||||
logger.log('info', 'Processing payment');
|
||||
// Output includes: { ...context, message: 'Processing payment' }
|
||||
```
|
||||
|
||||
### Scoped Logging
|
||||
|
||||
Create scoped loggers for different components:
|
||||
|
||||
```typescript
|
||||
const dbLogger = logger.createScope('database');
|
||||
const apiLogger = logger.createScope('api');
|
||||
|
||||
dbLogger.log('info', 'Executing query');
|
||||
apiLogger.log('info', 'Handling request');
|
||||
// Logs include scope information for filtering
|
||||
```
|
||||
|
||||
### Minimum Log Levels
|
||||
|
||||
Control log verbosity per environment:
|
||||
Set a minimum log level that destinations can use to filter messages:
|
||||
|
||||
```typescript
|
||||
const logger = new Smartlog({
|
||||
logContext: { environment: 'production' },
|
||||
minimumLogLevel: 'warn' // Only warn and above in production
|
||||
logContext: { environment: 'production', runtime: 'node' },
|
||||
minimumLogLevel: 'warn' // Destinations can check this to filter
|
||||
});
|
||||
|
||||
// These won't be logged in production
|
||||
logger.log('debug', 'Detailed debug info');
|
||||
logger.log('info', 'Regular info');
|
||||
|
||||
// These will be logged
|
||||
logger.log('warn', 'Warning message');
|
||||
logger.log('error', 'Error message');
|
||||
// The minimumLogLevel is available as a public property
|
||||
console.log(logger.minimumLogLevel); // 'warn'
|
||||
```
|
||||
|
||||
### Increment Logging
|
||||
|
||||
Perfect for metrics and counters:
|
||||
Track metrics and counters alongside your logs:
|
||||
|
||||
```typescript
|
||||
// Track API calls
|
||||
logger.increment('info', 'api.requests', { endpoint: '/users', method: 'GET' });
|
||||
logger.increment('info', 'api.requests', { endpoint: '/users', method: 'POST' });
|
||||
|
||||
// Track errors by type
|
||||
// Track error types
|
||||
logger.increment('error', 'payment.failed', { reason: 'insufficient_funds' });
|
||||
logger.increment('error', 'payment.failed', { reason: 'card_declined' });
|
||||
```
|
||||
|
||||
Increment logs are routed to all destinations with `type: 'increment'` so analytics backends can aggregate them.
|
||||
|
||||
### Capture All Console Output
|
||||
|
||||
Redirect all console.log and console.error through smartlog:
|
||||
Redirect all `console.log` and `console.error` through smartlog:
|
||||
|
||||
```typescript
|
||||
logger.enableConsole({
|
||||
captureAll: true // Capture all console.* methods
|
||||
logger.enableConsole({
|
||||
captureAll: true
|
||||
});
|
||||
|
||||
console.log('This goes through smartlog!');
|
||||
console.error('This too!');
|
||||
// All console output is now structured and routed through your destinations
|
||||
console.log('This goes through smartlog as info!');
|
||||
console.error('This goes through smartlog as error!');
|
||||
|
||||
// Strings containing "Error:" are automatically classified as error level
|
||||
// All captured output is prefixed with "LOG =>" to prevent recursion
|
||||
```
|
||||
|
||||
### Log Receiver Server
|
||||
|
||||
Accept authenticated log packages from remote services:
|
||||
|
||||
```typescript
|
||||
import { SmartlogReceiver } from '@push.rocks/smartlog/receiver';
|
||||
|
||||
const receiver = new SmartlogReceiver({
|
||||
smartlogInstance: logger,
|
||||
passphrase: 'shared-secret',
|
||||
validatorFunction: async (logPackage) => {
|
||||
// Custom validation logic
|
||||
return logPackage.context.company === 'MyCompany';
|
||||
}
|
||||
});
|
||||
|
||||
// Handle incoming authenticated log packages (e.g., from an HTTP endpoint)
|
||||
app.post('/logs', async (req, res) => {
|
||||
const result = await receiver.handleAuthenticatedLog(req.body);
|
||||
res.json(result); // { status: 'ok' } or { status: 'error' }
|
||||
});
|
||||
|
||||
// Or handle batches
|
||||
app.post('/logs/batch', async (req, res) => {
|
||||
await receiver.handleManyAuthenticatedLogs(req.body);
|
||||
res.json({ status: 'ok' });
|
||||
});
|
||||
```
|
||||
|
||||
## 🏗️ Real-World Examples
|
||||
@@ -403,6 +468,7 @@ console.error('This too!');
|
||||
```typescript
|
||||
import { Smartlog } from '@push.rocks/smartlog';
|
||||
import { SmartlogDestinationClickhouse } from '@push.rocks/smartlog/destination-clickhouse';
|
||||
import { DestinationLocal } from '@push.rocks/smartlog/destination-local';
|
||||
|
||||
// Initialize logger with service context
|
||||
const logger = new Smartlog({
|
||||
@@ -410,47 +476,39 @@ const logger = new Smartlog({
|
||||
company: 'TechCorp',
|
||||
companyunit: 'Platform',
|
||||
containerName: 'user-service',
|
||||
environment: process.env.NODE_ENV,
|
||||
environment: 'production',
|
||||
runtime: 'node',
|
||||
zone: process.env.CLOUD_ZONE
|
||||
zone: 'eu-central'
|
||||
}
|
||||
});
|
||||
|
||||
// Add ClickHouse for analytics
|
||||
const clickhouse = await SmartlogDestinationClickhouse.createAndStart({
|
||||
host: process.env.CLICKHOUSE_HOST,
|
||||
url: process.env.CLICKHOUSE_URL,
|
||||
database: 'microservices_logs'
|
||||
});
|
||||
logger.addLogDestination(clickhouse);
|
||||
|
||||
// Enable local console for development
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
logger.enableConsole();
|
||||
}
|
||||
// Add local console for container stdout
|
||||
logger.addLogDestination(new DestinationLocal());
|
||||
|
||||
// Express middleware for request tracking
|
||||
app.use((req, res, next) => {
|
||||
const requestId = generateRequestId();
|
||||
const logGroup = logger.createLogGroup(requestId);
|
||||
|
||||
// Attach logger to request
|
||||
req.logger = logGroup;
|
||||
|
||||
// Log request
|
||||
const logGroup = logger.createLogGroup(req.headers['x-request-id'] || 'unknown');
|
||||
|
||||
logGroup.log('info', 'Incoming request', {
|
||||
method: req.method,
|
||||
path: req.path,
|
||||
ip: req.ip
|
||||
});
|
||||
|
||||
// Track response
|
||||
|
||||
res.on('finish', () => {
|
||||
logGroup.log('info', 'Request completed', {
|
||||
statusCode: res.statusCode,
|
||||
duration: Date.now() - req.startTime
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
next();
|
||||
});
|
||||
```
|
||||
@@ -464,7 +522,8 @@ import { SmartlogSourceInteractive, SmartlogProgressBar } from '@push.rocks/smar
|
||||
const logger = new Smartlog({
|
||||
logContext: {
|
||||
containerName: 'migration-tool',
|
||||
environment: 'cli'
|
||||
environment: 'local',
|
||||
runtime: 'node'
|
||||
}
|
||||
});
|
||||
|
||||
@@ -472,32 +531,29 @@ logger.enableConsole();
|
||||
|
||||
async function migrateDatabase() {
|
||||
const spinner = new SmartlogSourceInteractive();
|
||||
|
||||
// Connect to database
|
||||
|
||||
spinner.text('🔌 Connecting to database...');
|
||||
await connectDB();
|
||||
spinner.finishSuccess('✅ Connected to database');
|
||||
|
||||
// Get migrations
|
||||
|
||||
spinner.text('📋 Loading migrations...');
|
||||
const migrations = await getMigrations();
|
||||
spinner.finishSuccess(`✅ Found ${migrations.length} migrations`);
|
||||
|
||||
// Run migrations with progress bar
|
||||
|
||||
const progress = new SmartlogProgressBar({
|
||||
total: migrations.length,
|
||||
width: 40,
|
||||
showEta: true
|
||||
});
|
||||
|
||||
|
||||
for (const [index, migration] of migrations.entries()) {
|
||||
logger.log('info', `Running migration: ${migration.name}`);
|
||||
await logger.log('info', `Running migration: ${migration.name}`);
|
||||
await runMigration(migration);
|
||||
progress.update(index + 1);
|
||||
}
|
||||
|
||||
|
||||
progress.complete();
|
||||
logger.log('success', '🎉 All migrations completed successfully!');
|
||||
await logger.log('success', '🎉 All migrations completed successfully!');
|
||||
}
|
||||
```
|
||||
|
||||
@@ -517,10 +573,10 @@ const logger = new Smartlog({
|
||||
runtime: 'node',
|
||||
zone: 'us-east-1'
|
||||
},
|
||||
minimumLogLevel: 'info' // No debug logs in production
|
||||
minimumLogLevel: 'info'
|
||||
});
|
||||
|
||||
// Console for container logs (structured for log aggregators)
|
||||
// Color-coded console for container logs
|
||||
logger.addLogDestination(new DestinationLocal());
|
||||
|
||||
// File for audit trail
|
||||
@@ -528,221 +584,121 @@ logger.addLogDestination(new SmartlogDestinationFile('/var/log/app/audit.log'));
|
||||
|
||||
// Central logging service
|
||||
logger.addLogDestination(new SmartlogDestinationReceiver({
|
||||
endpoint: 'https://logs.enterprise.com/ingest',
|
||||
apiKey: process.env.LOG_API_KEY,
|
||||
batchSize: 100,
|
||||
flushInterval: 5000
|
||||
passphrase: process.env.LOG_PASSPHRASE,
|
||||
receiverEndpoint: 'https://logs.enterprise.com/ingest'
|
||||
}));
|
||||
|
||||
// Critical error alerts
|
||||
// Custom inline destination for critical alerts
|
||||
logger.addLogDestination({
|
||||
async handleLog(logPackage) {
|
||||
if (logPackage.level === 'error' && logPackage.data?.critical) {
|
||||
await sendAlert({
|
||||
channel: 'ops-team',
|
||||
message: `🚨 Critical error: ${logPackage.message}`,
|
||||
data: logPackage
|
||||
});
|
||||
await sendSlackAlert(`🚨 Critical error: ${logPackage.message}`);
|
||||
}
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
## 🔌 Integration with Other Tools
|
||||
|
||||
### PM2 Integration
|
||||
|
||||
```typescript
|
||||
// ecosystem.config.js
|
||||
module.exports = {
|
||||
apps: [{
|
||||
name: 'api',
|
||||
script: './dist/index.js',
|
||||
error_file: '/dev/null', // Disable PM2 error log
|
||||
out_file: '/dev/null', // Disable PM2 out log
|
||||
merge_logs: true,
|
||||
env: {
|
||||
NODE_ENV: 'production',
|
||||
// smartlog handles all logging
|
||||
}
|
||||
}]
|
||||
};
|
||||
```
|
||||
|
||||
### Docker Integration
|
||||
|
||||
```dockerfile
|
||||
FROM node:18-alpine
|
||||
WORKDIR /app
|
||||
COPY . .
|
||||
RUN pnpm install --production
|
||||
|
||||
# smartlog handles structured logging
|
||||
# No need for special log drivers
|
||||
CMD ["node", "dist/index.js"]
|
||||
```
|
||||
|
||||
```yaml
|
||||
# docker-compose.yml
|
||||
services:
|
||||
app:
|
||||
build: .
|
||||
environment:
|
||||
- NODE_ENV=production
|
||||
# Logs are structured JSON, perfect for log aggregators
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "10m"
|
||||
max-file: "3"
|
||||
```
|
||||
|
||||
## 🏆 Best Practices
|
||||
|
||||
### 1. Use Semantic Log Levels
|
||||
|
||||
```typescript
|
||||
// ✅ Good - semantic and meaningful
|
||||
logger.log('lifecycle', 'Server starting on port 3000');
|
||||
logger.log('success', 'Database connection established');
|
||||
logger.log('error', 'Failed to process payment', { orderId, error });
|
||||
|
||||
// ❌ Bad - everything is 'info'
|
||||
logger.log('info', 'Server starting');
|
||||
logger.log('info', 'Database connected');
|
||||
logger.log('info', 'Error: payment failed');
|
||||
```
|
||||
|
||||
### 2. Include Structured Data
|
||||
|
||||
```typescript
|
||||
// ✅ Good - structured data for analysis
|
||||
logger.log('error', 'API request failed', {
|
||||
endpoint: '/api/users',
|
||||
statusCode: 500,
|
||||
duration: 1234,
|
||||
userId: 'usr_123',
|
||||
errorCode: 'INTERNAL_ERROR'
|
||||
});
|
||||
|
||||
// ❌ Bad - data embedded in message
|
||||
logger.log('error', `API request to /api/users failed with 500 in 1234ms for user usr_123`);
|
||||
```
|
||||
|
||||
### 3. Use Log Groups for Correlation
|
||||
|
||||
```typescript
|
||||
// ✅ Good - correlated logs
|
||||
async function processOrder(orderId: string) {
|
||||
const logGroup = logger.createLogGroup(orderId);
|
||||
|
||||
logGroup.log('info', 'Processing order');
|
||||
logGroup.log('debug', 'Validating items');
|
||||
logGroup.log('info', 'Charging payment');
|
||||
logGroup.log('success', 'Order processed');
|
||||
}
|
||||
|
||||
// ❌ Bad - no correlation
|
||||
async function processOrder(orderId: string) {
|
||||
logger.log('info', `Processing order ${orderId}`);
|
||||
logger.log('debug', `Validating items for ${orderId}`);
|
||||
logger.log('info', `Charging payment for ${orderId}`);
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Environment-Specific Configuration
|
||||
|
||||
```typescript
|
||||
// ✅ Good - different configs per environment
|
||||
const logger = new Smartlog({
|
||||
logContext: {
|
||||
environment: process.env.NODE_ENV,
|
||||
// ... other context
|
||||
},
|
||||
minimumLogLevel: process.env.NODE_ENV === 'production' ? 'info' : 'silly'
|
||||
});
|
||||
|
||||
// Add destinations based on environment
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
logger.addLogDestination(clickhouseDestination);
|
||||
logger.addLogDestination(alertingDestination);
|
||||
} else {
|
||||
logger.enableConsole();
|
||||
}
|
||||
```
|
||||
|
||||
## 📖 API Reference
|
||||
|
||||
### Smartlog Class
|
||||
|
||||
```typescript
|
||||
class Smartlog {
|
||||
constructor(options?: ISmartlogOptions)
|
||||
|
||||
// Logging methods
|
||||
log(level: TLogLevel, message: string, data?: any, correlation?: ILogCorrelation): void
|
||||
increment(level: TLogLevel, key: string, data?: any): void
|
||||
|
||||
// Static factory
|
||||
static createForCommitinfo(commitinfo: ICommitInfo): Smartlog;
|
||||
|
||||
// Constructor
|
||||
constructor(options: {
|
||||
logContext: ILogContext;
|
||||
minimumLogLevel?: TLogLevel; // default: 'silly'
|
||||
});
|
||||
|
||||
// Logging
|
||||
log(level: TLogLevel, message: string, data?: any, correlation?: ILogCorrelation): Promise<void>;
|
||||
increment(level: TLogLevel, message: string, data?: any, correlation?: ILogCorrelation): void;
|
||||
|
||||
// Configuration
|
||||
enableConsole(options?: IConsoleOptions): void
|
||||
addLogDestination(destination: ILogDestination): void
|
||||
addLogContext(context: Partial<ILogContext>): void
|
||||
|
||||
// Log groups
|
||||
createLogGroup(transactionId?: string): LogGroup
|
||||
createScope(scopeName: string): Smartlog
|
||||
enableConsole(options?: { captureAll: boolean }): void;
|
||||
addLogDestination(destination: ILogDestination): void;
|
||||
|
||||
// Correlation
|
||||
createLogGroup(transactionId?: string): LogGroup;
|
||||
|
||||
// Forwarding (for receiver pattern)
|
||||
handleLog(logPackage: ILogPackage): Promise<void>;
|
||||
|
||||
// Instance identity
|
||||
uniInstanceId: string;
|
||||
logContext: ILogContext;
|
||||
minimumLogLevel: TLogLevel;
|
||||
}
|
||||
```
|
||||
|
||||
### Log Levels
|
||||
### LogGroup Class
|
||||
|
||||
```typescript
|
||||
type TLogLevel =
|
||||
| 'silly' // Very verbose debugging
|
||||
| 'debug' // Debug information
|
||||
| 'info' // Informational messages
|
||||
| 'note' // Notable events
|
||||
| 'ok' // Success confirmation
|
||||
| 'success' // Major success
|
||||
| 'warn' // Warning messages
|
||||
| 'error' // Error messages
|
||||
| 'lifecycle' // Application lifecycle events
|
||||
```
|
||||
class LogGroup {
|
||||
groupId: string; // Auto-generated unique ID
|
||||
transactionId: string; // The transaction ID passed at creation
|
||||
smartlogRef: Smartlog; // Reference to the parent Smartlog
|
||||
|
||||
### Log Package Structure
|
||||
|
||||
```typescript
|
||||
interface ILogPackage {
|
||||
timestamp: number;
|
||||
level: TLogLevel;
|
||||
message: string;
|
||||
data?: any;
|
||||
context: ILogContext;
|
||||
correlation?: ILogCorrelation;
|
||||
log(level: TLogLevel, message: string, data?: any): void;
|
||||
}
|
||||
```
|
||||
|
||||
For complete API documentation, see [API.md](API.md).
|
||||
### ILogDestination Interface
|
||||
|
||||
## 🤝 Contributing
|
||||
```typescript
|
||||
interface ILogDestination {
|
||||
handleLog(logPackage: ILogPackage): Promise<void>;
|
||||
}
|
||||
```
|
||||
|
||||
We welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) for details.
|
||||
### ILogPackage Interface
|
||||
|
||||
## 📄 License and Legal Information
|
||||
```typescript
|
||||
interface ILogPackage<T = unknown> {
|
||||
timestamp: number; // Unix timestamp in milliseconds
|
||||
type: TLogType; // 'log' | 'increment' | 'gauge' | ...
|
||||
context: ILogContext; // Environment context
|
||||
level: TLogLevel; // Log severity
|
||||
correlation: ILogCorrelation; // Correlation metadata
|
||||
message: string; // The log message
|
||||
data?: T; // Optional structured data
|
||||
}
|
||||
```
|
||||
|
||||
This repository contains open-source code that is licensed under the MIT License. A copy of the MIT License can be found in the [license](license) file within this repository.
|
||||
### Available Log Levels
|
||||
|
||||
| Level | Description | Use Case |
|
||||
|-------|-------------|----------|
|
||||
| `silly` | Ultra-verbose | Function entry/exit, variable dumps |
|
||||
| `debug` | Debug info | Development-time diagnostics |
|
||||
| `info` | Informational | Standard operational messages |
|
||||
| `note` | Notable events | Important but non-critical events |
|
||||
| `ok` | Success confirmation | Health checks, validations |
|
||||
| `success` | Major success | Completed operations |
|
||||
| `warn` | Warnings | Degraded performance, approaching limits |
|
||||
| `error` | Errors | Failures requiring attention |
|
||||
| `lifecycle` | Lifecycle events | Start, stop, restart, deploy |
|
||||
|
||||
## 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.
|
||||
|
||||
**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.
|
||||
|
||||
### Trademarks
|
||||
|
||||
This project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH and are not included within the scope of the MIT license granted herein. Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines, and any usage must be approved in writing by Task Venture Capital GmbH.
|
||||
This project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH or third parties, and are not included within the scope of the MIT license granted herein.
|
||||
|
||||
Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines or the guidelines of the respective third-party owners, and any usage must be approved in writing. Third-party trademarks used herein are the property of their respective owners and used only in a descriptive manner, e.g. for an implementation of an API or similar.
|
||||
|
||||
### Company Information
|
||||
|
||||
Task Venture Capital GmbH
|
||||
Registered at District court Bremen HRB 35230 HB, Germany
|
||||
Task Venture Capital GmbH
|
||||
Registered at District Court Bremen HRB 35230 HB, Germany
|
||||
|
||||
For any legal inquiries or if you require further information, please contact us via email at hello@task.vc.
|
||||
For any legal inquiries or further information, please contact us via email at hello@task.vc.
|
||||
|
||||
By using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.
|
||||
By using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.
|
||||
|
||||
@@ -53,10 +53,7 @@ tap.test('should attempt to send logs to the receiver endpoint', async () => {
|
||||
// Create a mock version of the webrequest.postJson method to avoid actual HTTP calls
|
||||
const originalPostJson = testDestination['webrequest'].postJson;
|
||||
testDestination['webrequest'].postJson = async () => {
|
||||
return {
|
||||
body: { status: 'ok' },
|
||||
statusCode: 200
|
||||
};
|
||||
return { status: 'ok' };
|
||||
};
|
||||
|
||||
try {
|
||||
|
||||
@@ -3,6 +3,6 @@
|
||||
*/
|
||||
export const commitinfo = {
|
||||
name: '@push.rocks/smartlog',
|
||||
version: '3.1.10',
|
||||
version: '3.1.11',
|
||||
description: 'A minimalistic, distributed, and extensible logging tool supporting centralized log management.'
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ export interface ISmartlogDestinationReceiverConstructorOptions {
|
||||
|
||||
export class SmartlogDestinationReceiver implements ILogDestination {
|
||||
private options: ISmartlogDestinationReceiverConstructorOptions;
|
||||
private webrequest = new plugins.webrequest.WebRequest();
|
||||
private webrequest = new plugins.webrequest.WebrequestClient();
|
||||
|
||||
constructor(optionsArg: ISmartlogDestinationReceiverConstructorOptions) {
|
||||
this.options = optionsArg;
|
||||
@@ -30,6 +30,6 @@ export class SmartlogDestinationReceiver implements ILogDestination {
|
||||
}
|
||||
this.errorCounter++;
|
||||
});
|
||||
return response.body;
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user