初始化(可选)

零配置启动(默认行为)

// 无需任何初始化——直接使用即可。
// 首次写日志时自动启动,日志输出到 JAR 所在目录下的 vklogs/,INFO 级别。
Vostok.Log.info("server started");

显式初始化

// 使用内置默认配置
Vostok.Log.init();

// 自定义配置
Vostok.Log.init(new VKLogConfig()
    .level(VKLogLevel.DEBUG)
    .outputDir("logs")
    .filePrefix("app")
    .maxFileSizeMb(64)             // 64 MB 按大小滚动
    .maxBackups(20)
    .maxBackupDays(30)
    .rollInterval(VKLogRollInterval.DAILY)
    .compressRolledFiles(true)
    .consoleEnabled(true)
    .consoleColor(true)
    .queueCapacity(32768)
    .queueFullPolicy(VKLogQueueFullPolicy.DROP)
    .flushIntervalMs(1000)
);
零配置启动
Log 模块无需显式初始化。首次写日志时,若尚未初始化,将自动以默认配置启动: 日志目录为 JAR 所在目录下的 vklogs/(IDE 运行时为 class 目录下的 vklogs/), INFO 级别,开启控制台输出,自动 GZIP 压缩,所有命名 Logger 共用单文件(autoCreateLoggerSink=false)。 如需独立配置,在首次写日志前调用 init(VKLogConfig) 即可。 JVM 退出时会自动 flush 并关闭所有日志队列。

写日志

静态 API(自动以调用方类名作为 logger 名)

Vostok.Log.trace("entering method");
Vostok.Log.debug("config loaded: {}", configPath);
Vostok.Log.info("server started on port {}", port);
Vostok.Log.warn("slow query: {} ms", elapsed);
Vostok.Log.error("failed to connect");
Vostok.Log.error("connect failed: {}", host, exception);  // 最后一个 Throwable 参数自动识别
Vostok.Log.error("payment failed", exception);             // 单独传 Throwable

占位符为 SLF4J 风格 {},多余参数追加在末尾([value]),不足时占位符保留原样。

级别检查(高频路径防护)

// 避免在级别未启用时执行昂贵的参数计算
if (Vostok.Log.isDebugEnabled()) {
    Vostok.Log.debug("state: {}", computeExpensiveState());
}

命名 Logger

命名 Logger 将日志路由到独立文件(当 autoCreateLoggerSink=true 时)。同名 Logger 返回相同实例(缓存复用)。

// 获取命名 Logger(两种写法等价)
VKLogger log = Vostok.Log.logger("payment");
VKLogger log = Vostok.Log.getLogger("payment");

log.info("processing order {}", orderId);
log.warn("retry #{}", retryCount);
log.error("payment failed", ex);

// 高频路径防护
if (log.isDebugEnabled()) {
    log.debug("detail: {}", detail());
}

VKLogger 提供与静态 API 完全相同的方法集:

方法说明
name()返回此 Logger 的名称
isTraceEnabled() / isDebugEnabled() / …级别检查
trace/debug/info/warn/error(msg)纯消息日志
trace/debug/info/warn/error(template, args...)模板日志({} 占位符)
error(msg, Throwable)附带异常栈的 ERROR 日志

MDC(映射诊断上下文)

MDC 已集成到 Vostok.Log 门面,也可直接操作底层 VKLogMDC 类,两种方式完全等价。写入的键值对会自动注入到当前线程后续所有日志中。

// 通过门面访问 MDC(推荐,与其他 Log API 风格统一)
Vostok.Log.mdcPut("requestId", requestId);
Vostok.Log.mdcPut("userId", userId);

Vostok.Log.info("handling request");  // 日志自动包含 requestId / userId

// 移除单个键
Vostok.Log.mdcRemove("userId");

// 读取当前线程 MDC 值
String rid = Vostok.Log.mdcGet("requestId");

