{"id":31625014,"url":"https://github.com/tolongames/scribelog","last_synced_at":"2025-10-06T18:55:22.624Z","repository":{"id":290612851,"uuid":"975039960","full_name":"tolongames/scribelog","owner":"tolongames","description":"An advanced, configurable logging library for Node.js applications.","archived":false,"fork":false,"pushed_at":"2025-08-23T14:18:35.000Z","size":1125,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-10-02T03:46:19.246Z","etag":null,"topics":["express","fastify","koa","logger","logging","nestjs","nextjs","nodejs","npm","opensource","typescript"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/tolongames.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2025-04-29T17:28:51.000Z","updated_at":"2025-08-23T14:18:38.000Z","dependencies_parsed_at":"2025-04-29T18:51:45.907Z","dependency_job_id":null,"html_url":"https://github.com/tolongames/scribelog","commit_stats":null,"previous_names":["tolongames/scribelog"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/tolongames/scribelog","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tolongames%2Fscribelog","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tolongames%2Fscribelog/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tolongames%2Fscribelog/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tolongames%2Fscribelog/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tolongames","download_url":"https://codeload.github.com/tolongames/scribelog/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tolongames%2Fscribelog/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":278663383,"owners_count":26024389,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-06T02:00:05.630Z","response_time":65,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["express","fastify","koa","logger","logging","nestjs","nextjs","nodejs","npm","opensource","typescript"],"created_at":"2025-10-06T18:55:21.515Z","updated_at":"2025-10-06T18:55:22.618Z","avatar_url":"https://github.com/tolongames.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Scribelog 🪵📝\n\n[![npm version](https://img.shields.io/npm/v/scribelog.svg)](https://www.npmjs.com/package/scribelog)\n[![npm downloads](https://img.shields.io/npm/dt/scribelog.svg)](https://www.npmjs.com/package/scribelog)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n[![Build Status](https://github.com/tolongames/scribelog/actions/workflows/node.js.yml/badge.svg)](https://github.com/tolongames/scribelog/actions/workflows/node.js.yml) \u003c!-- Zaktualizuj URL, jeśli trzeba --\u003e\n\n**Scribelog** is an advanced, highly configurable logging library for Node.js applications, written in TypeScript. It offers flexible formatting, support for multiple destinations (transports like Console and File), child loggers, automatic error catching, and printf-style interpolation, aiming for a great developer experience.\n\n---\n\n## ✨ Key Features\n\n**Standard \u0026 Custom Logging Levels:** Use familiar levels (`error`, `warn`, `info`, etc.) or define your own custom levels.\n\n- **Highly Flexible Formatting:** Combine powerful formatters (`simple`, `json`, `timestamp`, `metadata`, `errors`, `splat`) using a composable API (`format.combine`). Customize outputs easily, including color themes.\n- **Printf-Style Logging:** Use `printf`-like placeholders (`%s`, `%d`, `%j`) via `format.splat()` for easy message interpolation.\n- **Console Color Support:** Automatic, readable colorization for the `simple` format in TTY environments, with customizable themes.\n- **Multiple Transports:** Log to different destinations. Built-in `ConsoleTransport` and `FileTransport` with rotation options.\n- **Child Loggers:** Easily create contextual loggers (`logger.child({...})`) that inherit settings but add specific metadata (like `requestId`).\n- **Framework adapters (Express, Koa, Fastify, NestJS, Next.js):**\n  Ready-to-use middleware/hooks/interceptors for request/response logging with automatic requestId (AsyncLocalStorage), duration, status, and framework tags. Minimal boilerplate.\n- **Profiling \u0026 Timing:** Lightweight high-resolution timers (profile/time APIs), including sync/async helpers and start/end merging of metadata.\n  - Configurable levels and thresholds: promote slow operations to warn/error via `thresholdWarnMs`/`thresholdErrorMs` or custom `getLevel(duration, meta)`.\n  - Concurrency-safe timers, orphan cleanup (TTL/maxActive), fast path, configurable tags/fields, and metrics hook.\n- **Automatic Error Handling:** Optionally catch and log `uncaughtException` and `unhandledRejection` events, including stack traces.\n- **Remote Transports (HTTP, WebSocket, TCP, UDP):**\n  Send logs over the network to ELK/Logstash, Graylog, Datadog, or custom collectors. Supports batching (AsyncBatch) and gzip (HTTP).\n- **Tagging \u0026 Context:** Add tags to log messages for easy filtering and richer context. See examples in Quick Start.\n- **Asynchronous Batch Logging:** Buffer and batch log messages before sending them to a target transport to reduce I/O overhead. See examples in Basic Configuration.\n- **Automatic Request/Trace ID Propagation:** Automatically attaches a request/trace ID to every log message using AsyncLocalStorage. See usage in Basic Configuration.\n- **Sensitive Data Masking:** Mask sensitive fields (passwords, tokens, API keys) with the built‑in `format.maskSensitive` formatter. See usage in Basic Configuration.\n- **Logger Lifecycle:** Graceful shutdown via `logger.close()`; optional auto-close on `beforeExit`.\n- **Non-invasive Error Listeners:** Handlers are registered via `process.prependListener`/`process.on` (no removing foreign listeners), configurable modes.\n- **TypeScript First:** Written entirely in TypeScript for type safety and excellent editor autocompletion.\n\n---\n\n## 📦 Installation\n\n```bash\n# Core library\nnpm install scribelog\n# or\nyarn add scribelog\n# or\npnpm add scribelog\n```\n\n---\n\n## 🚀 Quick Start\n\nGet up and running in seconds:\n\n```ts\nimport { createLogger, format } from 'scribelog';\nimport chalk from 'chalk';\n\n// Logger with custom color theme and default settings\nconst logger = createLogger({\n  level: 'debug',\n  format: format.combine(\n    format.timestamp(),\n    format.simple({\n      colors: true,\n      levelColors: {\n        error: chalk.bgRed.white,\n        warn: chalk.yellow.bold,\n        info: chalk.green,\n        debug: chalk.blue,\n      },\n    })\n  ),\n});\n\nlogger.info('Scribelog is ready!');\nlogger.warn('Something seems off...', { detail: 'Cache size exceeded limit' });\nlogger.error('An error occurred!', { error: new Error('Test error') });\n\n// --- NEW: Logging with tags ---\nlogger.info('User login', { tags: ['auth', 'user'], userId: 123 });\n```\n\n**Default Console Output (example):**\n\n```bash\n2025-05-01T12:00:00.123Z [INFO]: Scribelog is ready!\n2025-05-01T12:00:00.125Z [WARN]: Something seems off... { detail: 'Cache size exceeded limit' }\n2025-05-01T12:00:00.127Z [ERROR]: An error occurred! { errorName: 'Error', exception: true }\nError: Test error\n    at \u003canonymous\u003e:... (stack trace)\n2025-05-01T12:00:00.130Z [INFO] [auth, user]: User login { userId: 123 }\n```\n\n## ⏱️ Profiling \u0026 Timing\n\nHigh‑resolution timers for measuring any code block, with smart defaults and advanced controls.\n\nConfiguration (examples):\n\n```ts\nimport { createLogger } from 'scribelog';\n\nconst logger = createLogger({\n  profiler: {\n    // Level heuristics\n    level: 'debug', // base level for profiling logs (optional)\n    thresholdWarnMs: 200,\n    thresholdErrorMs: 1000,\n    // getLevel: (durationMs, meta) =\u003e (durationMs \u003e 500 ? 'warn' : 'info'),\n    // ...existing code...\n    // Metrics hook (no extra log)\n    onMeasure: (e) =\u003e {\n      // e = { label, durationMs, success?: boolean, level, tags?, requestId?, meta, key? }\n      // Send to Prometheus, StatsD, etc.\n    },\n    // Optional: filter/trim metric event before onMeasure\n    onMeasureFilter: (e) =\u003e {\n      if (e.meta) {\n        const { error, stack, ...rest } = e.meta;\n        e.meta = rest; // drop heavy fields\n      }\n      return e; // or return null to drop the event entirely\n    },\n  },\n});\n```\n\nUsage patterns:\n\n```ts\n// 1) Manual start/stop with handle (best for concurrency)\nconst h = logger.profile('db', { query: 'SELECT 1' });\n// ... work ...\nlogger.profileEnd(h, { rows: 10 }); // merges start+end meta and logs\n\n// 2) Aliases (still handle-capable via time/timeEnd)\nlogger.time('calc');\n// ... work ...\nlogger.timeEnd('calc'); // ends the latest 'calc' via LIFO per label\n\n// 3) Sync block helper\nconst value = logger.timeSync('compute', () =\u003e 2 + 2, { component: 'math' });\n\n// 4) Async block helper (logs success or error, rethrows on error)\nawait logger.timeAsync('load-user', async () =\u003e getUser(42), {\n  component: 'users',\n});\n```\n\nNotes:\n\n- Concurrency-safe: profile(label) returns a unique handle; profileEnd(handle) ends that exact timer. If you pass a plain label, LIFO per label is used.\n\n- Fast path: when profiling is effectively disabled (no debug and no thresholds/getLevel/profiler.level), time\\*/profile calls skip overhead and do not log.\n- Orphan cleanup: timers not ended in time are removed by TTL sweep; the oldest timers can be dropped if maxActiveProfiles is exceeded. Stop the sweeper via logger.dispose().\n- Tags \u0026 fields: tagsDefault/tagsMode and fieldsDefault are applied consistently; tags are deduplicated.\n- Add `profiler.onMeasureFilter(event)` to trim or drop heavy fields before `onMeasure` runs.\n- `onMeasure(event)` is still called after each measurement (no extra log produced).\n\n---\n\n## 📘 Full Documentation\n\nThis README covers the basics. For a comprehensive guide covering **all configuration options, formatters (like `json`, custom `timestamp` formats), transports (`FileTransport` with rotation), child loggers, error handling details, and advanced examples**, please see the:\n\n➡️ **[Detailed Documentation](./DOCUMENTATION.md)** ⬅️\n\n---\n\n## ⚙️ Basic Configuration (Overview)\n\nConfigure your logger via `createLogger(options)`. Key options:\n\n- `level`: `'info'` (default), `'debug'`, `'warn'`, etc., or **custom levels** (e.g., `'critical'`, `'trace'`).\n- `format`: Use `format.combine(...)` with formatters like `format.simple()`, `format.json()`, `format.timestamp()`, `format.splat()`, `format.errors()`, `format.metadata()`. Default is `format.defaultSimpleFormat`. You can also define **custom color themes** for log levels.\n- `transports`: Array of `new transports.Console({...})` or `new transports.File({...})`. Default is one Console transport.\n- `defaultMeta`: An object with data to add to every log message.\n- `handleExceptions`, `handleRejections`, `exitOnError`: For automatic error catching.\n- `exceptionHandlerMode` / `rejectionHandlerMode`: `'prepend' | 'append'` — how to register process listeners (default: `'prepend'`).\n- `autoCloseOnBeforeExit`: `boolean` — automatically call `logger.close()` on Node’s `beforeExit` (default: `true`).\n\n**Example 1: Logging JSON to a File**\n\n```ts\nimport { createLogger, format, transports } from 'scribelog';\n\nconst fileJsonLogger = createLogger({\n  level: 'debug',\n  // Use the predefined JSON format (includes error handling, splat, timestamp etc.)\n  format: format.defaultJsonFormat,\n  transports: [\n    new transports.File({\n      filename: 'application.log', // Log to application.log\n      level: 'debug', // Log debug and above to the file\n      size: '10M', // Rotate at 10 MB\n      maxFiles: 5, // Keep 5 rotated files\n    }),\n  ],\n  defaultMeta: { service: 'file-writer' },\n});\n\nfileJsonLogger.debug('Writing JSON log to file', { id: 1 });\nfileJsonLogger.error('File write error occurred', {\n  error: new Error('Disk full'),\n  file: 'data.txt',\n});\n```\n\n**Example 2: Using Custom Levels and Colors**\n\n```ts\nimport { createLogger, format } from 'scribelog';\nimport chalk from 'chalk';\n\nconst customLevels = {\n  critical: 0,\n  error: 1,\n  warn: 2,\n  info: 3,\n  debug: 4,\n  trace: 5,\n};\n\nconst logger = createLogger({\n  levels: customLevels,\n  level: 'trace',\n  format: format.combine(\n    format.timestamp(),\n    format.simple({\n      colors: true,\n      levelColors: {\n        critical: chalk.bgRed.white.bold,\n        error: chalk.red,\n        warn: chalk.yellow,\n        info: chalk.green,\n        debug: chalk.blue,\n        trace: chalk.cyan,\n      },\n    })\n  ),\n});\n\nlogger.critical('Critical issue!');\nlogger.trace('Trace message for debugging.');\n```\n\n**Example 3: Batching logs (AsyncBatch)**\n\n```ts\nimport { createLogger, transports } from 'scribelog';\n\nconst fileTransport = new transports.File({ filename: 'batched.log' });\n\nconst asyncBatch = new transports.AsyncBatch({\n  target: fileTransport,\n  batchSize: 5, // Send logs in batches of 5\n  flushIntervalMs: 2000, // Or every 2 seconds\n});\n\nconst logger = createLogger({ transports: [asyncBatch] });\n\nlogger.info('First log');\nlogger.info('Second log');\n// ...more logs\n```\n\n**Example 4: Request/Trace ID propagation**\n\n```ts\nimport { runWithRequestContext, setRequestId, createLogger } from 'scribelog';\nconst logger = createLogger();\n\n// In your middleware:\napp.use((req, res, next) =\u003e {\n  const reqId = req.headers['x-request-id'] || generateRandomId();\n  runWithRequestContext({ requestId: String(reqId) }, () =\u003e {\n    next();\n  });\n});\n\n// Anywhere later in the same async flow:\nlogger.info('Handled request'); // requestId is attached automatically\n```\n\n**Example 5: Sensitive data masking**\n\n```ts\nimport { createLogger, format } from 'scribelog';\n\nconst logger = createLogger({\n  format: format.combine(\n    format.maskSensitive(['password', 'token', 'apiKey']),\n    format.simple()\n  ),\n});\n\nlogger.info('User login', {\n  username: 'bob',\n  password: 'sekret123',\n  token: 'abc',\n  profile: { apiKey: 'xyz' },\n});\n// Output (example): password: '***', token: '***', profile: { apiKey: '***' }\n```\n\n## Framework adapters (quick integration)\n\nExpress:\n\n```ts\nimport express from 'express';\nimport { createLogger, adapters } from 'scribelog';\n\nconst app = express();\nconst logger = createLogger();\napp.use(adapters.express.createMiddleware({ logger }));\n```\n\nKoa:\n\n```ts\nimport Koa from 'koa';\nimport { createLogger, adapters } from 'scribelog';\n\nconst app = new Koa();\nconst logger = createLogger();\napp.use(adapters.koa.createMiddleware({ logger }));\n```\n\nFastify:\n\n```ts\nimport Fastify from 'fastify';\nimport { createLogger, adapters } from 'scribelog';\n\nconst app = Fastify();\nconst logger = createLogger();\nconst register = adapters.fastify.createPlugin({ logger });\nregister(app);\n```\n\nNestJS (global interceptor):\n\n```ts\nimport { adapters, createLogger } from 'scribelog';\nconst app = await NestFactory.create(AppModule);\nconst logger = createLogger();\napp.useGlobalInterceptors(adapters.nest.createInterceptor({ logger }));\n```\n\nNext.js (API Route):\n\n```ts\nimport { adapters, createLogger } from 'scribelog';\nconst logger = createLogger();\nexport default adapters.next.createApiHandler(\n  async (req, res) =\u003e {\n    res.statusCode = 200;\n    res.end('OK');\n  },\n  { logger }\n);\n```\n\n## Remote transports (network logging)\n\nHTTP (+ batching + gzip):\n\n```ts\nimport { createLogger, transports, format } from 'scribelog';\n\nconst httpT = new transports.Http({\n  url: 'https://logs.example.com/ingest',\n  headers: { 'x-api-key': 'your-key' },\n  compress: true, // gzip the body\n  timeoutMs: 8000,\n  // format defaults to format.defaultJsonFormat if not provided\n});\n\nconst batched = new transports.AsyncBatch({\n  target: httpT,\n  batchSize: 20,\n  flushIntervalMs: 1000,\n});\n\nconst logger = createLogger({\n  transports: [batched],\n  format: format.defaultJsonFormat,\n});\nlogger.info('Remote log', { service: 'api' });\n```\n\nWebSocket (requires ws: npm i ws):\n\n```ts\nimport { createLogger, transports } from 'scribelog';\n\nconst wsT = new transports.WebSocket({ url: 'wss://logs.example.com/stream' });\nconst logger = createLogger({ transports: [wsT] });\nlogger.error('Streamed error', { code: 500 });\n```\n\nTCP (newline-delimited JSON) + batching:\n\n```ts\nimport { createLogger, transports } from 'scribelog';\n\nconst tcp = new transports.Tcp({ host: '127.0.0.1', port: 5000 });\nconst batched = new transports.AsyncBatch({\n  target: tcp,\n  batchSize: 50,\n  flushIntervalMs: 500,\n});\nconst logger = createLogger({ transports: [batched] });\nlogger.info('Hello over TCP', { env: 'prod' });\n```\n\nUDP (best-effort, e.g., GELF-like):\n\n```ts\nimport { createLogger, transports } from 'scribelog';\n\nconst udp = new transports.Udp({ host: '127.0.0.1', port: 12201 });\nconst logger = createLogger({ transports: [udp] });\nlogger.warn('Warning via UDP');\n```\n\nNotes:\n\n- Prefer JSON format for collectors: use format.defaultJsonFormat.\n- Use AsyncBatch to reduce network overhead and smooth bursts.\n- Redact secrets before sending: format.maskSensitive([...]).\n- requestId from async context is automatically attached to logs.\n\n## 🔌 Lifecycle: close() \u0026 beforeExit\n\n- `await logger.close()`: flushes and closes all transports (supports async), stops internal profilers’ cleanup, and removes the internal `beforeExit` hook.\n- `autoCloseOnBeforeExit` (default true): when enabled, the logger registers a `beforeExit` hook that calls `close()` automatically.\n\n---\n\n## 📚 Future Work\n\n- Syslog/journald transports for system logging\n- OpenTelemetry integration (trace/span IDs propagation)\n- More adapters and richer redaction/masking presets\n\n---\n\n## 🤝 Contributing\n\nContributions are welcome! Please feel free to submit issues and pull requests on the [GitHub repository](https://github.com/tolongames/scribelog).\n\n---\n\n## 📄 License\n\nMIT License\nCopyright (c) 2025 tolongames\nSee [LICENSE](./LICENSE) for details.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftolongames%2Fscribelog","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftolongames%2Fscribelog","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftolongames%2Fscribelog/lists"}