Glosur/nestjs-actuator

View on GitHub
src/endpoints/httptrace/httptrace.interceptor.ts

Summary

Maintainability
A
0 mins
Test Coverage
import {
  CallHandler,
  ExecutionContext,
  HttpException,
  Injectable,
  NestInterceptor,
} from "@nestjs/common";
import { Observable, throwError } from "rxjs";
import { tap, catchError } from "rxjs/operators";
import { IncomingMessage, ServerResponse } from "http";
import { HttptraceRepository } from "./httptrace.repository";

interface HttpTrace {
  request: {
    headers: { [key: string]: string[] };
    method: string;
    uri: string;
  };
  response: { headers: { [key: string]: string[] }; status: number };
  timeTaken: number;
  timestamp: number;
}

@Injectable()
export class HttptraceInterceptor implements NestInterceptor {
  constructor(private traceRepository: HttptraceRepository) {}

  intercept(
    context: ExecutionContext,
    call$: CallHandler
  ): Observable<unknown> {
    const now = Date.now();
    return call$.handle().pipe(
      tap(() => {
        this.addHttptrace(context, now);
      }),
      catchError((err) => {
        this.addHttptrace(context, now, err);
        return throwError(err);
      })
    );
  }

  /* testing */ addHttptrace(
    context: ExecutionContext,
    incomingTime: number,
    error?: Error
  ): void {
    const request = context.switchToHttp().getRequest();
    const delay = Date.now() - incomingTime;

    const response = context.switchToHttp().getResponse();
    if (
      request instanceof IncomingMessage &&
      response instanceof ServerResponse
    ) {
      const currentTrace: HttpTrace = {
        request: {
          headers: HttptraceInterceptor.arrayizeHeaders(request.headers),
          method: request.method,
          uri: request.url,
        },
        response: {
          headers: HttptraceInterceptor.arrayizeHeaders(response.getHeaders()),
          status: HttptraceInterceptor.getResponseStatus(error, response),
        },
        timeTaken: delay,
        timestamp: incomingTime,
      };
      this.traceRepository.addTrace(currentTrace);
    }
  }

  private static getResponseStatus(error: Error, response: ServerResponse) {
    return error
      ? HttptraceInterceptor.getErrorCode(error)
      : response.statusCode;
  }

  /**
   * Try to find the error code through exceptiont ype. If not present, we can't know, so set to 0
   * @param error the error that triggered the request to stop
   */
  private static getErrorCode(error: Error | HttpException) {
    return error instanceof HttpException ? error.getStatus() : 0;
  }

  /**
   * Format headers so each one is an array of values
   * @param headers
   */
  private static arrayizeHeaders(
    headers: NodeJS.Dict<number | string | string[]>
  ): { [key: string]: string[] } {
    const arrayizedHeaders = {};
    Object.keys(headers).forEach((key) => {
      if (headers[key]) arrayizedHeaders[key] = [headers[key]];
    });
    return arrayizedHeaders;
  }
}