// 获取全部 MDC 快照(只读视图)
Map<String, String> snapshot = Vostok.Log.mdcGetAll();

// 批量设置(常用于跨线程传播)
Vostok.Log.mdcPutAll(snapshot);

// 清除当前线程全部 MDC(请求结束时务必调用,防止线程复用时上下文泄漏)
Vostok.Log.mdcClear();
两种访问方式
Vostok.Log.mdcPut() 等门面方法与直接调用 VKLogMDC.put() 完全等价,底层共享同一个 ThreadLocal。 门面方式更简洁,不需要额外 import;VKLogMDC 方式适合已有大量直接调用的代码场景。

Web 请求中的典型用法

// 在拦截器 / 过滤器中
Vostok.Log.mdcPut("requestId", ctx.requestId());
Vostok.Log.mdcPut("userId", String.valueOf(ctx.userId()));
try {
    handle(ctx);
} finally {
    Vostok.Log.mdcClear();  // 必须清除,防止泄漏到下一个请求
}

跨线程传播 MDC

// 在提交任务前捕获快照
Map<String, String> snapshot = Vostok.Log.mdcGetAll();
executor.submit(() -> {
    Vostok.Log.mdcPutAll(snapshot);  // 在子线程中恢复 MDC
    try {
        doWork();
    } finally {
        Vostok.Log.mdcClear();
    }
});

命名 Logger 路由

每个命名 Logger 的日志路由策略由 autoCreateLoggerSink 控制:

