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 }