Skip to content

SaveReloadLog.kt

src/jvmMain/kotlin/afield/app/diagnostics/logging/SaveReloadLog.kt · 67 lines

A timestamped append-only log of document save and reload events, written to ~/.afield/logs/save_reload.log.

Each line is an ISO instant, the event name in brackets and a message. logTiming adds the elapsed milliseconds since a System.nanoTime() start and skips events faster than its threshold. The file rolls over to save_reload.log.1 once it reaches 50 MB. Every line also goes to LogDispatcher on info,persist,savereload, so the log widget and other sinks see it; the console mutes info,persist by default.

package afield.app.diagnostics.logging

import afield.ai.diagnostics.LogDispatcher
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.Paths
import java.nio.file.StandardCopyOption
import java.nio.file.StandardOpenOption
import java.time.Instant
import java.time.format.DateTimeFormatter

object SaveReloadLog

object SaveReloadLog {
    private val logPath: Path = Paths.get(System.getProperty("user.home"), ".afield", "logs", "save_reload.log")
    private val rolledPath: Path = logPath.resolveSibling("save_reload.log.1")
    private const val maxBytes = 50L * 1024 * 1024
    private val lock = Any()
    private val formatter = DateTimeFormatter.ISO_INSTANT

const val CHANNEL = "info,persist,savereload"

fun elapsedMillis(startedAtNs: Long): Long

    fun elapsedMillis(startedAtNs: Long): Long =
        (System.nanoTime() - startedAtNs) / 1_000_000L

fun logTiming(event: String, startedAtNs: Long, message: String, thresholdMs: Long = 0L): Long

    fun logTiming(event: String, startedAtNs: Long, message: String, thresholdMs: Long = 0L): Long {
        val elapsedMs = elapsedMillis(startedAtNs)
        if (elapsedMs >= thresholdMs) {
            log(event, "ms=$elapsedMs $message")
        }
        return elapsedMs
    }

fun log(event: String, message: String)

    fun log(event: String, message: String) {
        LogDispatcher.log(CHANNEL, "[$event] $message")
        val line = buildString {
            append(formatter.format(Instant.now()))
            append(" [")
            append(event)
            append("] ")
            append(message)
            append('\n')
        }
        synchronized(lock) {
            Files.createDirectories(logPath.parent)
            // Roll to save_reload.log.1 past the cap; this file once grew to 2 GB unbounded.
            if (Files.exists(logPath) && Files.size(logPath) >= maxBytes) {
                Files.move(logPath, rolledPath, StandardCopyOption.REPLACE_EXISTING)
            }
            Files.writeString(
                logPath,
                line,
                StandardOpenOption.CREATE,
                StandardOpenOption.APPEND
            )
        }
    }
}