autoCreateLoggerSinkthrowOnUnknownLogger行为
true(默认)未注册的 logger 自动创建独立文件 sink(文件名为 <filePrefix>-<loggerName>.log
falsefalse(默认)未注册的 logger 路由到默认 sink(单文件模式)
falsetrue未注册的 logger 抛 IllegalArgumentException(严格注册模式)

预注册 Logger 并指定独立配置

Vostok.Log.init(new VKLogConfig()
    .outputDir("logs")
    .level(VKLogLevel.INFO)
    .autoCreateLoggerSink(false)   // 关闭自动创建,使用严格注册模式
    .throwOnUnknownLogger(true)    // 调用未注册 logger 时抛异常

    // 仅预注册名称,使用全局配置
    .registerLogger("payment")
    .registerLoggers("order", "inventory")

    // 预注册并指定独立的 sink 配置
    .registerLogger("audit", new VKLogSinkConfig()
        .outputDir("logs/audit")
        .filePrefix("audit")
        .level(VKLogLevel.DEBUG)
        .rollInterval(VKLogRollInterval.HOURLY)
        .compressRolledFiles(true)
    )
);

动态配置(运行时热切换)

以下方法无需重启,实时生效,会推送到所有现有 sink:

// 修改全局日志级别
Vostok.Log.setLevel(VKLogLevel.DEBUG);

// 修改指定命名 Logger 的级别(不影响其他 Logger)
Vostok.Log.setLevel("payment", VKLogLevel.TRACE);

// 查询当前全局级别
VKLogLevel lv = Vostok.Log.level();

// 其他运行时修改(不常用,但支持)
Vostok.Log.setConsoleEnabled(true);
Vostok.Log.setConsoleColor(true);
Vostok.Log.setMaxFileSizeMb(128);
Vostok.Log.setMaxBackups(30);
Vostok.Log.setMaxBackupDays(7);
Vostok.Log.setRollInterval(VKLogRollInterval.HOURLY);
Vostok.Log.setCompressRolledFiles(true);
Vostok.Log.setQueueFullPolicy(VKLogQueueFullPolicy.BLOCK);
Vostok.Log.setFlushIntervalMs(500);
Vostok.Log.setFormatter(myFormatter);
Vostok.Log.setErrorListener(myListener);

自定义格式化器

VKLogFormatter 是函数式接口,可完全自定义输出格式。默认格式为:yyyy-MM-dd HH:mm:ss.SSS [LEVEL] [loggerName] {mdc} message

// JSON 格式示例
VKLogFormatter jsonFmt = (level, loggerName, msg, t, ts, mdc) -> {
    StringBuilder sb = new StringBuilder();
    sb.append("{\"ts\":").append(ts)
      .append(",\"level\":\"").append(level).append("\"")
      .append(",\"logger\":\"").append(loggerName).append("\"")
      .append(",\"msg\":\"").append(msg).append("\"");
    if (!mdc.isEmpty()) {
        sb.append(",\"mdc\":").append(mdc);
    }
    sb.append("}\n");
    return sb.toString();
};

Vostok.Log.init(new VKLogConfig()
    .outputDir("logs")
    .formatter(jsonFmt)           // 全局格式化器
);

// 或运行时动态修改
Vostok.Log.setFormatter(jsonFmt);
Vostok.Log.setFormatter(null);  // 传 null 恢复默认格式

VKLogFormatter.format() 参数说明:

参数类型说明
levelVKLogLevel日志级别
loggerNameStringLogger 名称(命名 Logger 名 或 调用方类名)
msgString已完成 {} 替换的消息
tThrowable关联异常,可为 null
tslong事件时间戳(epoch millis)
mdcMap<String,String>MDC 上下文快照(不可修改),无上下文时为空 Map

ERROR 告警回调

每次写入 ERROR 日志后,在 worker 线程上调用 VKLogErrorListener。实现必须非阻塞、低延迟,不可执行 IO 或网络请求。

Vostok.Log.init(new VKLogConfig()
    .outputDir("logs")
    .errorListener((loggerName, message, error, timestamp) -> {
        // 投递到独立告警队列,避免阻塞 worker 线程
        alertQueue.offer(new AlertEvent(loggerName, message, timestamp));
    })
);

刷盘与关闭

// 刷新所有 logger 队列(等待异步写入完成)
Vostok.Log.flush();

// 关闭日志系统,等待队列清空(JVM 退出时自动调用)
Vostok.Log.close();
Vostok.Log.shutdown();  // close() 的别名

// 重新初始化(替换配置,重建所有 sink)
Vostok.Log.reinit(new VKLogConfig().level(VKLogLevel.WARN));

// 重置为默认配置
Vostok.Log.resetDefaults();

监控指标

// 因队列满而丢弃的日志条数(DROP 策略时)
long dropped = Vostok.Log.droppedLogs();

// 队列满时同步写入的次数(SYNC_FALLBACK 策略时)
long fallback = Vostok.Log.fallbackWrites();

// 文件写入错误次数(磁盘满等 IO 异常)
long writeErrors = Vostok.Log.fileWriteErrors();

// 当前排队等待异步 GZIP 压缩的文件数(等于 0 表示压缩全部完成)
int pending = Vostok.Log.pendingCompressions();

接入外部日志框架(VKLogBackend)

通过 VKLogBackend SPI,可将 Vostok 的日志输出委托给任意外部框架(SLF4J、Log4j2、Logback 等)。AsyncEngine 在 flush 时判断:配置了 Backend 则调用 backend.emit(),内置文件写入与控制台输出被替换;未配置时行为不变。

VKLogBackend 接口

public interface VKLogBackend {

    /**
     * 输出一条日志事件。在 AsyncEngine worker 线程调用,必须非阻塞。
     *
     * @param level        日志级别
     * @param loggerName   Logger 名称(命名 Logger 名 或 调用方类名)
     * @param formattedMsg 已完成 {} 替换的消息
     * @param t            关联异常,可为 null
     * @param tsMillis     事件时间戳(epoch millis)
     * @param mdc          调用线程的 MDC 快照(不可修改)
     */
    void emit(VKLogLevel level, String loggerName,
              String formattedMsg, Throwable t, long tsMillis,
              Map<String, String> mdc);

    default void start() {}  // 初始化时调用(可在此建立连接、分配资源)
    default void stop()  {}  // 关闭时调用(可在此释放资源)
}
使用约束
emit() 在 AsyncEngine 的 worker 线程调用,必须非阻塞、无 IO(与 VKLogErrorListener 相同)。可调用外部框架的异步 API 或投递到外部队列。配置 Backend 后,内置文件写入和控制台输出被跳过;如需同时保留,使用 VKCompositeBackend 组合多个 Backend。

配置方式

// 全局 Backend(替换内置文件+控制台)
Vostok.Log.init(new VKLogConfig()
    .backend(new Slf4jBackend())
);

// Per-logger Backend(仅覆盖指定命名 Logger 的输出)
Vostok.Log.init(new VKLogConfig()
    .registerLogger("audit", new VKLogSinkConfig()
        .backend(new Slf4jBackend())
    )
);

// 多目标扇出:内置文件写入 + 外部框架同时输出
VKLogBackend composite = VKCompositeBackend.of(
    VKBuiltinBackend.INSTANCE,  // 保留内置文件写入
    new Slf4jBackend()          // 同时推给 SLF4J
);
Vostok.Log.init(new VKLogConfig().backend(composite));

SLF4J Backend 适配器示例

以下适配器将 Vostok 日志事件委托给 SLF4J,classpath 中需包含 slf4j-api 及任意实现(Logback、Log4j2-SLF4J 绑定等)。适用于 Vostok 运行在已有 SLF4J 体系的应用中,统一由 logback.xml / log4j2.xml 管控输出行为。

import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import yueyang.vostok.log.VKLogBackend;
import yueyang.vostok.log.VKLogLevel;
import java.util.Map;

public class Slf4jBackend implements VKLogBackend {

    @Override
    public void emit(VKLogLevel level, String loggerName,
                      String formattedMsg, Throwable t,
                      long tsMillis, Map<String, String> mdc) {

        // 1. 将 Vostok MDC 快照同步到 SLF4J MDC(emit 在 worker 线程运行,SLF4J MDC 为空)
        if (!mdc.isEmpty()) {
            MDC.setContextMap(mdc);
        }
        try {
            // 2. 按 loggerName 获取 SLF4J Logger(SLF4J 内部有缓存,无需自行缓存)
            org.slf4j.Logger log = LoggerFactory.getLogger(loggerName);

            // 3. t 为 null 时调用无 Throwable 重载,避免 SLF4J 将 null 打印为 "null"
            switch (level) {
                case TRACE -> { if (t != null) log.trace(formattedMsg, t); else log.trace(formattedMsg); }
                case DEBUG -> { if (t != null) log.debug(formattedMsg, t); else log.debug(formattedMsg); }
                case INFO  -> { if (t != null) log.info(formattedMsg, t);  else log.info(formattedMsg);  }
                case WARN  -> { if (t != null) log.warn(formattedMsg, t);  else log.warn(formattedMsg);  }
                case ERROR -> { if (t != null) log.error(formattedMsg, t); else log.error(formattedMsg); }
            }
        } finally {
            // 4. 清除 worker 线程上的 SLF4J MDC,防止泄漏到下一条日志
            if (!mdc.isEmpty()) {
                MDC.clear();
            }
        }
    }
}
消除异常栈重复输出
Vostok 默认格式化器会将异常栈追加到 formattedMsg,再将 t 传给 SLF4J 会造成重复输出。建议配合简洁格式化器,只传递消息正文,异常栈交由 SLF4J / Logback 统一渲染:
Vostok.Log.init(new VKLogConfig()
    .backend(new Slf4jBackend())
    // 仅输出消息正文,不追加异常栈(由 SLF4J / Logback 渲染)
    .formatter((level, name, msg, t, ts, mdc) -> msg + "\n")
    .consoleEnabled(false)  // 控制台由 Logback 接管
);

接入完整示例

// 应用启动时配置:
Vostok.Log.init(new VKLogConfig()
    .level(VKLogLevel.DEBUG)
    .backend(new Slf4jBackend())
    .formatter((level, name, msg, t, ts, mdc) -> msg + "\n")
    .consoleEnabled(false)
);

// 业务代码无需任何改动,以下调用最终路由到 Logback,受 logback.xml 统一管控:
Vostok.Log.info("server started on port {}", port);

VKLogger log = Vostok.Log.logger("payment");
log.error("payment failed", ex);  // → Logback logger "payment",含完整异常栈

配置参数

参数类型默认值说明
基础
levelVKLogLevelINFO全局日志级别(TRACE/DEBUG/INFO/WARN/ERROR)
outputDirString"logs"日志文件输出目录(懒加载默认为 JAR 所在目录/vklogs)
filePrefixString"vostok"日志文件名前缀,命名 Logger 文件名为 <prefix>-<name>.log
defaultLoggerNameString"app"静态 API 使用的默认 Logger 名
滚动与归档
maxFileSizeByteslong64 MB单文件最大大小,超出后滚动(也可用 maxFileSizeMb() 设置)
rollIntervalVKLogRollIntervalDAILY按时间滚动:NONE / HOURLY / DAILY / WEEKLY,与大小滚动同时生效
maxBackupsint20保留历史文件数量
maxBackupDaysint30历史文件保留天数
maxTotalSizeByteslong1 GB历史文件总大小上限(也可用 maxTotalSizeMb() 设置)
compressRolledFilesbooleanfalse归档文件异步 GZIP 压缩(懒加载默认 true)
控制台
consoleEnabledbooleantrue同时输出到控制台(System.out)
consoleColorbooleanfalse控制台 ANSI 彩色(TRACE=灰/DEBUG=青/INFO=绿/WARN=黄/ERROR=红)
异步队列
queueCapacityint32768异步队列容量(每个 sink 独立)
queueFullPolicyVKLogQueueFullPolicyDROP队列满时策略:DROP / BLOCK / SYNC_FALLBACK
flushIntervalMslong1000自动刷盘间隔(ms)
flushBatchSizeint256每次 flush 最多消费的日志条数
可靠性
fsyncPolicyVKLogFsyncPolicyNEVERfsync 策略:NEVER / EVERY_FLUSH / EVERY_WRITE
shutdownTimeoutMslong5000关闭时等待队列清空的超时时间(ms)
fileRetryIntervalMslong3000文件写入失败后的重试间隔(ms)
命名 Logger 路由
autoCreateLoggerSinkbooleantrue是否为未注册的命名 Logger 自动创建独立文件 sink
throwOnUnknownLoggerbooleanfalseautoCreateLoggerSink=false 时,未注册的 logger 是否抛异常
扩展
formatterVKLogFormatternull(内置格式)自定义日志格式化器(函数式接口),null 表示使用默认格式
errorListenerVKLogErrorListenernull(禁用)ERROR 级别事件回调(函数式接口),在 worker 线程调用,必须非阻塞
backendVKLogBackendnull(内置)自定义输出 Backend(SPI 接口),null 时走内置文件 + 控制台路径;配置后内置输出被替换,可通过 VKCompositeBackend 同时保留

枚举说明

VKLogQueueFullPolicy

枚举值行为
DROP队列满时丢弃当前日志(默认,不阻塞业务线程)
BLOCK队列满时阻塞业务线程直到有空位
SYNC_FALLBACK队列满时在业务线程上同步写入文件

VKLogFsyncPolicy

枚举值行为
NEVER不调用 fsync,依赖 OS 刷盘(默认,性能最高)
EVERY_FLUSH每次批量 flush 后调用一次 fsync
EVERY_WRITE每条日志写入后立即 fsync(最安全,性能最低)

VKLogRollInterval

枚举值行为
NONE不按时间滚动,仅按文件大小滚动
HOURLY按小时滚动,整点触发
DAILY按天滚动(默认),每日零点触发
WEEKLY按周滚动,每周一零点触发(ISO 周)

API 速查

初始化与生命周期

方法返回值说明
init()void以内置默认配置显式初始化
init(VKLogConfig)void显式初始化(已初始化时幂等,不重复初始化)
reinit(VKLogConfig)void重新初始化(替换配置,重建所有 sink)
resetDefaults()void重置为默认配置并重新初始化
initialized()boolean是否已初始化
flush()void刷新所有 logger 队列
close()void关闭日志系统,等待队列清空(JVM 退出时自动调用)
shutdown()voidclose() 的别名

静态日志 API

方法说明
trace(msg) / trace(template, args...)TRACE 级别日志
debug(msg) / debug(template, args...)DEBUG 级别日志
info(msg) / info(template, args...)INFO 级别日志
warn(msg) / warn(template, args...)WARN 级别日志
error(msg) / error(template, args...)ERROR 级别日志
error(msg, Throwable)ERROR 级别日志(含异常栈)
isTraceEnabled()isErrorEnabled()全局级别检查
level()获取当前全局日志级别

命名 Logger

方法返回值说明
logger(name)VKLogger获取命名 Logger 实例(缓存复用)
getLogger(name)VKLoggerlogger(name) 的别名

动态配置

方法说明
setLevel(VKLogLevel)修改全局日志级别(热切换)
setLevel(loggerName, VKLogLevel)修改指定命名 Logger 的级别
setOutputDir(dir)修改日志输出目录
setFilePrefix(prefix)修改文件名前缀
setMaxFileSizeMb(mb)修改单文件最大大小(MB)
setMaxFileSizeBytes(bytes)修改单文件最大大小(字节)
setMaxBackups(n)修改保留备份数
setMaxBackupDays(days)修改备份保留天数
setMaxTotalSizeMb(mb)修改历史文件总大小上限(MB)
setConsoleEnabled(bool)开启/关闭控制台输出
setConsoleColor(bool)开启/关闭控制台 ANSI 彩色
setRollInterval(interval)修改时间滚动策略
setCompressRolledFiles(bool)开启/关闭归档压缩
setQueueFullPolicy(policy)修改队列满策略
setQueueCapacity(n)修改队列容量
setFlushIntervalMs(ms)修改自动刷盘间隔
setFlushBatchSize(n)修改每次 flush 批量大小
setShutdownTimeoutMs(ms)修改关闭等待超时
setFsyncPolicy(policy)修改 fsync 策略
setFileRetryIntervalMs(ms)修改文件写入重试间隔
setFormatter(VKLogFormatter)修改格式化器(null 恢复默认格式)
setErrorListener(VKLogErrorListener)修改 ERROR 告警回调(null 禁用)
setThrowOnUnknownLogger(bool)修改未注册 Logger 是否抛异常

MDC

门面方法(Vostok.Log.mdc*())与 VKLogMDC 静态方法完全等价,任选其一。

门面方法等价的底层方法返回值说明
Vostok.Log.mdcPut(key, value)VKLogMDC.put(key, value)void设置当前线程 MDC 键值
Vostok.Log.mdcPutAll(map)VKLogMDC.putAll(map)void批量设置(跨线程传播时使用)
Vostok.Log.mdcGet(key)VKLogMDC.get(key)String读取指定键的值,不存在时返回 null
Vostok.Log.mdcGetAll()VKLogMDC.getAll()Map<String,String>获取全部 MDC 只读快照
Vostok.Log.mdcRemove(key)VKLogMDC.remove(key)void移除指定键
Vostok.Log.mdcClear()VKLogMDC.clear()void清除当前线程全部 MDC(请求结束时务必调用)

监控指标

方法返回值说明
droppedLogs()long因队列满丢弃的日志条数(DROP 策略)
fallbackWrites()long队列满时同步写入的次数(SYNC_FALLBACK 策略)
fileWriteErrors()long文件写入错误次数
pendingCompressions()int等待异步 GZIP 压缩的文件数,为 0 表示全部完成