libeufin

Integration and sandbox testing for FinTech APIs and data formats
Log | Files | Refs | Submodules | README | LICENSE

log.kt (4643B)


      1 /*
      2  * This file is part of LibEuFin.
      3  * Copyright (C) 2025 Taler Systems S.A.
      4 
      5  * LibEuFin is free software; you can redistribute it and/or modify
      6  * it under the terms of the GNU Affero General Public License as
      7  * published by the Free Software Foundation; either version 3, or
      8  * (at your option) any later version.
      9 
     10  * LibEuFin is distributed in the hope that it will be useful, but
     11  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
     12  * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General
     13  * Public License for more details.
     14 
     15  * You should have received a copy of the GNU Affero General Public
     16  * License along with LibEuFin; see the file COPYING.  If not, see
     17  * <http://www.gnu.org/licenses/>
     18  */
     19 
     20 package tech.libeufin.common
     21 
     22 import java.util.concurrent.ConcurrentMap;
     23 import java.util.concurrent.ConcurrentHashMap;
     24 import org.slf4j.event.Level;
     25 import org.slf4j.Logger;
     26 import org.slf4j.Marker;
     27 import org.slf4j.ILoggerFactory;
     28 import org.slf4j.IMarkerFactory;
     29 import org.slf4j.helpers.LegacyAbstractLogger;
     30 import org.slf4j.helpers.MessageFormatter;
     31 import org.slf4j.helpers.BasicMarkerFactory;
     32 import org.slf4j.helpers.BasicMDCAdapter;
     33 import org.slf4j.spi.MDCAdapter;
     34 import org.slf4j.spi.SLF4JServiceProvider;
     35 import java.time.LocalDateTime
     36 import java.time.format.DateTimeFormatter
     37 import java.io.PrintStream
     38 import java.util.concurrent.CancellationException
     39 
     40 
     41 class TalerLogger(private val loggerName: String): LegacyAbstractLogger() {
     42     private fun isLevelEnabled(level: Level): Boolean = level.toInt() >= TalerServiceProvider.currentLevel.toInt()
     43     override fun isTraceEnabled(): Boolean = isLevelEnabled(Level.TRACE)
     44     override fun isDebugEnabled(): Boolean = isLevelEnabled(Level.DEBUG)
     45     override fun isInfoEnabled(): Boolean = isLevelEnabled(Level.INFO)
     46     override fun isWarnEnabled(): Boolean = isLevelEnabled(Level.WARN)
     47     override fun isErrorEnabled(): Boolean = isLevelEnabled(Level.ERROR)
     48 
     49     override fun getFullyQualifiedCallerName(): String = loggerName
     50 
     51     override fun handleNormalizedLoggingCall(level: Level, marker: Marker?, messagePattern: String?, arguments: Array<Any>?, throwable: Throwable?) {
     52         val name = fullyQualifiedCallerName;
     53         if (
     54             !isLevelEnabled(level) ||
     55             (name.startsWith("io.ktor") && level.toInt() < Level.WARN.toInt()) || 
     56             // Ktor reports a disconnected client's cancelled request coroutine
     57             // as an unhandled server error.  Cancellation is normal control flow,
     58             // in particular when a wire-gateway long poll is stopped.
     59             (name.startsWith("io.ktor") && throwable is CancellationException) ||
     60             name.startsWith("com.zaxxer.hikari")
     61         ) return
     62         val callId = org.slf4j.MDC.get("call-id")
     63         val logEntry = buildString {
     64             if (timestampFmt != null) {
     65                 append(LocalDateTime.now().format(timestampFmt))
     66                 append(' ')
     67             }
     68             if (callId != null) {
     69                 append(callId)
     70                 append(' ')
     71             }
     72             append(level.name.padEnd(5))
     73             append(' ')
     74             append(name)
     75             append(" - ")
     76             append(MessageFormatter.basicArrayFormat(messagePattern, arguments))
     77             
     78             throwable?.let { t ->
     79                 append("${t.javaClass.simpleName}: ${t.message}")
     80                 t.stackTrace.take(10).forEach { stackElement ->
     81                     append("\n\tat $stackElement")
     82                 }
     83             }
     84         }
     85         
     86         System.err.println(logEntry)
     87     }
     88 
     89     companion object {
     90         // We skip logging timestamp if systemd is used
     91         private val skipTimestamp = System.getenv("JOURNAL_STREAM") != null
     92         // A null timestamp formatter mean we should skip it
     93         private val timestampFmt = if (skipTimestamp) null else DateTimeFormatter.ofPattern("dd-MMM-yyyy'T'HH:mm:ss.SSS")
     94     }
     95 }
     96 
     97 class TalerServiceProvider: SLF4JServiceProvider {
     98     private val markerFactory = BasicMarkerFactory()
     99     private val mdcAdapter = BasicMDCAdapter()
    100 
    101     override fun getLoggerFactory() = TalerServiceProvider
    102     override fun getMarkerFactory() = markerFactory
    103     override fun getMDCAdapter() = mdcAdapter
    104     override fun getRequestedApiVersion() = "2.0.99"
    105     override fun initialize() {}
    106 
    107     companion object: ILoggerFactory {
    108         var currentLevel = Level.TRACE
    109         private val loggerMap: ConcurrentMap<String, TalerLogger> = ConcurrentHashMap() 
    110 
    111         override fun getLogger(name: String): Logger 
    112             = loggerMap.computeIfAbsent(name, ::TalerLogger)
    113     }
    114 }