cn.bestwu.logging.logback.Logback2LoggingSystem.kt Maven / Gradle / Ivy
package cn.bestwu.logging.logback
import ch.qos.logback.classic.Level
import ch.qos.logback.classic.LoggerContext
import ch.qos.logback.classic.boolex.OnMarkerEvaluator
import ch.qos.logback.classic.encoder.PatternLayoutEncoder
import ch.qos.logback.classic.filter.LevelFilter
import ch.qos.logback.classic.html.HTMLLayout
import ch.qos.logback.classic.net.SMTPAppender
import ch.qos.logback.classic.spi.ILoggingEvent
import ch.qos.logback.core.Appender
import ch.qos.logback.core.boolex.EventEvaluatorBase
import ch.qos.logback.core.filter.AbstractMatcherFilter
import ch.qos.logback.core.rolling.RollingFileAppender
import ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy
import ch.qos.logback.core.spi.ContextAware
import ch.qos.logback.core.spi.FilterReply
import ch.qos.logback.core.spi.LifeCycle
import ch.qos.logback.core.util.FileSize
import ch.qos.logback.core.util.OptionHelper
import org.slf4j.ILoggerFactory
import org.slf4j.impl.StaticLoggerBinder
import org.springframework.boot.context.properties.bind.Bindable
import org.springframework.boot.context.properties.bind.Binder
import org.springframework.boot.logging.LogFile
import org.springframework.boot.logging.LoggingInitializationContext
import org.springframework.boot.logging.LoggingSystem
import org.springframework.boot.logging.logback.LogbackLoggingSystem
import org.springframework.core.env.Environment
import org.springframework.util.Assert
import org.springframework.util.StringUtils
/**
* 自定义 LogbackLoggingSystem
* @author Peter Wu
* @since 0.0.1
*/
open class Logback2LoggingSystem(classLoader: ClassLoader) : LogbackLoggingSystem(classLoader) {
private val loggerContext: LoggerContext
get() {
val factory = StaticLoggerBinder.getSingleton().loggerFactory
Assert.isInstanceOf(LoggerContext::class.java, factory,
String.format(
"LoggerFactory is not a Logback LoggerContext but Logback is on "
+ "the classpath. Either remove Logback or the competing "
+ "implementation (%s loaded from %s). If you are using "
+ "WebLogic you will need to add 'org.slf4j' to "
+ "prefer-application-packages in WEB-INF/weblogic.xml",
factory.javaClass, getLocation(factory)))
return factory as LoggerContext
}
override fun loadDefaults(initializationContext: LoggingInitializationContext, logFile: LogFile?) {
super.loadDefaults(initializationContext, logFile)
val context = loggerContext
val environment = initializationContext.environment
val smtpProperties = bind(environment, "logging.smtp")
if (StringUtils.hasText(smtpProperties["host"])) {
synchronized(context.configurationLock) {
val defaultSubject = "${environment.getProperty("spring.application.name", "")} ${environment.getProperty("spring.profiles.active", "")} System exception;"
val levelMailAppender = mailAppender(context, smtpProperties, defaultSubject)
val mailMarker = smtpProperties["marker"]
val markerMailAppender = if (!mailMarker.isNullOrBlank())
mailAppender(context, smtpProperties, defaultSubject, mailMarker)
else
null
val loggerNames = smtpProperties.getOrDefault("logger", "root")
loggerNames.split(",".toRegex())
.map { loggerName -> context.getLogger(loggerName.trim()) }
.forEach {
it.addAppender(levelMailAppender)
if (markerMailAppender != null)
it.addAppender(markerMailAppender)
}
}
}
val files = bind(environment, "logging.files")
if (StringUtils.hasText(files["path"])) {
val fileLogPattern = environment.getProperty("logging.pattern.file", FILE_LOG_PATTERN)
val spilts = bind(environment, "logging.spilt")
val markers = bind(environment, "logging.spilt-marker")
val levels = Binder.get(environment).bind("logging.spilt-level", Bindable.setOf(String::class.java)).orElseGet { setOf() }
val rootName = LoggingSystem.ROOT_LOGGER_NAME.toLowerCase()
val rootLevel = spilts[rootName]
spilts.remove(rootName)
setRootFileAppender(context, fileLogPattern, files, rootLevel, spilts.keys, markers.keys, levels)
for ((key, value) in markers) {
setMarkerFileAppender(context, fileLogPattern, files, key, value)
}
for ((key, value) in spilts) {
setFileAppender(context, fileLogPattern, files, key, value)
}
for (level in levels) {
setLevelFileAppender(context, fileLogPattern, files, level)
}
}
}
private fun bind(environment: Environment, key: String): MutableMap {
val bindable = Bindable
.mapOf(String::class.java, String::class.java)
val binder = Binder.get(environment)
return binder.bind(key, bindable).orElseGet({ mutableMapOf() })
}
private fun setRootFileAppender(context: LoggerContext, fileLogPattern: String, files: MutableMap,
rootLevel: String?, loggerNames: Set, markers: Set, levels: Set) {
val appender = RollingFileAppender()
val encoder = PatternLayoutEncoder()
encoder.pattern = OptionHelper.substVars(fileLogPattern, context)
appender.encoder = encoder
start(context, encoder)
val name = LoggingSystem.ROOT_LOGGER_NAME.toLowerCase()
val logFile = (files["path"] + "/" + name)
appender.file = "$logFile.log"
setRollingPolicy(appender, context, files, logFile)
val filter = object : AbstractMatcherFilter() {
override fun decide(event: ILoggingEvent): FilterReply {
if (!isStarted) {
return FilterReply.NEUTRAL
}
val loggerName = event.loggerName
for (it in loggerNames) {
if (loggerName.startsWith(it)) {
return onMismatch
}
}
val eventMarker = event.marker
if (eventMarker != null) {
for (marker in markers) {
if (eventMarker.contains(marker)) {
return onMismatch
}
}
}
for (level in levels) {
if (event.level == Level.valueOf(level)) {
return onMismatch
}
}
return onMatch
}
}
filter.onMatch = FilterReply.ACCEPT
filter.onMismatch = FilterReply.DENY
start(context, filter)
appender.addFilter(filter)
start(context, appender)
synchronized(context.configurationLock) {
val logger = context.getLogger(LoggingSystem.ROOT_LOGGER_NAME)
if (rootLevel != null) {
logger.level = Level.toLevel(rootLevel)
}
logger.addAppender(appender)
}
}
private fun setLevelFileAppender(context: LoggerContext, fileLogPattern: String, files: MutableMap, level: String) {
val appender = RollingFileAppender()
val encoder = PatternLayoutEncoder()
encoder.pattern = OptionHelper.substVars(fileLogPattern, context)
appender.encoder = encoder
start(context, encoder)
val logFile = files["path"] + "/level/" + level
appender.file = "$logFile.log"
setRollingPolicy(appender, context, files, logFile)
val filter = LevelFilter()
filter.setLevel(Level.toLevel(level))
filter.onMatch = FilterReply.ACCEPT
filter.onMismatch = FilterReply.DENY
start(context, filter)
appender.addFilter(filter)
start(context, appender)
synchronized(context.configurationLock) {
val logger = context.getLogger(LoggingSystem.ROOT_LOGGER_NAME)
logger.addAppender(appender)
}
}
private fun setMarkerFileAppender(context: LoggerContext, fileLogPattern: String, files: MutableMap, marker: String, level: String) {
val appender = RollingFileAppender()
val encoder = PatternLayoutEncoder()
encoder.pattern = OptionHelper.substVars(fileLogPattern, context)
appender.encoder = encoder
start(context, encoder)
val logFile = files["path"] + "/" + marker + "/" + marker
appender.file = "$logFile.log"
setRollingPolicy(appender, context, files, logFile)
val filter = object : AbstractMatcherFilter() {
override fun decide(event: ILoggingEvent): FilterReply {
if (!isStarted) {
return FilterReply.NEUTRAL
}
val eventMarker = event.marker ?: return onMismatch
return if (eventMarker.contains(marker)) {
onMatch
} else {
onMismatch
}
}
}
filter.onMatch = FilterReply.ACCEPT
filter.onMismatch = FilterReply.DENY
start(context, filter)
appender.addFilter(filter)
start(context, appender)
synchronized(context.configurationLock) {
val logger = context.getLogger(LoggingSystem.ROOT_LOGGER_NAME)
logger.level = Level.toLevel(level)
logger.addAppender(appender)
}
}
private fun setFileAppender(context: LoggerContext, fileLogPattern: String, files: MutableMap, name: String,
level: String) {
val appender = RollingFileAppender()
val encoder = PatternLayoutEncoder()
encoder.pattern = OptionHelper.substVars(fileLogPattern, context)
appender.encoder = encoder
start(context, encoder)
val logFile = files["path"] + "/" + name + "/" + name
appender.file = "$logFile.log"
setRollingPolicy(appender, context, files, logFile)
start(context, appender)
synchronized(context.configurationLock) {
val logger = context.getLogger(name)
logger.level = Level.toLevel(level)
logger.addAppender(appender)
}
}
private fun setRollingPolicy(appender: RollingFileAppender,
context: LoggerContext, files: Map, logFile: String) {
val rollingPolicy = SizeAndTimeBasedRollingPolicy()
appender.rollingPolicy = rollingPolicy
rollingPolicy.run {
fileNamePattern = "$logFile-%d{yyyy-MM-dd}-%i.gz"
setMaxFileSize(FileSize.valueOf(files.getOrDefault("maxFileSize", "10MB")))
maxHistory = files.getOrDefault("maxHistory", "10").toInt()
setParent(appender)
start(context, rollingPolicy)
}
}
/**
* 发送邮件
*/
private fun mailAppender(context: LoggerContext,
smtpProperties: MutableMap, defaultSubject: String, mailMarker: String? = null): Appender {
val appender = SMTPAppender()
with(appender) {
this.context = context
name = "mail"
setSMTPHost(smtpProperties["host"])
localhost = smtpProperties["localhost"]
jndiLocation = smtpProperties.getOrDefault("jndi-location", "java:comp/env/mail/Session")
setSMTPPort(smtpProperties.getOrDefault("port", "25").toInt())
username = smtpProperties["username"]
password = smtpProperties["password"]
from = smtpProperties["from"]
addTo(smtpProperties["to"])
isAsynchronousSending = smtpProperties.getOrDefault("asynchronous-sending", "true").toBoolean()
isIncludeCallerData = smtpProperties.getOrDefault("include-caller-data", "false").toBoolean()
isSTARTTLS = smtpProperties.getOrDefault("starttls", "false").toBoolean()
isSSL = smtpProperties.getOrDefault("ssl", "false").toBoolean()
isSessionViaJNDI = smtpProperties.getOrDefault("session-via-jndi", "false").toBoolean()
charsetEncoding = smtpProperties.getOrDefault("charset-encoding", "UTF-8")
subject = smtpProperties.getOrDefault("subject", defaultSubject)
val htmlLayout = HTMLLayout()
start(context, htmlLayout)
layout = htmlLayout
if (mailMarker.isNullOrBlank()) {
val level = smtpProperties.getOrDefault("filter", "ERROR")
if (!level.equals("ERROR", true)) {
val filter = object : EventEvaluatorBase() {
override fun evaluate(event: ILoggingEvent): Boolean {
return event.level.isGreaterOrEqual(Level.valueOf(level))
}
}
start(context, filter)
setEvaluator(filter)
}
} else {//marker 过滤
val filter = OnMarkerEvaluator()
filter.addMarker(mailMarker)
start(context, filter)
setEvaluator(filter)
}
start()
}
return appender
}
private fun start(context: LoggerContext, lifeCycle: LifeCycle) {
if (lifeCycle is ContextAware) {
(lifeCycle as ContextAware).context = context
}
lifeCycle.start()
}
private fun getLocation(factory: ILoggerFactory): Any {
try {
val protectionDomain = factory.javaClass.protectionDomain
val codeSource = protectionDomain.codeSource
if (codeSource != null) {
return codeSource.location
}
} catch (ex: SecurityException) {
// Unable to determine location
}
return "unknown location"
}
companion object {
private const val FILE_LOG_PATTERN = "%d{yyyy-MM-dd HH:mm:ss.SSS} " + "\${LOG_LEVEL_PATTERN:-%5p} \${PID:- } --- [%t] %-40.40logger{39} : %m%n\${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}"
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy