How to handle logging of HttpClient requests?
Asked Answered
H

1

7

I'm using Angular and rxjs 5.x.x . For each http request I must log the response in a centralized place -either if success or fail.

The problem which i'm facing is in a cituation where a catch clause is being called.

For example :

  callRemote() {
    return this.http.get("http://aaa.dom")
                    .catch(f=>Rx.Observable.of(f))
                    .do(f => console.log("logging response both bad and ok..."))
  }

This is an http request which fails. But I must invoke the do operator becuase I need to log it. However if a .catch is called , the .do won't be called. That's why I did this :

  .catch(f=>Rx.Observable.of(f))

So I'm basically catching the error and wrap it as a new observable and continue to the .do function.

So far so good.

The problem now is that ,eventually , when subscribing , I want to know if request failed or OK.

So basically this is how I subscribe :

 this._http.callRemote().subscribe(
      (x => console.log('OK')),
      (x => console.log('ERROR')),
      (() => console.log('COMPLETE'))
    );

Problem is that everything is redirected to the sucess callback function. Even failures. I do understand why it happend - it's becuase I wrapped catch fails as new Observables.

Question

Is it possible that wrapped errors will go to the error callback , and not to the success callback ?

Online demo

Hypabyssal answered 20/2, 2018 at 20:16 Comment(8)
If you want it to continue being an error, why don't you Observable.throw it instead of turning it into a successful stream? Just log it where you catch it instead.Ph
@Ph you mean .catch(f=>Rx.Observable.throw (f)) ? If I do that - then do is not called.Hypabyssal
Yes, exactly. Otherwise you could e.g. wrap it in some kind of object like { error, result } and then unwrap it later, which seems kind of awkward.Ph
The catch shouldn't swallow the error in the first place, thats a nogo for sure. Eventually you could do your logging in the catch and then rethrow, but thats kinda overkill for only log side effect. Simplest option would be to use the error callback in the do operator.Dorothydorp
@Dorothydorp What do you mean by use the error callback in the do operator ?Hypabyssal
See e.g. learnrxjs.io/operators/utility/do.html, it takes the same arguments as subscribe.Ph
Ill add an answer with a better approach to handle this globallyDorothydorp
Fyi, this is now the top google result for the search "angular log request". Please consider changing the title if at all possible. It is misleading and many people will go here in vain. You are asking about responses, not requests.Antemortem
D
17

As already mentioned in the comments, the catch shouldn't swallow the error in the first place, thats a nogo for sure. By doing that you are simply blocking the error signal in the rxjs chain.

Eventually you could do your logging in the catch and then re throw, but thats kinda overkill for only log side effect.

The simplest option would be to use the error callback arguent in the do operator.

According to this tutorial page, the do operator can take 3 arguments (callback fns), what basically matches the subscribe signature:

1s argument: callback on next

2nd argument: callback on error

3rd argument: callback on complete

So you could refactor into the following:

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");
  }

So basically you could attach that operation to every single HttpClient call in your base code. Pretty neat, no?

Wait wait! This might look neat on a first glance, but it will backfire at the very same moment that you want to:

  • Modify the logging behavior
  • Refactor in any way

Why?

You are going to basically monkey-patch every single possible back end call with that do() operation. And if making 1 change to that logic means changing code in more than 3 places, there something smelly going on.

A better approach

With the introduction of the HttpClient, another API was added: the HttpInterceptor API.

Basically, you could intercept all your outgoing requests in a single point.

How? As it follows:

First step, create an injectable service that you can use to capsulate the logging logic;

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")
       ));
  }
}

Second step, make angular aware of the existence of the LoggingInterceptor by providing it through a token:

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 {}

And thats it! Now you can log all your outgoing requests and do some other cool stuff with those logs if necessary, in a truly centralized way.

Dorothydorp answered 20/2, 2018 at 20:28 Comment(6)
Hold on, Im updating the answerDorothydorp
BTW it works perfectly. Now I can log both in .do ok and .do fail and still it is redirecting to the error callback of subscribe- just like i wanted. ( never knew that do has error callback :-) thanks)Hypabyssal
Why am I not getting the tap to work here ? ?Hypabyssal
there is a typoDorothydorp
arr thanks). , console didn't show that. Thank you very muchHypabyssal
I use interceptor for adding the access token, but I also now use it for logging requests to the console and other header manipulation too.Skiplane

© 2022 - 2024 — McMap. All rights reserved.