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.ONAP_INVOCATION_ID
30 import org.onap.ccsdk.cds.controllerblueprints.core.BluePrintConstants.ONAP_PARTNER_NAME
31 import org.onap.ccsdk.cds.controllerblueprints.core.BluePrintConstants.ONAP_REQUEST_ID
32 import org.onap.ccsdk.cds.controllerblueprints.core.MDCContext
33 import org.onap.ccsdk.cds.controllerblueprints.core.defaultToEmpty
34 import org.onap.ccsdk.cds.controllerblueprints.core.defaultToUUID
35 import org.onap.ccsdk.cds.controllerblueprints.core.logger
37 import org.springframework.http.server.reactive.ServerHttpRequest
38 import org.springframework.http.server.reactive.ServerHttpResponse
39 import reactor.core.Disposable
40 import reactor.core.publisher.Mono
41 import reactor.core.publisher.MonoSink
42 import java.net.InetAddress
43 import java.time.ZoneOffset
44 import java.time.ZonedDateTime
45 import java.time.format.DateTimeFormatter
47 import kotlin.coroutines.CoroutineContext
48 import kotlin.coroutines.EmptyCoroutineContext
50 class RestLoggerService {
51 private val log = logger(RestLoggerService::class)
54 /** Used before invoking any REST outbound request, Inbound Invocation ID is used as request Id
55 * for outbound Request, If invocation Id is missing then default Request Id will be generated.
57 fun httpInvoking(headers: Array<BasicHeader>) {
58 headers.plusElement(BasicHeader(ONAP_REQUEST_ID, MDC.get("InvocationID").defaultToUUID()))
59 headers.plusElement(BasicHeader(ONAP_INVOCATION_ID, UUID.randomUUID().toString()))
60 val partnerName = System.getProperty("APPNAME") ?: "BlueprintsProcessor"
61 headers.plusElement(BasicHeader(ONAP_PARTNER_NAME, partnerName))
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 val partnerName = System.getProperty("APPNAME") ?: "BlueprintsProcessor"
89 resHeaders[ONAP_PARTNER_NAME] = partnerName
90 } catch (e: Exception) {
91 log.warn("couldn't set response headers", e)
98 /** Used in Rest controller API methods to populate MDC context to nested coroutines from reactor web filter context. */
99 @UseExperimental(InternalCoroutinesApi::class)
101 context: CoroutineContext = EmptyCoroutineContext,
102 block: suspend CoroutineScope.() -> T?
103 ): Mono<T> = Mono.create { sink ->
105 val reactorContext = (context[ReactorContext]?.context?.putAll(sink.currentContext())
106 ?: sink.currentContext()).asCoroutineContext()
107 /** Populate MDC context only if present in Reactor Context */
108 val newContext = if (!reactorContext.context.isEmpty &&
109 reactorContext.context.hasKey(MDCContext)
111 val mdcContext = reactorContext.context.get<MDCContext>(MDCContext)
112 GlobalScope.newCoroutineContext(context + reactorContext + mdcContext)
113 } else GlobalScope.newCoroutineContext(context + reactorContext)
115 val coroutine = MonoMDCCoroutine(newContext, sink)
116 sink.onDispose(coroutine)
117 coroutine.start(CoroutineStart.DEFAULT, coroutine, block)
120 @InternalCoroutinesApi
121 class MonoMDCCoroutine<in T>(
122 parentContext: CoroutineContext,
123 private val sink: MonoSink<T>
124 ) : AbstractCoroutine<T>(parentContext, true), Disposable {
126 private var disposed = false
128 override fun onCompleted(value: T) {
130 if (value == null) sink.success() else sink.success(value)
134 override fun onCancelled(cause: Throwable, handled: Boolean) {
137 } else if (!handled) {
138 handleCoroutineException(context, cause)
142 override fun dispose() {
147 override fun isDisposed(): Boolean = disposed