4b9bbb19f43593c83a85b9a04a975a4d4a16f5f0
[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  * Modifications Copyright © 2020 Bell Canada.
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at
8  *
9  *     http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */
17
18 package org.onap.ccsdk.cds.blueprintsprocessor.rest.service
19
20 import kotlinx.coroutines.AbstractCoroutine
21 import kotlinx.coroutines.CoroutineScope
22 import kotlinx.coroutines.CoroutineStart
23 import kotlinx.coroutines.GlobalScope
24 import kotlinx.coroutines.InternalCoroutinesApi
25 import kotlinx.coroutines.coroutineScope
26 import kotlinx.coroutines.handleCoroutineException
27 import kotlinx.coroutines.newCoroutineContext
28 import kotlinx.coroutines.reactor.ReactorContext
29 import kotlinx.coroutines.reactor.asCoroutineContext
30 import kotlinx.coroutines.withContext
31 import org.apache.http.message.BasicHeader
32 import org.onap.ccsdk.cds.blueprintsprocessor.core.api.data.ExecutionServiceInput
33 import org.onap.ccsdk.cds.controllerblueprints.core.BluePrintConstants
34 import org.onap.ccsdk.cds.controllerblueprints.core.BluePrintConstants.ONAP_INVOCATION_ID
35 import org.onap.ccsdk.cds.controllerblueprints.core.BluePrintConstants.ONAP_ORIGINATOR_ID
36 import org.onap.ccsdk.cds.controllerblueprints.core.BluePrintConstants.ONAP_PARTNER_NAME
37 import org.onap.ccsdk.cds.controllerblueprints.core.BluePrintConstants.ONAP_REQUEST_ID
38 import org.onap.ccsdk.cds.controllerblueprints.core.BluePrintConstants.ONAP_SUBREQUEST_ID
39 import org.onap.ccsdk.cds.controllerblueprints.core.MDCContext
40 import org.onap.ccsdk.cds.controllerblueprints.core.defaultToEmpty
41 import org.onap.ccsdk.cds.controllerblueprints.core.defaultToUUID
42 import org.onap.ccsdk.cds.controllerblueprints.core.logger
43 import org.slf4j.MDC
44 import org.springframework.http.server.reactive.ServerHttpRequest
45 import org.springframework.http.server.reactive.ServerHttpResponse
46 import reactor.core.Disposable
47 import reactor.core.publisher.Mono
48 import reactor.core.publisher.MonoSink
49 import java.net.InetAddress
50 import java.time.ZoneOffset
51 import java.time.ZonedDateTime
52 import java.time.format.DateTimeFormatter
53 import java.util.UUID
54 import kotlin.coroutines.CoroutineContext
55 import kotlin.coroutines.EmptyCoroutineContext
56
57 class RestLoggerService {
58
59     private val log = logger(RestLoggerService::class)
60
61     companion object {
62
63         /** Used before invoking any REST outbound request, Inbound Invocation ID is used as request Id
64          * for outbound Request, If invocation Id is missing then default Request Id will be generated.
65          */
66         fun httpInvoking(headers: Array<BasicHeader>) {
67             headers.plusElement(BasicHeader(ONAP_REQUEST_ID, MDC.get("InvocationID").defaultToUUID()))
68             headers.plusElement(BasicHeader(ONAP_INVOCATION_ID, UUID.randomUUID().toString()))
69             headers.plusElement(BasicHeader(ONAP_PARTNER_NAME, BluePrintConstants.APP_NAME))
70         }
71     }
72
73     fun entering(request: ServerHttpRequest) {
74         val localhost = InetAddress.getLocalHost()
75         val headers = request.headers
76         val requestID = headers.getFirst(ONAP_REQUEST_ID).defaultToUUID()
77         val subrequestID = headers.getFirst(ONAP_SUBREQUEST_ID).defaultToEmpty()
78         val originatorID = headers.getFirst(ONAP_ORIGINATOR_ID).defaultToEmpty()
79         val invocationID = headers.getFirst(ONAP_INVOCATION_ID).defaultToUUID()
80         val partnerName = headers.getFirst(ONAP_PARTNER_NAME).defaultToEmpty()
81         MDC.put("InvokeTimestamp", ZonedDateTime.now(ZoneOffset.UTC).format(DateTimeFormatter.ISO_INSTANT))
82         MDC.put("RequestID", requestID)
83         MDC.put("SubRequestID", subrequestID)
84         MDC.put("OriginatorID", originatorID)
85         MDC.put("InvocationID", invocationID)
86         MDC.put("PartnerName", partnerName)
87         MDC.put("ClientIPAddress", request.remoteAddress?.address?.hostAddress.defaultToEmpty())
88         MDC.put("ServerFQDN", localhost.hostName.defaultToEmpty())
89         if (MDC.get("ServiceName") == null || MDC.get("ServiceName").equals("", ignoreCase = true)) {
90             MDC.put("ServiceName", request.uri.path)
91         }
92     }
93
94     fun exiting(request: ServerHttpRequest, response: ServerHttpResponse) {
95         try {
96             val reqHeaders = request.headers
97             val resHeaders = response.headers
98             resHeaders[ONAP_REQUEST_ID] = MDC.get("RequestID")
99             resHeaders[ONAP_SUBREQUEST_ID] = MDC.get("SubRequestID")
100             resHeaders[ONAP_ORIGINATOR_ID] = MDC.get("OriginatorID")
101             resHeaders[ONAP_INVOCATION_ID] = MDC.get("InvocationID")
102             resHeaders[ONAP_PARTNER_NAME] = BluePrintConstants.APP_NAME
103         } catch (e: Exception) {
104             log.warn("couldn't set response headers", e)
105         } finally {
106             MDC.clear()
107         }
108     }
109 }
110
111 /**
112  * Used in Rest controller API methods to populate MDC context to nested coroutines from reactor web filter context.
113  * @param executionServiceInput (Optional) Used to override values populated in the MDC Context
114  */
115 suspend fun <T> mdcWebCoroutineScope(
116     executionServiceInput: ExecutionServiceInput? = null,
117     block: suspend CoroutineScope.() -> T
118 ) = coroutineScope {
119     val reactorContext = this.coroutineContext[ReactorContext]
120
121     /** Populate MDC context only if present in Reactor Context */
122     val newContext = if (reactorContext != null &&
123         !reactorContext.context.isEmpty &&
124         reactorContext.context.hasKey(MDCContext)
125     ) {
126         val mdcContext = if (executionServiceInput != null) {
127             // MDC Context with overridden request ID
128             MDC.put("RequestID", executionServiceInput.commonHeader.requestId)
129             MDC.put("SubRequestID", executionServiceInput.commonHeader.subRequestId)
130             MDC.put("OriginatorID", executionServiceInput.commonHeader.originatorId)
131             MDCContext(MDC.getCopyOfContextMap())
132         } else {
133             // Default MDC Context
134             reactorContext.context.get(MDCContext)
135         }
136         if (mdcContext != null)
137             newCoroutineContext(this.coroutineContext + reactorContext + mdcContext)
138         else
139             newCoroutineContext(this.coroutineContext + reactorContext)
140     } else this.coroutineContext
141
142     // Execute the block with new and old context
143     withContext(newContext) {
144         block()
145     }
146 }
147
148 @Deprecated(
149     message = "Now CDS supports Coruoutin rest controller",
150     replaceWith = ReplaceWith("mdcWebCoroutineScope")
151 )
152 /** Used in Rest controller API methods to populate MDC context to nested coroutines from reactor web filter context. */
153 @UseExperimental(InternalCoroutinesApi::class)
154 fun <T> monoMdc(
155     context: CoroutineContext = EmptyCoroutineContext,
156     block: suspend CoroutineScope.() -> T?
157 ): Mono<T> = Mono.create { sink ->
158
159     val reactorContext = (
160         context[ReactorContext]?.context?.putAll(sink.currentContext())
161             ?: sink.currentContext()
162         ).asCoroutineContext()
163
164     /** Populate MDC context only if present in Reactor Context */
165     val newContext = if (!reactorContext.context.isEmpty &&
166         reactorContext.context.hasKey(MDCContext)
167     ) {
168         val mdcContext = reactorContext.context.get<MDCContext>(MDCContext)
169         GlobalScope.newCoroutineContext(context + reactorContext + mdcContext)
170     } else GlobalScope.newCoroutineContext(context + reactorContext)
171
172     val coroutine = MonoMDCCoroutine(newContext, sink)
173     sink.onDispose(coroutine)
174     coroutine.start(CoroutineStart.DEFAULT, coroutine, block)
175 }
176
177 @InternalCoroutinesApi
178 class MonoMDCCoroutine<in T>(
179     parentContext: CoroutineContext,
180     private val sink: MonoSink<T>
181 ) : AbstractCoroutine<T>(parentContext, true), Disposable {
182
183     private var disposed = false
184
185     override fun onCompleted(value: T) {
186         if (!disposed) {
187             if (value == null) sink.success() else sink.success(value)
188         }
189     }
190
191     override fun onCancelled(cause: Throwable, handled: Boolean) {
192         if (!disposed) {
193             sink.error(cause)
194         } else if (!handled) {
195             handleCoroutineException(context, cause)
196         }
197     }
198
199     override fun dispose() {
200         disposed = true
201         cancel()
202     }
203
204     override fun isDisposed(): Boolean = disposed
205 }