如何处理 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 回调?

Online demo

正如评论中已经提到的那样,捕获 不应该 首先吞下错误,这肯定是不行的。通过这样做,您只是简单地阻止了 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 {}

就是这样! 现在您可以记录所有传出请求,并在必要时以真正集中的方式使用这些日志做一些其他很酷的事情。