如何处理 HttpClient 请求的日志记录?
How to handle logging of HttpClient requests?
我正在使用 Angular 和 rxjs 5.x.x 。对于每个 http 请求,我必须在一个集中的地方记录响应——无论是成功还是失败。
我面临的问题是在调用 catch
子句的情况下。
例如:
callRemote() {
return this.http.get("http://aaa.dom")
.catch(f=>Rx.Observable.of(f))
.do(f => console.log("logging response both bad and ok..."))
}
这是一个失败的 http
请求。但我必须调用 do
运算符,因为我需要记录它。
但是,如果调用 .catch
,则不会调用 .do
。这就是我这样做的原因:
.catch(f=>Rx.Observable.of(f))
所以我基本上是在捕获错误并将其包装为一个新的可观察对象,然后继续 .do
函数。
到目前为止一切顺利。
现在的问题是,最终,在订阅时,我想知道请求是失败还是成功。
所以基本上这就是我订阅的方式:
this._http.callRemote().subscribe(
(x => console.log('OK')),
(x => console.log('ERROR')),
(() => console.log('COMPLETE'))
);
问题是所有内容都被重定向到 sucess
回调函数。甚至失败。我明白为什么会这样——因为我包装 catch
作为新的 Observables 失败了。
问题
包装错误是否有可能进入 error
回调,而不是 success
回调?
正如评论中已经提到的那样,捕获 不应该 首先吞下错误,这肯定是不行的。通过这样做,您只是简单地阻止了 rxjs 链中的错误信号。
最终您可以在 catch 中进行记录,然后重新抛出,但这对于仅记录副作用来说有点矫枉过正。
最简单的选择是在 do
运算符中使用 错误回调 参数。
根据this tutorial page,do操作符可以接受3个参数(callback fns),基本匹配订阅签名:
1s 参数:下一个回调
第二个参数:错误回调
第三个参数:完成时回调
因此您可以重构为以下内容:
callRemote() {
return this.http.get("http://aaa.dom")
.do(
response => console.log("logging response both bad and ok..."),
error => console.log("Something exploded, call 911");
}
所以基本上您可以将该操作附加到基本代码中的每个 HttpClient
调用。很整洁,不是吗?
等等!乍一看这可能看起来很整洁,但它会适得其反:
- 修改日志记录行为
- 以任何方式重构
为什么?
您基本上 monkey-patch 每个可能的后端调用都使用该 do() 操作。如果对该逻辑进行 1 处更改意味着在 3 个以上的地方更改代码,那么就会发生一些问题。
更好的方法
随着 HttpClient 的引入,添加了另一个 API:HttpInterceptor API。
基本上,您可以在一个点拦截所有传出请求。
怎么样?如下:
第一步,创建可用于封装日志逻辑的可注入服务;
import { Injectable } from '@angular/core';
import {
HttpRequest,
HttpHandler,
HttpEvent,
HttpInterceptor
} from '@angular/common/http';
import { AuthService } from './auth/auth.service';
import { Observable } from 'rxjs/Observable';
import { tap } from 'rxjs/operators'; // fancy pipe-able operators
@Injectable()
export class LoggingInterceptor implements HttpInterceptor {
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
return next.handle(request).pipe(
tap(
response => console.log("Oh boy we got an answer"),
error => console.log("Something might be burning back there")
));
}
}
第二步,通过令牌提供LoggingInterceptor
让angular知道LoggingInterceptor
的存在:
import { HTTP_INTERCEPTORS } from '@angular/common/http';
import { NgModule, ClassProvider } from '@angular/core';
import { LoggingInterceptor } from '../some-place';
const LOGGING_INTERCEPTOR_PROVIDER: ClassProvider = {
provide: HTTP_INTERCEPTORS ,
useClass: LoggingInterceptor,
multi: true
};
@NgModule({
...
providers: [
LOGGING_INTERCEPTOR_PROVIDER
]
...
})
export class AppModule {}
就是这样! 现在您可以记录所有传出请求,并在必要时以真正集中的方式使用这些日志做一些其他很酷的事情。
我正在使用 Angular 和 rxjs 5.x.x 。对于每个 http 请求,我必须在一个集中的地方记录响应——无论是成功还是失败。
我面临的问题是在调用 catch
子句的情况下。
例如:
callRemote() {
return this.http.get("http://aaa.dom")
.catch(f=>Rx.Observable.of(f))
.do(f => console.log("logging response both bad and ok..."))
}
这是一个失败的 http
请求。但我必须调用 do
运算符,因为我需要记录它。
但是,如果调用 .catch
,则不会调用 .do
。这就是我这样做的原因:
.catch(f=>Rx.Observable.of(f))
所以我基本上是在捕获错误并将其包装为一个新的可观察对象,然后继续 .do
函数。
到目前为止一切顺利。
现在的问题是,最终,在订阅时,我想知道请求是失败还是成功。
所以基本上这就是我订阅的方式:
this._http.callRemote().subscribe(
(x => console.log('OK')),
(x => console.log('ERROR')),
(() => console.log('COMPLETE'))
);
问题是所有内容都被重定向到 sucess
回调函数。甚至失败。我明白为什么会这样——因为我包装 catch
作为新的 Observables 失败了。
问题
包装错误是否有可能进入 error
回调,而不是 success
回调?
正如评论中已经提到的那样,捕获 不应该 首先吞下错误,这肯定是不行的。通过这样做,您只是简单地阻止了 rxjs 链中的错误信号。
最终您可以在 catch 中进行记录,然后重新抛出,但这对于仅记录副作用来说有点矫枉过正。
最简单的选择是在 do
运算符中使用 错误回调 参数。
根据this tutorial page,do操作符可以接受3个参数(callback fns),基本匹配订阅签名:
1s 参数:下一个回调
第二个参数:错误回调
第三个参数:完成时回调
因此您可以重构为以下内容:
callRemote() {
return this.http.get("http://aaa.dom")
.do(
response => console.log("logging response both bad and ok..."),
error => console.log("Something exploded, call 911");
}
所以基本上您可以将该操作附加到基本代码中的每个 HttpClient
调用。很整洁,不是吗?
等等!乍一看这可能看起来很整洁,但它会适得其反:
- 修改日志记录行为
- 以任何方式重构
为什么?
您基本上 monkey-patch 每个可能的后端调用都使用该 do() 操作。如果对该逻辑进行 1 处更改意味着在 3 个以上的地方更改代码,那么就会发生一些问题。
更好的方法
随着 HttpClient 的引入,添加了另一个 API:HttpInterceptor API。
基本上,您可以在一个点拦截所有传出请求。
怎么样?如下:
第一步,创建可用于封装日志逻辑的可注入服务;
import { Injectable } from '@angular/core';
import {
HttpRequest,
HttpHandler,
HttpEvent,
HttpInterceptor
} from '@angular/common/http';
import { AuthService } from './auth/auth.service';
import { Observable } from 'rxjs/Observable';
import { tap } from 'rxjs/operators'; // fancy pipe-able operators
@Injectable()
export class LoggingInterceptor implements HttpInterceptor {
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
return next.handle(request).pipe(
tap(
response => console.log("Oh boy we got an answer"),
error => console.log("Something might be burning back there")
));
}
}
第二步,通过令牌提供LoggingInterceptor
让angular知道LoggingInterceptor
的存在:
import { HTTP_INTERCEPTORS } from '@angular/common/http';
import { NgModule, ClassProvider } from '@angular/core';
import { LoggingInterceptor } from '../some-place';
const LOGGING_INTERCEPTOR_PROVIDER: ClassProvider = {
provide: HTTP_INTERCEPTORS ,
useClass: LoggingInterceptor,
multi: true
};
@NgModule({
...
providers: [
LOGGING_INTERCEPTOR_PROVIDER
]
...
})
export class AppModule {}
就是这样! 现在您可以记录所有传出请求,并在必要时以真正集中的方式使用这些日志做一些其他很酷的事情。