javascript - Hur man loggar alla externa HTTP-förfrågningar med Axios i NestJS
Jag vill kunna logga varje axios-förfrågan med fullständig URL, headers, etc., men har för närvarande inte hittat ett sätt att göra det.
Det jag har uppnått hittills är att skriva en Http-interceptor baserad på detta svar
export class HttpLoggerInterceptor implements NestInterceptor {
intercept(
context: ExecutionContext,
call$: Observable<any>,
): Observable<any> {
return call$.pipe(
map(data => {
// pipe call to add / modify header(s) after remote method
const req = context.switchToHttp().getRequest();
return data;
}),
);
}
}
Nu bläddrar jag igenom objekten req och context egenskaper vid felsökning men kunde inte se Asios förfrågnings-URL, etc. Om jag inte har missat det.
Min kontrollerrutt (api/data i det fallet) har N antal externa HTTP-anrop som sker men interceptorn avlyssnar endast kontrollerns anrop, inte Axios-anropen.
Några tankar?
Det är context-objektet:
args:Array(2) [IncomingMessage, ServerResponse]
constructorRef:class AppController { … }
getRequest:() => …
getResponse:() => …
handler:data() { … }
__proto__:Object {constructor: , getClass: , getHandler: , …}
det är req:
_dumped:false
_events:Object {}
_eventsCount:0
_maxListeners:undefined
_parsedOriginalUrl:Url {protocol: null, slashes: null, auth: null, …}
_parsedUrl:Url {protocol: null, slashes: null, auth: null, …}
_readableState:ReadableState {objectMode: false, highWaterMark: 16384, buffer: BufferList, …}
baseUrl:""
body:Object {}
client:Socket {connecting: false, _hadError: false, _handle: TCP, …}
complete:true
connection:Socket {connecting: false, _hadError: false, _handle: TCP, …}
destroyed:false
fresh:false
headers:Object {accept: "application/json, text/plain, */*", user-agent: "axios/0.18.0", host: "localhost:3000", …}
host:"localhost"
hostname:"localhost"
httpVersion:"1.1"
httpVersionMajor:1
httpVersionMinor:1
ip:"::ffff:127.0.0.1"
ips:Array(0)
method:"GET"
next:function next(err) { … }
originalUrl:"/api/data"
params:Object {}
__proto__:Object {constructor: , __defineGetter__: , __defineSetter__: , …}
path:"/api/data"
protocol:"http"
query:Object {}
rawHeaders:Array(8) ["Accept", "application/json, text/plain, */*", "User-Agent", …]
rawTrailers:Array(0) []
readable:true
readableBuffer:BufferList
readableFlowing:null
readableHighWaterMark:16384
readableLength:0
res:ServerResponse {_events: Object, _eventsCount: 1, _maxListeners: undefined, …}
route:Route {path: "/api/data", stack: Array(1), methods: Object}
secure:false
socket:Socket {connecting: false, _hadError: false, _handle: TCP, …}
stale:true
statusCode:null
statusMessage:null
subdomains:Array(0)
trailers:Object {}
upgrade:false
url:"/api/data"
xhr:false
Svar #1
Nest.js-interceptors hanterar endast den förfrågan som hanteras av din controller och svaret som skickas ut. Om du gör HTTP-förfrågningar med Axios medan du hanterar en controllerförfrågan kommer de inte att hanteras av en interceptor.
Axios-interceptor
HttpService exponerar sin axios-instans direkt via get axiosRef(). Med den kan du lägga till en axios-interceptor:
this.httpService.axiosRef.interceptors.request.use(config => { /*...*/ return config })
Du kan till exempel göra det i onModuleInit() av din AppModule.
Delegera till fasad
Som ett alternativ kan du skapa ett HttpService-fasad, som loggar begäran och delegerar alla anrop till den inbyggda HttpService:
@Injectable()
export class MyHttpService {
private logger: Logger = new Logger(MyHttpService.name);
constructor (private httpService: HttpService) {}
public get<T = any>(url: string, config?: AxiosRequestConfig): Observable<AxiosResponse<T>> {
this.logger.log({url, config});
return this.httpService.get(url, config)
.pipe(tap(response => this.logger.log(response)));
}
// ... all the other methods you need.
}
Du kan skapa din egen LoggingHttpModule som importerar den inbyggda HttpModule och exporterar din MyHttpService.
Svar #2
För de som letar efter en centraliserad lösning utan extra bibliotek, är detta hur jag lade till min bärare-token i alla externa API-anrop, med en logg när det händer:
@Injectable()
export class IdkAxiosInterceptor implements OnModuleInit {
private readonly logger = new Logger(IdkAxiosInterceptor.name);
private token = undefined;
constructor(
@Inject(HttpService)
private httpService: HttpService,
@Inject(TokenExchangeService)
private tokenExchangeService: TokenExchangeService,
) {
this.refreshToken();
}
onModuleInit(): any {
const { axiosRef: axios } = this.httpService;
axios.interceptors.request.use((config) => {
return this.onRequest(config);
}, Promise.reject);
}
onRequest(config) {
this.logger.log('external call for ' + config.url);
config.headers['Authorization'] = `Bearer ${this.token}`;
return config;
}
@Cron(EVERY_50_MINUTES)
refreshToken() {
this.tokenExchangeService.getAccessToken(Environment.PRODUCTION).subscribe(({ access_token }) => {
this.token = access_token;
});
}
}
Nu behöver du bara lägga till det i din huvudmodul som en leverantör och det är allt.
Om du vill kan jag posta testet för det :D
