Skip to content

Commit

Permalink
Merge pull request #2322 from constantine2nd/develop
Browse files Browse the repository at this point in the history
Add response_body to metric table
  • Loading branch information
simonredfern authored Nov 13, 2023
2 parents 0eb7fc1 + 4d52ab6 commit 0532f1f
Show file tree
Hide file tree
Showing 6 changed files with 204 additions and 150 deletions.
4 changes: 4 additions & 0 deletions obp-api/src/main/resources/props/sample.props.template
Original file line number Diff line number Diff line change
Expand Up @@ -1259,6 +1259,10 @@ retain_metrics_move_limit = 50000
# Defines the interval of the scheduler
retain_metrics_scheduler_interval_in_seconds = 3600


# Defines endpoints we want to store responses at Metric table
metrics_store_response_body_for_operation_ids=

#if same session used for different ip address, we can show this warning, default is false.
show_ip_address_change_warning=false

Expand Down
5 changes: 3 additions & 2 deletions obp-api/src/main/scala/code/api/OBPRestHelper.scala
Original file line number Diff line number Diff line change
Expand Up @@ -355,7 +355,8 @@ trait OBPRestHelper extends RestHelper with MdcLoggable {
correlationId = correlationId,
url = url,
ipAddress = remoteIpAddress,
requestHeaders = reqHeaders
requestHeaders = reqHeaders,
operationId = rd.map(_.operationId)
)

// before authentication interceptor build response
Expand Down Expand Up @@ -565,7 +566,7 @@ trait OBPRestHelper extends RestHelper with MdcLoggable {
failIfBadJSON(r, handler)
}
val endTime = Helpers.now
writeEndpointMetric(startTime, endTime.getTime - startTime.getTime, rd)
WriteMetricUtil.writeEndpointMetric(startTime, endTime.getTime - startTime.getTime, rd)
response
}
def isDefinedAt(r : Req) = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2228,8 +2228,9 @@ object SwaggerDefinitionsJSON {
verb = "get",
correlation_id = "v8ho6h5ivel3uq7a5zcnv0w1",
duration = 39,
source_ip = "2001:0db8:3c4d:0015:0000:0000:1a2f:1a2b",
target_ip = "2001:0db8:3c4d:0015:0000:0000:1a2f:1a2b",
source_ip = "2001:0db8:3c4d:0015:0000:0000:1a2f:1a2b"
response_body = """{"code":401,"message":"OBP-20001: User not logged in. Authentication is required!"}""".stripMargin
)

val resourceUserJSON = ResourceUserJSON(
Expand Down
151 changes: 6 additions & 145 deletions obp-api/src/main/scala/code/api/util/APIUtil.scala
Original file line number Diff line number Diff line change
Expand Up @@ -312,147 +312,8 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
}
}

def writeEndpointMetric(callContext: Option[CallContextLight]) = {
callContext match {
case Some(cc) =>
if(getPropsAsBoolValue("write_metrics", false)) {
val userId = cc.userId.orNull
val userName = cc.userName.orNull

val implementedByPartialFunction = cc.partialFunctionName

val duration =
(cc.startTime, cc.endTime) match {
case (Some(s), Some(e)) => (e.getTime - s.getTime)
case _ => -1
}

//execute saveMetric in future, as we do not need to know result of the operation
Future {
val consumerId = cc.consumerId.getOrElse(-1)
val appName = cc.appName.orNull
val developerEmail = cc.developerEmail.orNull

APIMetrics.apiMetrics.vend.saveMetric(
userId,
cc.url,
cc.startTime.getOrElse(null),
duration,
userName,
appName,
developerEmail,
consumerId.toString,
implementedByPartialFunction,
cc.implementedInVersion,
cc.verb,
cc.httpCode,
cc.correlationId,
cc.httpBody.getOrElse(""),
cc.requestHeaders.find(_.name.toLowerCase() == "x-forwarded-for").map(_.values.mkString(",")).getOrElse(""),
cc.requestHeaders.find(_.name.toLowerCase() == "x-forwarded-host").map(_.values.mkString(",")).getOrElse("")
)
}
}
case _ =>
logger.error("CallContextLight is not defined. Metrics cannot be saved.")
}
}

