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.*
20 import kotlinx.coroutines.reactor.ReactorContext
21 import kotlinx.coroutines.reactor.asCoroutineContext
22 import org.apache.http.message.BasicHeader
23 import org.onap.ccsdk.cds.controllerblueprints.core.BluePrintConstants.ONAP_INVOCATION_ID
24 import org.onap.ccsdk.cds.controllerblueprints.core.BluePrintConstants.ONAP_PARTNER_NAME
25 import org.onap.ccsdk.cds.controllerblueprints.core.BluePrintConstants.ONAP_REQUEST_ID
26 import org.onap.ccsdk.cds.controllerblueprints.core.MDCContext
27 import org.onap.ccsdk.cds.controllerblueprints.core.defaultToEmpty
28 import org.onap.ccsdk.cds.controllerblueprints.core.defaultToUUID
29 import org.onap.ccsdk.cds.controllerblueprints.core.logger
31 import org.springframework.http.server.reactive.ServerHttpRequest
32 import org.springframework.http.server.reactive.ServerHttpResponse
33 import reactor.core.Disposable
34 import reactor.core.publisher.Mono
35 import reactor.core.publisher.MonoSink
36 import java.net.InetAddress
37 import java.time.ZoneOffset
38 import java.time.ZonedDateTime
39 import java.time.format.DateTimeFormatter
41 import kotlin.coroutines.CoroutineContext
42 import kotlin.coroutines.EmptyCoroutineContext
44 class RestLoggerService {
45 private val log = logger(RestLoggerService::class)
48 /** Used before invoking any REST outbound request, Inbound Invocation ID is used as request Id
49 * for outbound Request, If invocation Id is missing then default Request Id will be generated.
51 fun httpInvoking(headers: Array<BasicHeader>) {
52 headers.plusElement(BasicHeader(ONAP_REQUEST_ID, MDC.get("InvocationID").defaultToUUID()))
53 headers.plusElement(BasicHeader(ONAP_INVOCATION_ID, UUID.randomUUID().toString()))
54 val partnerName = System.getProperty("APPNAME") ?: "BlueprintsProcessor"
55 headers.plusElement(BasicHeader(ONAP_PARTNER_NAME, partnerName))
59 fun entering(request: ServerHttpRequest) {
60 val localhost = InetAddress.getLocalHost()
61 val headers = request.headers
62 val requestID = headers.getFirst(ONAP_REQUEST_ID).defaultToUUID()
63 val invocationID = headers.getFirst(ONAP_INVOCATION_ID).defaultToUUID()
64 val partnerName = headers.getFirst(ONAP_PARTNER_NAME).defaultToEmpty()
65 MDC.put("InvokeTimestamp", ZonedDateTime.now(ZoneOffset.UTC).format(DateTimeFormatter.ISO_INSTANT))
66 MDC.put("RequestID", requestID)
67 MDC.put("InvocationID", invocationID)
68 MDC.put("PartnerName", partnerName)
69 MDC.put("ClientIPAddress", request.remoteAddress?.address?.hostAddress.defaultToEmpty())
70 MDC.put("ServerFQDN", localhost.hostName.defaultToEmpty())
71 if (MDC.get("ServiceName") == null || MDC.get("ServiceName").equals("", ignoreCase = true)) {
72 MDC.put("ServiceName", request.uri.path)
76 fun exiting(request: ServerHttpRequest, response: ServerHttpResponse) {
78 val reqHeaders = request.headers
79 val resHeaders = response.headers
80 resHeaders[ONAP_REQUEST_ID] = MDC.get("RequestID")
81 resHeaders[ONAP_INVOCATION_ID] = MDC.get("InvocationID")
82 val partnerName = System.getProperty("APPNAME") ?: "BlueprintsProcessor"
83 resHeaders[ONAP_PARTNER_NAME] = partnerName
84 } catch (e: Exception) {
85 log.warn("couldn't set response headers", e)
93 /** Used in Rest controller API methods to populate MDC context to nested coroutines from reactor web filter context. */
94 @UseExperimental(InternalCoroutinesApi::class)
95 fun <T> monoMdc(context: CoroutineContext = EmptyCoroutineContext,
96 block: suspend CoroutineScope.() -> T?): Mono<T> = Mono.create { sink ->
98 val reactorContext = (context[ReactorContext]?.context?.putAll(sink.currentContext())
99 ?: sink.currentContext()).asCoroutineContext()
100 /** Populate MDC context only if present in Reactor Context */
101 val newContext = if (!reactorContext.context.isEmpty
102 && reactorContext.context.hasKey(MDCContext)) {
103 val mdcContext = reactorContext.context.get<MDCContext>(MDCContext)
104 GlobalScope.newCoroutineContext(context + reactorContext + mdcContext)
105 } else GlobalScope.newCoroutineContext(context + reactorContext)
107 val coroutine = MonoMDCCoroutine(newContext, sink)
108 sink.onDispose(coroutine)
109 coroutine.start(CoroutineStart.DEFAULT, coroutine, block)
112 @InternalCoroutinesApi
113 class MonoMDCCoroutine<in T>(
114 parentContext: CoroutineContext,
115 private val sink: MonoSink<T>
116 ) : AbstractCoroutine<T>(parentContext, true), Disposable {
117 private var disposed = false
119 override fun onCompleted(value: T) {
121 if (value == null) sink.success() else sink.success(value)
125 override fun onCancelled(cause: Throwable, handled: Boolean) {
128 } else if (!handled) {
129 handleCoroutineException(context, cause)
133 override fun dispose() {
138 override fun isDisposed(): Boolean = disposed