axios-interceptor-logger 1.0.1 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -32,8 +32,8 @@ import { AxiosLoggerSingleton } from "axios-interceptor-logger";
32
32
  // 1. Initialize the Logger Configuration
33
33
  const logger = AxiosLoggerSingleton.getInstance({
34
34
  verbose: true, // Prints ECS logs to the console
35
- maxPayloadBytes: 5000, // Truncates massive JSON bodies
36
- redactKeys: ["password", "secret", "token", "authorization"],
35
+ maxPayloadBytes: 5000, // Caps how many characters of each string field are logged (hard ceiling: 10000)
36
+ redactKeys: ["password", "secret", "token", "authorization", "apikey", "api_key"],
37
37
  ignoreDomains: ["localhost"],
38
38
  });
39
39
 
@@ -52,6 +52,17 @@ await apiClient.post("/login", {
52
52
  });
53
53
  ```
54
54
 
55
+ ### Default Redacted Keys
56
+
57
+ If you don't pass `redactKeys`, any header/body key whose name *contains* one of the following (case-insensitive) is replaced with `[REDACTED]`:
58
+
59
+ ```
60
+ authorization, password, token, secret, cookie, apikey, api_key, api-key,
61
+ privatekey, private_key, private-key
62
+ ```
63
+
64
+ This is a substring match against common secret names — it is **not** a guarantee that every secret-shaped field in your API traffic will be caught. Review the [Responsibility & Disclaimer](#responsibility--disclaimer) section below and pass your own `redactKeys` for anything app-specific (custom auth headers, PII fields, etc.).
65
+
55
66
  ## Environment Variables
56
67
 
57
68
  - `APP_HOST`: If set, the logger will populate the `source.domain` field in the ECS log with this value. If missing, it defaults to `localhost`.
@@ -67,7 +78,7 @@ The logger intercepts the traffic and outputs standard Packetbeat ECS JSON:
67
78
  "agent": {
68
79
  "name": "axios-interceptor-logger",
69
80
  "type": "axios-logger",
70
- "version": "1.0.1"
81
+ "version": "1.0.2"
71
82
  },
72
83
  "event": {
73
84
  "start": "2026-09-11T09:34:31.799Z",
@@ -97,3 +108,27 @@ The logger intercepts the traffic and outputs standard Packetbeat ECS JSON:
97
108
  }
98
109
  }
99
110
  ```
111
+
112
+ ## Requirements
113
+
114
+ - Node.js >= 16 (uses `crypto.randomUUID` and Node's `fs`/streams APIs).
115
+ - **Server-side only.** This package imports Node core modules directly and is not meant to be bundled into a browser build — using it there will throw or fail to bundle.
116
+
117
+ ## Responsibility & Disclaimer
118
+
119
+ This library sits between your code and the network, so "is my logging safe/reliable" depends on more than just this package. To be explicit about where each concern is handled:
120
+
121
+ **This library (`axios-interceptor-logger`) is responsible for:**
122
+ - Not crashing or leaking memory under normal or adversarial payload shapes (bounded recursion depth/keys/array items, backpressure-aware file writes, a circuit breaker on disk errors).
123
+ - Redacting the key names listed in [Default Redacted Keys](#default-redacted-keys), or any additional keys you pass via `redactKeys`.
124
+ - Falling back to `console` output if the configured log file becomes unwritable, so log entries aren't silently dropped.
125
+
126
+ **`axios` (the peer dependency) is responsible for:**
127
+ - Actually performing the HTTP request/response lifecycle this library hooks into — bugs in header normalization, redirects, or request/response shape are upstream of this package. Pin an `axios@^1.x` version you trust and keep it patched.
128
+
129
+ **Your application (the dependent) is responsible for:**
130
+ - **Log rotation and disk space.** This library appends to a single file forever; it does not rotate or cap file size. Point `filePath` at a location managed by `logrotate`, your process manager, or a log-shipping sidecar — otherwise the log file will grow until the disk fills up.
131
+ - **Writable storage.** On read-only filesystems (some serverless/container platforms), file writes will fail and the logger falls back to `console`; set `filePath` to a writable path (e.g. `/tmp`) explicitly rather than relying on the fallback.
132
+ - **What actually counts as sensitive for your API.** The default `redactKeys` only catches common secret *names*. Fields like SSNs, card numbers, or app-specific PII in the body will be logged in cleartext (subject to the per-string truncation cap) unless you add them to `redactKeys` or route those endpoints through `ignoreDomains`.
133
+ - **Compliance requirements** (GDPR, PCI-DSS, etc.) for what you're allowed to persist to disk — this library logs request/response bodies by design; it's your call which traffic should go through it.
134
+ - **Throughput at scale.** Sanitizing and serializing very large request/response bodies is synchronous CPU work (deferred one tick via `setImmediate`, not offloaded to a worker). For endpoints with large payloads, consider `ignoreDomains` or trimming what you send/receive.
package/dist/index.js CHANGED
@@ -1,2 +1,3 @@
1
- "use strict";var k=Object.create;var h=Object.defineProperty;var P=Object.getOwnPropertyDescriptor;var U=Object.getOwnPropertyNames;var q=Object.getPrototypeOf,b=Object.prototype.hasOwnProperty;var N=(s,e)=>{for(var t in e)h(s,t,{get:e[t],enumerable:!0})},C=(s,e,t,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let i of U(e))!b.call(s,i)&&i!==t&&h(s,i,{get:()=>e[i],enumerable:!(r=P(e,i))||r.enumerable});return s};var D=(s,e,t)=>(t=s!=null?k(q(s)):{},C(e||!s||!s.__esModule?h(t,"default",{value:s,enumerable:!0}):t,s)),O=s=>C(h({},"__esModule",{value:!0}),s);var M={};N(M,{AxiosLoggerSingleton:()=>T});module.exports=O(M);var v=D(require("crypto"));function g(s,e=["authorization","password","token","secret","cookie"],t=10240){let r=new WeakSet;function i(n,u=0){if(n==null)return n;if(typeof n!="object"&&typeof n!="function")return typeof n=="string"&&Buffer.byteLength(n,"utf8")>t?n.substring(0,t/2)+`... [TRUNCATED ${Buffer.byteLength(n,"utf8")-t/2} BYTES]`:n;if(Buffer.isBuffer(n)||n instanceof ArrayBuffer||typeof Blob<"u"&&n instanceof Blob){let o=Buffer.isBuffer(n)?"Buffer":n instanceof ArrayBuffer?"ArrayBuffer":"Blob",f=n.byteLength||n.size||0;return{"[BINARY_DATA]":`<Type: ${o}, Size: ${f} bytes>`}}if(r.has(n))return"[CIRCULAR_REFERENCE]";if(r.add(n),Array.isArray(n))return n.map(o=>i(o,u+1));let a={};for(let o of Object.keys(n))e.some(f=>o.toLowerCase().includes(f.toLowerCase()))?a[o]="[REDACTED]":a[o]=i(n[o],u+1);return r.delete(n),a}try{let n=JSON.stringify(s);if(n&&Buffer.byteLength(n,"utf8")>t*2)return{"[TRUNCATED_NESTED_DEPTH]":!0}}catch{}return i(s)}var m=class{log(e){console.log(JSON.stringify(e,null,2))}};var l=D(require("fs")),y=D(require("path")),c=class{filePath;constructor(e){this.filePath=e||y.join(process.cwd(),"logs","http-transactions.log"),this.ensureDirectoryExists(this.filePath)}ensureDirectoryExists(e){let t=y.dirname(e);l.existsSync(t)||l.mkdirSync(t,{recursive:!0})}log(e){let t=JSON.stringify(e)+`
2
- `;l.appendFile(this.filePath,t,r=>{r&&console.error(`Failed to write to log file: ${this.filePath}`,r)})}};var L="_axios_logging_metadata";function w(s){return/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(s)}var T=class s{static instance;config;transport;constructor(e){this.config=e||{},this.config.customLogger?this.transport=this.config.customLogger:this.config.transportMode==="otlp"?(console.warn("OTLP transport is marked as future scope. Falling back to FileTransport."),this.transport=new c(this.config.filePath)):this.config.transportMode==="file"||!this.config.transportMode?this.transport=new c(this.config.filePath):this.transport=new c(this.config.filePath)}static getInstance(e){return s.instance||(s.instance=new s(e)),s.instance}attach(e){let t=e.interceptors.request.use(i=>this.handleRequest(i),i=>Promise.reject(i)),r=e.interceptors.response.use(i=>this.handleResponse(i),i=>this.handleError(i));return{eject:()=>{e.interceptors.request.eject(t),e.interceptors.response.eject(r)}}}isDomainIgnored(e){if(!e||!this.config.ignoreDomains)return!1;let t=e.url||"";e.baseURL&&!t.startsWith("http")&&(t=e.baseURL.replace(/\/+$/,"")+"/"+t.replace(/^\/+/,""));try{let r=new URL(t,t.startsWith("/")?"http://localhost":void 0);return this.config.ignoreDomains.includes(r.hostname)}catch{return!1}}handleRequest(e){if(this.isDomainIgnored(e))return e;let t=e.headers["X-Correlation-ID"];return(!t||!w(t))&&(t=v.default.randomUUID()),e.headers["X-Correlation-ID"]=t,e[L]={id:t,startTime:Date.now(),logged:!1},e}handleResponse(e){return this.isDomainIgnored(e.config)||this.logTransaction(e.config,e,null),e}handleError(e){if(e.config&&this.isDomainIgnored(e.config))return Promise.reject(e);let t=e.config,r,i=!1;if(t&&(r=t[L]),!r){let n;t&&t.headers&&t.headers["X-Correlation-ID"]?n=t.headers["X-Correlation-ID"]:e.response&&e.response.config&&e.response.config.headers["X-Correlation-ID"]?n=e.response.config.headers["X-Correlation-ID"]:e.request&&typeof e.request.getHeader=="function"&&(n=e.request.getHeader("X-Correlation-ID")),(!n||!w(n))&&(n=v.default.randomUUID(),i=!0),r={id:n,startTime:Date.now(),logged:!1},t&&(t[L]=r)}return this.logTransaction(t,e.response,e,i),Promise.reject(e)}logTransaction(e,t,r,i=!1){if(!e)return;let n=e[L];if(!n||n.logged)return;n.logged=!0;let u=Date.now()-n.startTime,a=e.url||"";e.baseURL&&!a.startsWith("http")&&(a=e.baseURL.replace(/\/+$/,"")+"/"+a.replace(/^\/+/,""));let o,f;try{o=new URL(a,a.startsWith("/")?"http://localhost":void 0),o.port&&(f=parseInt(o.port,10))}catch{}let A=e.headers?g({...e.headers},this.config.redactKeys,this.config.maxPayloadBytes):{},I;A["content-length"]?I=parseInt(A["content-length"],10):e.data&&typeof e.data=="string"&&(I=Buffer.byteLength(e.data,"utf8"));let E=new Date().toISOString(),B=new Date(n.startTime).toISOString(),S=u*1e6,R=o?o.hostname:"unknown",p={"@timestamp":E,ecs:{version:"8.0.0"},agent:{name:"axios-interceptor-logger",type:"axios-logger",version:"1.0.1"},event:{start:B,end:E,duration:S,kind:"event",category:["network"],type:["connection","protocol"],dataset:"http"},http:{request:{method:(e.method||"GET").toUpperCase(),bytes:I,headers:A,body:e.data?{content:g(e.data,this.config.redactKeys,this.config.maxPayloadBytes)}:void 0},version:"1.1"},url:{full:a,path:o?o.pathname:a,query:o&&o.search?o.search.replace("?",""):void 0,scheme:o?o.protocol.replace(":",""):"unknown",domain:R,port:f},source:{domain:typeof process<"u"&&process.env&&process.env.APP_HOST?process.env.APP_HOST:"localhost"},destination:{domain:R},server:{domain:R},network:{protocol:"http"},status:r?"Error":"OK","trace.id":n.id};if(t){let d=t.headers?g({...t.headers},this.config.redactKeys,this.config.maxPayloadBytes):{},x;d["content-length"]?x=parseInt(d["content-length"],10):t.data&&typeof t.data=="string"&&(x=Buffer.byteLength(t.data,"utf8")),p.http.response={status_code:t.status,headers:d,body:t.data?{content:g(t.data,this.config.redactKeys,this.config.maxPayloadBytes)}:void 0,bytes:x}}r&&(p.error={message:r.message,code:r.code||r.code}),this.config.verbose&&new m().log(p),this.transport.log(p)}};0&&(module.exports={AxiosLoggerSingleton});
1
+ "use strict";var M=Object.create;var A=Object.defineProperty;var B=Object.getOwnPropertyDescriptor;var U=Object.getOwnPropertyNames;var b=Object.getPrototypeOf,P=Object.prototype.hasOwnProperty;var q=(i,e)=>{for(var t in e)A(i,t,{get:e[t],enumerable:!0})},I=(i,e,t,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let n of U(e))!P.call(i,n)&&n!==t&&A(i,n,{get:()=>e[n],enumerable:!(r=B(e,n))||r.enumerable});return i};var _=(i,e,t)=>(t=i!=null?M(b(i)):{},I(e||!i||!i.__esModule?A(t,"default",{value:i,enumerable:!0}):t,i)),O=i=>I(A({},"__esModule",{value:!0}),i);var Y={};q(Y,{AxiosLoggerSingleton:()=>S});module.exports=O(Y);var v=_(require("crypto"));var X=["authorization","password","token","secret","cookie","apikey","api_key","api-key","privatekey","private_key","private-key"];function m(i,e=X,t=10240){let r=Math.min(Math.max(0,t||0),1e4),n=new WeakSet;function o(s,l=0){if(s==null)return s;if(l>=10)return"[MAX_DEPTH_EXCEEDED]";if(typeof s!="object"&&typeof s!="function")return typeof s=="string"?s.length>r?s.substring(0,r)+`... [TRUNCATED ${s.length-r} CHARACTERS]`:s:typeof s=="bigint"?s.toString():s;if(Buffer.isBuffer(s)||s instanceof ArrayBuffer||typeof Blob<"u"&&s instanceof Blob){let f=Buffer.isBuffer(s)?"Buffer":s instanceof ArrayBuffer?"ArrayBuffer":"Blob",c=s.byteLength||s.size||0;return{"[BINARY_DATA]":`<Type: ${f}, Size: ${c} bytes>`}}if(n.has(s))return"[CIRCULAR_REFERENCE]";n.add(s);let a;if(Array.isArray(s)){let f=s.length>100;a=s.slice(0,100).map(c=>o(c,l+1)),f&&a.push(`[... ${s.length-100} MORE ITEMS TRUNCATED]`)}else{a={};let f=Object.keys(s),c=f.length>50;for(let u of f.slice(0,50))if(e.some(g=>u.toLowerCase().includes(g.toLowerCase())))a[u]="[REDACTED]";else try{a[u]=o(s[u],l+1)}catch{a[u]="[UNREADABLE_PROPERTY]"}c&&(a["[TRUNCATED_KEYS]"]=`${f.length-50} more keys omitted`)}return n.delete(s),a}return o(i)}var h=class{log(e){console.log(JSON.stringify(e,null,2))}};var E=_(require("fs")),T=_(require("path"));var d=class{filePath;stream=null;fallbackTransport=null;initializing=!1;backpressured=!1;queue=[];queueBytes=0;MAX_QUEUE_BYTES=20*1024*1024;lastErrorTime=0;COOLDOWN_MS=6e4;constructor(e){this.filePath=e||T.join(process.cwd(),"logs","http-transactions.log")}async initStream(){if(!(this.initializing||this.stream)&&!(Date.now()-this.lastErrorTime<this.COOLDOWN_MS)){this.initializing=!0;try{let e=T.dirname(this.filePath);await E.promises.mkdir(e,{recursive:!0}),this.stream=E.createWriteStream(this.filePath,{flags:"a"}),this.stream.on("error",t=>{this.handleStreamError(t)}),this.stream.on("drain",()=>{this.backpressured=!1,this.flushQueue()}),this.flushQueue()}catch(e){this.handleStreamError(e)}finally{this.initializing=!1}}}handleStreamError(e){let t=!this.fallbackTransport;this.stream&&(this.stream.destroy(),this.stream=null),this.lastErrorTime=Date.now(),t&&(this.fallbackTransport=new h,console.error(`[Axios Logger] Critical failure creating file transport at ${this.filePath}. Falling back to console. Error:`,e)),this.flushQueue()}flushQueue(){if(this.queue.length!==0){if(!this.stream||this.backpressured){if(this.fallbackTransport)for(;this.queue.length>0;){let e=this.queue.shift();e&&(this.queueBytes-=e.length*2,process.stdout.write(e))}return}for(;this.queue.length>0;){let e=this.queue.shift();if(e&&(this.queueBytes-=e.length*2,!this.stream.write(e))){this.backpressured=!0;break}}}}log(e){if(!this.stream&&this.lastErrorTime>0){if(Date.now()-this.lastErrorTime>this.COOLDOWN_MS)this.initStream();else if(this.fallbackTransport){this.fallbackTransport.log(e);return}}!this.stream&&!this.initializing&&this.lastErrorTime===0&&this.initStream();let t;try{t=JSON.stringify(e)+`
2
+ `}catch(r){t=JSON.stringify({error:"Failed to stringify log payload",message:r.message})+`
3
+ `}if(this.stream&&!this.backpressured)this.stream.write(t)||(this.backpressured=!0);else{let r=t.length*2,n=e.status==="Error"||!!e.error,o=this.MAX_QUEUE_BYTES*.8;if(this.queueBytes+r<(n?this.MAX_QUEUE_BYTES:o))this.queue.push(t),this.queueBytes+=r;else{let s=Date.now();(s-this.lastDropWarningTime>1e4||!this.lastDropWarningTime)&&(this.lastDropWarningTime=s,console.warn(`[Axios Logger] Dropping ${n?"ERROR":"INFO"} logs due to disk backpressure. Queue memory limit reached.`))}}}};var k="1.0.2";var R=Symbol("axios_logging_metadata");function C(i){return/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(i)}function N(){if(!(typeof process<"u"&&!!process.versions&&!!process.versions.node))throw new Error("[axios-interceptor-logger] This library uses Node.js core modules (fs, crypto) and is not supported outside a Node.js runtime (e.g. browser bundles).")}var S=class i{static instance;config;transport;constructor(e){N(),this.config=e||{},this.config.customLogger?this.transport=this.config.customLogger:this.config.transportMode==="otlp"?(console.warn("OTLP transport is marked as future scope. Falling back to FileTransport."),this.transport=new d(this.config.filePath)):this.config.transportMode==="file"||!this.config.transportMode?this.transport=new d(this.config.filePath):this.transport=new d(this.config.filePath)}static getInstance(e){return i.instance||(i.instance=new i(e)),i.instance}attach(e){let t=e.interceptors.request.use(n=>this.handleRequest(n),n=>Promise.reject(n)),r=e.interceptors.response.use(n=>this.handleResponse(n),n=>this.handleError(n));return{eject:()=>{e.interceptors.request.eject(t),e.interceptors.response.eject(r)}}}isDomainIgnored(e){if(!e||!this.config.ignoreDomains)return!1;let t=e.url||"";e.baseURL&&!t.startsWith("http")&&(t=e.baseURL.replace(/\/+$/,"")+"/"+t.replace(/^\/+/,""));try{let r=new URL(t,t.startsWith("/")?"http://localhost":void 0);return this.config.ignoreDomains.includes(r.hostname)}catch{return!1}}handleRequest(e){if(this.isDomainIgnored(e))return e;let t=e.headers["X-Correlation-ID"];return(!t||!C(t))&&(t=v.default.randomUUID()),e.headers["X-Correlation-ID"]=t,e[R]={id:t,startTime:Date.now(),logged:!1},e}handleResponse(e){return this.isDomainIgnored(e.config)||setImmediate(()=>this.logTransaction(e.config,e,null)),e}handleError(e){if(e.config&&this.isDomainIgnored(e.config))return Promise.reject(e);let t=e.config,r,n=!1;if(t&&(r=t[R]),!r){let o;t&&t.headers&&t.headers["X-Correlation-ID"]?o=t.headers["X-Correlation-ID"]:e.response&&e.response.config&&e.response.config.headers["X-Correlation-ID"]?o=e.response.config.headers["X-Correlation-ID"]:e.request&&typeof e.request.getHeader=="function"&&(o=e.request.getHeader("X-Correlation-ID")),(!o||!C(o))&&(o=v.default.randomUUID(),n=!0),r={id:o,startTime:Date.now(),logged:!1},t&&(t[R]=r)}return setImmediate(()=>this.logTransaction(t,e.response,e,n)),Promise.reject(e)}logTransaction(e,t,r,n=!1){if(!e)return;let o=e[R];if(!o||o.logged)return;o.logged=!0;let s=Date.now()-o.startTime,l=e.url||"";e.baseURL&&!l.startsWith("http")&&(l=e.baseURL.replace(/\/+$/,"")+"/"+l.replace(/^\/+/,""));let a,f;try{a=new URL(l,l.startsWith("/")?"http://localhost":void 0),a.port&&(f=parseInt(a.port,10))}catch{}let c=e.headers?m({...e.headers},this.config.redactKeys,this.config.maxPayloadBytes):{},u;c["content-length"]?u=parseInt(c["content-length"],10):e.data&&typeof e.data=="string"&&(u=Buffer.byteLength(e.data,"utf8"));let g=new Date().toISOString(),w=new Date(o.startTime).toISOString(),x=s*1e6,L=a?a.hostname:"unknown",y={"@timestamp":g,ecs:{version:"8.0.0"},agent:{name:"axios-interceptor-logger",type:"axios-logger",version:k},event:{start:w,end:g,duration:x,kind:"event",category:["network"],type:["connection","protocol"],dataset:"http"},http:{request:{method:(e.method||"GET").toUpperCase(),bytes:u,headers:c,body:e.data?{content:m(e.data,this.config.redactKeys,this.config.maxPayloadBytes)}:void 0},version:"1.1"},url:{full:l,path:a?a.pathname:l,query:a&&a.search?a.search.replace("?",""):void 0,scheme:a?a.protocol.replace(":",""):"unknown",domain:L,port:f},source:{domain:typeof process<"u"&&process.env&&process.env.APP_HOST?process.env.APP_HOST:"localhost"},destination:{domain:L},server:{domain:L},network:{protocol:"http"},status:r?"Error":"OK","trace.id":o.id};if(t){let p=t.headers?m({...t.headers},this.config.redactKeys,this.config.maxPayloadBytes):{},D;p["content-length"]?D=parseInt(p["content-length"],10):t.data&&typeof t.data=="string"&&(D=Buffer.byteLength(t.data,"utf8")),y.http.response={status_code:t.status,headers:p,body:t.data?{content:m(t.data,this.config.redactKeys,this.config.maxPayloadBytes)}:void 0,bytes:D}}r&&(y.error={message:r.message?String(r.message):"Unknown error",code:r.code||r.code?String(r.code||r.code):void 0}),this.config.verbose&&new h().log(y),this.transport.log(y)}};0&&(module.exports={AxiosLoggerSingleton});
package/dist/index.mjs CHANGED
@@ -1,2 +1,3 @@
1
- import D from"crypto";function g(o,e=["authorization","password","token","secret","cookie"],t=10240){let s=new WeakSet;function i(n,u=0){if(n==null)return n;if(typeof n!="object"&&typeof n!="function")return typeof n=="string"&&Buffer.byteLength(n,"utf8")>t?n.substring(0,t/2)+`... [TRUNCATED ${Buffer.byteLength(n,"utf8")-t/2} BYTES]`:n;if(Buffer.isBuffer(n)||n instanceof ArrayBuffer||typeof Blob<"u"&&n instanceof Blob){let r=Buffer.isBuffer(n)?"Buffer":n instanceof ArrayBuffer?"ArrayBuffer":"Blob",f=n.byteLength||n.size||0;return{"[BINARY_DATA]":`<Type: ${r}, Size: ${f} bytes>`}}if(s.has(n))return"[CIRCULAR_REFERENCE]";if(s.add(n),Array.isArray(n))return n.map(r=>i(r,u+1));let a={};for(let r of Object.keys(n))e.some(f=>r.toLowerCase().includes(f.toLowerCase()))?a[r]="[REDACTED]":a[r]=i(n[r],u+1);return s.delete(n),a}try{let n=JSON.stringify(o);if(n&&Buffer.byteLength(n,"utf8")>t*2)return{"[TRUNCATED_NESTED_DEPTH]":!0}}catch{}return i(o)}var h=class{log(e){console.log(JSON.stringify(e,null,2))}};import*as l from"fs";import*as m from"path";var c=class{filePath;constructor(e){this.filePath=e||m.join(process.cwd(),"logs","http-transactions.log"),this.ensureDirectoryExists(this.filePath)}ensureDirectoryExists(e){let t=m.dirname(e);l.existsSync(t)||l.mkdirSync(t,{recursive:!0})}log(e){let t=JSON.stringify(e)+`
2
- `;l.appendFile(this.filePath,t,s=>{s&&console.error(`Failed to write to log file: ${this.filePath}`,s)})}};var y="_axios_logging_metadata";function v(o){return/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(o)}var R=class o{static instance;config;transport;constructor(e){this.config=e||{},this.config.customLogger?this.transport=this.config.customLogger:this.config.transportMode==="otlp"?(console.warn("OTLP transport is marked as future scope. Falling back to FileTransport."),this.transport=new c(this.config.filePath)):this.config.transportMode==="file"||!this.config.transportMode?this.transport=new c(this.config.filePath):this.transport=new c(this.config.filePath)}static getInstance(e){return o.instance||(o.instance=new o(e)),o.instance}attach(e){let t=e.interceptors.request.use(i=>this.handleRequest(i),i=>Promise.reject(i)),s=e.interceptors.response.use(i=>this.handleResponse(i),i=>this.handleError(i));return{eject:()=>{e.interceptors.request.eject(t),e.interceptors.response.eject(s)}}}isDomainIgnored(e){if(!e||!this.config.ignoreDomains)return!1;let t=e.url||"";e.baseURL&&!t.startsWith("http")&&(t=e.baseURL.replace(/\/+$/,"")+"/"+t.replace(/^\/+/,""));try{let s=new URL(t,t.startsWith("/")?"http://localhost":void 0);return this.config.ignoreDomains.includes(s.hostname)}catch{return!1}}handleRequest(e){if(this.isDomainIgnored(e))return e;let t=e.headers["X-Correlation-ID"];return(!t||!v(t))&&(t=D.randomUUID()),e.headers["X-Correlation-ID"]=t,e[y]={id:t,startTime:Date.now(),logged:!1},e}handleResponse(e){return this.isDomainIgnored(e.config)||this.logTransaction(e.config,e,null),e}handleError(e){if(e.config&&this.isDomainIgnored(e.config))return Promise.reject(e);let t=e.config,s,i=!1;if(t&&(s=t[y]),!s){let n;t&&t.headers&&t.headers["X-Correlation-ID"]?n=t.headers["X-Correlation-ID"]:e.response&&e.response.config&&e.response.config.headers["X-Correlation-ID"]?n=e.response.config.headers["X-Correlation-ID"]:e.request&&typeof e.request.getHeader=="function"&&(n=e.request.getHeader("X-Correlation-ID")),(!n||!v(n))&&(n=D.randomUUID(),i=!0),s={id:n,startTime:Date.now(),logged:!1},t&&(t[y]=s)}return this.logTransaction(t,e.response,e,i),Promise.reject(e)}logTransaction(e,t,s,i=!1){if(!e)return;let n=e[y];if(!n||n.logged)return;n.logged=!0;let u=Date.now()-n.startTime,a=e.url||"";e.baseURL&&!a.startsWith("http")&&(a=e.baseURL.replace(/\/+$/,"")+"/"+a.replace(/^\/+/,""));let r,f;try{r=new URL(a,a.startsWith("/")?"http://localhost":void 0),r.port&&(f=parseInt(r.port,10))}catch{}let L=e.headers?g({...e.headers},this.config.redactKeys,this.config.maxPayloadBytes):{},T;L["content-length"]?T=parseInt(L["content-length"],10):e.data&&typeof e.data=="string"&&(T=Buffer.byteLength(e.data,"utf8"));let x=new Date().toISOString(),E=new Date(n.startTime).toISOString(),C=u*1e6,A=r?r.hostname:"unknown",p={"@timestamp":x,ecs:{version:"8.0.0"},agent:{name:"axios-interceptor-logger",type:"axios-logger",version:"1.0.1"},event:{start:E,end:x,duration:C,kind:"event",category:["network"],type:["connection","protocol"],dataset:"http"},http:{request:{method:(e.method||"GET").toUpperCase(),bytes:T,headers:L,body:e.data?{content:g(e.data,this.config.redactKeys,this.config.maxPayloadBytes)}:void 0},version:"1.1"},url:{full:a,path:r?r.pathname:a,query:r&&r.search?r.search.replace("?",""):void 0,scheme:r?r.protocol.replace(":",""):"unknown",domain:A,port:f},source:{domain:typeof process<"u"&&process.env&&process.env.APP_HOST?process.env.APP_HOST:"localhost"},destination:{domain:A},server:{domain:A},network:{protocol:"http"},status:s?"Error":"OK","trace.id":n.id};if(t){let d=t.headers?g({...t.headers},this.config.redactKeys,this.config.maxPayloadBytes):{},I;d["content-length"]?I=parseInt(d["content-length"],10):t.data&&typeof t.data=="string"&&(I=Buffer.byteLength(t.data,"utf8")),p.http.response={status_code:t.status,headers:d,body:t.data?{content:g(t.data,this.config.redactKeys,this.config.maxPayloadBytes)}:void 0,bytes:I}}s&&(p.error={message:s.message,code:s.code||s.code}),this.config.verbose&&new h().log(p),this.transport.log(p)}};export{R as AxiosLoggerSingleton};
1
+ import _ from"crypto";var C=["authorization","password","token","secret","cookie","apikey","api_key","api-key","privatekey","private_key","private-key"];function m(o,e=C,t=10240){let r=Math.min(Math.max(0,t||0),1e4),a=new WeakSet;function n(s,l=0){if(s==null)return s;if(l>=10)return"[MAX_DEPTH_EXCEEDED]";if(typeof s!="object"&&typeof s!="function")return typeof s=="string"?s.length>r?s.substring(0,r)+`... [TRUNCATED ${s.length-r} CHARACTERS]`:s:typeof s=="bigint"?s.toString():s;if(Buffer.isBuffer(s)||s instanceof ArrayBuffer||typeof Blob<"u"&&s instanceof Blob){let f=Buffer.isBuffer(s)?"Buffer":s instanceof ArrayBuffer?"ArrayBuffer":"Blob",c=s.byteLength||s.size||0;return{"[BINARY_DATA]":`<Type: ${f}, Size: ${c} bytes>`}}if(a.has(s))return"[CIRCULAR_REFERENCE]";a.add(s);let i;if(Array.isArray(s)){let f=s.length>100;i=s.slice(0,100).map(c=>n(c,l+1)),f&&i.push(`[... ${s.length-100} MORE ITEMS TRUNCATED]`)}else{i={};let f=Object.keys(s),c=f.length>50;for(let u of f.slice(0,50))if(e.some(g=>u.toLowerCase().includes(g.toLowerCase())))i[u]="[REDACTED]";else try{i[u]=n(s[u],l+1)}catch{i[u]="[UNREADABLE_PROPERTY]"}c&&(i["[TRUNCATED_KEYS]"]=`${f.length-50} more keys omitted`)}return a.delete(s),i}return n(o)}var h=class{log(e){console.log(JSON.stringify(e,null,2))}};import*as A from"fs";import*as E from"path";var d=class{filePath;stream=null;fallbackTransport=null;initializing=!1;backpressured=!1;queue=[];queueBytes=0;MAX_QUEUE_BYTES=20*1024*1024;lastErrorTime=0;COOLDOWN_MS=6e4;constructor(e){this.filePath=e||E.join(process.cwd(),"logs","http-transactions.log")}async initStream(){if(!(this.initializing||this.stream)&&!(Date.now()-this.lastErrorTime<this.COOLDOWN_MS)){this.initializing=!0;try{let e=E.dirname(this.filePath);await A.promises.mkdir(e,{recursive:!0}),this.stream=A.createWriteStream(this.filePath,{flags:"a"}),this.stream.on("error",t=>{this.handleStreamError(t)}),this.stream.on("drain",()=>{this.backpressured=!1,this.flushQueue()}),this.flushQueue()}catch(e){this.handleStreamError(e)}finally{this.initializing=!1}}}handleStreamError(e){let t=!this.fallbackTransport;this.stream&&(this.stream.destroy(),this.stream=null),this.lastErrorTime=Date.now(),t&&(this.fallbackTransport=new h,console.error(`[Axios Logger] Critical failure creating file transport at ${this.filePath}. Falling back to console. Error:`,e)),this.flushQueue()}flushQueue(){if(this.queue.length!==0){if(!this.stream||this.backpressured){if(this.fallbackTransport)for(;this.queue.length>0;){let e=this.queue.shift();e&&(this.queueBytes-=e.length*2,process.stdout.write(e))}return}for(;this.queue.length>0;){let e=this.queue.shift();if(e&&(this.queueBytes-=e.length*2,!this.stream.write(e))){this.backpressured=!0;break}}}}log(e){if(!this.stream&&this.lastErrorTime>0){if(Date.now()-this.lastErrorTime>this.COOLDOWN_MS)this.initStream();else if(this.fallbackTransport){this.fallbackTransport.log(e);return}}!this.stream&&!this.initializing&&this.lastErrorTime===0&&this.initStream();let t;try{t=JSON.stringify(e)+`
2
+ `}catch(r){t=JSON.stringify({error:"Failed to stringify log payload",message:r.message})+`
3
+ `}if(this.stream&&!this.backpressured)this.stream.write(t)||(this.backpressured=!0);else{let r=t.length*2,a=e.status==="Error"||!!e.error,n=this.MAX_QUEUE_BYTES*.8;if(this.queueBytes+r<(a?this.MAX_QUEUE_BYTES:n))this.queue.push(t),this.queueBytes+=r;else{let s=Date.now();(s-this.lastDropWarningTime>1e4||!this.lastDropWarningTime)&&(this.lastDropWarningTime=s,console.warn(`[Axios Logger] Dropping ${a?"ERROR":"INFO"} logs due to disk backpressure. Queue memory limit reached.`))}}}};var D="1.0.2";var T=Symbol("axios_logging_metadata");function v(o){return/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(o)}function w(){if(!(typeof process<"u"&&!!process.versions&&!!process.versions.node))throw new Error("[axios-interceptor-logger] This library uses Node.js core modules (fs, crypto) and is not supported outside a Node.js runtime (e.g. browser bundles).")}var L=class o{static instance;config;transport;constructor(e){w(),this.config=e||{},this.config.customLogger?this.transport=this.config.customLogger:this.config.transportMode==="otlp"?(console.warn("OTLP transport is marked as future scope. Falling back to FileTransport."),this.transport=new d(this.config.filePath)):this.config.transportMode==="file"||!this.config.transportMode?this.transport=new d(this.config.filePath):this.transport=new d(this.config.filePath)}static getInstance(e){return o.instance||(o.instance=new o(e)),o.instance}attach(e){let t=e.interceptors.request.use(a=>this.handleRequest(a),a=>Promise.reject(a)),r=e.interceptors.response.use(a=>this.handleResponse(a),a=>this.handleError(a));return{eject:()=>{e.interceptors.request.eject(t),e.interceptors.response.eject(r)}}}isDomainIgnored(e){if(!e||!this.config.ignoreDomains)return!1;let t=e.url||"";e.baseURL&&!t.startsWith("http")&&(t=e.baseURL.replace(/\/+$/,"")+"/"+t.replace(/^\/+/,""));try{let r=new URL(t,t.startsWith("/")?"http://localhost":void 0);return this.config.ignoreDomains.includes(r.hostname)}catch{return!1}}handleRequest(e){if(this.isDomainIgnored(e))return e;let t=e.headers["X-Correlation-ID"];return(!t||!v(t))&&(t=_.randomUUID()),e.headers["X-Correlation-ID"]=t,e[T]={id:t,startTime:Date.now(),logged:!1},e}handleResponse(e){return this.isDomainIgnored(e.config)||setImmediate(()=>this.logTransaction(e.config,e,null)),e}handleError(e){if(e.config&&this.isDomainIgnored(e.config))return Promise.reject(e);let t=e.config,r,a=!1;if(t&&(r=t[T]),!r){let n;t&&t.headers&&t.headers["X-Correlation-ID"]?n=t.headers["X-Correlation-ID"]:e.response&&e.response.config&&e.response.config.headers["X-Correlation-ID"]?n=e.response.config.headers["X-Correlation-ID"]:e.request&&typeof e.request.getHeader=="function"&&(n=e.request.getHeader("X-Correlation-ID")),(!n||!v(n))&&(n=_.randomUUID(),a=!0),r={id:n,startTime:Date.now(),logged:!1},t&&(t[T]=r)}return setImmediate(()=>this.logTransaction(t,e.response,e,a)),Promise.reject(e)}logTransaction(e,t,r,a=!1){if(!e)return;let n=e[T];if(!n||n.logged)return;n.logged=!0;let s=Date.now()-n.startTime,l=e.url||"";e.baseURL&&!l.startsWith("http")&&(l=e.baseURL.replace(/\/+$/,"")+"/"+l.replace(/^\/+/,""));let i,f;try{i=new URL(l,l.startsWith("/")?"http://localhost":void 0),i.port&&(f=parseInt(i.port,10))}catch{}let c=e.headers?m({...e.headers},this.config.redactKeys,this.config.maxPayloadBytes):{},u;c["content-length"]?u=parseInt(c["content-length"],10):e.data&&typeof e.data=="string"&&(u=Buffer.byteLength(e.data,"utf8"));let g=new Date().toISOString(),I=new Date(n.startTime).toISOString(),k=s*1e6,R=i?i.hostname:"unknown",y={"@timestamp":g,ecs:{version:"8.0.0"},agent:{name:"axios-interceptor-logger",type:"axios-logger",version:D},event:{start:I,end:g,duration:k,kind:"event",category:["network"],type:["connection","protocol"],dataset:"http"},http:{request:{method:(e.method||"GET").toUpperCase(),bytes:u,headers:c,body:e.data?{content:m(e.data,this.config.redactKeys,this.config.maxPayloadBytes)}:void 0},version:"1.1"},url:{full:l,path:i?i.pathname:l,query:i&&i.search?i.search.replace("?",""):void 0,scheme:i?i.protocol.replace(":",""):"unknown",domain:R,port:f},source:{domain:typeof process<"u"&&process.env&&process.env.APP_HOST?process.env.APP_HOST:"localhost"},destination:{domain:R},server:{domain:R},network:{protocol:"http"},status:r?"Error":"OK","trace.id":n.id};if(t){let p=t.headers?m({...t.headers},this.config.redactKeys,this.config.maxPayloadBytes):{},S;p["content-length"]?S=parseInt(p["content-length"],10):t.data&&typeof t.data=="string"&&(S=Buffer.byteLength(t.data,"utf8")),y.http.response={status_code:t.status,headers:p,body:t.data?{content:m(t.data,this.config.redactKeys,this.config.maxPayloadBytes)}:void 0,bytes:S}}r&&(y.error={message:r.message?String(r.message):"Unknown error",code:r.code||r.code?String(r.code||r.code):void 0}),this.config.verbose&&new h().log(y),this.transport.log(y)}};export{L as AxiosLoggerSingleton};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "axios-interceptor-logger",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "description": "Axios Unified HTTP Transaction Logger",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -17,7 +17,7 @@
17
17
  ],