def writeEndpointMetric(date: TimeSpan, duration: Long, rd: Option[ResourceDoc]) = {
val authorization = S.request.map(_.header("Authorization")).flatten
val directLogin: Box[String] = S.request.map(_.header("DirectLogin")).flatten
if(getPropsAsBoolValue("write_metrics", false)) {
val user =
if (hasAnOAuthHeader(authorization)) {
getUser match {
case Full(u) => Full(u)
case _ => Empty
}
} // Direct Login
else if (getPropsAsBoolValue("allow_direct_login", true) && directLogin.isDefined) {
DirectLogin.getUser match {
case Full(u) => Full(u)
case _ => Empty
}
} // Direct Login Deprecated
else if (getPropsAsBoolValue("allow_direct_login", true) && hasDirectLoginHeader(authorization)) {
DirectLogin.getUser match {
case Full(u) => Full(u)
case _ => Empty
}
} else {
Empty
}

val consumer =
if (hasAnOAuthHeader(authorization)) {
getConsumer match {
case Full(c) => Full(c)
case _ => Empty
}
} // Direct Login
else if (getPropsAsBoolValue("allow_direct_login", true) && directLogin.isDefined) {
DirectLogin.getConsumer match {
case Full(c) => Full(c)
case _ => Empty
}
} // Direct Login Deprecated
else if (getPropsAsBoolValue("allow_direct_login", true) && hasDirectLoginHeader(authorization)) {
DirectLogin.getConsumer match {
case Full(c) => Full(c)
case _ => Empty
}
} else {
Empty
}

// TODO This should use Elastic Search or Kafka not an RDBMS
val u: User = user.orNull
val userId = if (u != null) u.userId else "null"
val userName = if (u != null) u.name else "null"

val c: Consumer = consumer.orNull
//The consumerId, not key
val consumerId = if (u != null) c.id.toString() else "null"
var appName = if (u != null) c.name.toString() else "null"
var developerEmail = if (u != null) c.developerEmail.toString() else "null"
val implementedByPartialFunction = rd match {
case Some(r) => r.partialFunctionName
case _ => ""
}
//name of version where the call is implemented) -- S.request.get.view
val implementedInVersion = S.request.openOrThrowException(attemptedToOpenAnEmptyBox).view
//(GET, POST etc.) --S.request.get.requestType.method
val verb = S.request.openOrThrowException(attemptedToOpenAnEmptyBox).requestType.method
val url = ObpS.uriAndQueryString.getOrElse("")
val correlationId = getCorrelationId()
val body: Box[String] = getRequestBody(S.request)
val reqHeaders = S.request.openOrThrowException(attemptedToOpenAnEmptyBox).request.headers

//execute saveMetric in future, as we do not need to know result of operation
Future {
APIMetrics.apiMetrics.vend.saveMetric(
userId,
url,
date,
duration: Long,
userName,
appName,
developerEmail,
consumerId,
implementedByPartialFunction,
implementedInVersion,
verb,
None,
correlationId,
body.getOrElse(""),
reqHeaders.find(_.name.toLowerCase() == "x-forwarded-for").map(_.values.mkString(",")).getOrElse(""),
reqHeaders.find(_.name.toLowerCase() == "x-forwarded-host").map(_.values.mkString(",")).getOrElse("")
)
}

}
}


/*
Expand Down Expand Up @@ -2596,7 +2457,7 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
result
}

def writeMetricEndpointTiming[R](callContext: Option[CallContextLight])(blockOfCode: => R): R = {
def writeMetricEndpointTiming[R](responseBody: Any, callContext: Option[CallContextLight])(blockOfCode: => R): R = {
val result = blockOfCode
// call-by-name
val endTime = Helpers.now
Expand All @@ -2607,7 +2468,7 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
case _ =>
// There are no enough information for logging
}
writeEndpointMetric(callContext.map(_.copy(endTime = Some(endTime))))
WriteMetricUtil.writeEndpointMetric(responseBody, callContext.map(_.copy(endTime = Some(endTime))))
result
}

Expand Down Expand Up @@ -2950,7 +2811,7 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
def futureToResponse[T](in: LAFuture[(T, Option[CallContext])]): JsonResponse = {
RestContinuation.async(reply => {
in.onSuccess(
t => writeMetricEndpointTiming(t._2.map(_.toLight))(reply.apply(successJsonResponseNewStyle(cc = t._1, t._2)(getHeadersNewStyle(t._2.map(_.toLight)))))
t => writeMetricEndpointTiming(t._1, t._2.map(_.toLight))(reply.apply(successJsonResponseNewStyle(cc = t._1, t._2)(getHeadersNewStyle(t._2.map(_.toLight)))))
)
in.onFail {
case Failure(_, Full(JsonResponseException(jsonResponse)), _) =>
Expand All @@ -2963,7 +2824,7 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
extractAPIFailureNewStyle(msg) match {
case Some(af) =>
val callContextLight = af.ccl.map(_.copy(httpCode = Some(af.failCode)))
writeMetricEndpointTiming(callContextLight)(reply.apply(errorJsonResponse(af.failMsg, af.failCode, callContextLight)(getHeadersNewStyle(af.ccl))))
writeMetricEndpointTiming(af.failMsg, callContextLight)(reply.apply(errorJsonResponse(af.failMsg, af.failCode, callContextLight)(getHeadersNewStyle(af.ccl))))
case _ =>
val errorResponse: JsonResponse = errorJsonResponse(msg)
reply.apply(errorResponse)
Expand Down Expand Up @@ -3000,7 +2861,7 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
case (Full(jsonResponse: JsonResponse), _: Option[_]) =>
reply(jsonResponse)
case t => Full(
writeMetricEndpointTiming(t._2.map(_.toLight))(
writeMetricEndpointTiming(t._1, t._2.map(_.toLight))(
reply.apply(successJsonResponseNewStyle(t._1, t._2)(getHeadersNewStyle(t._2.map(_.toLight))))
)
)
Expand All @@ -3024,7 +2885,7 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
extractAPIFailureNewStyle(msg) match {
case Some(af) =>
val callContextLight = af.ccl.map(_.copy(httpCode = Some(af.failCode)))
Full(writeMetricEndpointTiming(callContextLight)(reply.apply(errorJsonResponse(af.failMsg, af.failCode, callContextLight)(getHeadersNewStyle(af.ccl)))))
Full(writeMetricEndpointTiming(af.failMsg, callContextLight)(reply.apply(errorJsonResponse(af.failMsg, af.failCode, callContextLight)(getHeadersNewStyle(af.ccl)))))
case _ =>
val errorResponse: JsonResponse = errorJsonResponse(msg)
Full((reply.apply(errorResponse)))
Expand Down
Loading

0 comments on commit 0532f1f

Please sign in to comment.