axios-interceptor-logger 1.0.0 → 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
@@ -13,56 +13,55 @@ This allows you to inject application-layer traffic directly into Elasticsearch/
13
13
  - ⏱️ **Nanosecond Latency**: Tracks transaction duration down to the nanosecond, mapped to `event.duration`.
14
14
  - 🔗 **Correlation IDs**: Generates (or passes through) UUID trace IDs for distributed tracing.
15
15
 
16
- ## Installation from GitHub
16
+ ## Installation
17
17
 
18
- To install this package directly from your public GitHub repository, you first need to push this code to GitHub.
18
+ Install the package directly from npm:
19
19
 
20
- ### 1. Push to GitHub
21
- Create a new public repository on GitHub (e.g., named `axios-interceptor-logger`), and push this code:
22
20
  ```bash
23
- git init
24
- git add .
25
- git commit -m "Initial commit"
26
- git branch -M main
27
- git remote add origin https://github.com/YOUR_GITHUB_USERNAME/axios-interceptor-logger.git
28
- git push -u origin main
21
+ npm install axios-interceptor-logger
29
22
  ```
30
23
 
31
- ### 2. Install in your Consumer App
32
- Once published as a public repository, you or anyone else can install it directly via npm using the GitHub URL format:
33
-
34
- ```bash
35
- npm install github:YOUR_GITHUB_USERNAME/axios-interceptor-logger
36
- ```
37
- *(Replace `YOUR_GITHUB_USERNAME` with your actual GitHub username).*
38
-
39
24
  ## Usage
40
25
 
41
- Import the `AxiosLoggerSingleton` and attach it to your Axios instance(s).
26
+ Import the `AxiosLoggerSingleton` and attach it to your Axios instance(s).
42
27
 
43
28
  ```typescript
44
- import axios from 'axios';
45
- import { AxiosLoggerSingleton } from 'axios-interceptor-logger';
29
+ import axios from "axios";
30
+ import { AxiosLoggerSingleton } from "axios-interceptor-logger";
46
31
 
47
32
  // 1. Initialize the Logger Configuration
48
33
  const logger = AxiosLoggerSingleton.getInstance({
49
34
  verbose: true, // Prints ECS logs to the console
50
- maxPayloadBytes: 5000, // Truncates massive JSON bodies
51
- redactKeys: ['password', 'secret', 'token', 'authorization'],
52
- ignoreDomains: ['localhost'],
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
+ ignoreDomains: ["localhost"],
53
38
  });
54
39
 
55
40
  // 2. Create an Axios Instance
56
41
  const apiClient = axios.create({
57
- baseURL: 'https://api.example.com',
42
+ baseURL: "https://api.example.com",
58
43
  });
59
44
 
60
45
  // 3. Attach the Interceptor
61
46
  logger.attach(apiClient);
62
47
 
63
48
  // 4. Make requests (they will now be automatically logged in ECS format!)
64
- await apiClient.post('/login', { username: 'admin', password: 'supersecretpassword' });
49
+ await apiClient.post("/login", {
50
+ username: "admin",
51
+ password: "supersecretpassword",
52
+ });
53
+ ```
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
+
65
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.).
66
65
 
67
66
  ## Environment Variables
68
67
 
@@ -78,8 +77,8 @@ The logger intercepts the traffic and outputs standard Packetbeat ECS JSON:
78
77
  "ecs": { "version": "8.0.0" },
79
78
  "agent": {
80
79
  "name": "axios-interceptor-logger",
81
- "type": "packetbeat",
82
- "version": "1.0.0"
80
+ "type": "axios-logger",
81
+ "version": "1.0.2"
83
82
  },
84
83
  "event": {
85
84
  "start": "2026-09-11T09:34:31.799Z",
@@ -109,3 +108,27 @@ The logger intercepts the traffic and outputs standard Packetbeat ECS JSON:
109
108
  }
110
109
  }
111
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 S=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?S(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(),k=u*1e6,R=o?o.hostname:"unknown",p={"@timestamp":E,ecs:{version:"8.0.0"},agent:{name:"axios-interceptor-logger",type:"packetbeat",version:"1.0.0"},event:{start:B,end:E,duration:k,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:"packetbeat",version:"1.0.0"},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.0",
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
  }