@wai-industries/polylogger
v0.1.2
Published
SDK for sending logs, audits, and API access events to PolyLogs service
Downloads
157
Maintainers
Readme
@wai-industries/polylogger
SDK for shipping logs, API access telemetry, and audits from any
Node.js / Express service to a Poly-x poly-logs instance.
npm install @wai-industries/polyloggerThe SDK forwards documents to two endpoints:
| Kind | HTTP | entryType (in body) | Typical fields |
|------|------|-----------------------|----------------|
| Application log (request log, manual log) | POST /v1/logs | application | level, message, context.{route, method, status, latencyMs, ...} |
| API access / latency row | POST /v1/logs | api_access | method, route, status, latencyMs, optional bytesIn / bytesOut |
| Audit | POST /v1/audits | — | service, userId, action, entityType, entityId, result, metadata |
Logs and api-access live in the same poly-logs collection (
logs), distinguished byentryType. Audits live in their ownauditscollection. Every SDK-originated document is stamped withsource: "polyLogger"automatically — you don't set it yourself.
Two integration approaches
Pick whichever fits your project.
A. All endpoints (one-line wiring)
import express from 'express';
import {
traceMiddleware,
createPolyLoggerTelemetryMiddleware,
createAuditMiddleware,
} from '@wai-industries/polylogger';
const app = express();
app.use(traceMiddleware); // adds x-trace-id (recommended)
app.use(express.json());
app.use(createPolyLoggerTelemetryMiddleware({
serviceName: process.env.SERVICE_NAME,
// organizationCode (or per-request headers) — see below
organizationCode: process.env.DEFAULT_ORGANIZATION_CODE,
}));
app.use(createAuditMiddleware({
serviceName: process.env.SERVICE_NAME,
organizationCode: process.env.DEFAULT_ORGANIZATION_CODE,
// default auditMethods: POST/PUT/PATCH/DELETE
// default auditEntityType: 'HTTP'
}));
app.get('/v1/orders/:id', /* ... */);
app.post('/v1/orders', /* ... */);
app.listen(4000);Every non-OPTIONS / non-HEAD request hitting your app now produces:
- one
api_accessrow with method/route/status/latency, - one
applicationrequest log (request body / query / select headers), - one audit row for mutating verbs.
/metrics, /favicon.ico, /health, /status are skipped by default.
B. Specific endpoints only
Use includePathPrefixes (or includePaths for exact matches) to cover only
parts of your API surface:
app.use(createPolyLoggerTelemetryMiddleware({
serviceName: 'orders-api',
includePathPrefixes: ['/v1/orders', '/v1/checkout'],
skip: ['/v1/orders/internal-hook'], // override defaults if needed
}));
app.use(createAuditMiddleware({
serviceName: 'orders-api',
includePathPrefixes: ['/v1/orders'],
auditMethods: ['POST', 'PUT', 'DELETE'],
auditEntityType: 'ORDER',
}));You can also call the SDK manually from inside a single handler:
import { logInfo, emitAudit } from '@wai-industries/polylogger';
app.post('/v1/checkout', async (req, res) => {
// ...do work...
void logInfo('checkout.completed', {
organizationCode: req.headers['organization-code'] as string,
route: '/v1/checkout',
method: 'POST',
status: 200,
cartId: 'cart_42',
});
void emitAudit(req, {
action: 'CHECKOUT_COMPLETE',
entityType: 'cart',
entityId: 'cart_42',
result: 'SUCCESS',
});
res.json({ ok: true });
});Required configuration
Environment variables (consumer service)
| Variable | Required | Why |
|---|---|---|
| JWT_SECRET | yes | Shared with poly-logs / poly-auth. The SDK signs short-lived service tokens with it; poly-logs verifies them. Without it the SDK drops events. |
| POLY_LOGS_SERVICE_URL | yes | Base URL of poly-logs. With or without /v1 — the SDK normalizes (e.g. http://localhost:5200 or http://localhost:5200/v1). |
| SERVICE_NAME | yes | Identifier shown in poly-x UI on every log/audit/api_access row, also used as iss on minted JWTs. |
| POLY_LOGS_TIMEOUT_MS | no | Axios timeout per call (default 2000). |
| LOG_LEVEL | no | Winston level for the local console transport. |
| LOGGER_FORMAT | no | Set to json for JSON console output (otherwise pretty + colorized). |
Required fields on a payload
The SDK fills most of these, but you must ensure these exist for every event:
| Field | Source if you don't pass it | Notes |
|---|---|---|
| service | SERVICE_NAME env / middleware option | Required on every payload (logs + audits + api_access). |
| orgId or organizationCode | header (organization-id / organization-code), body (organizationId / organizationCode), query string, or middleware option organizationCode | Required to attribute the event to a tenant. If neither is present, the SDK drops the event. |
| userId | request req.user._id / req.user.id, JWT sub/userId/id, or metadata.userId for audits | Defaulted to 'anonymous' only on audits. |
| traceId | x-trace-id / traceparent header (use traceMiddleware to auto-fill) | Optional but recommended for correlation. |
| source | always polyLogger (set by SDK) | Do not override. |
Sending the org per-request
You typically don't hard-code organizationCode; instead pass headers per
request from your gateway / client:
GET /v1/orders/123
organization-code: ORG-ABC # or
organization-id: 65f9...
x-trace-id: abc-123 # optionalThe middlewares pick them up automatically. If you also set
organizationCode as a middleware option it acts as a fallback when no
header is present.
API reference
Middlewares
createPolyLoggerTelemetryMiddleware(opts?: MiddlewareOptions)Combined: emits application request log + api_access latency row to
POST /v1/logs.
createApiAccessMiddleware(opts?: MiddlewareOptions) // api_access only
createLogMiddleware(opts?: MiddlewareOptions) // application logs only
createAuditMiddleware(opts?: AuditMiddlewareOptions) // audits for mutating verbs
traceMiddleware // adds x-trace-idMiddlewareOptions:
{
serviceName?: string; // default: process.env.SERVICE_NAME
organizationCode?: string; // fallback if no header on the request
polyLogsUrl?: string; // overrides POLY_LOGS_SERVICE_URL
timeoutMs?: number; // overrides POLY_LOGS_TIMEOUT_MS
skip?: string[]; // exact paths to ignore
includePaths?: string[]; // exact paths to include
includePathPrefixes?: string[];// prefixes to include (any one matches)
}AuditMiddlewareOptions extends MiddlewareOptions with:
{
auditMethods?: string[]; // default ['POST','PUT','PATCH','DELETE']
auditEntityType?: string; // default 'HTTP'
}Manual helpers
import {
logInfo, logWarn, logError, logDebug, // -> POST /v1/logs (application)
emitAudit, // -> POST /v1/audits
PolyLogsClient, // -> all three endpoints
POLYLOGGER_LOG_SOURCE, // 'polyLogger'
} from '@wai-industries/polylogger';PolyLogsClient (lower-level):
const client = new PolyLogsClient({
baseUrl: process.env.POLY_LOGS_SERVICE_URL,
serviceName: process.env.SERVICE_NAME,
timeoutMs: 5000,
});
// application log
await client.sendLog(
'info',
'job.finished',
{ jobId: 'abc', durationMs: 142 },
{ organizationCode: 'ORG-ABC', traceId: 'trace-1' },
);
// api_access (lat metric)
await client.sendApiAccess(
{
entryType: 'api_access',
timestamp: Date.now(),
service: process.env.SERVICE_NAME!,
method: 'GET',
route: '/v1/report',
status: 200,
latencyMs: 12,
},
{ organizationCode: 'ORG-ABC' },
);
// audit
await client.sendAudit(
{
timestamp: Date.now(),
userId: 'user-1',
service: process.env.SERVICE_NAME!,
action: 'REPORT_VIEW',
entityType: 'report',
entityId: 'rpt-9',
result: 'SUCCESS',
},
{ organizationCode: 'ORG-ABC' },
);TypeScript exports
import type {
LogEvent,
AuditEvent,
ApiAccessEvent,
SDKConfig,
MiddlewareOptions,
AuditMiddlewareOptions,
AuditEmitterOptions,
SendOptions,
} from '@wai-industries/polylogger';Where do these documents end up in poly-x?
- Tenant views (inside an organization in poly-x UI):
Logs / Audits / Observability only show rows with
source === 'polyLogger', i.e. those emitted by services using this SDK. - System views (poly-x super-admin /
(home)/logs,(home)/audit-trails): Show only platform-internal traffic (poly-x services); SDK rows are excluded so the system view stays focused on the platform itself.
The split is enforced server-side in poly-logs:
GET /v1/logs(tenant) ⇒source === 'polyLogger'GET /v1/logs/system(system) ⇒source !== 'polyLogger'- Same pattern on
/audits,/logs/stats,/logs/metrics,/search/*.
End-to-end demo
A working example app (Express + this SDK) lives at
poly-logger-sdk-demo. It uses the SDK from npm
(no local source link required) and wires up:
traceMiddlewareforx-trace-idcreatePolyLoggerTelemetryMiddleware(all endpoints)createAuditMiddlewarefor/v1/demo/*mutating verbs- a few HTTP routes (
/v1/demo,/v1/demo/audit, …) and read-proxies for poly-logs (/v1/poly-logs/logs,/v1/poly-logs/audits,/v1/poly-logs/logs/stats,/v1/poly-logs/logs/metrics).
Follow poly-logger-sdk-demo/README.md for a step-by-step walkthrough.
Publishing (maintainers only)
npm version patch # bump
npm publish --access publicprepublishOnly runs npm run build (CJS + ESM via tsconfig.cjs.json /
tsconfig.esm.json).
Never commit npm tokens.
License
ISC
