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

ai.platon.pulsar.common.MessageWriter.kt Maven / Gradle / Ivy

package ai.platon.pulsar.common

import org.slf4j.LoggerFactory
import java.io.PrintWriter
import java.io.Writer
import java.nio.file.*
import java.text.SimpleDateFormat
import java.time.Duration
import java.time.Instant
import java.util.concurrent.atomic.AtomicBoolean
import java.util.concurrent.atomic.AtomicLong
import kotlin.io.path.isRegularFile
import kotlin.reflect.KClass

/**
 * A simple log system
 */
class MessageWriter(
    val filePath: Path,
    var levelFile: Int = DEFAULT_LOG_LEVEL
): AutoCloseable {

    companion object {
        const val OFF = 0
        const val ERROR = 1
        const val WARN = 2
        const val INFO = 3
        const val DEBUG = 4
        /**
         * The default level for file log messages.
         */
        var DEFAULT_LOG_LEVEL = INFO
        /**
         * The default maximum trace file size. It is currently 512 MB. Additionally,
         * there could be a .1, .2, ... file of the same size.
         */
        var DEFAULT_MAX_FILE_SIZE = 512 * 1024 * 1024

        var CHECK_SIZE_EACH_WRITES = 4096

        var IDLE_TIMEOUT = Duration.ofMinutes(5)
        
        private val ID_SUPPLIER = AtomicLong()
    }

    private val logger = LoggerFactory.getLogger(MessageWriter::class.java)

    private var fileWriter: Writer? = null
    private var printWriter: PrintWriter? = null
    private val closed = AtomicBoolean()

    val id = ID_SUPPLIER.incrementAndGet()
    
    var lastActiveTime = Instant.now()
        private set
    val idleTime get() = Duration.between(lastActiveTime, Instant.now())
    var idleTimeout = IDLE_TIMEOUT
    val isIdle get() = DateTimes.isExpired(lastActiveTime, idleTimeout)
    var maxFileSize = DEFAULT_MAX_FILE_SIZE

    var dateFormat: SimpleDateFormat = SimpleDateFormat("yyyy-MM-dd HH:mm:ss")

    var checkSize: Int = 0
    var writingError: Boolean = false
    
    var closeCount = 0

    fun write(s: String) {
        writeFile(s)
    }

    fun write(level: Int, clazz: KClass<*>, s: String, t: Throwable? = null) {
        if (level > this.levelFile) {
            return
        }
        write(level, clazz.simpleName?:"", s, t)
    }

    fun write(level: Int, module: String, s: String, t: Throwable? = null) {
        if (level > this.levelFile) {
            return
        }
        writeFile(format(module, s), t)
    }
    
    fun flush() {
        printWriter?.flush()
    }
    
    override fun close() {
        if (closed.compareAndSet(false, true)) {
            closeWriter("close writer")
        }
    }

    @Synchronized
    private fun format(module: String, s: String): String {
        return dateFormat.format(System.currentTimeMillis()) + " " + module + ": " + s
    }

    @Synchronized
    private fun writeFile(s: String, t: Throwable? = null) {
        try {
            if (checkSize++ >= CHECK_SIZE_EACH_WRITES) {
                checkSize = 0
                closeWriter("rotate file")
                val count = Files.list(filePath.parent)
                    .filter { it.isRegularFile() }
                    .filter { it.fileName.toString().contains(filePath.fileName.toString()) }
                    .count()
                if (maxFileSize > 0 && Files.size(filePath) > maxFileSize) {
                    val old = Paths.get("$filePath.$count")
                    Files.move(filePath, old, StandardCopyOption.REPLACE_EXISTING)
                }
            }

            openWriter()?.also {
                it.println(s)
                t?.printStackTrace(it)
            }
        } catch (e: Exception) {
            logWritingError(e)
        }
    }

    private fun logWritingError(e: Exception) {
        if (writingError) {
            return
        }
        writingError = true
        e.printStackTrace()
        writingError = false
    }

    private fun openWriter(): PrintWriter? {
        if (printWriter == null) {
            try {
                Files.createDirectories(filePath.parent)
                // println("Create printer writer to $path")
                fileWriter = Files.newBufferedWriter(filePath, StandardOpenOption.CREATE, StandardOpenOption.APPEND)
                printWriter = PrintWriter(fileWriter!!, true)
            } catch (e: Exception) {
                logWritingError(e)
                return null
            }
        }

        return printWriter
    }

    @Synchronized
    private fun closeWriter(message: String) {
        if (closeCount++ < 20) {
            // logger.info("Closing writer #$id | ${idleTime.readable()} | $message | $filePath")
        }

        printWriter?.flush()
        printWriter?.close()
        fileWriter?.close()

        printWriter = null
        fileWriter = null
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy