All Downloads are FREE. Search and download functionalities are using the official Maven repository.

cn.bestwu.logging.logback.Logback2LoggingSystem.kt Maven / Gradle / Ivy

There is a newer version: 2.0.11
Show newest version
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