Compare commits

...

3 Commits

Author SHA1 Message Date
Ryuichi Leo Takashige
aeff7b9d19 send all exo.log on disk. 2026-02-10 18:31:00 +00:00
Ryuichi Leo Takashige
036fda70a5 use zstd instead 2026-02-10 18:17:52 +00:00
Ryuichi Leo Takashige
eb9391810a add log rotation for .exo/exo.log 2026-02-10 18:12:51 +00:00
2 changed files with 49 additions and 10 deletions

View File

@@ -44,7 +44,7 @@ struct BugReportService {
let dayPrefix = Self.dayPrefixString(now)
let prefix = "reports/\(dayPrefix)/\(timestamp)/"
let logData = readLog()
let logFiles = readAllLogs()
let ifconfigText = try await captureIfconfig()
let hostName = Host.current().localizedName ?? "unknown"
let debugInfo = readDebugInfo()
@@ -67,12 +67,14 @@ struct BugReportService {
clusterTbBridgeStatus: clusterTbBridgeStatus
)
let uploads: [(path: String, data: Data?)] = [
("\(prefix)exo.log", logData),
var uploads: [(path: String, data: Data?)] = logFiles.map { (path, data) in
("\(prefix)\(path)", data)
}
uploads.append(contentsOf: [
("\(prefix)state.json", stateData),
("\(prefix)events.json", eventsData),
("\(prefix)report.json", reportJSON),
]
])
let uploadItems: [(key: String, body: Data)] = uploads.compactMap { item in
guard let body = item.data else { return nil }
@@ -149,11 +151,26 @@ struct BugReportService {
return decoded.urls
}
private func readLog() -> Data? {
let logURL = URL(fileURLWithPath: NSHomeDirectory())
.appendingPathComponent(".exo")
.appendingPathComponent("exo.log")
return try? Data(contentsOf: logURL)
private func readAllLogs() -> [(path: String, data: Data)] {
let exoDir = URL(fileURLWithPath: NSHomeDirectory()).appendingPathComponent(".exo")
var results: [(path: String, data: Data)] = []
// Current log
let currentLog = exoDir.appendingPathComponent("exo.log")
if let data = try? Data(contentsOf: currentLog) {
results.append(("exo.log", data))
}
// Archived logs (.zst)
let contents = (try? FileManager.default.contentsOfDirectory(atPath: exoDir.path)) ?? []
for name in contents
where name.hasPrefix("exo.") && name.hasSuffix(".log.zst")) {
if let data = try? Data(contentsOf: exoDir.appendingPathComponent(name)) {
results.append((name, data))
}
}
return results
}
private func captureIfconfig() async throws -> String {

View File

@@ -1,11 +1,30 @@
import logging
import sys
from collections.abc import Iterator
from pathlib import Path
import zstandard
from hypercorn import Config
from hypercorn.logging import Logger as HypercornLogger
from loguru import logger
_MAX_LOG_ARCHIVES = 5
def _zstd_compress(filepath: str) -> None:
source = Path(filepath)
dest = source.with_suffix(source.suffix + ".zst")
cctx = zstandard.ZstdCompressor()
with open(source, "rb") as f_in, open(dest, "wb") as f_out:
cctx.copy_stream(f_in, f_out)
source.unlink()
def _once_then_never() -> Iterator[bool]:
yield True
while True:
yield False
class InterceptLogger(HypercornLogger):
def __init__(self, config: Config):
@@ -53,13 +72,16 @@ def logger_setup(log_file: Path | None, verbosity: int = 0):
enqueue=True,
)
if log_file:
rotate_once = _once_then_never()
logger.add(
log_file,
format="[ {time:YYYY-MM-DD HH:mm:ss.SSS} | {level: <8} | {name}:{function}:{line} ] {message}",
level="INFO",
colorize=False,
enqueue=True,
rotation="1 week",
rotation=lambda _, __: next(rotate_once),
retention=_MAX_LOG_ARCHIVES,
compression=_zstd_compress,
)