18
18
  "scripts": {
19
19
  "build": "tsup src/index.ts --format cjs,esm --dts --clean --minify",
20
- "test": "jest"
20
+ "test": "NODE_OPTIONS='--max-old-space-size=16' node --import tsx --test tests/**/*.test.ts"
21
21
  },
22
22
  "keywords": [
23
23
  "axios",
@@ -30,6 +30,9 @@
30
30
  ],
31
31
  "author": "Kamalyesh Kannadkar <k.kamalyesh@gmail.com>",
32
32
  "license": "MIT",
33
+ "engines": {
34
+ "node": ">=16.0.0"
35
+ },
33
36
  "repository": {
34
37
  "type": "git",
35
38
  "url": "git+https://github.com/kkamalyesh-ops/axios-interceptor-logger.git"
@@ -38,7 +41,6 @@
38
41
  "url": "https://github.com/kkamalyesh-ops/axios-interceptor-logger/issues"
39
42
  },
40
43
  "homepage": "https://github.com/kkamalyesh-ops/axios-interceptor-logger#readme",
41
- "dependencies": {},
42
44
  "peerDependencies": {
43
45
  "axios": "^1.0.0"
44
46
  },
@@ -46,6 +48,7 @@
46
48
  "@types/node": "^20.0.0",
47
49
  "axios": "^1.6.0",
48
50
  "tsup": "^8.0.0",
51
+ "tsx": "^4.23.13",
49
52
  "typescript": "^5.0.0"
50
53
  }
51
54
  }