{"_id":"@donkeylabs/audit-logs","name":"@donkeylabs/audit-logs","dist-tags":{"latest":"0.1.0"},"versions":{"0.1.0":{"name":"@donkeylabs/audit-logs","version":"0.1.0","description":"Structured audit logging for server and client applications","type":"module","exports":{".":"./src/shared/index.ts","./server":"./src/server/index.ts","./client":"./src/client/index.ts"},"scripts":{"typecheck":"tsc --noEmit","test":"bun test"},"keywords":["audit","logging","security","typescript"],"author":{"name":"donkeylabs"},"license":"MIT","repository":{"type":"git","url":"git+https://github.com/donkeylabs-io/donkeylabs.git","directory":"packages/audit-logs"},"dependencies":{"chalk":"^5.4.1","jsonwebtoken":"^9.0.2","zod":"^4.1.12"},"peerDependencies":{"kysely":"^0.27.6","kysely-bun-sqlite":"^0.4.0"},"devDependencies":{"@types/express":"^5.0.3","@types/jsonwebtoken":"^9.0.9","bun-types":"^1.2.15","typescript":"^5.8.3"},"gitHead":"251335bb027049e9d70c5ae54ac032837a7f5e29","_id":"@donkeylabs/audit-logs@0.1.0","bugs":{"url":"https://github.com/donkeylabs-io/donkeylabs/issues"},"homepage":"https://github.com/donkeylabs-io/donkeylabs#readme","_nodeVersion":"25.2.1","_npmVersion":"11.6.2","dist":{"integrity":"sha512-YOTuuehVwEl1B5oQC9QMR+/cLiDbGT24+y11AvjYkEEsMQPkJzoZiuHq5y/imY4npCdhwZqzZ7kFLyfqs5xOGQ==","shasum":"19c8d9cebd58a9f36ce6da8c0340bb0f3139c67b","tarball":"https://registry.npmjs.org/@donkeylabs/audit-logs/-/audit-logs-0.1.0.tgz","fileCount":25,"unpackedSize":224883,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQDwbiqcyutr/UlfYv/9pnP4e1qKfnlGvLFsfBZtHYhyxgIhANfI4qEn8bD48ARDMSyajg6BBGTEeu90//AQKkYRMpSt"}]},"_npmUser":{"name":"donkey-agent","email":"pacosw@pitsafrp.com"},"directories":{},"maintainers":[{"name":"donkey-agent","email":"pacosw@pitsafrp.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/audit-logs_0.1.0_1766730868028_0.7587537970245617"},"_hasShrinkwrap":false}},"time":{"created":"2025-12-26T06:34:27.932Z","0.1.0":"2025-12-26T06:34:28.207Z","modified":"2025-12-26T06:34:28.515Z"},"maintainers":[{"name":"donkey-agent","email":"pacosw@pitsafrp.com"}],"description":"Structured audit logging for server and client applications","homepage":"https://github.com/donkeylabs-io/donkeylabs#readme","keywords":["audit","logging","security","typescript"],"repository":{"type":"git","url":"git+https://github.com/donkeylabs-io/donkeylabs.git","directory":"packages/audit-logs"},"author":{"name":"donkeylabs"},"bugs":{"url":"https://github.com/donkeylabs-io/donkeylabs/issues"},"license":"MIT","readme":"# @pitsa/audit-logs\n\nA comprehensive audit logging system with real-time streaming, sensitive data redaction, and structured logging.\n\n## Features\n\n- **Structured Logging** - Pre-configured domain loggers (server, db, cache, auth, http, cron)\n- **Request Context** - AsyncLocalStorage-based trace ID propagation\n- **Audit Persistence** - SQLite-based log storage with FTS5 full-text search\n- **Sensitive Data Redaction** - Automatic redaction of passwords, tokens, JWTs, credit cards\n- **Real-time Streaming** - WebSocket-based live log streaming with filters\n- **Retention Management** - Configurable retention periods per log level\n\n## Installation\n\n```bash\nbun add @pitsa/audit-logs\n```\n\nAdd as a workspace dependency in `package.json`:\n\n```json\n{\n  \"dependencies\": {\n    \"@pitsa/audit-logs\": \"workspace:*\"\n  }\n}\n```\n\n## Quick Start\n\n### Basic Logging\n\n```typescript\nimport { logger } from \"@pitsa/audit-logs\";\n\n// Use pre-configured domain loggers\nlogger.server.info(\"Server started on port 8000\");\nlogger.db.tag(\"SLOW\").warn(\"Query took 150ms\");\nlogger.auth.error(\"Login failed\", { userId: 123 });\nlogger.http.debug(\"Request received\", { method: \"GET\", path: \"/api/users\" });\n\n// Tag chaining for additional context\nlogger.auth.tag(\"Login\").tag(\"MFA\").info(\"MFA code sent\");\n```\n\n### Log Levels\n\n| Level    | Priority | Description                          |\n|----------|----------|--------------------------------------|\n| debug    | 0        | Verbose debugging information        |\n| info     | 1        | General informational messages       |\n| warn     | 2        | Warning conditions                   |\n| error    | 3        | Error conditions                     |\n| security | 4        | Security-related events (always logged) |\n\nSet log level via environment:\n\n```bash\nLOG_LEVEL=debug bun run server.ts  # Enable all logs\nLOG_LEVEL=warn bun run server.ts   # Only warn, error, security\n```\n\n### Request Context\n\nWrap request handlers with `runWithRequestContext` for automatic trace ID propagation:\n\n```typescript\nimport { runWithRequestContext, generateTraceId, logger } from \"@pitsa/audit-logs\";\n\napp.use((req, res, next) => {\n  const context = {\n    traceId: generateTraceId(),\n    method: req.method,\n    path: req.path,\n    startTime: Date.now(),\n  };\n\n  runWithRequestContext(context, () => {\n    // All logs within this context will include the trace ID\n    logger.http.info(\"Request started\");\n    next();\n  });\n});\n```\n\n### Structured Events\n\nLog structured events for easy filtering in audit logs:\n\n```typescript\nlogger.auth.event(\"info\", \"login_attempt\", {\n  method: \"password\",\n  success: true,\n});\n\nlogger.http.event(\"warn\", \"rate_limit_exceeded\", {\n  ip: \"1.2.3.4\",\n  limit: 100,\n});\n```\n\n## Server Setup\n\n### Full Audit System\n\n```typescript\nimport { AuditLogSystem, Logger, logger } from \"@pitsa/audit-logs/server\";\n\n// Initialize the audit system\nconst auditSystem = new AuditLogSystem({\n  dbFile: \"./data/audit.db\",\n  runMigrations: true,\n  jwtSecret: process.env.JWT_SECRET,\n  retention: {\n    default: 3,     // 3 months\n    security: 12,   // 1 year\n    error: 6,       // 6 months\n  },\n});\nawait auditSystem.initialize();\n\n// Set global audit service - ALL loggers will automatically use this\n// No need to manually connect individual loggers\nLogger.setGlobalAuditService(auditSystem.service);\n\n// Now ALL loggers persist to the database automatically:\nlogger.auth.warn(\"Failed login attempt\", { userId: 123 });\n\n// Custom loggers also work without explicit connection:\nconst myLogger = new Logger(\"MyModule\");\nmyLogger.warn(\"This is also persisted!\"); // Uses global audit service\n\n// Loggers from other packages (like bun-server-core) also work:\n// The trace ID is automatically propagated via AsyncLocalStorage\n```\n\n### Express Middleware\n\n```typescript\nimport { createAuditMiddleware, createRequestLogger } from \"@pitsa/audit-logs/server\";\n\n// Add audit context to requests\napp.use(createAuditMiddleware({\n  excludePaths: [\"/health\", \"/metrics\"],\n  extractContext: (req) => ({\n    userId: req.user?.id,\n    username: req.user?.email,\n  }),\n}));\n\n// Log all requests\napp.use(createRequestLogger());\n```\n\n### WebSocket Streaming\n\n```typescript\n// Handle WebSocket upgrades for real-time log streaming\nserver.on(\"upgrade\", (request, socket, head) => {\n  if (request.url === \"/audit/stream\") {\n    auditSystem.handleUpgrade(request, socket, head);\n  }\n});\n```\n\n## API Reference\n\n### Logger\n\n```typescript\nimport { Logger, logger } from \"@pitsa/audit-logs\";\n\n// Pre-configured loggers\nlogger.server  // Server operations\nlogger.db      // Database operations\nlogger.cache   // Cache operations\nlogger.auth    // Authentication\nlogger.http    // HTTP requests\nlogger.cron    // Scheduled jobs\n\n// Create custom logger\nconst myLogger = new Logger(\"MyModule\");\n\n// Fire-and-forget methods (logs persist asynchronously)\nmyLogger.debug(...args)           // Debug level\nmyLogger.info(...args)            // Info level\nmyLogger.warn(...args)            // Warning level\nmyLogger.error(...args)           // Error level\nmyLogger.success(...args)         // Success (info level, green badge)\nmyLogger.security(event, meta)    // Security event (always persisted)\nmyLogger.event(level, name, meta) // Structured event\n\n// Awaitable methods (use these in route handlers to ensure persistence before response)\nawait myLogger.infoAsync(...args)            // Awaitable info\nawait myLogger.warnAsync(...args)            // Awaitable warning\nawait myLogger.errorAsync(...args)           // Awaitable error\nawait myLogger.eventAsync(level, name, meta) // Awaitable structured event\n\n// Tag for additional context (also supports async methods)\nmyLogger.tag(\"Subsystem\").info(\"Message\")\nawait myLogger.tag(\"Subsystem\").infoAsync(\"Message\")\n\n// Instance-level audit connection\nmyLogger.connectAudit(auditService)\nmyLogger.disconnectAudit()\nmyLogger.isAuditConnected          // boolean\n\n// Static methods for global configuration\nLogger.setGlobalAuditService(service)  // All loggers use this by default\nLogger.clearGlobalAuditService()       // Clear global service\nLogger.hasGlobalAuditService           // boolean\n\n// Log level control\nLogger.setLevel(\"debug\")               // Set global log level\nLogger.setLevel(null)                  // Reset to environment default\nLogger.getEffectiveLevel()             // Get current level\nawait Logger.silent(() => fn())        // Run with logs disabled\nawait Logger.withLevel(\"debug\", fn)    // Run with specific level\n```\n\n### AuditLogService\n\n```typescript\nimport { AuditLogService } from \"@pitsa/audit-logs/server\";\n\n// Query logs\nconst logs = await service.query({\n  userId: 123,\n  minLevel: \"warn\",\n  startTime: Date.now() - 86400000, // Last 24 hours\n  limit: 100,\n});\n\n// Full-text search\nconst results = await service.search(\"login failed\");\n\n// Get statistics\nconst stats = await service.getStats({\n  startTime: Date.now() - 604800000, // Last week\n});\n\n// Cleanup old logs\nconst deleted = await service.cleanupOldLogs();\n```\n\n### Redactor\n\n```typescript\nimport { Redactor, defaultRedactor } from \"@pitsa/audit-logs/server\";\n\n// Use default redactor\nconst safe = defaultRedactor.redact({\n  username: \"john\",\n  password: \"secret123\",  // Will be [REDACTED]\n  apiKey: \"sk-abc123\",    // Will be [REDACTED]\n});\n\n// Custom patterns\nconst customRedactor = new Redactor([\n  { type: \"field\", pattern: /internalId/i },\n  { type: \"value\", pattern: /^CUSTOM-\\d+$/ },\n]);\n```\n\n## Log Entry Schema\n\n```typescript\ninterface LogEntry {\n  id: string;\n  timestamp: number;\n  level: \"debug\" | \"info\" | \"warn\" | \"error\" | \"security\";\n  event: string;\n\n  // Context\n  userId?: number;\n  companyId?: number;\n  employeeId?: number;\n  username?: string;\n\n  // Request info\n  ipAddress?: string;\n  userAgent?: string;\n  geoCountry?: string;\n  geoCity?: string;\n\n  // API-specific\n  method?: string;\n  path?: string;\n  statusCode?: number;\n  durationMs?: number;\n\n  // Payload\n  metadata?: object;\n  message?: string;\n\n  // Correlation\n  traceId?: string;\n}\n```\n\n## Retention Configuration\n\nConfigure how long logs are retained:\n\n```typescript\nconst retention = {\n  default: 3,     // 3 months for most logs\n  security: 12,   // 1 year for security events\n  error: 6,       // 6 months for errors\n  warn: 3,        // 3 months for warnings\n  info: 3,        // 3 months for info\n  debug: 1,       // 1 month for debug logs\n};\n```\n\n## Testing\n\nUse in-memory SQLite for tests:\n\n```typescript\nimport { Database } from \"bun:sqlite\";\nimport { Kysely, BunSqliteDialect } from \"kysely\";\nimport { AuditLogService } from \"@pitsa/audit-logs/server\";\n\n// Create in-memory database for tests\nconst sqlite = new Database(\":memory:\");\nconst db = new Kysely({ dialect: new BunSqliteDialect({ database: sqlite }) });\n\n// Run migrations manually or use test utilities\nconst service = new AuditLogService(db);\n```\n\nSilence logs in tests:\n\n```typescript\nimport { Logger } from \"@pitsa/audit-logs\";\n\n// In test setup\nbeforeAll(() => Logger.setLevel(\"silent\"));\nafterAll(() => Logger.setLevel(null));\n\n// Or wrap specific code\nawait Logger.silent(async () => {\n  // Logs disabled here\n});\n```\n\n## Scaling & Production\n\n### WebSocket Limitations\n\nThe WebSocket hub maintains in-memory connection state. This means:\n\n- **Single-instance deployment**: WebSocket connections are not synchronized across multiple server instances\n- For multi-instance deployments, consider adding a pub/sub layer (Redis) or routing WebSocket connections to a dedicated instance\n- Connection limits and rate limiting are per-instance\n\n### Configuration Options\n\n```typescript\nconst auditSystem = await AuditLogSystem.create({\n  dbFile: \"./audit.db\",\n  websocket: {\n    maxConnectionsPerUser: 5,      // Max connections per user (default: 5)\n    rateLimitMessages: 100,        // Max messages per window (default: 100)\n    rateLimitWindowMs: 60000,      // Rate limit window in ms (default: 1 minute)\n  },\n});\n```\n\n### Monitoring Audit Failures\n\nTrack audit log persistence failures for alerting:\n\n```typescript\nimport { auditMetrics } from \"@pitsa/audit-logs/server\";\n\n// Get current failure metrics\nconst metrics = auditMetrics.getSnapshot();\nconsole.log({\n  failureCount: metrics.failureCount,\n  lastFailureAt: metrics.lastFailureAt,\n  lastError: metrics.lastError,\n});\n\n// Example: Alert if failure rate is high\nsetInterval(() => {\n  const { failureCount } = auditMetrics.getSnapshot();\n  if (failureCount > 100) {\n    // Send alert to monitoring system\n  }\n}, 60000);\n\n// Reset metrics after handling (e.g., after sending alert)\nauditMetrics.reset();\n```\n\n### Performance Considerations\n\n- **Database indexes**: The schema includes indexes for `timestamp`, `user_id`, `company_id`, `event`, `level`, `ip_address`, and `trace_id`\n- **FTS5 search**: Full-text search uses SQLite FTS5 for efficient text queries\n- **WAL mode**: SQLite uses WAL journal mode for better concurrent read performance\n- **Batch inserts**: Use `logBatch()` for inserting multiple entries efficiently\n\n## License\n\nPrivate - PITSA FRP\n","readmeFilename":"README.md","_rev":"1-7376d2b50c51ddced8f85db3f40aec81"}