Add Message tracing logger service.
[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.onap.ccsdk.cds.controllerblueprints.core.BluePrintConstants.ONAP_INVOCATION_ID
23 import org.onap.ccsdk.cds.controllerblueprints.core.BluePrintConstants.ONAP_PARTNER_NAME
24 import org.onap.ccsdk.cds.controllerblueprints.core.BluePrintConstants.ONAP_REQUEST_ID
25 import org.onap.ccsdk.cds.controllerblueprints.core.MDCContext
26 import org.onap.ccsdk.cds.controllerblueprints.core.defaultToEmpty
27 import org.onap.ccsdk.cds.controllerblueprints.core.defaultToUUID
28 import org.onap.ccsdk.cds.controllerblueprints.core.logger
29 import org.slf4j.MDC
30 import org.springframework.http.server.reactive.ServerHttpRequest
31 import org.springframework.http.server.reactive.ServerHttpResponse
32 import reactor.core.Disposable
33 import reactor.core.publisher.Mono
34 import reactor.core.publisher.MonoSink
35 import java.net.InetAddress
36 import java.time.ZoneOffset
37 import java.time.ZonedDateTime
38 import java.time.format.DateTimeFormatter
39 import kotlin.coroutines.CoroutineContext
40 import kotlin.coroutines.EmptyCoroutineContext
41
42 class RestLoggerService {
43     private val log = logger(RestLoggerService::class)
44
45
46     fun entering(request: ServerHttpRequest) {
47         val localhost = InetAddress.getLocalHost()
48         val headers = request.headers
49         val requestID = headers.getFirst(ONAP_REQUEST_ID).defaultToUUID()
50         val invocationID = headers.getFirst(ONAP_INVOCATION_ID).defaultToUUID()
51         val partnerName = headers.getFirst(ONAP_PARTNER_NAME).defaultToEmpty()
52         MDC.put("InvokeTimestamp", ZonedDateTime.now(ZoneOffset.UTC).format(DateTimeFormatter.ISO_INSTANT))
53         MDC.put("RequestID", requestID)
54         MDC.put("InvocationID", invocationID)
55         MDC.put("PartnerName", partnerName)
56         MDC.put("ClientIPAddress", request.remoteAddress?.address?.hostAddress.defaultToEmpty())
57         MDC.put("ServerFQDN",localhost.hostName.defaultToEmpty())
58         if (MDC.get("ServiceName") == null || MDC.get("ServiceName").equals("", ignoreCase = true)) {
59             MDC.put("ServiceName", request.uri.path)
60         }
61     }
62
63     fun exiting(request: ServerHttpRequest, response: ServerHttpResponse) {
64         try {
65             val reqHeaders = request.headers
66             val resHeaders = response.headers
67             resHeaders[ONAP_REQUEST_ID] = MDC.get("RequestID")
68             resHeaders[ONAP_INVOCATION_ID] = MDC.get("InvocationID")
69             val partnerName = System.getProperty("APPNAME") ?: "BlueprintsProcessor"
70             resHeaders[ONAP_PARTNER_NAME] = partnerName
71         } catch (e: Exception) {
72             log.warn("couldn't set response headers", e)
73         } finally {
74             MDC.clear()
75         }
76     }
77 }
78
79
80 /** Used in Rest controller API methods to populate MDC context to nested coroutines from reactor web filter context. */
81 @UseExperimental(InternalCoroutinesApi::class)
82 fun <T> monoMdc(context: CoroutineContext = EmptyCoroutineContext,
83                 block: suspend CoroutineScope.() -> T?): Mono<T> = Mono.create { sink ->
84
85     val reactorContext = (context[ReactorContext]?.context?.putAll(sink.currentContext())
86             ?: sink.currentContext()).asCoroutineContext()
87     /** Populate MDC context only if present in Reactor Context */
88     val newContext = if (!reactorContext.context.isEmpty
89             && reactorContext.context.hasKey(MDCContext)) {
90         val mdcContext = reactorContext.context.get<MDCContext>(MDCContext)
91         GlobalScope.newCoroutineContext(context + reactorContext + mdcContext)
92     } else GlobalScope.newCoroutineContext(context + reactorContext)
93
94     val coroutine = MonoMDCCoroutine(newContext, sink)
95     sink.onDispose(coroutine)
96     coroutine.start(CoroutineStart.DEFAULT, coroutine, block)
97 }
98
99 @InternalCoroutinesApi
100 class MonoMDCCoroutine<in T>(
101         parentContext: CoroutineContext,
102         private val sink: MonoSink<T>
103 ) : AbstractCoroutine<T>(parentContext, true), Disposable {
104     private var disposed = false
105
106     override fun onCompleted(value: T) {
107         if (!disposed) {
108             if (value == null) sink.success() else sink.success(value)
109         }
110     }
111
112     override fun onCancelled(cause: Throwable, handled: Boolean) {
113         if (!disposed) {
114             sink.error(cause)
115         } else if (!handled) {
116             handleCoroutineException(context, cause)
117         }
118     }
119
120     override fun dispose() {
121         disposed = true
122         cancel()
123     }
124
125     override fun isDisposed(): Boolean = disposed
126 }