axios-interceptor-logger 1.0.0
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 +111 -0
- package/dist/index.d.mts +98 -0
- package/dist/index.d.ts +98 -0
- package/dist/index.js +2 -0
- package/dist/index.mjs +2 -0
- package/package.json +51 -0
package/README.md
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# Axios Interceptor Logger (Packetbeat ECS Format)
|
|
2
|
+
|
|
3
|
+
An advanced Axios interceptor that captures and logs HTTP transactions at the application layer. By hooking directly into Axios, this logger captures **decrypted HTTPS traffic** and formats it natively into the **Elastic Common Schema (ECS)**, perfectly mirroring the JSON structure of network sniffers like Packetbeat.
|
|
4
|
+
|
|
5
|
+
This allows you to inject application-layer traffic directly into Elasticsearch/Kibana alongside your raw network metrics without requiring complex Logstash mapping!
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- 🔐 **Native HTTPS Interception**: Logs exact request and response payloads, completely bypassing TLS encryption limitations.
|
|
10
|
+
- 🔄 **Packetbeat ECS Parity**: Outputs logs in strict Elastic Common Schema (ECS) format.
|
|
11
|
+
- 🛡️ **Auto-Sanitization**: Automatically redacts sensitive fields like passwords, secrets, tokens, and Authorization headers.
|
|
12
|
+
- 🚫 **Domain Ignoral**: Ignore traffic to specific domains (e.g. `localhost`).
|
|
13
|
+
- ⏱️ **Nanosecond Latency**: Tracks transaction duration down to the nanosecond, mapped to `event.duration`.
|
|
14
|
+
- 🔗 **Correlation IDs**: Generates (or passes through) UUID trace IDs for distributed tracing.
|
|
15
|
+
|
|
16
|
+
## Installation from GitHub
|
|
17
|
+
|
|
18
|
+
To install this package directly from your public GitHub repository, you first need to push this code to GitHub.
|
|
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
|
+
```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
|
|
29
|
+
```
|
|
30
|
+
|
|
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
|
+
## Usage
|
|
40
|
+
|
|
41
|
+
Import the `AxiosLoggerSingleton` and attach it to your Axios instance(s).
|
|
42
|
+
|
|
43
|
+
```typescript
|
|
44
|
+
import axios from 'axios';
|
|
45
|
+
import { AxiosLoggerSingleton } from 'axios-interceptor-logger';
|
|
46
|
+
|
|
47
|
+
// 1. Initialize the Logger Configuration
|
|
48
|
+
const logger = AxiosLoggerSingleton.getInstance({
|
|
49
|
+
verbose: true, // Prints ECS logs to the console
|
|
50
|
+
maxPayloadBytes: 5000, // Truncates massive JSON bodies
|
|
51
|
+
redactKeys: ['password', 'secret', 'token', 'authorization'],
|
|
52
|
+
ignoreDomains: ['localhost'],
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
// 2. Create an Axios Instance
|
|
56
|
+
const apiClient = axios.create({
|
|
57
|
+
baseURL: 'https://api.example.com',
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
// 3. Attach the Interceptor
|
|
61
|
+
logger.attach(apiClient);
|
|
62
|
+
|
|
63
|
+
// 4. Make requests (they will now be automatically logged in ECS format!)
|
|
64
|
+
await apiClient.post('/login', { username: 'admin', password: 'supersecretpassword' });
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Environment Variables
|
|
68
|
+
|
|
69
|
+
- `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`.
|
|
70
|
+
|
|
71
|
+
## Output Example
|
|
72
|
+
|
|
73
|
+
The logger intercepts the traffic and outputs standard Packetbeat ECS JSON:
|
|
74
|
+
|
|
75
|
+
```json
|
|
76
|
+
{
|
|
77
|
+
"@timestamp": "2026-09-11T09:34:32.765Z",
|
|
78
|
+
"ecs": { "version": "8.0.0" },
|
|
79
|
+
"agent": {
|
|
80
|
+
"name": "axios-interceptor-logger",
|
|
81
|
+
"type": "packetbeat",
|
|
82
|
+
"version": "1.0.0"
|
|
83
|
+
},
|
|
84
|
+
"event": {
|
|
85
|
+
"start": "2026-09-11T09:34:31.799Z",
|
|
86
|
+
"end": "2026-09-11T09:34:32.765Z",
|
|
87
|
+
"duration": 965000000,
|
|
88
|
+
"dataset": "http"
|
|
89
|
+
},
|
|
90
|
+
"http": {
|
|
91
|
+
"request": {
|
|
92
|
+
"method": "POST",
|
|
93
|
+
"headers": {
|
|
94
|
+
"Accept": "application/json",
|
|
95
|
+
"Authorization": "[REDACTED]"
|
|
96
|
+
},
|
|
97
|
+
"body": {
|
|
98
|
+
"content": "{\"username\":\"admin\",\"password\":\"[REDACTED]\"}"
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
"response": {
|
|
102
|
+
"status_code": 200
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
"url": {
|
|
106
|
+
"full": "https://api.example.com/login",
|
|
107
|
+
"scheme": "https",
|
|
108
|
+
"domain": "api.example.com"
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
```
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { AxiosInstance } from 'axios';
|
|
2
|
+
|
|
3
|
+
interface LogEntry {
|
|
4
|
+
'@timestamp': string;
|
|
5
|
+
ecs: {
|
|
6
|
+
version: string;
|
|
7
|
+
};
|
|
8
|
+
agent: {
|
|
9
|
+
name: string;
|
|
10
|
+
type: string;
|
|
11
|
+
version: string;
|
|
12
|
+
};
|
|
13
|
+
event: {
|
|
14
|
+
start: string;
|
|
15
|
+
end: string;
|
|
16
|
+
duration: number;
|
|
17
|
+
kind: string;
|
|
18
|
+
category: string[];
|
|
19
|
+
type: string[];
|
|
20
|
+
dataset: string;
|
|
21
|
+
};
|
|
22
|
+
http: {
|
|
23
|
+
request: {
|
|
24
|
+
method: string;
|
|
25
|
+
bytes?: number;
|
|
26
|
+
headers?: Record<string, any>;
|
|
27
|
+
body?: {
|
|
28
|
+
content: any;
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
response?: {
|
|
32
|
+
status_code: number;
|
|
33
|
+
bytes?: number;
|
|
34
|
+
headers?: Record<string, any>;
|
|
35
|
+
body?: {
|
|
36
|
+
content: any;
|
|
37
|
+
};
|
|
38
|
+
};
|
|
39
|
+
version?: string;
|
|
40
|
+
};
|
|
41
|
+
url: {
|
|
42
|
+
full: string;
|
|
43
|
+
path: string;
|
|
44
|
+
query?: string;
|
|
45
|
+
scheme: string;
|
|
46
|
+
domain: string;
|
|
47
|
+
port?: number;
|
|
48
|
+
};
|
|
49
|
+
source?: {
|
|
50
|
+
domain?: string;
|
|
51
|
+
};
|
|
52
|
+
destination?: {
|
|
53
|
+
domain?: string;
|
|
54
|
+
};
|
|
55
|
+
server?: {
|
|
56
|
+
domain?: string;
|
|
57
|
+
};
|
|
58
|
+
network: {
|
|
59
|
+
protocol: string;
|
|
60
|
+
};
|
|
61
|
+
error?: {
|
|
62
|
+
message: string;
|
|
63
|
+
code?: string;
|
|
64
|
+
};
|
|
65
|
+
status: string;
|
|
66
|
+
'trace.id'?: string;
|
|
67
|
+
}
|
|
68
|
+
interface LogTransport {
|
|
69
|
+
log(entry: LogEntry): void | Promise<void>;
|
|
70
|
+
}
|
|
71
|
+
interface AxiosLoggerConfig {
|
|
72
|
+
ignoreDomains?: string[];
|
|
73
|
+
maxPayloadBytes?: number;
|
|
74
|
+
redactKeys?: string[];
|
|
75
|
+
transportMode?: 'file' | 'otlp' | 'custom';
|
|
76
|
+
filePath?: string;
|
|
77
|
+
otlpEndpoint?: string;
|
|
78
|
+
customLogger?: LogTransport;
|
|
79
|
+
verbose?: boolean;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
declare class AxiosLoggerSingleton {
|
|
83
|
+
private static instance;
|
|
84
|
+
private config;
|
|
85
|
+
private transport;
|
|
86
|
+
private constructor();
|
|
87
|
+
static getInstance(config?: AxiosLoggerConfig): AxiosLoggerSingleton;
|
|
88
|
+
attach(axiosInstance: AxiosInstance): {
|
|
89
|
+
eject: () => void;
|
|
90
|
+
};
|
|
91
|
+
private isDomainIgnored;
|
|
92
|
+
private handleRequest;
|
|
93
|
+
private handleResponse;
|
|
94
|
+
private handleError;
|
|
95
|
+
private logTransaction;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export { type AxiosLoggerConfig, AxiosLoggerSingleton, type LogEntry, type LogTransport };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { AxiosInstance } from 'axios';
|
|
2
|
+
|
|
3
|
+
interface LogEntry {
|
|
4
|
+
'@timestamp': string;
|
|
5
|
+
ecs: {
|
|
6
|
+
version: string;
|
|
7
|
+
};
|
|
8
|
+
agent: {
|
|
9
|
+
name: string;
|
|
10
|
+
type: string;
|
|
11
|
+
version: string;
|
|
12
|
+
};
|
|
13
|
+
event: {
|
|
14
|
+
start: string;
|
|
15
|
+
end: string;
|
|
16
|
+
duration: number;
|
|
17
|
+
kind: string;
|
|
18
|
+
category: string[];
|
|
19
|
+
type: string[];
|
|
20
|
+
dataset: string;
|
|
21
|
+
};
|
|
22
|
+
http: {
|
|
23
|
+
request: {
|
|
24
|
+
method: string;
|
|
25
|
+
bytes?: number;
|
|
26
|
+
headers?: Record<string, any>;
|
|
27
|
+
body?: {
|
|
28
|
+
content: any;
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
response?: {
|
|
32
|
+
status_code: number;
|
|
33
|
+
bytes?: number;
|
|
34
|
+
headers?: Record<string, any>;
|
|
35
|
+
body?: {
|
|
36
|
+
content: any;
|
|
37
|
+
};
|
|
38
|
+
};
|
|
39
|
+
version?: string;
|
|
40
|
+
};
|
|
41
|
+
url: {
|
|
42
|
+
full: string;
|
|
43
|
+
path: string;
|
|
44
|
+
query?: string;
|
|
45
|
+
scheme: string;
|
|
46
|
+
domain: string;
|
|
47
|
+
port?: number;
|
|
48
|
+
};
|
|
49
|
+
source?: {
|
|
50
|
+
domain?: string;
|
|
51
|
+
};
|
|
52
|
+
destination?: {
|
|
53
|
+
domain?: string;
|
|
54
|
+
};
|
|
55
|
+
server?: {
|
|
56
|
+
domain?: string;
|
|
57
|
+
};
|
|
58
|
+
network: {
|
|
59
|
+
protocol: string;
|
|
60
|
+
};
|
|
61
|
+
error?: {
|
|
62
|
+
message: string;
|
|
63
|
+
code?: string;
|
|
64
|
+
};
|
|
65
|
+
status: string;
|
|
66
|
+
'trace.id'?: string;
|
|
67
|
+
}
|
|
68
|
+
interface LogTransport {
|
|
69
|
+
log(entry: LogEntry): void | Promise<void>;
|
|
70
|
+
}
|
|
71
|
+
interface AxiosLoggerConfig {
|
|
72
|
+
ignoreDomains?: string[];
|
|
73
|
+
maxPayloadBytes?: number;
|
|
74
|
+
redactKeys?: string[];
|
|
75
|
+
transportMode?: 'file' | 'otlp' | 'custom';
|
|
76
|
+
filePath?: string;
|
|
77
|
+
otlpEndpoint?: string;
|
|
78
|
+
customLogger?: LogTransport;
|
|
79
|
+
verbose?: boolean;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
declare class AxiosLoggerSingleton {
|
|
83
|
+
private static instance;
|
|
84
|
+
private config;
|
|
85
|
+
private transport;
|
|
86
|
+
private constructor();
|
|
87
|
+
static getInstance(config?: AxiosLoggerConfig): AxiosLoggerSingleton;
|
|
88
|
+
attach(axiosInstance: AxiosInstance): {
|
|
89
|
+
eject: () => void;
|
|
90
|
+
};
|
|
91
|
+
private isDomainIgnored;
|
|
92
|
+
private handleRequest;
|
|
93
|
+
private handleResponse;
|
|
94
|
+
private handleError;
|
|
95
|
+
private logTransaction;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export { type AxiosLoggerConfig, AxiosLoggerSingleton, type LogEntry, type LogTransport };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
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});
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
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};
|
package/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "axios-interceptor-logger",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Axios Unified HTTP Transaction Logger",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"module": "dist/index.mjs",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.mjs",
|
|
12
|
+
"require": "./dist/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist"
|
|
17
|
+
],
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "tsup src/index.ts --format cjs,esm --dts --clean --minify",
|
|
20
|
+
"test": "jest"
|
|
21
|
+
},
|
|
22
|
+
"keywords": [
|
|
23
|
+
"axios",
|
|
24
|
+
"interceptor",
|
|
25
|
+
"logger",
|
|
26
|
+
"packetbeat",
|
|
27
|
+
"ecs",
|
|
28
|
+
"elastic",
|
|
29
|
+
"logging"
|
|
30
|
+
],
|
|
31
|
+
"author": "Kamalyesh Kannadkar <k.kamalyesh@gmail.com>",
|
|
32
|
+
"license": "MIT",
|
|
33
|
+
"repository": {
|
|
34
|
+
"type": "git",
|
|
35
|
+
"url": "git+https://github.com/kkamalyesh-ops/axios-interceptor-logger.git"
|
|
36
|
+
},
|
|
37
|
+
"bugs": {
|
|
38
|
+
"url": "https://github.com/kkamalyesh-ops/axios-interceptor-logger/issues"
|
|
39
|
+
},
|
|
40
|
+
"homepage": "https://github.com/kkamalyesh-ops/axios-interceptor-logger#readme",
|
|
41
|
+
"dependencies": {},
|
|
42
|
+
"peerDependencies": {
|
|
43
|
+
"axios": "^1.0.0"
|
|
44
|
+
},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"@types/node": "^20.0.0",
|
|
47
|
+
"axios": "^1.6.0",
|
|
48
|
+
"tsup": "^8.0.0",
|
|
49
|
+
"typescript": "^5.0.0"
|
|
50
|
+
}
|
|
51
|
+
}
|