846a94a091e69c2980309f92018e9a708133b3f8
[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.AbstractCoroutine
20 import kotlinx.coroutines.CoroutineScope
21 import kotlinx.coroutines.CoroutineStart
22 import kotlinx.coroutines.GlobalScope
23 import kotlinx.coroutines.InternalCoroutinesApi
24 import kotlinx.coroutines.coroutineScope
25 import kotlinx.coroutines.handleCoroutineException
26 import kotlinx.coroutines.newCoroutineContext
27 import kotlinx.coroutines.reactor.ReactorContext
28 import kotlinx.coroutines.reactor.asCoroutineContext
29 import kotlinx.coroutines.withContext
30 import org.apache.http.message.BasicHeader
31 import org.onap.ccsdk.cds.controllerblueprints.core.BluePrintConstants
32 import org.onap.ccsdk.cds.controllerblueprints.core.BluePrintConstants.ONAP_INVOCATION_ID
33 import org.onap.ccsdk.cds.controllerblueprints.core.BluePrintConstants.ONAP_PARTNER_NAME
34 import org.onap.ccsdk.cds.controllerblueprints.core.BluePrintConstants.ONAP_REQUEST_ID
35 import org.onap.ccsdk.cds.controllerblueprints.core.MDCContext
36 import org.onap.ccsdk.cds.controllerblueprints.core.defaultToEmpty
37 import org.onap.ccsdk.cds.controllerblueprints.core.defaultToUUID
38 import org.onap.ccsdk.cds.controllerblueprints.core.logger
39 import org.slf4j.MDC
40 import org.springframework.http.server.reactive.ServerHttpRequest
41 import org.springframework.http.server.reactive.ServerHttpResponse
42 import reactor.core.Disposable
43 import reactor.core.publisher.Mono
44 import reactor.core.publisher.MonoSink
45 import java.net.InetAddress
46 import java.time.ZoneOffset
47 import java.time.ZonedDateTime
48 import java.time.format.DateTimeFormatter
49 import java.util.UUID
50 import kotlin.coroutines.CoroutineContext
51 import kotlin.coroutines.EmptyCoroutineContext
52
53 class RestLoggerService {
54     private val log = logger(RestLoggerService::class)
55
56     companion object {
57         /** Used before invoking any REST outbound request, Inbound Invocation ID is used as request Id
58          * for outbound Request, If invocation Id is missing then default Request Id will be generated.
59          */
60         fun httpInvoking(headers: Array<BasicHeader>) {
61             headers.plusElement(BasicHeader(ONAP_REQUEST_ID, MDC.get("InvocationID").defaultToUUID()))
62             headers.plusElement(BasicHeader(ONAP_INVOCATION_ID, UUID.randomUUID().toString()))
63             headers.plusElement(BasicHeader(ONAP_PARTNER_NAME, BluePrintConstants.APP_NAME))
64         }
65     }
66
67     fun entering(request: ServerHttpRequest) {
68         val localhost = InetAddress.getLocalHost()
69         val headers = request.headers
70         val requestID = headers.getFirst(ONAP_REQUEST_ID).defaultToUUID()
71         val invocationID = headers.getFirst(ONAP_INVOCATION_ID).defaultToUUID()
72         val partnerName = headers.getFirst(ONAP_PARTNER_NAME).defaultToEmpty()
73         MDC.put("InvokeTimestamp", ZonedDateTime.now(ZoneOffset.UTC).format(DateTimeFormatter.ISO_INSTANT))
74         MDC.put("RequestID", requestID)
75         MDC.put("InvocationID", invocationID)
76         MDC.put("PartnerName", partnerName)
77         MDC.put("ClientIPAddress", request.remoteAddress?.address?.hostAddress.defaultToEmpty())
78         MDC.put("ServerFQDN", localhost.hostName.defaultToEmpty())
79         if (MDC.get("ServiceName") == null || MDC.get("ServiceName").equals("", ignoreCase = true)) {
80             MDC.put("ServiceName", request.uri.path)
81         }
82     }
83
84     fun exiting(request: ServerHttpRequest, response: ServerHttpResponse) {
85         try {
86             val reqHeaders = request.headers
87             val resHeaders = response.headers
88             resHeaders[ONAP_REQUEST_ID] = MDC.get("RequestID")
89             resHeaders[ONAP_INVOCATION_ID] = MDC.get("InvocationID")
90             resHeaders[ONAP_PARTNER_NAME] = BluePrintConstants.APP_NAME
91         } catch (e: Exception) {
92             log.warn("couldn't set response headers", e)
93         } finally {
94             MDC.clear()
95         }
96     }
97 }
98
99 /** Used in Rest controller API methods to populate MDC context to nested coroutines from reactor web filter context. */
100 suspend fun <T> mdcWebCoroutineScope(
101     block: suspend CoroutineScope.() -> T
102 ) = coroutineScope {
103     val reactorContext = this.coroutineContext[ReactorContext]
104     /** Populate MDC context only if present in Reactor Context */
105     val newContext = if (reactorContext != null &&
106         !reactorContext.context.isEmpty &&
107         reactorContext.context.hasKey(MDCContext)
108     ) {
109         val mdcContext = reactorContext.context.get<MDCContext>(MDCContext)
110         if (mdcContext != null)
111             newCoroutineContext(this.coroutineContext + reactorContext + mdcContext)
112         else
113             newCoroutineContext(this.coroutineContext + reactorContext)
114     } else this.coroutineContext
115     // Execute the block with new and old context
116     withContext(newContext) {
117         block()
118     }
119 }
120
121 @Deprecated(
122     message = "Now CDS supports Coruoutin rest controller",
123     replaceWith = ReplaceWith("mdcWebCoroutineScope")
124 )
125 /** Used in Rest controller API methods to populate MDC context to nested coroutines from reactor web filter context. */
126 @UseExperimental(InternalCoroutinesApi::class)
127 fun <T> monoMdc(
128     context: CoroutineContext = EmptyCoroutineContext,
129     block: suspend CoroutineScope.() -> T?
130 ): Mono<T> = Mono.create { sink ->
131
132     val reactorContext = (context[ReactorContext]?.context?.putAll(sink.currentContext())
133         ?: sink.currentContext()).asCoroutineContext()
134     /** Populate MDC context only if present in Reactor Context */
135     val newContext = if (!reactorContext.context.isEmpty &&
136         reactorContext.context.hasKey(MDCContext)
137     ) {
138         val mdcContext = reactorContext.context.get<MDCContext>(MDCContext)
139         GlobalScope.newCoroutineContext(context + reactorContext + mdcContext)
140     } else GlobalScope.newCoroutineContext(context + reactorContext)
141
142     val coroutine = MonoMDCCoroutine(newContext, sink)
143     sink.onDispose(coroutine)
144     coroutine.start(CoroutineStart.DEFAULT, coroutine, block)
145 }
146
147 @InternalCoroutinesApi
148 class MonoMDCCoroutine<in T>(
149     parentContext: CoroutineContext,
150     private val sink: MonoSink<T>
151 ) : AbstractCoroutine<T>(parentContext, true), Disposable {
152
153     private var disposed = false
154
155     override fun onCompleted(value: T) {
156         if (!disposed) {
157             if (value == null) sink.success() else sink.success(value)
158         }
159     }
160
161     override fun onCancelled(cause: Throwable, handled: Boolean) {
162         if (!disposed) {
163             sink.error(cause)
164         } else if (!handled) {
165             handleCoroutineException(context, cause)
166         }
167     }
168
169     override fun dispose() {
170         disposed = true
171         cancel()
172     }
173
174     override fun isDisposed(): Boolean = disposed
175 }