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 }