Skip to content

About

按照可配置的规则拦截过滤日志系统输出条目

Resources

Stars

2 stars

Watchers

1 watching

Forks

Repository files navigation

Log Filter

pack

https://modrinth.com/mod/log-filter1

一个轻量级日志过滤模组,旨在减少控制台和日志文件中的垃圾信息,让开发者和玩家能更专注于关键错误,并节省硬盘空间。

特性

  • 正则表达式过滤:支持强大的正则表达式匹配,精准过滤特定内容的日志。
  • 完全匹配过滤:可以精准过滤掉指定的整条文本信息。
  • 按日志记录器过滤:支持按模组或类名(Logger Name)过滤整个模组的日志。
  • 按日志等级过滤:一键屏蔽 TRACE 或 DEBUG 等低级日志。

    屏蔽INFO日志?当然可以!这样你只会看到报错和警告,适用于调试。

  • 白名单模式:设置排除规则,确保重要日志(如严重错误)不会被误删。
  • 高效缓存:内置去重缓存机制,避免重复处理相同的日志。

安装

  1. 下载模组的 JAR 文件。
  2. 将 JAR 文件放入你的 Minecraft 安装目录下的 mods 文件夹中。
  3. 启动游戏一次。模组会自动生成默认配置文件。
  4. 根据需要修改配置文件,保存后配置自动热重载生效(无需重启游戏)。非法正则会被告警跳过,详见下文配置说明。

或手动新建logfilter-common.toml文件,按需修改配置文件后启动游戏即可。

配置简略说明

配置文件位于:config/logfilter-common.toml

默认配置:


[General]
	#Enable or disable log filtering
	#启用或禁用日志过滤
	enableFilter = true
	#Enable debug mode to see which logs are being filtered
	#启用调试模式以查看哪些日志被过滤
	debugMode = false
	#Maximum size of filtered log cache (for duplicate detection)
	#过滤日志缓存的最大容量(用于重复检测)
	#Range: 100 ~ 10000
	maxCacheSize = 1000

[FilterRules]
	#Regex patterns to filter log messages. Matching is PARTIAL (regex find): a rule hits when it matches any part of the message, so wrapping with '.*' is unnecessary.
	#用于过滤日志消息的正则表达式。匹配方式为包含匹配(find): 规则命中消息任意片段即生效, 无需用 '.*' 包裹。
	#REGEX ESCAPING: metacharacters ()[]{}*+?.^$|\ are special; escape them to match literally, e.g. literal parentheses must be written as \( \).
	#正则转义: 元字符 ()[]{}*+?.^$|\ 有特殊含义, 按字面匹配必须转义, 例如字面圆括号必须写成 \( \)。
	#TOML ESCAPING: in double-quoted strings TOML consumes one level of backslashes, so write "some \\(thing\\)"; in single-quoted literal strings write 'some \(thing\)' directly.
	#TOML 转义: 双引号字符串中 TOML 会消耗一层反斜杠, 因此需写 "some \\(thing\\)"; 单引号字面字符串直接写 'some \(thing\)' 即可。
	#An invalid regex (e.g. unbalanced parentheses) is safely skipped with a warning (log WARN + in-game chat message); other rules keep working.
	#非法正则(如括号不闭合)会被安全跳过并告警(日志 WARN + 进入世界时聊天框提示), 其余规则照常生效。
	filterRules = []
	#Exact message strings to filter (case-sensitive)
	#精确匹配并过滤的消息字符串(区分大小写)
	exactMatches = []
	#Logger names to completely filter (e.g., 'net.minecraft.server.MinecraftServer')
	#完全过滤的 Logger 名称(例如 'net.minecraft.server.MinecraftServer')
	loggerNames = []
	#Log levels to filter: TRACE, DEBUG, INFO, WARN, ERROR, FATAL
	#要过滤的日志级别: TRACE, DEBUG, INFO, WARN, ERROR, FATAL
	logLevels = ["TRACE", "DEBUG"]
	#Regex patterns that will EXCLUDE logs from filtering (whitelist)
	#Regex/TOML escaping rules are the same as filterRules (see its comment above).
	#将日志排除在过滤之外的正则表达式(白名单)
	#正则与 TOML 的转义规则与 filterRules 相同(见其上方注释)。
	excludePatterns = []

[async]
	#Enable async log output: wraps all root appenders into a Log4j2 AsyncAppender so console/file I/O runs on a background thread instead of the logging thread
	#启用异步日志输出: 将 root logger 的全部 appender 包装进 Log4j2 AsyncAppender, 控制台/文件 I/O 转移到后台线程执行, 打日志的线程只付入队成本
	enableAsyncLogging = true
	#Capacity of the async queue (number of buffered log events)
	#异步队列容量(缓冲的日志事件数量)
	#Range: 128 ~ 65536
	asyncQueueSize = 4096
	#When queue is full: true = briefly block the logging thread (no log loss), false = drop the log event
	#队列满时: true = 短暂阻塞打日志线程(不丢日志), false = 丢弃该条日志事件
	asyncBlockingWhenFull = true
	#Milliseconds to wait for the queue to drain before discarding remaining events (on pipeline rebuild and JVM shutdown)
	#等待队列排干的毫秒数, 超时后丢弃剩余事件(在管线重建与 JVM 关闭时生效)
	#Range: 0 ~ 600000
	asyncShutdownTimeoutMs = 5000
	#Capture caller location (class/method/line). Costs a stack walk per event; Minecraft's default log pattern does not need it
	#捕获调用方位置(类/方法/行号)。每条日志付出一次栈遍历成本; Minecraft 默认日志格式用不到它, 保持 false 即可
	asyncIncludeLocation = false
	#Fully qualified class name of a org.apache.logging.log4j.core.async.BlockingQueueFactory implementation (empty = default ArrayBlockingQueue). Upgrade hook: a Disruptor-based factory can be injected later via JarJar without code changes
	#BlockingQueueFactory 实现类的全限定名(留空 = 默认 ArrayBlockingQueue)。升级钩子: 后续可通过 JarJar 注入 Disruptor 队列工厂而无需改代码
	asyncQueueFactoryClass = ""


General (通用设置)

选项 默认值 说明
enableFilter true 是否启用日志过滤功能。
debugMode false 启用调试模式,会在控制台打印被过滤掉的日志,方便测试规则。
maxCacheSize 1000 过滤日志的缓存大小,用于去重。

FilterRules (过滤规则)

1. filterRules (正则表达式)

使用正则表达式匹配要过滤的日志。

filterRules = [
    ".*Exception.*",          # 过滤掉所有包含 "Exception" 的日志
    ".*stack trace.*",        # 过滤掉堆栈跟踪
    ".*Could not pass event.*"# 过滤掉特定事件报错
]

⚠ 常见坑:正则元字符与 TOML 双层转义

  1. 正则层:()[]{}*+?.^$|\ 是正则元字符,按字面匹配必须转义。例:日志 some (thing) 的圆括号必须写成 \(thing\),否则括号被解析为捕获组,规则静默失效(正则本身合法,无任何报错)。
  2. TOML 层:双引号字符串中 TOML 会先消耗一层反斜杠,双引号写法需再翻倍:"some \\(thing\\)";推荐用单引号字面字符串,免 TOML 转义:'some \(thing\)'。
  3. 匹配语义:filterRules 为包含匹配(find),规则命中消息任意片段即生效,无需用 .* 包裹。
  4. 非法正则(如括号不闭合)会被安全跳过:日志输出 WARN(含规则原文),玩家进入世界时聊天框会收到黄字警告,其余规则照常生效,不会导致启动崩溃。写规则前建议先在 regex101.com(选 Java flavor)验证。

2. exactMatches (完全匹配)

只有当日志内容完全一致时才会被过滤(区分大小写)。

exactMatches = [
    "This message will be completely filtered out"
]

3. loggerNames (记录器名称)

根据日志的来源(类名或模组包名)进行过滤。支持层级过滤。

loggerNames = [
    "net.minecraft.server.MinecraftServer",  # 过滤掉主服务器的日志
    "com.some.noisy.mod"                     # 过滤掉某个吵闹的模组的所有日志
]

4. logLevels (日志等级)

根据日志级别进行过滤。可选值:TRACE, DEBUG, INFO, WARN, ERROR, FATAL。

logLevels = [
    "TRACE",
    "DEBUG"
]

5. excludePatterns (白名单/排除规则)

非常重要。此处的规则匹配的日志将不会被过滤,即使其他规则匹配到了它。用于保留关键错误。

excludePatterns = [
    ".*CRITICAL ERROR.*",  # 即使匹配了 Exception,但如果是 CRITICAL ERROR,则保留
    ".*IMPORTANT.*"
]

🔨 构建

如果你想要自己编译此模组:

./gradlew build

编译后的 JAR 文件位于 build/libs/ 目录。


深入了解

第一部分:模组代码层面的工作原理

这个模组的核心思想是拦截。它在 Minecraft 的日志输出到达控制台或文件之前,提前“劫持”了这些数据,决定是放行还是丢弃。

1. 日志框架基础:Log4j 2

Minecraft 1.20.1 使用的是 Log4j 2 日志框架。

  • 日志流向: 游戏代码产生日志事件 (LogEvent) -> 传递给 Log4j 的 LoggerContext -> 传递给 Appender(如控制台输出器、文件写入器) -> 最终显示在屏幕上。
  • Filter 机制: Log4j 允许在 Logger 或 Appender 上挂载“过滤器 (Filter)”。
  • 异步输出管线: root logger 的全部 Appender 会被包装进 Log4j2 AsyncAppender(默认开启,可配置):控制台/文件 I/O 转移到后台线程执行,产生日志的线程只付入队成本,队列容量与满载策略均可配置。

2. 代码执行流程

我们的模组通过 LogFilterManager.java 在游戏启动时 (FMLCommonSetupEvent) 执行以下操作:

  1. 获取上下文:

    LoggerContext loggerContext = LoggerContext.getContext(false);

    我们拿到了 Minecraft 当前的日志环境上下文。

  2. 获取根配置:

    Configuration configuration = loggerContext.getConfiguration();
    LoggerConfig rootLoggerConfig = configuration.getRootLogger();

    我们拿到了“根 Logger”的配置。因为几乎所有日志最终都会流向根 Logger,所以在这里挂载过滤器可以捕获全局日志。

  3. 注册自定义过滤器:

    log4jFilter = new FilteringLogFilter(); // 我们自定义的过滤器类
    log4jFilter.start();
    rootLoggerConfig.addFilter(log4jFilter);

    我们将自定义的 FilteringLogFilter 插入到了日志处理链的最前端。

  4. 拦截与决策 (FilteringLogFilter.filter 方法): 每当 Minecraft 试图输出一行日志时,Log4j 会调用我们的 filter(LogEvent event) 方法。为了防止日志过滤导致游戏卡顿,我们采用了“快速失败”设计,逻辑优先级如下:

    • 步骤 A:提取关键信息。直接从 Log4j 的 LogEvent 中提取 Logger 名称、等级等关键信息。
    • 步骤 B:快速检查(等级与 Logger)。优先检查日志等级 (logLevels) 和 Logger 名称 (loggerNames)。这是一项极低开销的操作。如果匹配,直接丢弃日志,完全跳过后续昂贵的消息格式化和正则匹配,极大降低主线程开销。
    • 步骤 C:消息格式化。只有当日志通过了快速检查未被拦截时,才会调用 getFormattedMessage() 生成消息文本。
    • 步骤 D:缓存检查。计算日志内容的哈希值,看缓存里有没有这条日志。如果有,直接返回 DENY(拒绝),避免重复计算。
    • 步骤 E:白名单检查 (excludePatterns)。如果日志匹配了白名单正则,返回 NEUTRAL(中立/放行)。白名单优先级最高,用于保护重要日志。
    • 步骤 F:完全匹配检查 (exactMatches)。如果日志消息与配置的字符串完全一致,返回 DENY。
    • 步骤 G:正则规则检查 (filterRules)。如果日志消息匹配了配置的正则表达式,返回 DENY。
  5. 最终裁决:

    • 如果上述任何拦截条件匹配,我们返回 Result.DENY。Log4j 收到 DENY 后,会立即丢弃该日志,控制台和文件都不会显示。
    • 如果都没匹配,我们返回 Result.NEUTRAL。Log4j 认为过滤器“没意见”,继续将日志传给下一个处理器,最终正常显示。
  6. 配置热重载: 修改并保存 logfilter-common.toml 后,Forge 的配置文件监听立即触发重载事件:异步管线按参数签名幂等重建(参数未变则跳过),过滤规则重新编译,非法正则被跳过并告警——全程无需重启游戏。


第二部分:实战案例解析与配置

让我们来看看这条日志:

[241... 00:32:22.574] [Render thread/WARN] [net.minecraft.client.renderer.ShaderInstance/]: Shader rendertype_entity_translucent_emissive could not find sampler named Sampler2 in the specified shader program.

1. 日志条目解剖

我们需要把这条日志拆解成几个关键部分:

日志片段 含义 对应配置项 备注
WARN 日志等级 logLevels 表示这是一条警告,不是错误也不是信息。
net.minecraft.client.renderer.ShaderInstance Logger 名称 (记录器名称) loggerNames 发出这条日志的 Java 类的全限定名。
Shader ... program. 日志消息 filterRules, exactMatches 具体的报错内容。

2. 如何判断归属哪个配置项?

  • 如果你想屏蔽某个类/模组发出的所有消息(例如这个 Shader 类的所有报错我都懒得看),你应该用 loggerNames。
  • 如果你想屏蔽某种级别的所有消息(例如我不想看任何 WARN 级别的消息),你应该用 logLevels。
  • 如果你想精准屏蔽这一句话(哪怕它只出现一次),你应该用 exactMatches。
  • 如果你想模糊屏蔽这类报错(例如不管它找不到的是 Sampler1 还是 Sampler2,或者是哪个 Shader,只要是“找不到采样器”就屏蔽),你应该用 filterRules。

3. 实战配置方案

针对这条具体的 Shader 报错,我们有四种过滤策略,请根据你的需求选择:

方案 A:精准过滤这条报错 (推荐 - 使用 filterRules)

这条日志的核心特征是“找不到 sampler”。我们可以写一个正则表达式,涵盖所有找不到 sampler 的情况,而不影响其他日志。

适用场景:你认为所有关于找不到 Shader 采样器的警告都是无关紧要的,想全部屏蔽。
配置代码:

# 解释:.* 表示任意字符,匹配包含 "could not find sampler named" 的所有日志,不管它的前后有什么
	filterRules = [".*could not find sampler named.*"]
方案 B:只屏蔽这一句特定的长文本 (使用 exactMatches)

如果你只想屏蔽 Sampler2 这个特定找不到的警告,但保留其他 Shader 警告。

适用场景:极其精准的打击,不误伤友军。
配置代码:

# 必须和日志里的文字一模一样(通常不包括时间戳和线程名,只包括冒号后面的内容)
	exactMatches = ["Shader rendertype_entity_translucent_emissive could not find sampler named Sampler2 in the specified shader program."]
方案 C:屏蔽 ShaderInstance 这个类的所有日志 (使用 loggerNames)

如果 ShaderInstance 这个类非常吵,你根本不想看它输出的任何东西。

适用场景:激进过滤。警告:如果这个类后续输出了严重的错误日志,你也看不到了。
配置代码:

# 只要日志来源是 net.minecraft.client.renderer.ShaderInstance,全部屏蔽
	loggerNames = ["net.minecraft.client.renderer.ShaderInstance"]
方案 D:屏蔽所有 WARN 级别的日志 (使用 logLevels)

适用场景:不推荐。这会屏蔽所有的警告信息,可能导致你错过真正需要关注的潜在问题。
配置代码:

	logLevels = ["WARN"]

About

按照可配置的规则拦截过滤日志系统输出条目

Resources

Stars

2 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages