{"_id":"@app-logs-ai/node-logger","_rev":"4-2beb5b8e99d4623a6e8ea2759d2a20e2","name":"@app-logs-ai/node-logger","dist-tags":{"latest":"0.4.0"},"versions":{"0.1.0":{"name":"@app-logs-ai/node-logger","version":"0.1.0","_id":"@app-logs-ai/node-logger@0.1.0","maintainers":[{"name":"felixiho","email":"okekefelix1@gmail.com"}],"dist":{"shasum":"1208b1017c926ba305eeb220289d23c577df19bd","tarball":"https://registry.npmjs.org/@app-logs-ai/node-logger/-/node-logger-0.1.0.tgz","fileCount":7,"integrity":"sha512-TeJu38+rLsFeDAojjc4uUL2ZrYTEFaYeCJCtXx3WSsFJJwJ2Im/EIRPI/+WVJTgNsgEG86i3eVu6WwnLT3NRMg==","signatures":[{"sig":"MEYCIQDtktzVVXlhnnGVliJKWmhYzksx1xCexDhH2em7jp1DrgIhAMu6Gah840QBZn7zxW2uupr2t6sngoV+CdBBZsG989ob","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":49872},"main":"dist/cjs/index.js","type":"module","types":"dist/esm/index.d.ts","module":"dist/esm/index.js","exports":{".":{"types":"./dist/esm/index.d.ts","import":"./dist/esm/index.js","require":"./dist/cjs/index.js"}},"scripts":{"build":"rm -rf dist && tsc -p tsconfig.json && tsc -p tsconfig.cjs.json && echo '{\"type\":\"module\"}' > dist/esm/package.json && echo '{\"type\":\"commonjs\"}' > dist/cjs/package.json"},"_npmUser":{"name":"felixiho","email":"okekefelix1@gmail.com"},"description":"Backend log collector for AI Application Logs — batches and ships logs to the ingest API.","directories":{},"_nodeVersion":"26.0.0","dependencies":{"pino-abstract-transport":"^2.0.0"},"_hasShrinkwrap":false,"devDependencies":{"typescript":"^5.7.2","@types/node":"^22.10.2"},"_npmOperationalInternal":{"tmp":"tmp/node-logger_0.1.0_1784204156371_0.5384245307560835","host":"s3://npm-registry-packages-npm-production"}},"0.2.0":{"name":"@app-logs-ai/node-logger","version":"0.2.0","_id":"@app-logs-ai/node-logger@0.2.0","maintainers":[{"name":"felixiho","email":"okekefelix1@gmail.com"}],"dist":{"shasum":"d87708181305bf4c5934dfbb16527e4e6c027ce5","tarball":"https://registry.npmjs.org/@app-logs-ai/node-logger/-/node-logger-0.2.0.tgz","fileCount":7,"integrity":"sha512-k8nzChg2+IGoxKbnEyWUn6dZRvOUpxoRMMFEFXju91MGl3d7SkRULoPxzcLBJ5hyQFn+s/SH+7a7uf6pUmKksg==","signatures":[{"sig":"MEUCIQDkhx4YcKcsoVjYcFI4LFHfoI0u3qYlyWy2Mu+Tm4h0XwIgHprTh5BA2eQfqT7RZAG4XR3pMtz+rgsBG0i6tIFIHzU=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":57424},"main":"dist/cjs/index.js","type":"module","types":"dist/esm/index.d.ts","module":"dist/esm/index.js","exports":{".":{"types":"./dist/esm/index.d.ts","import":"./dist/esm/index.js","require":"./dist/cjs/index.js"}},"gitHead":"f4671829caa11437bc55e359c1daa1c1eb7fe20d","scripts":{"build":"rm -rf dist && tsc -p tsconfig.json && tsc -p tsconfig.cjs.json && echo '{\"type\":\"module\"}' > dist/esm/package.json && echo '{\"type\":\"commonjs\"}' > dist/cjs/package.json"},"_npmUser":{"name":"felixiho","email":"okekefelix1@gmail.com"},"_npmVersion":"11.16.0","description":"Backend log collector for AI Application Logs — batches and ships logs to the ingest API.","directories":{},"_nodeVersion":"24.18.0","dependencies":{"pino-abstract-transport":"^2.0.0"},"_hasShrinkwrap":false,"devDependencies":{"typescript":"^5.7.2","@types/node":"^22.10.2"},"_npmOperationalInternal":{"tmp":"tmp/node-logger_0.2.0_1784558989321_0.9163896939083771","host":"s3://npm-registry-packages-npm-production"}},"0.3.0":{"name":"@app-logs-ai/node-logger","version":"0.3.0","_id":"@app-logs-ai/node-logger@0.3.0","maintainers":[{"name":"felixiho","email":"okekefelix1@gmail.com"}],"dist":{"shasum":"45e339e55f7a4284cc64ca1db5c458c93d9792b6","tarball":"https://registry.npmjs.org/@app-logs-ai/node-logger/-/node-logger-0.3.0.tgz","fileCount":7,"integrity":"sha512-lnPjVe0LF7P/1JCoquTfCV6qpsE9dyRm7s6xfn2p9RTT/h9r4P7SLKV1Rvb10sHRhnLmyRnpmMBl+TaW13p7Cg==","signatures":[{"sig":"MEQCIBFoeyz0iiyLDNNxbYherrtiARDxVAQoELNVqWGxdJoyAiAqjtXg2l+aWMdhenRdXYp8dwh8R6EY+S170Rc1yap86A==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":63510},"main":"dist/cjs/index.js","type":"module","types":"dist/esm/index.d.ts","module":"dist/esm/index.js","exports":{".":{"types":"./dist/esm/index.d.ts","import":"./dist/esm/index.js","require":"./dist/cjs/index.js"}},"gitHead":"f4671829caa11437bc55e359c1daa1c1eb7fe20d","scripts":{"build":"rm -rf dist && tsc -p tsconfig.json && tsc -p tsconfig.cjs.json && echo '{\"type\":\"module\"}' > dist/esm/package.json && echo '{\"type\":\"commonjs\"}' > dist/cjs/package.json"},"_npmUser":{"name":"felixiho","email":"okekefelix1@gmail.com"},"_npmVersion":"11.16.0","description":"Backend log collector for AI Application Logs — batches and ships logs to the ingest API.","directories":{},"_nodeVersion":"24.18.0","dependencies":{"pino-abstract-transport":"^2.0.0"},"_hasShrinkwrap":false,"devDependencies":{"typescript":"^5.7.2","@types/node":"^22.10.2"},"_npmOperationalInternal":{"tmp":"tmp/node-logger_0.3.0_1784818487776_0.8921340166544152","host":"s3://npm-registry-packages-npm-production"}},"0.4.0":{"name":"@app-logs-ai/node-logger","version":"0.4.0","description":"Backend log collector for AI Application Logs — batches and ships logs to the ingest API.","type":"module","main":"dist/cjs/index.js","module":"dist/esm/index.js","types":"dist/esm/index.d.ts","exports":{".":{"types":"./dist/esm/index.d.ts","import":"./dist/esm/index.js","require":"./dist/cjs/index.js"}},"scripts":{"build":"rm -rf dist && tsc -p tsconfig.json && tsc -p tsconfig.cjs.json && echo '{\"type\":\"module\"}' > dist/esm/package.json && echo '{\"type\":\"commonjs\"}' > dist/cjs/package.json"},"dependencies":{"pino-abstract-transport":"^2.0.0"},"devDependencies":{"@types/node":"^22.10.2","typescript":"^5.7.2"},"gitHead":"48ed90d7b30aecefccb04526adcf6bc80188da6f","_id":"@app-logs-ai/node-logger@0.4.0","_nodeVersion":"24.18.0","_npmVersion":"11.16.0","dist":{"integrity":"sha512-4br+bUweV3ONpM9xQUAega2ZE+UMm5YdUzrXwi96US+8knzOruGxwTNfkVJWyK+DnPnJyZbhmB3rWmf/hPp0tA==","shasum":"a95e80a287ddd76f58613164e21f35d41f37fc25","tarball":"https://registry.npmjs.org/@app-logs-ai/node-logger/-/node-logger-0.4.0.tgz","fileCount":7,"unpackedSize":66704,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIA46N4UOEqhfPkTiT6oCH/Qhwot8YcyAP0lJOQVbD0eeAiAN8QUONiBMrCp8HEHTVGWDZmo1oWVRC2UCTAMgAxDAbw=="}]},"_npmUser":{"name":"felixiho","email":"okekefelix1@gmail.com"},"directories":{},"maintainers":[{"name":"felixiho","email":"okekefelix1@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/node-logger_0.4.0_1785337757947_0.9362869779109775"},"_hasShrinkwrap":false}},"time":{"created":"2026-07-16T12:15:56.141Z","modified":"2026-07-29T15:09:18.304Z","0.1.0":"2026-07-16T12:15:56.517Z","0.2.0":"2026-07-20T14:49:49.473Z","0.3.0":"2026-07-23T14:54:47.916Z","0.4.0":"2026-07-29T15:09:18.103Z"},"description":"Backend log collector for AI Application Logs — batches and ships logs to the ingest API.","maintainers":[{"name":"felixiho","email":"okekefelix1@gmail.com"}],"readme":"# @app-logs-ai/node-logger\n\nBackend log collector for **AI Application Logs**. Batches log entries and ships\nthem to the ingest API. One-line setup for a Railway-hosted (or any Node) service.\n\n## Install\n\n```bash\nnpm install @app-logs-ai/node-logger\n```\n\n## Direct use\n\n```ts\nimport { createLogger } from \"@app-logs-ai/node-logger\";\n\nconst logger = createLogger({\n  apiKey: process.env.APP_LOGS_KEY!,            // your project API key\n  endpoint: \"https://your-api.example.com/v1/ingest\", // defaults to https://api-app-logs.up.railway.app\n});\n\nlogger.info(\"server started\", { port: 8080 });\nlogger.error(\"checkout failed\", { orderId, status: 500, error: \"ECONNREFUSED\" });\n```\n\n## Exported API\n\n`@app-logs-ai/node-logger` exports:\n\n- `createLogger(options)`\n- `createWinstonTransport(options)`\n- default export (Pino transport target)\n- `recordHttpRequest(client, event)`\n- `recordException(client, event)`\n- `startMetricsHeartbeat(client, options?)`\n- `expressTelemetry(client, options?)`\n\nTypes are exported too (`Logger`, `LoggerOptions`, `Level`, `HttpRequestEvent`,\n`ExceptionEvent`, `MetricsOptions`, `TelemetryOptions`).\n\nEntries are buffered and flushed every 2s or once 20 are queued (both configurable).\nSending is best-effort — logging never throws into your app. On a graceful exit the\nlogger flushes automatically (see **Shutdown durability**); call `await\nlogger.flush()` yourself only if you've set `flushOnExit: false`.\n\n## Bandwidth & durability\n\nThe logger is built to be light on the integrator's network and to not lose logs\non a blip:\n\n- **Batched** — one POST per batch (every `flushIntervalMs`, or when `batchSize`\n  is reached), never one request per log line.\n- **Compressed** — bodies at/above `gzipThreshold` bytes are gzipped (the API\n  decompresses transparently).\n- **Retried** — network errors, `429`, and `5xx` are retried with exponential\n  backoff; if still failing, the batch stays buffered for the next cycle.\n  Non-retryable `4xx` (bad key/payload) is dropped with a throttled `console.warn`\n  rather than retried forever.\n- **Bounded** — the buffer is capped at `maxBufferSize`; during a long outage the\n  oldest entries are dropped so memory can't grow without bound. Dropped entries\n  aren't lost silently — see `fallbackToStderr` below.\n- **Flush on exit** (`flushOnExit`, default on) — on `SIGTERM`/`SIGINT`/`beforeExit`\n  the logger flushes, then checkpoints anything undelivered (to `persistPath` if set,\n  else to stderr). Covers rolling deploys and `docker stop`.\n- **stderr fallback** (`fallbackToStderr`, default on) — when entries can't be\n  delivered and there's no `persistPath`, they're printed to **stderr as JSON** so\n  the platform's log pipeline (`docker logs`, Railway, CloudWatch) captures them\n  instead of losing them. Zero infra required.\n- **Crash-durable** (`persistPath`) — point it at a writable file and undelivered\n  entries survive a restart (checkpointed on flush and on exit, reloaded on startup).\n\n```ts\nconst logger = createLogger({\n  apiKey: process.env.APP_LOGS_KEY!,\n  batchSize: 20,           // flush after N entries\n  flushIntervalMs: 2000,   // …or after this long\n  maxBufferSize: 10000,    // cap while offline (oldest dropped beyond this)\n  maxRetries: 4,           // attempts per flush before re-queueing\n  retryBackoffMs: 500,     // base for exponential backoff\n  gzipThreshold: 1024,     // gzip bodies ≥ 1KB (0 disables)\n  flushOnExit: true,       // flush + checkpoint on graceful shutdown (default)\n  fallbackToStderr: true,  // dump undeliverable entries to stderr (default)\n  persistPath: \"/data/app-logs-buffer.json\", // crash durability (optional)\n});\n```\n\n### Containers: don't lose logs on shutdown\n\n- Without `persistPath`, a graceful stop still delivers (flush on exit) when the API\n  is reachable; if it isn't, entries fall back to **stderr** — captured by your\n  platform's logs. A `SIGKILL`/OOM can't be caught, so nothing survives that without\n  a disk.\n- To survive hard kills, set `persistPath` to a path on a **mounted volume** (an\n  ephemeral container layer is wiped on recreation), e.g. in `docker-compose.yml`:\n\n  ```yaml\n  services:\n    your-app:\n      stop_grace_period: 30s          # give the flush time to finish\n      volumes:\n        - applogs-buffer:/data        # persistPath lives here\n  volumes:\n    applogs-buffer:\n  ```\n\n- If your app does its own signal handling or creates multiple loggers, set\n  `flushOnExit: false` (it calls `process.exit(0)`) and flush them yourself.\n\n### HTTP request sampling\n\n`expressTelemetry` records one `http_request` event per request. On a busy\nservice you can sample the *successful* ones to cut volume — errors (status ≥ 400)\nare always kept:\n\n```ts\napp.use(expressTelemetry(logger, { sampleRate: 0.1 })); // 10% of 2xx/3xx, all errors\n```\n\nNote: sampling scales down recorded request volume, so volume-based metrics\n(request_count, error-rate denominator) under-report by that factor.\n\n## Telemetry helpers\n\n### `recordHttpRequest`\n\nEmit a single completed request as a normalized `http_request` event.\n\n```ts\nimport { createLogger, recordHttpRequest } from \"@app-logs-ai/node-logger\";\n\nconst logger = createLogger({ apiKey: process.env.APP_LOGS_KEY! });\nrecordHttpRequest(logger, {\n  method: \"GET\",\n  route: \"/v1/orders/:id\",\n  status: 200,\n  durationMs: 37,\n  extra: { userId: \"u_123\" },\n});\n```\n\n### `recordException`\n\nEmit an exception event with route/method/status context and a shortened stack.\n\n```ts\nimport { createLogger, recordException } from \"@app-logs-ai/node-logger\";\n\nconst logger = createLogger({ apiKey: process.env.APP_LOGS_KEY! });\n\ntry {\n  await doWork();\n} catch (error) {\n  recordException(logger, {\n    error,\n    route: \"/v1/orders/:id\",\n    method: \"GET\",\n    status: 500,\n    extra: { orderId: \"ord_42\" },\n  });\n}\n```\n\n### `startMetricsHeartbeat`\n\nPeriodically emits process metrics (`rss`, heap, event-loop lag, uptime) as\n`metrics` events. Returns a stop function.\n\n```ts\nimport { createLogger, startMetricsHeartbeat } from \"@app-logs-ai/node-logger\";\n\nconst logger = createLogger({ apiKey: process.env.APP_LOGS_KEY! });\nconst stop = startMetricsHeartbeat(logger, { intervalMs: 15000 });\n\n// On shutdown/tests:\nstop();\nawait logger.flush();\n```\n\n### `expressTelemetry`\n\nExpress middleware that records one `http_request` event per response. You can\nsample successful traffic while always keeping 4xx/5xx.\n\n```ts\nimport express from \"express\";\nimport { createLogger, expressTelemetry, recordException } from \"@app-logs-ai/node-logger\";\n\nconst app = express();\nconst logger = createLogger({ apiKey: process.env.APP_LOGS_KEY! });\n\napp.use(expressTelemetry(logger, { sampleRate: 0.1 }));\n\napp.use((err: unknown, req, res, next) => {\n  recordException(logger, {\n    error: err,\n    route: req.route?.path ?? req.path,\n    method: req.method,\n    status: res.statusCode >= 400 ? res.statusCode : 500,\n  });\n  next(err);\n});\n```\n\n## With Pino\n\n```ts\nimport pino from \"pino\";\n\nconst logger = pino({\n  transport: {\n    target: \"@app-logs-ai/node-logger\",\n    options: { apiKey: process.env.APP_LOGS_KEY },\n  },\n});\n\nlogger.error({ orderId, status: 500 }, \"checkout failed\");\n```\n\nPino levels map to `debug | info | warn | error | fatal`; structured fields become\nthe log entry's `attributes`.\n\n## With Winston\n\n```ts\nimport winston from \"winston\";\nimport { createWinstonTransport } from \"@app-logs-ai/node-logger\";\n\nconst logger = winston.createLogger({\n  level: \"info\",\n  transports: [\n    new winston.transports.Stream({\n      stream: createWinstonTransport({\n        apiKey: process.env.APP_LOGS_KEY!,\n        endpoint: \"https://your-api.example.com/v1/ingest\",\n      }),\n    }),\n  ],\n});\n\nlogger.info(\"server started\", { port: 8080 });\nlogger.error(\"checkout failed\", { orderId, status: 500, error: \"ECONNREFUSED\" });\n```\n\nWinston levels are normalized to `debug | info | warn | error | fatal`.\nCommon HTTP fields (`status`, `statusCode`, `durationMs`, `responseTime`,\n`req/res`, `meta.req/meta.res`) are auto-normalized into `http_request` events\nso request count, error rate, and latency cards can populate without extra code.\n\nProcess metrics are auto-emitted by `createWinstonTransport(...)` and the Pino\ntransport every 15s by default (as `type=metrics`), so the dashboard Process\ncard populates without extra setup. Control this with:\n\n- `enableMetricsHeartbeat: false` to disable\n- `metricsHeartbeatMs: 10000` to change interval\n\n```ts\nnew winston.transports.Stream({\n  stream: createWinstonTransport({\n    apiKey: process.env.APP_LOGS_KEY!,\n    enableMetricsHeartbeat: true,\n    metricsHeartbeatMs: 15000,\n  }),\n});\n```\n","readmeFilename":"README.md"}