2 * Copyright © 2018-2019 AT&T Intellectual Property.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package org.onap.ccsdk.cds.blueprintsprocessor.rest.service
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.handleCoroutineException
25 import kotlinx.coroutines.newCoroutineContext
26 import kotlinx.coroutines.reactor.ReactorContext
27 import kotlinx.coroutines.reactor.asCoroutineContext
28 import org.apache.http.message.BasicHeader
29 import org.onap.ccsdk.cds.controllerblueprints.core.BluePrintConstants
30 import org.onap.ccsdk.cds.controllerblueprints.core.BluePrintConstants.ONAP_INVOCATION_ID
31 import org.onap.ccsdk.cds.controllerblueprints.core.BluePrintConstants.ONAP_PARTNER_NAME
32 import org.onap.ccsdk.cds.controllerblueprints.core.BluePrintConstants.ONAP_REQUEST_ID
33 import org.onap.ccsdk.cds.controllerblueprints.core.MDCContext
34 import org.onap.ccsdk.cds.controllerblueprints.core.defaultToEmpty
35 import org.onap.ccsdk.cds.controllerblueprints.core.defaultToUUID
36 import org.onap.ccsdk.cds.controllerblueprints.core.logger
38 import org.springframework.http.server.reactive.ServerHttpRequest
39 import org.springframework.http.server.reactive.ServerHttpResponse
40 import reactor.core.Disposable
41 import reactor.core.publisher.Mono
42 import reactor.core.publisher.MonoSink
43 import java.net.InetAddress
44 import java.time.ZoneOffset
45 import java.time.ZonedDateTime
46 import java.time.format.DateTimeFormatter
48 import kotlin.coroutines.CoroutineContext
49 import kotlin.coroutines.EmptyCoroutineContext
51 class RestLoggerService {
52 private val log = logger(RestLoggerService::class)
55 /** Used before invoking any REST outbound request, Inbound Invocation ID is used as request Id
56 * for outbound Request, If invocation Id is missing then default Request Id will be generated.
58 fun httpInvoking(headers: Array<BasicHeader>) {
59 headers.plusElement(BasicHeader(ONAP_REQUEST_ID, MDC.get("InvocationID").defaultToUUID()))
60 headers.plusElement(BasicHeader(ONAP_INVOCATION_ID, UUID.randomUUID().toString()))
61 headers.plusElement(BasicHeader(ONAP_PARTNER_NAME, BluePrintConstants.APP_NAME))
65 fun entering(request: ServerHttpRequest) {
66 val localhost = InetAddress.getLocalHost()
67 val headers = request.headers
68 val requestID = headers.getFirst(ONAP_REQUEST_ID).defaultToUUID()
69 val invocationID = headers.getFirst(ONAP_INVOCATION_ID).defaultToUUID()
70 val partnerName = headers.getFirst(ONAP_PARTNER_NAME).defaultToEmpty()
71 MDC.put("InvokeTimestamp", ZonedDateTime.now(ZoneOffset.UTC).format(DateTimeFormatter.ISO_INSTANT))
72 MDC.put("RequestID", requestID)
73 MDC.put("InvocationID", invocationID)
74 MDC.put("PartnerName", partnerName)
75 MDC.put("ClientIPAddress", request.remoteAddress?.address?.hostAddress.defaultToEmpty())
76 MDC.put("ServerFQDN", localhost.hostName.defaultToEmpty())
77 if (MDC.get("ServiceName") == null || MDC.get("ServiceName").equals("", ignoreCase = true)) {
78 MDC.put("ServiceName", request.uri.path)
82 fun exiting(request: ServerHttpRequest, response: ServerHttpResponse) {
84 val reqHeaders = request.headers
85 val resHeaders = response.headers
86 resHeaders[ONAP_REQUEST_ID] = MDC.get("RequestID")
87 resHeaders[ONAP_INVOCATION_ID] = MDC.get("InvocationID")
88 resHeaders[ONAP_PARTNER_NAME] = BluePrintConstants.APP_NAME
89 } catch (e: Exception) {
90 log.warn("couldn't set response headers", e)
97 /** Used in Rest controller API methods to populate MDC context to nested coroutines from reactor web filter context. */
98 @UseExperimental(InternalCoroutinesApi::class)
100 context: CoroutineContext = EmptyCoroutineContext,
101 block: suspend CoroutineScope.() -> T?
102 ): Mono<T> = Mono.create { sink ->
104 val reactorContext = (context[ReactorContext]?.context?.putAll(sink.currentContext())
105 ?: sink.currentContext()).asCoroutineContext()
106 /** Populate MDC context only if present in Reactor Context */
107 val newContext = if (!reactorContext.context.isEmpty &&
108 reactorContext.context.hasKey(MDCContext)
110 val mdcContext = reactorContext.context.get<MDCContext>(MDCContext)
111 GlobalScope.newCoroutineContext(context + reactorContext + mdcContext)
112 } else GlobalScope.newCoroutineContext(context + reactorContext)
114 val coroutine = MonoMDCCoroutine(newContext, sink)
115 sink.onDispose(coroutine)
116 coroutine.start(CoroutineStart.DEFAULT, coroutine, block)
119 @InternalCoroutinesApi
120 class MonoMDCCoroutine<in T>(
121 parentContext: CoroutineContext,
122 private val sink: MonoSink<T>
123 ) : AbstractCoroutine<T>(parentContext, true), Disposable {
125 private var disposed = false
127 override fun onCompleted(value: T) {
129 if (value == null) sink.success() else sink.success(value)
133 override fun onCancelled(cause: Throwable, handled: Boolean) {
136 } else if (!handled) {
137 handleCoroutineException(context, cause)
141 override fun dispose() {
146 override fun isDisposed(): Boolean = disposed