All Downloads are FREE. Search and download functionalities are using the official Maven repository.

pl.datart.http4s.timer.newrelic.NewRelicRequestTimer.scala Maven / Gradle / Ivy

The newest version!
package pl.datart.http4s.timer
package newrelic

import java.net.URI

import cats.effect.Sync
import cats.implicits._
import com.newrelic.api.agent.{Segment => NRSegment, _}
import fs2._
import org.http4s.{EntityBody, Request, Response}
import pl.datart.http4s.timer.newrelic.internal.{
  Http4sRequest,
  Http4sResponse
}

import scala.collection.JavaConverters._
import scala.util.Try

class NewRelicRequestTimer[F[_]](implicit F: Sync[F]) extends RequestTimer[F] {
  @Trace(dispatcher = true)
  private def _startExternalAndGetSegment(
      routesName: String,
      requestName: String,
      externalParameters: ExternalParameters) = {
    val agent = NewRelic.getAgent
    val method = agent.getTracedMethod

    method.reportAsExternal(externalParameters)
    method.setMetricName(routesName)
    agent.getTransaction.startSegment("External", requestName)
  }

  @Trace(dispatcher = true)
  private def _startWebTransactionAndGetSegment(routesName: String) = {
    NewRelic.getAgent.getTracedMethod.setMetricName(routesName)
    NewRelic.getAgent.getTransaction.startSegment("WebRequestPhase", "Headers")
  }

  private[timer] def setRequestInfo(requestName: String,
                                    transaction: Transaction,
                                    request: Request[F],
                                    user: Option[String]) = {
    F.delay {
      transaction.setWebRequest(new Http4sRequest(request, user))
      transaction.setTransactionName(TransactionNamePriority.CUSTOM_HIGH,
                                     true,
                                     "WebRequest",
                                     requestName)
    }
  }

  @Trace(async = true)
  private def _noticeError(segment: NRSegment,
                           t: Throwable,
                           attrs: Map[String, String]): Unit = {
    segment.getTransaction.getToken.linkAndExpire()
    NewRelic.noticeError(t, attrs.asJava, false)
  }

  private[timer] def noticeError(
      segment: NRSegment,
      t: Throwable,
      attrs: Map[String, String] = Map.empty): F[Unit] = {
    F.delay(_noticeError(segment, t, attrs))
  }

  private[timer] def setResponseInfo(
      segment: NRSegment,
      response: Either[Throwable, org.http4s.Response[F]])
    : F[org.http4s.Response[F]] = {
    response match {
      case Left(t) =>
        noticeError(segment, t) >> endSegment(segment) >> F.raiseError(t)

      case Right(response) =>
        val wrappedResponse = new Http4sResponse(response)

        F.delay {
          segment.getTransaction.setWebResponse(wrappedResponse)
          segment.getTransaction.addOutboundResponseHeaders()
          wrappedResponse.response
        }
    }
  }

  private[timer] def noticeErrorForStream[A](segment: NRSegment,
                                             s: Stream[F, A]): Stream[F, A] =
    s.handleErrorWith { e =>
      Stream.eval(noticeError(segment, e)).drain
    }

  private[timer] def endSegment(segment: NRSegment): F[Unit] =
    F.delay(segment.end())

  private[timer] def startBodySegment(segment: NRSegment): F[NRSegment] = {
    F.delay(segment.getTransaction.startSegment("WebRequestPhase", "Body")) <* endSegment(
      segment)
  }

  private[timer] def timeStream(segment: NRSegment,
                                body: EntityBody[F]): EntityBody[F] =
    fs2.Stream.bracket(startBodySegment(segment))(endSegment).flatMap(noticeErrorForStream(_, body))


  def time(routesName: String,
           requestName: String,
           request: org.http4s.Request[F],
           user: Option[String] = None)(
      response: F[org.http4s.Response[F]]): F[org.http4s.Response[F]] = {
    for {
      segment <- F.delay(
        _startWebTransactionAndGetSegment(routesName))
      _ <- setRequestInfo(s"$requestName (${request.method.name})",
                          segment.getTransaction,
                          request,
                          user)
      completedResponse <- response.attempt
      modifiedResponse <- setResponseInfo(segment, completedResponse)
      responseWithTimedBody = modifiedResponse.withBodyStream(
        timeStream(segment, modifiedResponse.body))
    } yield responseWithTimedBody
  }

  private[timer] def modifyExternalRequest[A](segment: NRSegment,
                                              completedRequest: F[A]) = {
    completedRequest.attempt flatMap {
      case Left(t) =>
        noticeError(segment, t) >> endSegment(segment) >> F.raiseError[A](t)
      case Right(result) =>
        endSegment(segment) >> F.pure(result)
    }
  }

  def timeExternal(
      routesName: String,
      requestName: String,
      request: Request[F])(response: F[Response[F]]): F[Response[F]] = {
    val uri = Try(URI.create(request.uri.toString())).toOption

    uri match {
      case Some(u) =>
        val externalParameters =
          HttpParameters
            .library("http4s")
            .uri(u)
            .procedure(request.method.name)
            .noInboundHeaders() // TOOD: add headers
            .build()

        for {
          segment <- F.delay(
            _startExternalAndGetSegment(routesName,
                                        requestName,
                                        externalParameters))
          completedRequest <- modifyExternalRequest(segment, response)
        } yield completedRequest

      case None => response
    }
  }

  def timeExternal[A](
      routesName: String,
      requestName: String,
      externalParameters: ExternalParameters)(action: F[A]): F[A] = {
    for {
      segment <- F.delay(
        _startExternalAndGetSegment(routesName,
                                    requestName,
                                    externalParameters))
      completedRequest <- modifyExternalRequest(segment, action)
    } yield completedRequest
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy