如何轮换日志文件

日志轮换对于长时间运行的应用程序很常见,例如网络服务器,请参见 logrotate 的 Linux 系统示例。日志轮换意味着根据某种标准替换日志文件。通常日志文件根据日期轮换,例如每天或每周,或根据文件大小轮换,例如将单个文件保持在 10MB 以下。

基于日期的日志轮换

LoggingExtras.jl 包实现了 DatetimeRotatingFileLogger,顾名思义,它是一个基于日期/时间的日志轮换记录器。日志轮换的频率是根据日期格式(请参阅 Dates.DateFormatdateformat"...")形式的输入文件名模式确定的。

让我们看一个初始示例:

using Logging, LoggingExtras

# Directory for our log files
logdir = joinpath(@__DIR__, "logs")
mkpath(logdir)

# Filename pattern (see note below about character escaping)
filename_pattern = raw"yyyy-mm-dd-\w\e\b\s\e\r\v\e\r.\l\o\g"

# Create the logger
logger = DatetimeRotatingFileLogger(logdir, filename_pattern)

# Install the logger globally
global_logger(logger)

这是一个每天轮换日志文件的记录器,因为“天”是文件名模式中最小的日期时间单位。

Note

请注意,文件名模式中不属于日期时间模式的所有字符都将被转义。如果没有转义,这些字符也将被 Dates.DateFormat 解释。从技术上讲,并非所有字符都需要转义,例如 w 没有意义,但像上面的示例一样转义所有字符是最安全的。

经过几天的日志记录,我们会在日志目录中找到以下文件:

$ ls logs/
2021-11-12-webserver.log
2021-11-13-webserver.log
2021-11-14-webserver.log
2021-11-15-webserver.log

现在让我们通过添加两个 logrotate 中的常用的功能来改进记录器:文件压缩和文件保留策略。日志文件通常是可压缩的,添加压缩可以为我们节省一些空间。文件保留策略让我们将日志文件保留固定天数,例如 30 天,然后自动删除它们。对压缩和保留策略的支持不是内置的,但我们可以使用外部包来实现这些目的,并在回调函数中使用 rotation_callback 关键字参数实现此功能。DatetimeRotatingFileLogger 每次轮换日志文件时都会调用此函数。该函数的唯一参数是“旧”文件。

我们将通过 Gzip_jll 包使用 gzip 压缩,:

using Gzip_jll

function logger_callback(file)
    # Compress the file
    Gzip_jll.gzip() do gzip
        run(`$(gzip) $(file)`)
    end
end

对于文件保留策略,我们将使用 FilesystemDatastructures 包中的 NFileCache。这里我们创建了一个可以保存 30 个文件的文件缓存,:

using FilesystemDatastructures

# Create a file cache that keeps 30 files
fc = NFileCache(logdir, 30, DiscardLRU();
                # Make sure only files ending with "webserver.log.gz" are included
                predicate = x -> endswith(x, r"webserver\.log\.gz")
)

现在我们只需要修改上面的回调,将轮换和压缩的文件添加到缓存中:

function logger_callback(file)
    # Compress the file
    Gzip_jll.gzip() do gzip
        run(`$(gzip) $(file)`)
    end
    # Add the compressed file to the cache (gzip adds the .gz extension)
    FilesystemDatastructures.add!(fc, file * ".gz")
end

当第 31 个文件添加到缓存中时,将自动删除最旧的文件来为新文件腾出空间。让应用程序运行一段时间后检查日志目录:

$ ls logs/
2021-11-20-webserver.log.gz
2021-11-21-webserver.log.gz
2021-11-22-webserver.log.gz
[...]
2021-12-17-webserver.log.gz
2021-12-18-webserver.log.gz
2021-12-19-webserver.log.gz
2021-12-20-webserver.log

30 个压缩文件,由缓存管理,还有一个“活动”文件尚未压缩并添加到缓存中。


这是完整示例:

using Logging, LoggingExtras, Gzip_jll, FilesystemDatastructures

# Directory for our log files
logdir = joinpath(@__DIR__, "logs")
mkpath(logdir)

# Filename pattern
filename_pattern = raw"yyyy-mm-dd-\w\e\b\s\e\r\v\e\r.\l\o\g"

# File cache that keeps 30 files
fc = NFileCache(logdir, 30, DiscardLRU();
                # Make sure only files ending with "webserver.log.gz" are included
                predicate = x -> endswith(x, r"webserver\.log\.gz")
)

# Callback function for compression and adding to cache
function logger_callback(file)
    # Compress the file
    Gzip_jll.gzip() do gzip
        run(`$(gzip) $(file)`)
    end
    # Add the compressed file to the cache (gzip adds the .gz extension)
    FilesystemDatastructures.add!(fc, file * ".gz")
end

# Create the logger
logger = DatetimeRotatingFileLogger(
             logdir, filename_pattern;
             rotation_callback = logger_callback,
)

# Install the logger globally
global_logger(logger)
Note

上面的设置与 Julia 的包服务器使用的日志记录设置非常相似,请参阅 JuliaPackaging/PkgServer.jl:bin/run_server.jl

基于文件大小的日志轮换

对于基于文件大小的轮换,例如当文件大小达到特定阈值时进行文件轮换,请查看 LogRoller.jl 包。