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:
2026-02-14 15:52:57 +00:00
parent 2d9a8e08f2
commit 0593c3e2d0
8 changed files with 2824 additions and 1886 deletions

View File

@@ -1,5 +1,15 @@
# Changelog # 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) ## 2025-09-22 - 3.1.10 - fix(tests)
Bump dependency versions and adjust test to use enableConsole() default Bump dependency versions and adjust test to use enableConsole() default

View File

@@ -1,5 +1,5 @@
{ {
"gitzone": { "@git.zone/cli": {
"projectType": "npm", "projectType": "npm",
"module": { "module": {
"githost": "code.foss.global", "githost": "code.foss.global",
@@ -25,13 +25,19 @@
"error tracking", "error tracking",
"development tools" "development tools"
] ]
},
"release": {
"registries": [
"https://verdaccio.lossless.digital",
"https://registry.npmjs.org"
],
"accessLevel": "public"
} }
}, },
"npmci": { "@git.zone/tsdoc": {
"npmGlobalTools": [],
"npmAccessLevel": "public"
},
"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" "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": []
} }
} }

View File

@@ -37,28 +37,28 @@
"license": "MIT", "license": "MIT",
"scripts": { "scripts": {
"test": "(tstest test/**/*.ts --verbose)", "test": "(tstest test/**/*.ts --verbose)",
"build": "(tsbuild tsfolders --allowimplicitany && tsbundle npm)", "build": "(tsbuild tsfolders)",
"format": "(gitzone format)", "format": "(gitzone format)",
"buildDocs": "tsdoc" "buildDocs": "tsdoc"
}, },
"devDependencies": { "devDependencies": {
"@git.zone/tsbuild": "^2.6.8", "@git.zone/tsbuild": "^4.1.2",
"@git.zone/tsbundle": "^2.5.1", "@git.zone/tsbundle": "^2.8.3",
"@git.zone/tsrun": "^1.3.3", "@git.zone/tsrun": "^2.0.1",
"@git.zone/tstest": "^2.3.6", "@git.zone/tstest": "^3.1.8",
"@types/node": "^22.15.20" "@types/node": "^22.15.20"
}, },
"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.4", "@push.rocks/isounique": "^1.0.5",
"@push.rocks/smartclickhouse": "^2.0.17", "@push.rocks/smartclickhouse": "^2.0.17",
"@push.rocks/smartfile": "^11.2.7", "@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/smartpromise": "^4.2.3",
"@push.rocks/smarttime": "^4.1.1", "@push.rocks/smarttime": "^4.1.1",
"@push.rocks/webrequest": "^3.0.37", "@push.rocks/webrequest": "^4.0.1",
"@tsclass/tsclass": "^9.2.0" "@tsclass/tsclass": "^9.3.0"
}, },
"files": [ "files": [
"ts/**/*", "ts/**/*",
@@ -86,6 +86,10 @@
"url": "https://code.foss.global/push.rocks/smartlog/issues" "url": "https://code.foss.global/push.rocks/smartlog/issues"
}, },
"pnpm": { "pnpm": {
"overrides": {} "overrides": {},
"onlyBuiltDependencies": [
"esbuild",
"puppeteer"
]
} }
} }

4019
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

598
readme.md
View File

@@ -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. 🎯 > **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? ## 🌟 Why smartlog?
- **🎨 Beautiful Console Output**: Color-coded, formatted logs that are actually readable - **🎨 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 - **🌍 Distributed by Design**: Built for microservices with correlation and context tracking
- **⚡ Zero-Config Start**: Works out of the box, scales when you need it - **⚡ 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 - **🎭 Interactive CLI Tools**: Spinners and progress bars that handle non-TTY environments gracefully
@@ -24,9 +28,6 @@ pnpm add @push.rocks/smartlog
# Using npm # Using npm
npm install @push.rocks/smartlog npm install @push.rocks/smartlog
# Using yarn
yarn add @push.rocks/smartlog
``` ```
## 🚀 Quick Start ## 🚀 Quick Start
@@ -48,25 +49,32 @@ const logger = new Smartlog({
} }
}); });
// Enable beautiful console output // Enable console output
logger.enableConsole(); logger.enableConsole();
// Start logging! // Start logging!
logger.log('info', '🎉 Application started successfully'); await logger.log('info', '🎉 Application started successfully');
logger.log('error', '💥 Database connection failed', { await logger.log('error', '💥 Database connection failed', {
errorCode: 'DB_TIMEOUT', 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 ```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 ## 📚 Core Concepts
@@ -77,25 +85,42 @@ smartlog supports semantic log levels for different scenarios:
```typescript ```typescript
// Lifecycle events // Lifecycle events
logger.log('lifecycle', '🔄 Container starting up...'); await logger.log('lifecycle', '🔄 Container starting up...');
// Success states // Success states
logger.log('success', '✅ Payment processed'); await logger.log('success', '✅ Payment processed');
logger.log('ok', '👍 Health check passed'); await logger.log('ok', '👍 Health check passed');
// Information and debugging // Information and debugging
logger.log('info', '📋 User profile updated'); await logger.log('info', '📋 User profile updated');
logger.log('note', '📌 Cache invalidated'); await logger.log('note', '📌 Cache invalidated');
logger.log('debug', '🔍 Query execution plan', { sql: 'SELECT * FROM users' }); await logger.log('debug', '🔍 Query execution plan', { sql: 'SELECT * FROM users' });
// Warnings and errors // Warnings and errors
logger.log('warn', '⚠️ Memory usage above 80%'); await logger.log('warn', '⚠️ Memory usage above 80%');
logger.log('error', '❌ Failed to send email'); await logger.log('error', '❌ Failed to send email');
// Verbose output // 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 ### Log Groups for Correlation
Perfect for tracking request flows through your system: 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('info', 'Creating user in database');
requestGroup.log('success', 'User created successfully', { userId: 'usr_123' }); 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 ## 🎯 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 ### Built-in Destinations
#### 🖥️ Local Console (Enhanced) #### 🖥️ Local Console (Enhanced)
Beautiful, colored output for local development: Beautiful, color-coded output for local development:
```typescript ```typescript
import { DestinationLocal } from '@push.rocks/smartlog/destination-local'; import { DestinationLocal } from '@push.rocks/smartlog/destination-local';
const localDestination = new DestinationLocal({ const localDestination = new DestinationLocal();
logLevel: 'debug' // Optional: filter by minimum log level
});
logger.addLogDestination(localDestination); 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 #### 📁 File Logging
Persist logs to files with automatic rotation support: Persist logs to files with timestamped entries:
```typescript ```typescript
import { SmartlogDestinationFile } from '@push.rocks/smartlog/destination-file'; 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); 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 #### 🌐 Browser DevTools
@@ -150,60 +214,54 @@ import { SmartlogDestinationDevtools } from '@push.rocks/smartlog/destination-de
const devtools = new SmartlogDestinationDevtools(); const devtools = new SmartlogDestinationDevtools();
logger.addLogDestination(devtools); 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 #### 📊 ClickHouse Analytics
Store logs in ClickHouse for powerful analytics: Store logs in ClickHouse for powerful time-series analytics:
```typescript ```typescript
import { SmartlogDestinationClickhouse } from '@push.rocks/smartlog/destination-clickhouse'; import { SmartlogDestinationClickhouse } from '@push.rocks/smartlog/destination-clickhouse';
const clickhouse = await SmartlogDestinationClickhouse.createAndStart({ const clickhouse = await SmartlogDestinationClickhouse.createAndStart({
host: 'analytics.example.com', url: 'https://analytics.example.com:8123',
port: 8123,
database: 'logs', database: 'logs',
user: 'logger', username: 'logger',
password: process.env.CLICKHOUSE_PASSWORD password: process.env.CLICKHOUSE_PASSWORD
}); });
logger.addLogDestination(clickhouse); logger.addLogDestination(clickhouse);
// Query your logs with SQL!
// SELECT * FROM logs WHERE level = 'error' AND timestamp > now() - INTERVAL 1 HOUR
``` ```
#### 🔗 Remote Receiver #### 🔗 Remote Receiver
Send logs to a centralized logging service: Send logs to a centralized logging service with authenticated transport:
```typescript ```typescript
import { SmartlogDestinationReceiver } from '@push.rocks/smartlog/destination-receiver'; import { SmartlogDestinationReceiver } from '@push.rocks/smartlog/destination-receiver';
const receiver = new SmartlogDestinationReceiver({ const receiver = new SmartlogDestinationReceiver({
endpoint: 'https://logs.mycompany.com/ingest', passphrase: process.env.LOG_PASSPHRASE,
apiKey: process.env.LOG_API_KEY, receiverEndpoint: 'https://logs.mycompany.com/ingest'
batchSize: 100, // Send logs in batches
flushInterval: 5000 // Flush every 5 seconds
}); });
logger.addLogDestination(receiver); logger.addLogDestination(receiver);
``` ```
Logs are sent as authenticated JSON payloads with SHA-256 hashed passphrases.
### 🛠️ Custom Destinations ### 🛠️ Custom Destinations
Build your own destination for any logging backend: Build your own destination for any logging backend by implementing the `ILogDestination` interface:
```typescript ```typescript
import { ILogDestination, ILogPackage } from '@push.rocks/smartlog/interfaces'; import type { ILogDestination, ILogPackage } from '@push.rocks/smartlog/interfaces';
class ElasticsearchDestination implements ILogDestination { class ElasticsearchDestination implements ILogDestination {
private client: ElasticsearchClient;
constructor(config: ElasticsearchConfig) {
this.client = new ElasticsearchClient(config);
}
async handleLog(logPackage: ILogPackage): Promise<void> { async handleLog(logPackage: ILogPackage): Promise<void> {
await this.client.index({ await this.client.index({
index: `logs-${new Date().toISOString().split('T')[0]}`, index: `logs-${new Date().toISOString().split('T')[0]}`,
@@ -219,10 +277,7 @@ class ElasticsearchDestination implements ILogDestination {
} }
} }
// Use your custom destination logger.addLogDestination(new ElasticsearchDestination());
logger.addLogDestination(new ElasticsearchDestination({
node: 'https://elasticsearch.example.com'
}));
``` ```
## 🎨 Interactive Console Features ## 🎨 Interactive Console Features
@@ -241,19 +296,20 @@ spinner.text('🔄 Fetching data from API...');
// ... perform async operation // ... perform async operation
spinner.finishSuccess('✅ Data fetched successfully!'); spinner.finishSuccess('✅ Data fetched successfully!');
// Chained operations // Chain success and move to next task
spinner spinner.text('📡 Connecting to database');
.text('📡 Connecting to database') // ... connect
.successAndNext('🔍 Running migrations') spinner.successAndNext('🔍 Running migrations');
.successAndNext('🌱 Seeding data') // ... migrate
.finishSuccess('🎉 Database ready!'); spinner.finishSuccess('🎉 Database ready!');
// Customize appearance // Customize appearance
spinner spinner
.setSpinnerStyle('dots') // dots, line, star, simple .setSpinnerStyle('dots') // 'dots' | 'line' | 'star' | 'simple'
.setColor('cyan') // any terminal color .setColor('cyan') // any terminal color
.setSpeed(80) // animation speed in ms .setSpeed(80); // animation speed in ms
.text('🚀 Deploying application...');
spinner.text('🚀 Deploying application...');
// Handle failures // Handle failures
try { try {
@@ -274,12 +330,12 @@ import { SmartlogProgressBar } from '@push.rocks/smartlog/source-interactive';
// Create a progress bar // Create a progress bar
const progress = new SmartlogProgressBar({ const progress = new SmartlogProgressBar({
total: 100, total: 100,
width: 40, width: 40, // Bar width in characters
complete: '█', complete: '█', // Fill character
incomplete: '░', incomplete: '░', // Empty character
showEta: true, showEta: true, // Show estimated time remaining
showPercent: true, showPercent: true, // Show percentage
showCount: true showCount: true // Show current/total count
}); });
// Update progress // Update progress
@@ -290,22 +346,21 @@ for (let i = 0; i <= 100; i++) {
progress.complete(); progress.complete();
// Real-world example: Processing files // Or use increment for simpler tracking
const files = await getFiles(); const files = await getFiles();
const progress = new SmartlogProgressBar({ const fileProgress = new SmartlogProgressBar({ total: files.length });
total: files.length,
width: 50
});
for (const [index, file] of files.entries()) { for (const file of files) {
await processFile(file); await processFile(file);
progress.increment(); // or progress.update(index + 1) fileProgress.increment();
} }
fileProgress.complete();
``` ```
### Non-Interactive Fallback ### 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 [Loading] Connecting to database
@@ -314,86 +369,96 @@ Both spinners and progress bars automatically detect non-interactive environment
Progress: 25% (25/100) Progress: 25% (25/100)
Progress: 50% (50/100) Progress: 50% (50/100)
Progress: 100% (100/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 ## 🔧 Advanced Features
### Context Management ### Minimum Log Level
Add context that flows through your entire application: Set a minimum log level that destinations can use to filter messages:
```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:
```typescript ```typescript
const logger = new Smartlog({ const logger = new Smartlog({
logContext: { environment: 'production' }, logContext: { environment: 'production', runtime: 'node' },
minimumLogLevel: 'warn' // Only warn and above in production minimumLogLevel: 'warn' // Destinations can check this to filter
}); });
// These won't be logged in production // The minimumLogLevel is available as a public property
logger.log('debug', 'Detailed debug info'); console.log(logger.minimumLogLevel); // 'warn'
logger.log('info', 'Regular info');
// These will be logged
logger.log('warn', 'Warning message');
logger.log('error', 'Error message');
``` ```
### Increment Logging ### Increment Logging
Perfect for metrics and counters: Track metrics and counters alongside your logs:
```typescript ```typescript
// Track API calls // Track API calls
logger.increment('info', 'api.requests', { endpoint: '/users', method: 'GET' }); 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: '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 ### Capture All Console Output
Redirect all console.log and console.error through smartlog: Redirect all `console.log` and `console.error` through smartlog:
```typescript ```typescript
logger.enableConsole({ logger.enableConsole({
captureAll: true // Capture all console.* methods captureAll: true
}); });
console.log('This goes through smartlog!'); console.log('This goes through smartlog as info!');
console.error('This too!'); console.error('This goes through smartlog as error!');
// All console output is now structured and routed through your destinations
// 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 ## 🏗️ Real-World Examples
@@ -403,6 +468,7 @@ console.error('This too!');
```typescript ```typescript
import { Smartlog } from '@push.rocks/smartlog'; import { Smartlog } from '@push.rocks/smartlog';
import { SmartlogDestinationClickhouse } from '@push.rocks/smartlog/destination-clickhouse'; import { SmartlogDestinationClickhouse } from '@push.rocks/smartlog/destination-clickhouse';
import { DestinationLocal } from '@push.rocks/smartlog/destination-local';
// Initialize logger with service context // Initialize logger with service context
const logger = new Smartlog({ const logger = new Smartlog({
@@ -410,40 +476,32 @@ const logger = new Smartlog({
company: 'TechCorp', company: 'TechCorp',
companyunit: 'Platform', companyunit: 'Platform',
containerName: 'user-service', containerName: 'user-service',
environment: process.env.NODE_ENV, environment: 'production',
runtime: 'node', runtime: 'node',
zone: process.env.CLOUD_ZONE zone: 'eu-central'
} }
}); });
// Add ClickHouse for analytics // Add ClickHouse for analytics
const clickhouse = await SmartlogDestinationClickhouse.createAndStart({ const clickhouse = await SmartlogDestinationClickhouse.createAndStart({
host: process.env.CLICKHOUSE_HOST, url: process.env.CLICKHOUSE_URL,
database: 'microservices_logs' database: 'microservices_logs'
}); });
logger.addLogDestination(clickhouse); logger.addLogDestination(clickhouse);
// Enable local console for development // Add local console for container stdout
if (process.env.NODE_ENV === 'development') { logger.addLogDestination(new DestinationLocal());
logger.enableConsole();
}
// Express middleware for request tracking // Express middleware for request tracking
app.use((req, res, next) => { app.use((req, res, next) => {
const requestId = generateRequestId(); const logGroup = logger.createLogGroup(req.headers['x-request-id'] || 'unknown');
const logGroup = logger.createLogGroup(requestId);
// Attach logger to request
req.logger = logGroup;
// Log request
logGroup.log('info', 'Incoming request', { logGroup.log('info', 'Incoming request', {
method: req.method, method: req.method,
path: req.path, path: req.path,
ip: req.ip ip: req.ip
}); });
// Track response
res.on('finish', () => { res.on('finish', () => {
logGroup.log('info', 'Request completed', { logGroup.log('info', 'Request completed', {
statusCode: res.statusCode, statusCode: res.statusCode,
@@ -464,7 +522,8 @@ import { SmartlogSourceInteractive, SmartlogProgressBar } from '@push.rocks/smar
const logger = new Smartlog({ const logger = new Smartlog({
logContext: { logContext: {
containerName: 'migration-tool', containerName: 'migration-tool',
environment: 'cli' environment: 'local',
runtime: 'node'
} }
}); });
@@ -473,17 +532,14 @@ logger.enableConsole();
async function migrateDatabase() { async function migrateDatabase() {
const spinner = new SmartlogSourceInteractive(); const spinner = new SmartlogSourceInteractive();
// Connect to database
spinner.text('🔌 Connecting to database...'); spinner.text('🔌 Connecting to database...');
await connectDB(); await connectDB();
spinner.finishSuccess('✅ Connected to database'); spinner.finishSuccess('✅ Connected to database');
// Get migrations
spinner.text('📋 Loading migrations...'); spinner.text('📋 Loading migrations...');
const migrations = await getMigrations(); const migrations = await getMigrations();
spinner.finishSuccess(`✅ Found ${migrations.length} migrations`); spinner.finishSuccess(`✅ Found ${migrations.length} migrations`);
// Run migrations with progress bar
const progress = new SmartlogProgressBar({ const progress = new SmartlogProgressBar({
total: migrations.length, total: migrations.length,
width: 40, width: 40,
@@ -491,13 +547,13 @@ async function migrateDatabase() {
}); });
for (const [index, migration] of migrations.entries()) { 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); await runMigration(migration);
progress.update(index + 1); progress.update(index + 1);
} }
progress.complete(); 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', runtime: 'node',
zone: 'us-east-1' 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()); logger.addLogDestination(new DestinationLocal());
// File for audit trail // File for audit trail
@@ -528,221 +584,121 @@ logger.addLogDestination(new SmartlogDestinationFile('/var/log/app/audit.log'));
// Central logging service // Central logging service
logger.addLogDestination(new SmartlogDestinationReceiver({ logger.addLogDestination(new SmartlogDestinationReceiver({
endpoint: 'https://logs.enterprise.com/ingest', passphrase: process.env.LOG_PASSPHRASE,
apiKey: process.env.LOG_API_KEY, receiverEndpoint: 'https://logs.enterprise.com/ingest'
batchSize: 100,
flushInterval: 5000
})); }));
// Critical error alerts // Custom inline destination for critical alerts
logger.addLogDestination({ logger.addLogDestination({
async handleLog(logPackage) { async handleLog(logPackage) {
if (logPackage.level === 'error' && logPackage.data?.critical) { if (logPackage.level === 'error' && logPackage.data?.critical) {
await sendAlert({ await sendSlackAlert(`🚨 Critical error: ${logPackage.message}`);
channel: 'ops-team',
message: `🚨 Critical error: ${logPackage.message}`,
data: logPackage
});
} }
} }
}); });
``` ```
## 🔌 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 ## 📖 API Reference
### Smartlog Class ### Smartlog Class
```typescript ```typescript
class Smartlog { class Smartlog {
constructor(options?: ISmartlogOptions) // Static factory
static createForCommitinfo(commitinfo: ICommitInfo): Smartlog;
// Logging methods // Constructor
log(level: TLogLevel, message: string, data?: any, correlation?: ILogCorrelation): void constructor(options: {
increment(level: TLogLevel, key: string, data?: any): void 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 // Configuration
enableConsole(options?: IConsoleOptions): void enableConsole(options?: { captureAll: boolean }): void;
addLogDestination(destination: ILogDestination): void addLogDestination(destination: ILogDestination): void;
addLogContext(context: Partial<ILogContext>): void
// Log groups // Correlation
createLogGroup(transactionId?: string): LogGroup createLogGroup(transactionId?: string): LogGroup;
createScope(scopeName: string): Smartlog
// Forwarding (for receiver pattern)
handleLog(logPackage: ILogPackage): Promise<void>;
// Instance identity
uniInstanceId: string;
logContext: ILogContext;
minimumLogLevel: TLogLevel;
} }
``` ```
### Log Levels ### LogGroup Class
```typescript ```typescript
type TLogLevel = class LogGroup {
| 'silly' // Very verbose debugging groupId: string; // Auto-generated unique ID
| 'debug' // Debug information transactionId: string; // The transaction ID passed at creation
| 'info' // Informational messages smartlogRef: Smartlog; // Reference to the parent Smartlog
| 'note' // Notable events
| 'ok' // Success confirmation
| 'success' // Major success
| 'warn' // Warning messages
| 'error' // Error messages
| 'lifecycle' // Application lifecycle events
```
### Log Package Structure log(level: TLogLevel, message: string, data?: any): void;
```typescript
interface ILogPackage {
timestamp: number;
level: TLogLevel;
message: string;
data?: any;
context: ILogContext;
correlation?: ILogCorrelation;
} }
``` ```
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. **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 ### 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 ### Company Information
Task Venture Capital GmbH Task Venture Capital GmbH
Registered at District court Bremen HRB 35230 HB, Germany 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.

View File

@@ -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 // Create a mock version of the webrequest.postJson method to avoid actual HTTP calls
const originalPostJson = testDestination['webrequest'].postJson; const originalPostJson = testDestination['webrequest'].postJson;
testDestination['webrequest'].postJson = async () => { testDestination['webrequest'].postJson = async () => {
return { return { status: 'ok' };
body: { status: 'ok' },
statusCode: 200
};
}; };
try { try {

View File

@@ -3,6 +3,6 @@
*/ */
export const commitinfo = { export const commitinfo = {
name: '@push.rocks/smartlog', name: '@push.rocks/smartlog',
version: '3.1.10', version: '3.1.11',
description: 'A minimalistic, distributed, and extensible logging tool supporting centralized log management.' description: 'A minimalistic, distributed, and extensible logging tool supporting centralized log management.'
} }

View File

@@ -12,7 +12,7 @@ export interface ISmartlogDestinationReceiverConstructorOptions {
export class SmartlogDestinationReceiver implements ILogDestination { export class SmartlogDestinationReceiver implements ILogDestination {
private options: ISmartlogDestinationReceiverConstructorOptions; private options: ISmartlogDestinationReceiverConstructorOptions;
private webrequest = new plugins.webrequest.WebRequest(); private webrequest = new plugins.webrequest.WebrequestClient();
constructor(optionsArg: ISmartlogDestinationReceiverConstructorOptions) { constructor(optionsArg: ISmartlogDestinationReceiverConstructorOptions) {
this.options = optionsArg; this.options = optionsArg;
@@ -30,6 +30,6 @@ export class SmartlogDestinationReceiver implements ILogDestination {
} }
this.errorCounter++; this.errorCounter++;
}); });
return response.body; return response;
} }
} }