Add rest client invocation log tracing.
[ccsdk/cds.git] / ms / blueprintsprocessor / modules / commons / rest-lib / src / main / kotlin / org / onap / ccsdk / cds / blueprintsprocessor / rest / service / RestLoggerService.kt
1 /*
2  * Copyright © 2018-2019 AT&T Intellectual Property.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 package org.onap.ccsdk.cds.blueprintsprocessor.rest.service
18
19 import kotlinx.coroutines.*
20 import kotlinx.coroutines.reactor.ReactorContext
21 import kotlinx.coroutines.reactor.asCoroutineContext
22 import org.apache.http.message.BasicHeader
23 import org.onap.ccsdk.cds.controllerblueprints.core.BluePrintConstants.ONAP_INVOCATION_ID
24 import org.onap.ccsdk.cds.controllerblueprints.core.BluePrintConstants.ONAP_PARTNER_NAME
25 import org.onap.ccsdk.cds.controllerblueprints.core.BluePrintConstants.ONAP_REQUEST_ID
26 import org.onap.ccsdk.cds.controllerblueprints.core.MDCContext
27 import org.onap.ccsdk.cds.controllerblueprints.core.defaultToEmpty
28 import org.onap.ccsdk.cds.controllerblueprints.core.defaultToUUID
29 import org.onap.ccsdk.cds.controllerblueprints.core.logger
30 import org.slf4j.MDC
31 import org.springframework.http.server.reactive.ServerHttpRequest
32 import org.springframework.http.server.reactive.ServerHttpResponse
33 import reactor.core.Disposable
34 import reactor.core.publisher.Mono
35 import reactor.core.publisher.MonoSink
36 import java.net.InetAddress
37 import java.time.ZoneOffset
38 import java.time.ZonedDateTime
39 import java.time.format.DateTimeFormatter
40 import java.util.*
41 import kotlin.coroutines.CoroutineContext
42 import kotlin.coroutines.EmptyCoroutineContext
43
44 class RestLoggerService {
45     private val log = logger(RestLoggerService::class)
46
47     companion object {
48         /** Used before invoking any REST outbound request, Inbound Invocation ID is used as request Id
49          * for outbound Request, If invocation Id is missing then default Request Id will be generated.
50          */
51         fun httpInvoking(headers: Array<BasicHeader>) {
52             headers.plusElement(BasicHeader(ONAP_REQUEST_ID, MDC.get("InvocationID").defaultToUUID()))
53             headers.plusElement(BasicHeader(ONAP_INVOCATION_ID, UUID.randomUUID().toString()))
54             val partnerName = System.getProperty("APPNAME") ?: "BlueprintsProcessor"
55             headers.plusElement(BasicHeader(ONAP_PARTNER_NAME, partnerName))
56         }
57     }
58
59     fun entering(request: ServerHttpRequest) {
60         val localhost = InetAddress.getLocalHost()
61         val headers = request.headers
62         val requestID = headers.getFirst(ONAP_REQUEST_ID).defaultToUUID()
63         val invocationID = headers.getFirst(ONAP_INVOCATION_ID).defaultToUUID()
64         val partnerName = headers.getFirst(ONAP_PARTNER_NAME).defaultToEmpty()
65         MDC.put("InvokeTimestamp", ZonedDateTime.now(ZoneOffset.UTC).format(DateTimeFormatter.ISO_INSTANT))
66         MDC.put("RequestID", requestID)
67         MDC.put("InvocationID", invocationID)
68         MDC.put("PartnerName", partnerName)
69         MDC.put("ClientIPAddress", request.remoteAddress?.address?.hostAddress.defaultToEmpty())
70         MDC.put("ServerFQDN", localhost.hostName.defaultToEmpty())
71         if (MDC.get("ServiceName") == null || MDC.get("ServiceName").equals("", ignoreCase = true)) {
72             MDC.put("ServiceName", request.uri.path)
73         }
74     }
75
76     fun exiting(request: ServerHttpRequest, response: ServerHttpResponse) {
77         try {
78             val reqHeaders = request.headers
79             val resHeaders = response.headers
80             resHeaders[ONAP_REQUEST_ID] = MDC.get("RequestID")
81             resHeaders[ONAP_INVOCATION_ID] = MDC.get("InvocationID")
82             val partnerName = System.getProperty("APPNAME") ?: "BlueprintsProcessor"
83             resHeaders[ONAP_PARTNER_NAME] = partnerName
84         } catch (e: Exception) {
85             log.warn("couldn't set response headers", e)
86         } finally {
87             MDC.clear()
88         }
89     }
90 }
91
92
93 /** Used in Rest controller API methods to populate MDC context to nested coroutines from reactor web filter context. */
94 @UseExperimental(InternalCoroutinesApi::class)
95 fun <T> monoMdc(context: CoroutineContext = EmptyCoroutineContext,
96                 block: suspend CoroutineScope.() -> T?): Mono<T> = Mono.create { sink ->
97
98     val reactorContext = (context[ReactorContext]?.context?.putAll(sink.currentContext())
99             ?: sink.currentContext()).asCoroutineContext()
100     /** Populate MDC context only if present in Reactor Context */
101     val newContext = if (!reactorContext.context.isEmpty
102             && reactorContext.context.hasKey(MDCContext)) {
103         val mdcContext = reactorContext.context.get<MDCContext>(MDCContext)
104         GlobalScope.newCoroutineContext(context + reactorContext + mdcContext)
105     } else GlobalScope.newCoroutineContext(context + reactorContext)
106
107     val coroutine = MonoMDCCoroutine(newContext, sink)
108     sink.onDispose(coroutine)
109     coroutine.start(CoroutineStart.DEFAULT, coroutine, block)
110 }
111
112 @InternalCoroutinesApi
113 class MonoMDCCoroutine<in T>(
114         parentContext: CoroutineContext,
115         private val sink: MonoSink<T>
116 ) : AbstractCoroutine<T>(parentContext, true), Disposable {
117     private var disposed = false
118
119     override fun onCompleted(value: T) {
120         if (!disposed) {
121             if (value == null) sink.success() else sink.success(value)
122         }
123     }
124
125     override fun onCancelled(cause: Throwable, handled: Boolean) {
126         if (!disposed) {
127             sink.error(cause)
128         } else if (!handled) {
129             handleCoroutineException(context, cause)
130         }
131     }
132
133     override fun dispose() {
134         disposed = true
135         cancel()
136     }
137
138     override fun isDisposed(): Boolean = disposed
139 }