/* * Copyright © 2018-2019 AT&T Intellectual Property. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.onap.ccsdk.cds.blueprintsprocessor.rest.service import kotlinx.coroutines.* import kotlinx.coroutines.reactor.ReactorContext import kotlinx.coroutines.reactor.asCoroutineContext import org.onap.ccsdk.cds.controllerblueprints.core.BluePrintConstants.ONAP_INVOCATION_ID import org.onap.ccsdk.cds.controllerblueprints.core.BluePrintConstants.ONAP_PARTNER_NAME import org.onap.ccsdk.cds.controllerblueprints.core.BluePrintConstants.ONAP_REQUEST_ID import org.onap.ccsdk.cds.controllerblueprints.core.MDCContext import org.onap.ccsdk.cds.controllerblueprints.core.defaultToEmpty import org.onap.ccsdk.cds.controllerblueprints.core.defaultToUUID import org.onap.ccsdk.cds.controllerblueprints.core.logger import org.slf4j.MDC import org.springframework.http.server.reactive.ServerHttpRequest import org.springframework.http.server.reactive.ServerHttpResponse import reactor.core.Disposable import reactor.core.publisher.Mono import reactor.core.publisher.MonoSink import java.net.InetAddress import java.time.ZoneOffset import java.time.ZonedDateTime import java.time.format.DateTimeFormatter import kotlin.coroutines.CoroutineContext import kotlin.coroutines.EmptyCoroutineContext class RestLoggerService { private val log = logger(RestLoggerService::class) fun entering(request: ServerHttpRequest) { val localhost = InetAddress.getLocalHost() val headers = request.headers val requestID = headers.getFirst(ONAP_REQUEST_ID).defaultToUUID() val invocationID = headers.getFirst(ONAP_INVOCATION_ID).defaultToUUID() val partnerName = headers.getFirst(ONAP_PARTNER_NAME).defaultToEmpty() MDC.put("InvokeTimestamp", ZonedDateTime.now(ZoneOffset.UTC).format(DateTimeFormatter.ISO_INSTANT)) MDC.put("RequestID", requestID) MDC.put("InvocationID", invocationID) MDC.put("PartnerName", partnerName) MDC.put("ClientIPAddress", request.remoteAddress?.address?.hostAddress.defaultToEmpty()) MDC.put("ServerFQDN",localhost.hostName.defaultToEmpty()) if (MDC.get("ServiceName") == null || MDC.get("ServiceName").equals("", ignoreCase = true)) { MDC.put("ServiceName", request.uri.path) } } fun exiting(request: ServerHttpRequest, response: ServerHttpResponse) { try { val reqHeaders = request.headers val resHeaders = response.headers resHeaders[ONAP_REQUEST_ID] = MDC.get("RequestID") resHeaders[ONAP_INVOCATION_ID] = MDC.get("InvocationID") val partnerName = System.getProperty("APPNAME") ?: "BlueprintsProcessor" resHeaders[ONAP_PARTNER_NAME] = partnerName } catch (e: Exception) { log.warn("couldn't set response headers", e) } finally { MDC.clear() } } } /** Used in Rest controller API methods to populate MDC context to nested coroutines from reactor web filter context. */ @UseExperimental(InternalCoroutinesApi::class) fun monoMdc(context: CoroutineContext = EmptyCoroutineContext, block: suspend CoroutineScope.() -> T?): Mono = Mono.create { sink -> val reactorContext = (context[ReactorContext]?.context?.putAll(sink.currentContext()) ?: sink.currentContext()).asCoroutineContext() /** Populate MDC context only if present in Reactor Context */ val newContext = if (!reactorContext.context.isEmpty && reactorContext.context.hasKey(MDCContext)) { val mdcContext = reactorContext.context.get(MDCContext) GlobalScope.newCoroutineContext(context + reactorContext + mdcContext) } else GlobalScope.newCoroutineContext(context + reactorContext) val coroutine = MonoMDCCoroutine(newContext, sink) sink.onDispose(coroutine) coroutine.start(CoroutineStart.DEFAULT, coroutine, block) } @InternalCoroutinesApi class MonoMDCCoroutine( parentContext: CoroutineContext, private val sink: MonoSink ) : AbstractCoroutine(parentContext, true), Disposable { private var disposed = false override fun onCompleted(value: T) { if (!disposed) { if (value == null) sink.success() else sink.success(value) } } override fun onCancelled(cause: Throwable, handled: Boolean) { if (!disposed) { sink.error(cause) } else if (!handled) { handleCoroutineException(context, cause) } } override fun dispose() { disposed = true cancel() } override fun isDisposed(): Boolean = disposed }