libeufin

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

log.kt (4276B)


      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 
     39 
     40 class TalerLogger(private val loggerName: String): LegacyAbstractLogger() {
     41     private fun isLevelEnabled(level: Level): Boolean = level.toInt() >= TalerServiceProvider.currentLevel.toInt()
     42     override fun isTraceEnabled(): Boolean = isLevelEnabled(Level.TRACE)
     43     override fun isDebugEnabled(): Boolean = isLevelEnabled(Level.DEBUG)
     44     override fun isInfoEnabled(): Boolean = isLevelEnabled(Level.INFO)
     45     override fun isWarnEnabled(): Boolean = isLevelEnabled(Level.WARN)
     46     override fun isErrorEnabled(): Boolean = isLevelEnabled(Level.ERROR)
     47 
     48     override fun getFullyQualifiedCallerName(): String = loggerName
     49 
     50     override fun handleNormalizedLoggingCall(level: Level, marker: Marker?, messagePattern: String?, arguments: Array<Any>?, throwable: Throwable?) {
     51         val name = fullyQualifiedCallerName;
     52         if (
     53             !isLevelEnabled(level) ||
     54             (name.startsWith("io.ktor") && level.toInt() < Level.WARN.toInt()) || 
     55             name.startsWith("com.zaxxer.hikari")
     56         ) return
     57         val callId = org.slf4j.MDC.get("call-id")
     58         val logEntry = buildString {
     59             if (timestampFmt != null) {
     60                 append(LocalDateTime.now().format(timestampFmt))
     61                 append(' ')
     62             }
     63             if (callId != null) {
     64                 append(callId)
     65                 append(' ')
     66             }
     67             append(level.name.padEnd(5))
     68             append(' ')
     69             append(name)
     70             append(" - ")
     71             append(MessageFormatter.basicArrayFormat(messagePattern, arguments))
     72             
     73             throwable?.let { t ->
     74                 append("${t.javaClass.simpleName}: ${t.message}")
     75                 t.stackTrace.take(10).forEach { stackElement ->
     76                     append("\n\tat $stackElement")
     77                 }
     78             }
     79         }
     80         
     81         System.err.println(logEntry)
     82     }
     83 
     84     companion object {
     85         // We skip logging timestamp if systemd is used
     86         private val skipTimestamp = System.getenv("JOURNAL_STREAM") != null
     87         // A null timestamp formatter mean we should skip it
     88         private val timestampFmt = if (skipTimestamp) null else DateTimeFormatter.ofPattern("dd-MMM-yyyy'T'HH:mm:ss.SSS")
     89     }
     90 }
     91 
     92 class TalerServiceProvider: SLF4JServiceProvider {
     93     private val markerFactory = BasicMarkerFactory()
     94     private val mdcAdapter = BasicMDCAdapter()
     95 
     96     override fun getLoggerFactory() = TalerServiceProvider
     97     override fun getMarkerFactory() = markerFactory
     98     override fun getMDCAdapter() = mdcAdapter
     99     override fun getRequestedApiVersion() = "2.0.99"
    100     override fun initialize() {}
    101 
    102     companion object: ILoggerFactory {
    103         var currentLevel = Level.TRACE
    104         private val loggerMap: ConcurrentMap<String, TalerLogger> = ConcurrentHashMap() 
    105 
    106         override fun getLogger(name: String): Logger 
    107             = loggerMap.computeIfAbsent(name, ::TalerLogger)
    108     }
    109 }