Skip to content

refactor: acta#21

Merged
Sn0wo2 merged 20 commits into
mainfrom
refactor/acta
Jun 13, 2026
Merged

refactor: acta#21
Sn0wo2 merged 20 commits into
mainfrom
refactor/acta

Conversation

@Sn0wo2

@Sn0wo2 Sn0wo2 commented May 21, 2026

Copy link
Copy Markdown
Owner

Summary by Sourcery

重构 acta 的日志核心,以简化 subscriber 组合方式,统一 formatter 和样式配置,并改进过滤器、主题、异步 writer 和文件日志的易用性。

增强内容:

  • 用扁平的 boxed layer 向量和更简单的 reload handle API,替换原本深度嵌套的泛型 tracing subscriber 栈。
  • 从仅支持 level 的配置迁移到更丰富的基于 Filter 的 API,该 API 支持原始的 EnvFilter 指令以及按 target 的覆盖配置,并相应更新 Config / ConfigBuilder
  • 通过改用构建时可配置的路径宽度、更安全的截断方式,以及基于 anstyle-lossy 的全新颜色样式后端,优化 Formatter 的默认值、路径格式化和着色。
  • 简化 Theme 和 Style 的构建方式,引入 LONG 和 MEDIUM 两套 level 标签集,并将 LONG 标签设为默认。
  • 收紧公共 / prelude API,仅重新导出高层的 init / build_layer / TracingGuard 接口,移除底层 subscriber 连接辅助工具。
  • 重做异步 writer 配置,以支持可配置的缓冲区大小并暴露 DEFAULT_ASYNC_BUFFER_SIZE,同时简化自定义异步 writer 的实现。
  • 引入 acta-build 辅助 crate,并将其集成到 build.rs 中,用于计算并持久化默认路径宽度,并改进失败处理。

文档:

  • 修订 README 示例和文档,使其反映新的基于 Filter 的配置、LayerConfig 的用法、异步缓冲区大小配置,以及用于路径宽度调优的 acta-build 辅助工具。

测试:

  • 扩展并更新 formatter、visitor、config、color、writer 以及集成路径上的测试,以覆盖新的路径格式化行为、过滤器语义、标签集、异步缓冲区大小,以及 TracingGuard 与过滤器的交互。
Original summary in English

Summary by Sourcery

Refactor acta’s logging core to simplify subscriber composition, unify formatter and style configuration, and improve filter, theme, async writer, and file logging ergonomics.

Enhancements:

  • Replace deeply nested generic tracing subscriber stack with a flat vector of boxed layers and a simpler reload handle API.
  • Move from level-only configuration to a richer Filter-based API that supports raw EnvFilter directives and per-target overrides, with Config/ConfigBuilder updated accordingly.
  • Refine Formatter defaults, path formatting, and coloring by switching to a build-time-configurable path width, safer truncation, and a new color styling backend using anstyle-lossy.
  • Simplify Theme and Style construction, introduce LONG and MEDIUM level label sets, and make LONG labels the default.
  • Tighten the public/prelude API by re-exporting only the high-level init/build_layer/TracingGuard surface and removing low-level subscriber wiring helpers.
  • Rework async writer configuration to support configurable buffer sizes and expose DEFAULT_ASYNC_BUFFER_SIZE, while simplifying the custom async writer implementation.
  • Introduce an acta-build helper crate and integrate it into build.rs to compute and persist default path width with better failure handling.

Documentation:

  • Revise README examples and documentation to reflect the new Filter-based configuration, LayerConfig usage, async buffer sizing, and the acta-build helper for path width tuning.

Tests:

  • Extend and update tests across formatter, visitor, config, color, writer, and integration paths to cover new path formatting behavior, filter semantics, label sets, async buffer sizing, and TracingGuard filter interactions.
Original summary in English

Summary by Sourcery

重构 acta 的日志核心,以简化 subscriber 组合方式,统一 formatter 和样式配置,并改进过滤器、主题、异步 writer 和文件日志的易用性。

增强内容:

  • 用扁平的 boxed layer 向量和更简单的 reload handle API,替换原本深度嵌套的泛型 tracing subscriber 栈。
  • 从仅支持 level 的配置迁移到更丰富的基于 Filter 的 API,该 API 支持原始的 EnvFilter 指令以及按 target 的覆盖配置,并相应更新 Config / ConfigBuilder
  • 通过改用构建时可配置的路径宽度、更安全的截断方式,以及基于 anstyle-lossy 的全新颜色样式后端,优化 Formatter 的默认值、路径格式化和着色。
  • 简化 Theme 和 Style 的构建方式,引入 LONG 和 MEDIUM 两套 level 标签集,并将 LONG 标签设为默认。
  • 收紧公共 / prelude API,仅重新导出高层的 init / build_layer / TracingGuard 接口,移除底层 subscriber 连接辅助工具。
  • 重做异步 writer 配置,以支持可配置的缓冲区大小并暴露 DEFAULT_ASYNC_BUFFER_SIZE,同时简化自定义异步 writer 的实现。
  • 引入 acta-build 辅助 crate,并将其集成到 build.rs 中,用于计算并持久化默认路径宽度,并改进失败处理。

文档:

  • 修订 README 示例和文档,使其反映新的基于 Filter 的配置、LayerConfig 的用法、异步缓冲区大小配置,以及用于路径宽度调优的 acta-build 辅助工具。

测试:

  • 扩展并更新 formatter、visitor、config、color、writer 以及集成路径上的测试,以覆盖新的路径格式化行为、过滤器语义、标签集、异步缓冲区大小,以及 TracingGuard 与过滤器的交互。
Original summary in English

Summary by Sourcery

Refactor acta’s logging core to simplify subscriber composition, unify formatter and style configuration, and improve filter, theme, async writer, and file logging ergonomics.

Enhancements:

  • Replace deeply nested generic tracing subscriber stack with a flat vector of boxed layers and a simpler reload handle API.
  • Move from level-only configuration to a richer Filter-based API that supports raw EnvFilter directives and per-target overrides, with Config/ConfigBuilder updated accordingly.
  • Refine Formatter defaults, path formatting, and coloring by switching to a build-time-configurable path width, safer truncation, and a new color styling backend using anstyle-lossy.
  • Simplify Theme and Style construction, introduce LONG and MEDIUM level label sets, and make LONG labels the default.
  • Tighten the public/prelude API by re-exporting only the high-level init/build_layer/TracingGuard surface and removing low-level subscriber wiring helpers.
  • Rework async writer configuration to support configurable buffer sizes and expose DEFAULT_ASYNC_BUFFER_SIZE, while simplifying the custom async writer implementation.
  • Introduce an acta-build helper crate and integrate it into build.rs to compute and persist default path width with better failure handling.

Documentation:

  • Revise README examples and documentation to reflect the new Filter-based configuration, LayerConfig usage, async buffer sizing, and the acta-build helper for path width tuning.

Tests:

  • Extend and update tests across formatter, visitor, config, color, writer, and integration paths to cover new path formatting behavior, filter semantics, label sets, async buffer sizing, and TracingGuard filter interactions.
Original summary in English

Summary by Sourcery

重构 acta 的日志核心,以简化 subscriber 组合方式,统一 formatter 和样式配置,并改进过滤器、主题、异步 writer 和文件日志的易用性。

增强内容:

  • 用扁平的 boxed layer 向量和更简单的 reload handle API,替换原本深度嵌套的泛型 tracing subscriber 栈。
  • 从仅支持 level 的配置迁移到更丰富的基于 Filter 的 API,该 API 支持原始的 EnvFilter 指令以及按 target 的覆盖配置,并相应更新 Config / ConfigBuilder
  • 通过改用构建时可配置的路径宽度、更安全的截断方式,以及基于 anstyle-lossy 的全新颜色样式后端,优化 Formatter 的默认值、路径格式化和着色。
  • 简化 Theme 和 Style 的构建方式,引入 LONG 和 MEDIUM 两套 level 标签集,并将 LONG 标签设为默认。
  • 收紧公共 / prelude API,仅重新导出高层的 init / build_layer / TracingGuard 接口,移除底层 subscriber 连接辅助工具。
  • 重做异步 writer 配置,以支持可配置的缓冲区大小并暴露 DEFAULT_ASYNC_BUFFER_SIZE,同时简化自定义异步 writer 的实现。
  • 引入 acta-build 辅助 crate,并将其集成到 build.rs 中,用于计算并持久化默认路径宽度,并改进失败处理。

文档:

  • 修订 README 示例和文档,使其反映新的基于 Filter 的配置、LayerConfig 的用法、异步缓冲区大小配置,以及用于路径宽度调优的 acta-build 辅助工具。

测试:

  • 扩展并更新 formatter、visitor、config、color、writer 以及集成路径上的测试,以覆盖新的路径格式化行为、过滤器语义、标签集、异步缓冲区大小,以及 TracingGuard 与过滤器的交互。
Original summary in English

Summary by Sourcery

Refactor acta’s logging core to simplify subscriber composition, unify formatter and style configuration, and improve filter, theme, async writer, and file logging ergonomics.

Enhancements:

  • Replace deeply nested generic tracing subscriber stack with a flat vector of boxed layers and a simpler reload handle API.
  • Move from level-only configuration to a richer Filter-based API that supports raw EnvFilter directives and per-target overrides, with Config/ConfigBuilder updated accordingly.
  • Refine Formatter defaults, path formatting, and coloring by switching to a build-time-configurable path width, safer truncation, and a new color styling backend using anstyle-lossy.
  • Simplify Theme and Style construction, introduce LONG and MEDIUM level label sets, and make LONG labels the default.
  • Tighten the public/prelude API by re-exporting only the high-level init/build_layer/TracingGuard surface and removing low-level subscriber wiring helpers.
  • Rework async writer configuration to support configurable buffer sizes and expose DEFAULT_ASYNC_BUFFER_SIZE, while simplifying the custom async writer implementation.
  • Introduce an acta-build helper crate and integrate it into build.rs to compute and persist default path width with better failure handling.

Documentation:

  • Revise README examples and documentation to reflect the new Filter-based configuration, LayerConfig usage, async buffer sizing, and the acta-build helper for path width tuning.

Tests:

  • Extend and update tests across formatter, visitor, config, color, writer, and integration paths to cover new path formatting behavior, filter semantics, label sets, async buffer sizing, and TracingGuard filter interactions.
Original summary in English

Summary by Sourcery

重构 acta 的日志核心,以简化 subscriber 组合方式,统一 formatter 和样式配置,并改进过滤器、主题、异步 writer 和文件日志的易用性。

增强内容:

  • 用扁平的 boxed layer 向量和更简单的 reload handle API,替换原本深度嵌套的泛型 tracing subscriber 栈。
  • 从仅支持 level 的配置迁移到更丰富的基于 Filter 的 API,该 API 支持原始的 EnvFilter 指令以及按 target 的覆盖配置,并相应更新 Config / ConfigBuilder
  • 通过改用构建时可配置的路径宽度、更安全的截断方式,以及基于 anstyle-lossy 的全新颜色样式后端,优化 Formatter 的默认值、路径格式化和着色。
  • 简化 Theme 和 Style 的构建方式,引入 LONG 和 MEDIUM 两套 level 标签集,并将 LONG 标签设为默认。
  • 收紧公共 / prelude API,仅重新导出高层的 init / build_layer / TracingGuard 接口,移除底层 subscriber 连接辅助工具。
  • 重做异步 writer 配置,以支持可配置的缓冲区大小并暴露 DEFAULT_ASYNC_BUFFER_SIZE,同时简化自定义异步 writer 的实现。
  • 引入 acta-build 辅助 crate,并将其集成到 build.rs 中,用于计算并持久化默认路径宽度,并改进失败处理。

文档:

  • 修订 README 示例和文档,使其反映新的基于 Filter 的配置、LayerConfig 的用法、异步缓冲区大小配置,以及用于路径宽度调优的 acta-build 辅助工具。

测试:

  • 扩展并更新 formatter、visitor、config、color、writer 以及集成路径上的测试,以覆盖新的路径格式化行为、过滤器语义、标签集、异步缓冲区大小,以及 TracingGuard 与过滤器的交互。
Original summary in English

Summary by Sourcery

Refactor acta’s logging core to simplify subscriber composition, unify formatter and style configuration, and improve filter, theme, async writer, and file logging ergonomics.

Enhancements:

  • Replace deeply nested generic tracing subscriber stack with a flat vector of boxed layers and a simpler reload handle API.
  • Move from level-only configuration to a richer Filter-based API that supports raw EnvFilter directives and per-target overrides, with Config/ConfigBuilder updated accordingly.
  • Refine Formatter defaults, path formatting, and coloring by switching to a build-time-configurable path width, safer truncation, and a new color styling backend using anstyle-lossy.
  • Simplify Theme and Style construction, introduce LONG and MEDIUM level label sets, and make LONG labels the default.
  • Tighten the public/prelude API by re-exporting only the high-level init/build_layer/TracingGuard surface and removing low-level subscriber wiring helpers.
  • Rework async writer configuration to support configurable buffer sizes and expose DEFAULT_ASYNC_BUFFER_SIZE, while simplifying the custom async writer implementation.
  • Introduce an acta-build helper crate and integrate it into build.rs to compute and persist default path width with better failure handling.

Documentation:

  • Revise README examples and documentation to reflect the new Filter-based configuration, LayerConfig usage, async buffer sizing, and the acta-build helper for path width tuning.

Tests:

  • Extend and update tests across formatter, visitor, config, color, writer, and integration paths to cover new path formatting behavior, filter semantics, label sets, async buffer sizing, and TracingGuard filter interactions.
Original summary in English

Summary by Sourcery

重构 acta 的日志核心,以简化 subscriber 组合方式,统一 formatter 和样式配置,并改进过滤器、主题、异步 writer 和文件日志的易用性。

增强内容:

  • 用扁平的 boxed layer 向量和更简单的 reload handle API,替换原本深度嵌套的泛型 tracing subscriber 栈。
  • 从仅支持 level 的配置迁移到更丰富的基于 Filter 的 API,该 API 支持原始的 EnvFilter 指令以及按 target 的覆盖配置,并相应更新 Config / ConfigBuilder
  • 通过改用构建时可配置的路径宽度、更安全的截断方式,以及基于 anstyle-lossy 的全新颜色样式后端,优化 Formatter 的默认值、路径格式化和着色。
  • 简化 Theme 和 Style 的构建方式,引入 LONG 和 MEDIUM 两套 level 标签集,并将 LONG 标签设为默认。
  • 收紧公共 / prelude API,仅重新导出高层的 init / build_layer / TracingGuard 接口,移除底层 subscriber 连接辅助工具。
  • 重做异步 writer 配置,以支持可配置的缓冲区大小并暴露 DEFAULT_ASYNC_BUFFER_SIZE,同时简化自定义异步 writer 的实现。
  • 引入 acta-build 辅助 crate,并将其集成到 build.rs 中,用于计算并持久化默认路径宽度,并改进失败处理。

文档:

  • 修订 README 示例和文档,使其反映新的基于 Filter 的配置、LayerConfig 的用法、异步缓冲区大小配置,以及用于路径宽度调优的 acta-build 辅助工具。

测试:

  • 扩展并更新 formatter、visitor、config、color、writer 以及集成路径上的测试,以覆盖新的路径格式化行为、过滤器语义、标签集、异步缓冲区大小,以及 TracingGuard 与过滤器的交互。
Original summary in English

Summary by Sourcery

Refactor acta’s logging core to simplify subscriber composition, unify formatter and style configuration, and improve filter, theme, async writer, and file logging ergonomics.

Enhancements:

  • Replace deeply nested generic tracing subscriber stack with a flat vector of boxed layers and a simpler reload handle API.
  • Move from level-only configuration to a richer Filter-based API that supports raw EnvFilter directives and per-target overrides, with Config/ConfigBuilder updated accordingly.
  • Refine Formatter defaults, path formatting, and coloring by switching to a build-time-configurable path width, safer truncation, and a new color styling backend using anstyle-lossy.
  • Simplify Theme and Style construction, introduce LONG and MEDIUM level label sets, and make LONG labels the default.
  • Tighten the public/prelude API by re-exporting only the high-level init/build_layer/TracingGuard surface and removing low-level subscriber wiring helpers.
  • Rework async writer configuration to support configurable buffer sizes and expose DEFAULT_ASYNC_BUFFER_SIZE, while simplifying the custom async writer implementation.
  • Introduce an acta-build helper crate and integrate it into build.rs to compute and persist default path width with better failure handling.

Documentation:

  • Revise README examples and documentation to reflect the new Filter-based configuration, LayerConfig usage, async buffer sizing, and the acta-build helper for path width tuning.

Tests:

  • Extend and update tests across formatter, visitor, config, color, writer, and integration paths to cover new path formatting behavior, filter semantics, label sets, async buffer sizing, and TracingGuard filter interactions.
Original summary in English

Summary by Sourcery

重构 acta 的日志核心,以简化 subscriber 组合方式,统一 formatter 和样式配置,并改进过滤器、主题、异步 writer 和文件日志的易用性。

增强内容:

  • 用扁平的 boxed layer 向量和更简单的 reload handle API,替换原本深度嵌套的泛型 tracing subscriber 栈。
  • 从仅支持 level 的配置迁移到更丰富的基于 Filter 的 API,该 API 支持原始的 EnvFilter 指令以及按 target 的覆盖配置,并相应更新 Config / ConfigBuilder
  • 通过改用构建时可配置的路径宽度、更安全的截断方式,以及基于 anstyle-lossy 的全新颜色样式后端,优化 Formatter 的默认值、路径格式化和着色。
  • 简化 Theme 和 Style 的构建方式,引入 LONG 和 MEDIUM 两套 level 标签集,并将 LONG 标签设为默认。
  • 收紧公共 / prelude API,仅重新导出高层的 init / build_layer / TracingGuard 接口,移除底层 subscriber 连接辅助工具。
  • 重做异步 writer 配置,以支持可配置的缓冲区大小并暴露 DEFAULT_ASYNC_BUFFER_SIZE,同时简化自定义异步 writer 的实现。
  • 引入 acta-build 辅助 crate,并将其集成到 build.rs 中,用于计算并持久化默认路径宽度,并改进失败处理。

文档:

  • 修订 README 示例和文档,使其反映新的基于 Filter 的配置、LayerConfig 的用法、异步缓冲区大小配置,以及用于路径宽度调优的 acta-build 辅助工具。

测试:

  • 扩展并更新 formatter、visitor、config、color、writer 以及集成路径上的测试,以覆盖新的路径格式化行为、过滤器语义、标签集、异步缓冲区大小,以及 TracingGuard 与过滤器的交互。
Original summary in English

Summary by Sourcery

Refactor acta’s logging core to simplify subscriber composition, unify formatter and style configuration, and improve filter, theme, async writer, and file logging ergonomics.

Enhancements:

  • Replace deeply nested generic tracing subscriber stack with a flat vector of boxed layers and a simpler reload handle API.
  • Move from level-only configuration to a richer Filter-based API that supports raw EnvFilter directives and per-target overrides, with Config/ConfigBuilder updated accordingly.
  • Refine Formatter defaults, path formatting, and coloring by switching to a build-time-configurable path width, safer truncation, and a new color styling backend using anstyle-lossy.
  • Simplify Theme and Style construction, introduce LONG and MEDIUM level label sets, and make LONG labels the default.
  • Tighten the public/prelude API by re-exporting only the high-level init/build_layer/TracingGuard surface and removing low-level subscriber wiring helpers.
  • Rework async writer configuration to support configurable buffer sizes and expose DEFAULT_ASYNC_BUFFER_SIZE, while simplifying the custom async writer implementation.
  • Introduce an acta-build helper crate and integrate it into build.rs to compute and persist default path width with better failure handling.

Documentation:

  • Revise README examples and documentation to reflect the new Filter-based configuration, LayerConfig usage, async buffer sizing, and the acta-build helper for path width tuning.

Tests:

  • Extend and update tests across formatter, visitor, config, color, writer, and integration paths to cover new path formatting behavior, filter semantics, label sets, async buffer sizing, and TracingGuard filter interactions.
Original summary in English

Summary by Sourcery

重构 acta 的日志核心,以简化 subscriber 组合方式,统一 formatter 和样式配置,并改进过滤器、主题、异步 writer 和文件日志的易用性。

增强内容:

  • 用扁平的 boxed layer 向量和更简单的 reload handle API,替换原本深度嵌套的泛型 tracing subscriber 栈。
  • 从仅支持 level 的配置迁移到更丰富的基于 Filter 的 API,该 API 支持原始的 EnvFilter 指令以及按 target 的覆盖配置,并相应更新 Config / ConfigBuilder
  • 通过改用构建时可配置的路径宽度、更安全的截断方式,以及基于 anstyle-lossy 的全新颜色样式后端,优化 Formatter 的默认值、路径格式化和着色。
  • 简化 Theme 和 Style 的构建方式,引入 LONG 和 MEDIUM 两套 level 标签集,并将 LONG 标签设为默认。
  • 收紧公共 / prelude API,仅重新导出高层的 init / build_layer / TracingGuard 接口,移除底层 subscriber 连接辅助工具。
  • 重做异步 writer 配置,以支持可配置的缓冲区大小并暴露 DEFAULT_ASYNC_BUFFER_SIZE,同时简化自定义异步 writer 的实现。
  • 引入 acta-build 辅助 crate,并将其集成到 build.rs 中,用于计算并持久化默认路径宽度,并改进失败处理。

文档:

  • 修订 README 示例和文档,使其反映新的基于 Filter 的配置、LayerConfig 的用法、异步缓冲区大小配置,以及用于路径宽度调优的 acta-build 辅助工具。

测试:

  • 扩展并更新 formatter、visitor、config、color、writer 以及集成路径上的测试,以覆盖新的路径格式化行为、过滤器语义、标签集、异步缓冲区大小,以及 TracingGuard 与过滤器的交互。
Original summary in English

Summary by Sourcery

Refactor acta’s logging core to simplify subscriber composition, unify formatter and style configuration, and improve filter, theme, async writer, and file logging ergonomics.

Enhancements:

  • Replace deeply nested generic tracing subscriber stack with a flat vector of boxed layers and a simpler reload handle API.
  • Move from level-only configuration to a richer Filter-based API that supports raw EnvFilter directives and per-target overrides, with Config/ConfigBuilder updated accordingly.
  • Refine Formatter defaults, path formatting, and coloring by switching to a build-time-configurable path width, safer truncation, and a new color styling backend using anstyle-lossy.
  • Simplify Theme and Style construction, introduce LONG and MEDIUM level label sets, and make LONG labels the default.
  • Tighten the public/prelude API by re-exporting only the high-level init/build_layer/TracingGuard surface and removing low-level subscriber wiring helpers.
  • Rework async writer configuration to support configurable buffer sizes and expose DEFAULT_ASYNC_BUFFER_SIZE, while simplifying the custom async writer implementation.
  • Introduce an acta-build helper crate and integrate it into build.rs to compute and persist default path width with better failure handling.

Documentation:

  • Revise README examples and documentation to reflect the new Filter-based configuration, LayerConfig usage, async buffer sizing, and the acta-build helper for path width tuning.

Tests:

  • Extend and update tests across formatter, visitor, config, color, writer, and integration paths to cover new path formatting behavior, filter semantics, label sets, async buffer sizing, and TracingGuard filter interactions.
Original summary in English

Summary by Sourcery

重构 acta 的日志核心,以简化 subscriber 组合方式,统一 formatter 和样式配置,并改进过滤器、主题、异步 writer 和文件日志的易用性。

增强内容:

  • 用扁平的 boxed layer 向量和更简单的 reload handle API,替换原本深度嵌套的泛型 tracing subscriber 栈。
  • 从仅支持 level 的配置迁移到更丰富的基于 Filter 的 API,该 API 支持原始的 EnvFilter 指令以及按 target 的覆盖配置,并相应更新 Config / ConfigBuilder
  • 通过改用构建时可配置的路径宽度、更安全的截断方式,以及基于 anstyle-lossy 的全新颜色样式后端,优化 Formatter 的默认值、路径格式化和着色。
  • 简化 Theme 和 Style 的构建方式,引入 LONG 和 MEDIUM 两套 level 标签集,并将 LONG 标签设为默认。
  • 收紧公共 / prelude API,仅重新导出高层的 init / build_layer / TracingGuard 接口,移除底层 subscriber 连接辅助工具。
  • 重做异步 writer 配置,以支持可配置的缓冲区大小并暴露 DEFAULT_ASYNC_BUFFER_SIZE,同时简化自定义异步 writer 的实现。
  • 引入 acta-build 辅助 crate,并将其集成到 build.rs 中,用于计算并持久化默认路径宽度,并改进失败处理。

文档:

  • 修订 README 示例和文档,使其反映新的基于 Filter 的配置、LayerConfig 的用法、异步缓冲区大小配置,以及用于路径宽度调优的 acta-build 辅助工具。

测试:

  • 扩展并更新 formatter、visitor、config、color、writer 以及集成路径上的测试,以覆盖新的路径格式化行为、过滤器语义、标签集、异步缓冲区大小,以及 TracingGuard 与过滤器的交互。
Original summary in English

Summary by Sourcery

Refactor acta’s logging core to simplify subscriber composition, unify formatter and style configuration, and improve filter, theme, async writer, and file logging ergonomics.

Enhancements:

  • Replace deeply nested generic tracing subscriber stack with a flat vector of boxed layers and a simpler reload handle API.
  • Move from level-only configuration to a richer Filter-based API that supports raw EnvFilter directives and per-target overrides, with Config/ConfigBuilder updated accordingly.
  • Refine Formatter defaults, path formatting, and coloring by switching to a build-time-configurable path width, safer truncation, and a new color styling backend using anstyle-lossy.
  • Simplify Theme and Style construction, introduce LONG and MEDIUM level label sets, and make LONG labels the default.
  • Tighten the public/prelude API by re-exporting only the high-level init/build_layer/TracingGuard surface and removing low-level subscriber wiring helpers.
  • Rework async writer configuration to support configurable buffer sizes and expose DEFAULT_ASYNC_BUFFER_SIZE, while simplifying the custom async writer implementation.
  • Introduce an acta-build helper crate and integrate it into build.rs to compute and persist default path width with better failure handling.

Documentation:

  • Revise README examples and documentation to reflect the new Filter-based configuration, LayerConfig usage, async buffer sizing, and the acta-build helper for path width tuning.

Tests:

  • Extend and update tests across formatter, visitor, config, color, writer, and integration paths to cover new path formatting behavior, filter semantics, label sets, async buffer sizing, and TracingGuard filter interactions.

@coderabbitai

coderabbitai Bot commented May 21, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

此 PR 系统性重构日志库:用 Styled 替换旧颜色函数,改写 Filter/Config 语义,简化 layer 构建与 TracingGuard 的运行时更新路径,重写格式化器路径截断逻辑,移除 AsyncWriter 的写入计数并同步更新文档与测试。

Changes

颜色与配置重构

Layer / File(s) Summary
依赖与构建工具
.gitignore, Cargo.toml, build.rs, crates/acta-build/...
更新依赖列表,增加 .claude/ 到 .gitignore;build.rs 改为写入 OUT_DIR/path_width 并在失败时仅 warning;acta-build walk_src_max_width 改为返回 usize 并使用兜底宽度。
颜色抽象与模块导出
src/color/style.rs, src/color/mod.rs, src/color/tests.rs
新增 Styled 类型与 From for OwoStyle,移除旧的 rgb_to_*/mapping 导出,收缩 color 模块导出并更新测试。
配置与 Filter 重写
src/config/mod.rs, src/config/depth.rs, src/config/test.rs
移除 detect/depth 公共 API;引入 CompactString-backed FilterFilter::from_directiveFileConfigDEFAULT_ASYNC_BUFFER_SIZEConfig 改用 filter 字段,ConfigBuilder 支持直接设置 filter
Layer/Init 重构
src/builder.rs, src/lib.rs, src/prelude.rs
build_layer 变为非泛型返回 BoxedLayer,新增 InnerSubscriber/ReloadHandle 别名;init 直接组装层并创建 EnvFilter reload 层;移除 build_reload_filter/SubscriberParts/build_subscriber 导出,更新 TracingGuard 字段与 set_filter/set_level/set_target_level 方法。
Formatter 与 EventVisitor
src/fmt/mod.rs, src/fmt/visitor.rs, src/fmt/test.rs
Formatter 使用 Styled/OwoStyle 生成样式,format_path 改为 String 且实现 UTF-8 安全截断;移除 style_handle,调整 with_time_format 签名;EventVisitor 切换为 CompactString 存储,相关测试更新。
AsyncWriter 简化
src/writer/custom.rs, src/writer/mod.rs, src/writer/test.rs
移除 atomic 计数与 pending_writes、删除默认 async_writer() 工厂,async_writer_for 接受 capacity 参数并保留 sender 与 capacity 字段;导出调整移除 async_writer 重导出。
文档与演示更新
README.md, crates/acta-debug/src/main.rs
README、演示与示例代码更新以反映 Filter::from_directive、FileConfig::new(...).with_rotation(...)、Format::Compact(LayerConfig) 和 AsyncMode::Custom { buffer_size } 等新用法。

Sequence Diagram — 初始化与 reload 装配:

sequenceDiagram
  participant Client as init(config)
  participant LayerBuilder as build_layer(writer)
  participant ColorDetect as detect_color_depth
  participant EnvFilter as EnvFilter(reload)
  participant Registry
  Client->>LayerBuilder: 为每个 writer 请求 BoxedLayer
  LayerBuilder->>ColorDetect: 检查/决定 ColorDepth
  ColorDetect-->>LayerBuilder: 返回 ColorDepth
  LayerBuilder-->>Client: 返回 BoxedLayer
  Client->>EnvFilter: 使用 Filter 构造 EnvFilter
  EnvFilter->>Registry: 包装到 Registry
  Registry-->>Client: 全局 subscriber 设置完成
Loading

Estimated code review effort

🎯 4 (复杂) | ⏱️ ~75 分钟

🐰 在五彩斑斓的颜色森林里,
Styled 小兔蹦蹦跳跳替换了老旧函数;
Filter 指令串编织新的过滤之轮,
LayerBuilder 为初始化道路扫清了杂草。
简洁 API 在春风中绽放光彩!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Title check ❓ Inconclusive 标题过于简洁,未能充分说明此次重构的范围和主要目的。 建议改为更具体的标题,例如'refactor: simplify acta logging pipeline with Filter API and Styled abstraction',以更清楚地传达主要变化内容。
✅ Passed checks (4 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 83.76% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description check ✅ Passed PR 描述详细说明了对 acta 日志核心的重构,涵盖了 subscriber 组合、配置、格式化、过滤器等多个方面的改进。

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@sourcery-ai

sourcery-ai Bot commented May 21, 2026

Copy link
Copy Markdown

Reviewer's Guide

此变更重构了 acta 的日志管线:围绕 boxed layer 向量和新的 Filter/Config API 搭建框架,现代化了颜色与格式化工具(包括构建期的路径宽度支持),简化了异步/文件 writer 的内部实现,并移除低层级的 reload 钩子,改为通过 init/TracingGuard 流程进行控制。

通过 TracingGuard 在运行时重载过滤器的时序图

sequenceDiagram
    actor App
    participant Guard as TracingGuard
    participant Filter as Filter
    participant Env as EnvFilter
    participant Handle as reload::Handle

    App->>Guard: set_level(Level)
    Guard->>Filter: Filter::new(Level)
    Guard->>Guard: apply_current_filter()
    Guard->>Env: EnvFilter::try_new(Filter::as_directive())
    Env-->>Guard: EnvFilter
    Guard->>Handle: modify(|f| *f = EnvFilter)

    App->>Guard: set_filter(Filter)
    Guard->>Env: EnvFilter::try_new(Filter::as_directive())
    Env-->>Guard: EnvFilter
    Guard->>Handle: modify(|f| *f = EnvFilter)
Loading

基于 init 的日志管线构建流程图

flowchart LR
    A[Config] -->|filter, writers| B[init]
    B --> C[build_layers]
    C -->|Vec<BoxedLayer>| D[Registry]
    D --> E[EnvFilter]
    E -->|Layer::new| F[reload::Layer]
    F --> G[InnerSubscriber]
    G --> H[set_global_default]

    subgraph Writer_to_Layer
        W[Writer] -->|build_layer_with| L1[BoxedLayer]
        Wf[Writer target FileConfig] -->|build_file_layer| L2[BoxedLayer]
    end

    C -. uses .-> W
    C -. uses .-> Wf
Loading

文件级改动

Change Details Files
将硬编码的 subscriber layer 金字塔替换为 boxed layer 向量,并简化 reload/init 流程。
  • 引入 BoxedLayer 和基于 Layers 的 InnerSubscriber,通过 Vec 层集合构建,而不是嵌套的 Layered 类型。
  • 让 ReloadHandle 使用新的 InnerSubscriber 类型,并通过 tracing_subscriber::reload::Layer::new 直接构造 EnvFilter layers。
  • build_subscriber 内联到 init 中,直接返回 TracingGuard,并在所有非文件 writer 之间共享一个样式句柄。
  • 移除 SubscriberPartsbuild_reload_filterbuild_subscriber 以及公共的 shutdown/reload API,改为推荐更高层的 init/TracingGuard 用法。
  • 调整测试和 acta-debug 可执行文件,通过 build_layer/build_layer_with 以及新的测试辅助工具构建临时 subscribers。
src/builder.rs
src/test.rs
crates/acta-debug/src/main.rs
src/prelude.rs
src/lib.rs
重构 filter、level 和配置 API,以支持结构化过滤器与原始 directive,以及文件配置和异步选项。
  • Config.filter 替换 Config.level,新增 ConfigBuilder::filter,并暴露 Filter::from_directive 以支持 EnvFilter 兼容的字符串。
  • 简化 Level 为固定枚举变体,让 Level::as_directive 返回 &'static str,移除 Custom(String) 变体。
  • 重构 Filter:使用 CompactString 作为基础 directive,并配合 HashMap 存储每个 target 的 Level 覆盖;Default::default() 指向 info。
  • 引入 FileConfig 和新的 WriterTarget::File(FileConfig),以及 DEFAULT_ASYNC_BUFFER_SIZEAsyncMode::Custom{buffer_size}
  • 更新文档和测试,以使用 Filter::from_directiveConfig.filter、基于 LayerConfig 的格式,以及新的异步/文件配置接口。
src/config/mod.rs
src/config/test.rs
README.md
src/lib.rs
src/prelude.rs
crates/acta-debug/src/main.rs
通过 Styled 抽象和基于 anstyle-lossy 的 ANSI16 映射现代化颜色处理。
  • Styled 结构体替换 rgb_to_owo/rgb_to_owo_on/theme_fg_dimmed 以及原有映射模块,通过 ColorDepth 将其转换为 OwoStyle。
  • 使用 anstyle-lossy 的 rgb_to_ansi 和固定的 ANSI16_TABLE 将 RGB 映射到 AnsiColors,同时在 256 色路径上保留 ansi256_from_rgb
  • 调整 Formatter,通过 Styled 构建样式,包括用于时间、路径、span 和 level 括号的 dimmed/on 变体。
  • 精简颜色测试,聚焦 ansi256_from_rgb 的行为,并移除直接针对 rgb_to_ansi16 的测试。
  • 从 Cargo.toml 中移除未使用的颜色相关依赖(anstyle、derive_more、tap、smart-default)。
src/color/style.rs
src/color/mod.rs
src/color/tests.rs
src/fmt/mod.rs
Cargo.toml
优化主题和标签配置,使 Theme 构造基于调色板,并让 Style 默认使用长标签。
  • 引入 Theme::from_palette 作为内部构造函数,并重写内置主题(acta、monokai、dracula、nord、catppuccin、gruvbox、one_dark、tokyo_night)以使用该构造。
  • LevelLabels::DEFAULT 改为 LONG,新增 MEDIUM,并让 Style::default 使用 Theme::defaultIcons::defaultLevelLabels::LONG
  • 更新测试与文档,使其期望 LONG 标签为默认,同时保持 SHORT 和 MEDIUM 作为显式选项。
  • 将 Theme/Style 视为用于内部的“实际上穷尽”的类型,并相应调整基于 Debug 的相等性测试。
src/config/mod.rs
src/fmt/test.rs
README.md
改进格式化器的路径处理和构建期宽度计算,包括 Windows 规范化及 Unicode 安全。
  • 用拥有所有权的 String 格式化替换基于 ArrayString 的 PATH_BUF_SIZE 截断,并通过生成的 path_width 文件中的 DEFAULT_PATH_WIDTH 控制宽度。
  • 重写 Formatter::format_path:剥离 src/src\ 前缀,规范化反斜杠为正斜杠,在可能的情况下右对齐,并在保留文件名和 UTF-8 边界的前提下应用目录截断或省略号。
  • 暴露 Formatter::new_with_handle 并在 Formatter::new 中使用;将 with_time_format 改为接收 String,并依赖内部对 path_width 的可变性,而不是 with_path_width
  • 新增 acta-build crate 中的函数 walk_src_max_width,返回带有 padding 的宽度,并添加简单的 build.rs 写入 path_width;在 IO 错误时发出警告但不构建失败。
  • 扩展格式化器测试,覆盖右对齐、截断策略、Windows 规范化、Unicode 边界安全以及新的默认标签预期。
src/fmt/mod.rs
src/fmt/test.rs
crates/acta-build/src/lib.rs
build.rs
crates/acta-build/Cargo.toml
简化异步 writer 内部实现并暴露可配置缓冲区大小。
  • 重构 AsyncWriter,仅持有 sender 和 capacity,移除对 pending-writes 的跟踪以及相关的 AtomicUsize/状态。
  • 修改 async_writer_for,让其接收 AsyncWriterTargetcapacity,并将 capacity 接入有界 channel 和丢弃消息时的告警逻辑。
  • 调整 WriterTarget::AsyncStdout/AsyncStderr,携带 AsyncMode::Custom{buffer_size},并通过构建逻辑把 buffer_size 传递到 async_writer_for
  • 暴露 DEFAULT_ASYNC_BUFFER_SIZE,并让默认的 AsyncMode::Custom{buffer_size: DEFAULT_ASYNC_BUFFER_SIZE} 生效,在 README 和示例中记录 buffer_size 语义。
  • 清理 writer 测试与再导出,使其匹配新的异步接口,移除 async_writer() 和 pending-writes 调用。
src/writer/custom.rs
src/config/mod.rs
src/writer/mod.rs
src/writer/test.rs
README.md
src/lib.rs
src/prelude.rs
收紧 filter reload 语义并调整 TracingGuard 行为。
  • 更新 TracingGuard::set_filter,安装由 Filter::as_directive 构建的完整 EnvFilter,并保存 Filter 值以便后续更新。
  • 修改 set_level/set_target_level 以接收结构化 Level 值,通过 Filter 重建 directives,并确保每个 target 的覆盖能正确附加到结构化和原始基础 directive 上。
  • 移除 reloadshutdownapply_directive 方法,将公共 API 聚焦于 set_filter/set_level/set_target_level,内部通过 apply_current_filter 执行。
  • 调整测试,以覆盖 Filter 的默认行为、directive 组合方式以及在混用原始 directive 与运行时变更时 guard 的行为。
  • 重写 acta-debug 示例,仅通过 init/TracingGuard 展示 reload,包括 level 和 style 的变更以及文件日志检查。
src/builder.rs
src/test.rs
crates/acta-debug/src/main.rs
src/config/test.rs
README.md

Tips and commands

Interacting with Sourcery

  • 触发新的评审: 在 pull request 中评论 @sourcery-ai review
  • 继续讨论: 直接回复 Sourcery 的评审评论。
  • 从评审评论生成 GitHub issue: 在某条评审评论下回复,要求 Sourcery 基于该评论创建 issue。你也可以直接回复 @sourcery-ai issue 来从该评论创建 issue。
  • 生成 pull request 标题: 在 pull request 标题中的任意位置写上 @sourcery-ai,即可随时生成标题。你也可以在 pull request 中评论 @sourcery-ai title 以(重新)生成标题。
  • 生成 pull request 摘要: 在 pull request 描述正文的任意位置写上 @sourcery-ai summary,即可在对应位置生成 PR 摘要。你也可以在 pull request 中评论 @sourcery-ai summary 以(重新)生成摘要。
  • 生成评审者指南: 在 pull request 中评论 @sourcery-ai guide,即可(重新)生成评审者指南。
  • 一次性解决所有 Sourcery 评论: 在 pull request 中评论 @sourcery-ai resolve,将所有 Sourcery 评论标记为已解决。如果你已经处理完所有评论且不希望再看到它们,这会很有用。
  • 撤销所有 Sourcery 评审: 在 pull request 中评论 @sourcery-ai dismiss,撤销所有现有的 Sourcery 评审。尤其适用于你希望从头开始新的评审——别忘了再评论 @sourcery-ai review 触发新评审!

Customizing Your Experience

访问你的 dashboard 来:

  • 启用或禁用评审特性,例如 Sourcery 自动生成的 pull request 摘要、评审者指南等。
  • 更改评审语言。
  • 添加、删除或编辑自定义评审指令。
  • 调整其他评审设置。

Getting Help

Original review guide in English

Reviewer's Guide

Refactors acta’s logging pipeline around boxed layer vectors and a new Filter/Config API, modernizes color and formatting utilities (including build-time path-width support), simplifies async/file writer internals, and removes low-level reload hooks in favor of the init/TracingGuard flow.

Sequence diagram for runtime filter reload via TracingGuard

sequenceDiagram
    actor App
    participant Guard as TracingGuard
    participant Filter as Filter
    participant Env as EnvFilter
    participant Handle as reload::Handle

    App->>Guard: set_level(Level)
    Guard->>Filter: Filter::new(Level)
    Guard->>Guard: apply_current_filter()
    Guard->>Env: EnvFilter::try_new(Filter::as_directive())
    Env-->>Guard: EnvFilter
    Guard->>Handle: modify(|f| *f = EnvFilter)

    App->>Guard: set_filter(Filter)
    Guard->>Env: EnvFilter::try_new(Filter::as_directive())
    Env-->>Guard: EnvFilter
    Guard->>Handle: modify(|f| *f = EnvFilter)
Loading

Flow diagram for init-based logging pipeline construction

flowchart LR
    A[Config] -->|filter, writers| B[init]
    B --> C[build_layers]
    C -->|Vec<BoxedLayer>| D[Registry]
    D --> E[EnvFilter]
    E -->|Layer::new| F[reload::Layer]
    F --> G[InnerSubscriber]
    G --> H[set_global_default]

    subgraph Writer_to_Layer
        W[Writer] -->|build_layer_with| L1[BoxedLayer]
        Wf[Writer target FileConfig] -->|build_file_layer| L2[BoxedLayer]
    end

    C -. uses .-> W
    C -. uses .-> Wf
Loading

File-Level Changes

Change Details Files
Replace the hardcoded subscriber layer pyramid with boxed layer vectors and a simplified reload/init flow.
  • Introduce BoxedLayer and Layers-based InnerSubscriber built from a Vec of layers instead of nested Layered types.
  • Switch ReloadHandle to use the new InnerSubscriber type and construct EnvFilter layers directly via tracing_subscriber::reload::Layer::new.
  • Inline build_subscriber into init, returning TracingGuard directly and wiring a shared style handle across all non-file writers.
  • Drop SubscriberParts, build_reload_filter, build_subscriber, and the public shutdown/reload APIs in favor of higher-level init/TracingGuard usage.
  • Adjust tests and the acta-debug binary to build ad-hoc subscribers using build_layer/build_layer_with and the new test helper.
src/builder.rs
src/test.rs
crates/acta-debug/src/main.rs
src/prelude.rs
src/lib.rs
Rework filter, level, and configuration APIs to support structured filters and raw directives, plus file config and async options.
  • Replace Config.level with Config.filter, add ConfigBuilder::filter, and expose Filter::from_directive for EnvFilter-compatible strings.
  • Simplify Level into fixed variants with Level::as_directive returning &'static str, removing the Custom(String) variant.
  • Refactor Filter to use a CompactString base directive plus a HashMap of per-target Level overrides, with Default::default() -> info.
  • Introduce FileConfig and new WriterTarget::File(FileConfig) plus DEFAULT_ASYNC_BUFFER_SIZE and AsyncMode::Custom{buffer_size}.
  • Update docs and tests to use Filter::from_directive, Config.filter, LayerConfig-based formats, and the new async/file configuration surface.
src/config/mod.rs
src/config/test.rs
README.md
src/lib.rs
src/prelude.rs
crates/acta-debug/src/main.rs
Modernize color handling with a Styled abstraction and anstyle-lossy-backed ANSI16 mapping.
  • Replace rgb_to_owo/rgb_to_owo_on/theme_fg_dimmed and the mapping module with a Styled struct that converts to OwoStyle based on ColorDepth.
  • Use anstyle-lossy rgb_to_ansi and a fixed ANSI16_TABLE for mapping RGB to AnsiColors, while keeping ansi256_from_rgb for 256-color paths.
  • Adjust Formatter to construct styles via Styled, including dimmed/on variants for time, path, spans, and level brackets.
  • Trim color tests to focus on ansi256_from_rgb behavior and drop direct rgb_to_ansi16 tests.
  • Remove unused color-related dependencies (anstyle, derive_more, tap, smart-default) from Cargo.toml.
src/color/style.rs
src/color/mod.rs
src/color/tests.rs
src/fmt/mod.rs
Cargo.toml
Refine theming and label configuration, making Theme construction palette-based and Style defaults long labels.
  • Introduce Theme::from_palette as the internal constructor and rewrite built-in themes (acta, monokai, dracula, nord, catppuccin, gruvbox, one_dark, tokyo_night) to use it.
  • Change LevelLabels::DEFAULT to LONG, add MEDIUM, and set Style::default to use Theme::default, Icons::default, and LevelLabels::LONG.
  • Update tests and docs to expect LONG labels as the default while keeping SHORT and MEDIUM as explicit options.
  • Mark Theme/Style as effectively exhaustive for internal use and adjust Debug-based equality tests accordingly.
src/config/mod.rs
src/fmt/test.rs
README.md
Improve formatter path handling and build-time width computation, including Windows normalization and Unicode safety.
  • Replace ArrayString-based PATH_BUF_SIZE truncation with owned String formatting, using DEFAULT_PATH_WIDTH from a generated path_width file.
  • Reimplement Formatter::format_path to strip src/ or src\ prefixes, normalize backslashes to forward slashes, right-align when possible, and apply directory truncation or ellipsis as needed while preserving filenames and UTF-8 boundaries.
  • Expose Formatter::new_with_handle and use it from Formatter::new; change with_time_format to take a String and rely on internal path_width mutability instead of with_path_width.
  • Add a new acta-build crate function walk_src_max_width that returns a width with padding and a simple build.rs that writes path_width, warning but not failing on IO errors.
  • Expand formatter tests to cover right alignment, truncation strategies, Windows normalization, Unicode boundary safety, and new default label expectations.
src/fmt/mod.rs
src/fmt/test.rs
crates/acta-build/src/lib.rs
build.rs
crates/acta-build/Cargo.toml
Simplify async writer internals and expose configurable buffer sizes.
  • Refactor AsyncWriter to hold only a sender and capacity, removing pending-writes tracking and related AtomicUsize/state.
  • Change async_writer_for to take an AsyncWriterTarget and capacity, wiring the capacity into the bounded channel and drop-message warning.
  • Adjust WriterTarget::AsyncStdout/AsyncStderr to carry AsyncMode::Custom{buffer_size} and pass buffer_size through builder logic into async_writer_for.
  • Expose DEFAULT_ASYNC_BUFFER_SIZE and default AsyncMode::Custom{buffer_size: DEFAULT_ASYNC_BUFFER_SIZE}, and document buffer_size semantics in README and examples.
  • Clean up writer tests and re-exports to match the new async surface, removing async_writer() and pending-writes calls.
src/writer/custom.rs
src/config/mod.rs
src/writer/mod.rs
src/writer/test.rs
README.md
src/lib.rs
src/prelude.rs
Tighten filter reload semantics and TracingGuard behavior.
  • Update TracingGuard::set_filter to install a full EnvFilter built from Filter::as_directive and store the Filter value for later updates.
  • Change set_level/set_target_level to take structured Level values, rebuild directives via Filter, and ensure per-target overrides append correctly to both structured and raw base directives.
  • Remove reload, shutdown, and apply_directive methods, focusing the public API on set_filter/set_level/set_target_level and internal apply_current_filter.
  • Adjust tests to cover default Filter behavior, directive composition, and guard behavior when mixing raw directives with runtime changes.
  • Rework acta-debug to demonstrate reload via init/TracingGuard only, including level and style changes plus file logging inspection.
src/builder.rs
src/test.rs
crates/acta-debug/src/main.rs
src/config/test.rs
README.md

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request refactors the subscriber initialization by replacing a complex layered type system with a vector of boxed layers and simplifies the configuration and filtering logic. It also removes several dependencies, including arrayvec, smart-default, and derive_more. Feedback identifies critical bugs in the path formatting logic, including incorrect string manipulation that reverses directory names and potential panics due to unsafe string slicing on non-character boundaries. Additionally, the reviewer raised concerns regarding performance regressions caused by introducing heap allocations in the logging hot path.

Comment thread src/fmt/mod.rs Outdated
Comment thread src/fmt/mod.rs Outdated
Comment thread src/fmt/mod.rs
@Sn0wo2

Sn0wo2 commented May 21, 2026

Copy link
Copy Markdown
Owner Author

@sourcery-ai review

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - 我发现了 5 个问题,并提供了一些高层次的反馈:

  • 新的 Formatter::format_path 实现比较难以理解(多次分配、先反转再切分的逻辑等);建议把截断逻辑抽取到一个有文档的小辅助函数中,并简化目录尾部计算逻辑,这样预期行为会更清晰、更容易推理。
  • color::style::Styled::from 中,ANSI16 映射每次转换都会新建一个 16 元素数组并调用一次 Palette::default();你可以通过复用一个静态的调色板和颜色表(或者一个小的辅助函数),避免每次调用都重新构建它们,从而降低开销。
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The new `Formatter::format_path` implementation is quite hard to follow (multiple allocations, reverse-then-split logic, etc.); consider extracting the truncation logic into a small, documented helper and simplifying the directory tail calculation so the intended behavior is clearer and easier to reason about.
- In `color::style::Styled::from`, the ANSI16 mapping allocates a fresh 16-element array and calls `Palette::default()` on every conversion; you could reduce overhead by reusing a static palette and color table (or a small helper function) instead of rebuilding them per call.

## Individual Comments

### Comment 1
<location path="src/builder.rs" line_range="18-27" />
<code_context>
-    }
-}
+pub(crate) type BoxedLayer = Box<dyn tracing_subscriber::Layer<Registry> + Send + Sync>;
+pub(crate) type InnerSubscriber = Layered<Vec<BoxedLayer>, Registry>;
+pub(crate) type ReloadHandle =
+    tracing_subscriber::reload::Handle<tracing_subscriber::EnvFilter, InnerSubscriber>;
</code_context>
<issue_to_address>
**issue (bug_risk):** 使用 `Vec<BoxedLayer>` 作为 `Registry``Layer` 看起来并不安全,而且很可能会因为孤儿规则(orphan rules)而无法通过编译。

`InnerSubscriber` 假设 `Vec<BoxedLayer>` 实现了 `Layer<Registry>`,从而让 `Registry::default().with(layers)` 能通过类型检查,但你不能在这里添加 `impl Layer<Registry> for Vec<BoxedLayer>`,因为根据孤儿规则,`Layer``Vec` 都是外部类型。这意味着在当前 crate 中不可能让 `Vec<BoxedLayer>` 满足 `Layer<S>` 约束。

如果现在代码是可以编译的,很可能说明 `InnerSubscriber` 已经不再匹配实际的订阅者类型,因此 `reload::Handle<EnvFilter, InnerSubscriber>` 也是不正确的。相反,建议使用一个本地的新类型包装,例如 `struct Layers(Vec<BoxedLayer>); impl<S> Layer<S> for Layers where S: Subscriber + LookupSpan<'_> { … }`,然后在 `.with(...)``InnerSubscriber` 类型别名中都使用 `Layers`。
</issue_to_address>

### Comment 2
<location path="src/fmt/mod.rs" line_range="135-144" />
<code_context>
+    pub(crate) fn format_path(&self, file: &str, line: u32) -> String {
</code_context>
<issue_to_address>
**issue (bug_risk):** 新的路径压缩逻辑比较难以推理,而且很可能没有产生你期望的目录前缀。

新的实现会对 `[..last_slash]` 做反转,然后在反转后的字符串上按 `'/'` 切分。由于目录分隔符也跟着字符一起被反转,得到的片段不再和原始路径组件一一对应,很难真正表示“仍然能容纳的尾部目录名”。它还在一个热点路径上创建了若干中间 `String`,并做了额外的逐 `char` 处理。

更简单、更安全的方法是在概念上保持旧行为:在正向路径字节上操作,从左侧裁剪直到 `len <= max_width`,如果发生了裁剪就前置 ``。如果你想保留文件名加部分目录上下文,可以:
1. 构造 `file_with_line = format!("{filename}:{line}")`2. 如果长度合适,就像现在一样右对齐。
3. 如果不合适,就计算在保证 `format!("{tail}:{line}")` 能放得下的前提下,可以保留 `path_str` 末尾的多少内容,或者退回到 `` + 后缀的逻辑——整个过程不需要反转字符串。

这样逻辑更容易理解,也可以避免被破坏的目录名称。
</issue_to_address>

### Comment 3
<location path="src/fmt/test.rs" line_range="9" />
<code_context>
 fn formatter_defaults() {
     let fmt = Formatter::new();
     assert_eq!(fmt.time_format, "%H:%M:%S");
-    assert_eq!(fmt.path_width, BUILD_PATH_WIDTH);
+    assert_eq!(fmt.path_width, DEFAULT_PATH_WIDTH);
     assert!(fmt.show_path);
     assert!(fmt.show_spans);
</code_context>
<issue_to_address>
**suggestion (testing):** 为新的 `Formatter::format_path` 截断和规范化逻辑添加有针对性的测试。

当前新的 `Formatter::format_path` 逻辑(基于字符串的截断、`src/``src\` 的处理、省略号以及目录尾部启发式规则)除了这个默认宽度检查外,没有被覆盖到。

请添加一些聚焦的测试,用来:
- 覆盖短路径(无需截断,右对齐),
- 覆盖会变成相对路径并被截断的长 `src/` 路径,
- 验证 Windows 风格路径被规范为 `/`- 覆盖 `file_with_line.len() + 2 <= max_width` 这个分支,
- 覆盖最终带前导省略号的分支。

你可以通过将 `fmt.path_width` 设置为较小值并断言精确的格式化字符串,让这些测试具有确定性。

Suggested implementation:

```rust
fn formatter_defaults() {
    let fmt = Formatter::new();
    assert_eq!(fmt.time_format, "%H:%M:%S");
    assert_eq!(fmt.path_width, DEFAULT_PATH_WIDTH);
    assert!(fmt.show_path);
    assert!(fmt.show_spans);
}

#[test]
fn format_path_short_no_truncation_right_aligned() {
    let mut fmt = Formatter::new();
    // Wide enough that no truncation happens
    fmt.path_width = 40;

    let formatted = fmt.format_path("src/lib.rs", 10);

    // No truncation markers
    assert!(!formatted.contains('…'), "expected no ellipsis: {formatted}");
    assert!(!formatted.starts_with("..."), "expected no ascii ellipsis: {formatted}");

    // Should end with the full file-with-line tail and be right-aligned within the field
    assert!(formatted.ends_with("src/lib.rs:10"), "expected to end with full path: {formatted}");
    assert!(
        formatted.len() <= fmt.path_width,
        "formatted length {} should not exceed path_width {}",
        formatted.len(),
        fmt.path_width
    );
}

#[test]
fn format_path_long_src_path_truncated_relative() {
    let mut fmt = Formatter::new();
    // Force truncation even after making it relative to src/
    fmt.path_width = 24;

    let formatted = fmt.format_path(
        "/home/user/project/src/module/submodule/deeply_nested_file_name.rs",
        42,
    );

    // Should be made relative to src/ and then truncated
    assert!(
        !formatted.contains("/home/user/project/"),
        "expected path to be made relative to src/: {formatted}",
    );
    assert!(
        formatted.contains("deeply_nested_file_name.rs:42") || formatted.contains("file_name.rs:42"),
        "expected file name with line to be present in truncated output: {formatted}",
    );

    // Truncation marker and width bound
    assert!(
        formatted.contains('…') || formatted.starts_with("..."),
        "expected some form of ellipsis/truncation marker: {formatted}",
    );
    assert!(
        formatted.len() <= fmt.path_width,
        "formatted length {} should not exceed path_width {}",
        formatted.len(),
        fmt.path_width
    );
}

#[test]
fn format_path_windows_style_paths_normalized() {
    let mut fmt = Formatter::new();
    fmt.path_width = 40;

    let formatted = fmt.format_path(r"C:\project\src\module\file.rs", 7);

    // Windows separators should be normalized to '/'
    assert!(
        !formatted.contains('\\'),
        "expected backslashes to be normalized out: {formatted}",
    );
    assert!(
        formatted.contains("src/module/file.rs:7"),
        "expected normalized src path with line: {formatted}",
    );
}

#[test]
fn format_path_hits_file_with_line_branch() {
    let mut fmt = Formatter::new();

    // Choose a width such that `file_with_line.len() + 2 <= max_width`
    // so that branch is exercised without needing truncation.
    let file_with_line = "src/main.rs:1";
    let max_width = file_with_line.len() + 2;
    fmt.path_width = max_width as u16;

    let formatted = fmt.format_path("src/main.rs", 1);

    // We expect the full file-with-line to appear without truncation markers
    assert!(
        formatted.contains(file_with_line),
        "expected full file_with_line in output: {formatted}",
    );
    assert!(
        !formatted.contains('…') && !formatted.starts_with("..."),
        "expected no ellipsis for file_with_line branch: {formatted}",
    );
    assert!(
        formatted.len() <= fmt.path_width as usize,
        "formatted length {} should not exceed path_width {}",
        formatted.len(),
        fmt.path_width
    );
}

#[test]
fn format_path_leading_ellipsis_for_very_long_paths() {
    let mut fmt = Formatter::new();

    // Very narrow width to ensure we hit the final leading-ellipsis truncation branch
    fmt.path_width = 16;

    let formatted = fmt.format_path(
        "/very/long/prefix/that/is/not/src/and/should/be/trimmed/file.rs",
        99,
    );

    // Final branch should produce a leading ellipsis and keep just the tail
    assert!(
        formatted.starts_with('…') || formatted.starts_with("..."),
        "expected leading ellipsis for aggressively truncated path: {formatted}",
    );
    assert!(
        formatted.contains("file.rs:99"),
        "expected file name with line to be preserved in final-branch truncation: {formatted}",
    );
    assert!(
        formatted.len() <= fmt.path_width,
        "formatted length {} should not exceed path_width {}",
        formatted.len(),
        fmt.path_width
    );
}

```

These tests assume:
1. `Formatter::format_path(&self, path: &str, line: u32) -> String` exists and is accessible from this module.
2. `Formatter::path_width` is a public or otherwise settable numeric field (here treated as `u16`; adjust the cast or type if it differs).
3. The implementation normalizes `\` to `/`, makes absolute `src/`-containing paths relative to `src/`, and uses either the Unicode ellipsis `` or a `"..."` prefix for truncation.

If the actual behavior differs (e.g., different ellipsis marker, no right-alignment, different relativization rule), update the assertions to match the concrete formatting rules while keeping the test structure (short/no truncation, long `src/` + truncation, Windows normalization, `file_with_line.len() + 2 <= max_width` branch, and final leading-ellipsis branch) the same.
</issue_to_address>

### Comment 4
<location path="src/test.rs" line_range="18-27" />
<code_context>
+fn build_test_guard(level: Level, style: Style) -> (TracingGuard, TestSubscriber) {
</code_context>
<issue_to_address>
**suggestion (testing):** 为新的 `TracingGuard::set_filter` 以及带原始指令(raw directive)的 level/target API 添加测试覆盖。

现有测试覆盖了 `with_style``set_target_level``remove_target_level`,但新的 `TracingGuard::set_filter` API 和更严格的 `Level`/`Filter` 语义目前还没有测试。

请添加一些测试,例如:
- 通过 `build_test_guard(Level::Info, ...)` 构建一个 guard,调用 `set_filter(Filter::from_directive("info,my_crate=debug"))`,并断言 `guard.filter.as_directive()` 匹配该指令且调用返回 `Ok(())`- 调用 `set_level(Level::Warn)` 并断言 `guard.filter.as_directive()` 变为 `"warn"` 且没有错误。
- 在基于原始指令的 `set_filter` 之后调用 `set_target_level("demo", Level::Trace)`,并断言每个 target 的覆盖设置被正确反映且调用成功。

这些测试将有助于锁定新的公共 API 行为,而无需依赖 `EnvFilter` 的内部细节。

Suggested implementation:

```rust
fn build_test_guard(level: Level, style: Style) -> (TracingGuard, TestSubscriber) {
    let filter = Filter::new(level);
    let env_filter =
        tracing_subscriber::EnvFilter::try_new(filter.as_directive()).unwrap_or_default();
    let (env_layer, raw): (_, ReloadHandle) = tracing_subscriber::reload::Layer::new(env_filter);

    let layers: Vec<BoxedLayer> = vec![
        tracing_subscriber::fmt::Layer::default()
            .with_writer(io::sink)
            .boxed(),
    ];

    // NOTE: The rest of `build_test_guard` is assumed to remain unchanged.

}

#[cfg(test)]
mod tracing_guard_filter_tests {
    use super::*;

    #[test]
    fn set_filter_with_raw_directive_updates_guard() {
        // Start from an initial guard with a simple level-based filter.
        let (mut guard, _subscriber) = build_test_guard(Level::Info, Style::default());

        // Apply a raw directive that includes a per-target override.
        let filter = Filter::from_directive("info,my_crate=debug");

        guard
            .set_filter(filter)
            .expect("set_filter with raw directive should succeed");

        // The guard's filter should now match the directive we provided.
        assert_eq!(
            guard.filter.as_directive(),
            "info,my_crate=debug",
            "guard.filter.as_directive() should reflect the raw directive applied via set_filter"
        );
    }

    #[test]
    fn set_level_after_filter_replaces_with_simple_level_directive() {
        let (mut guard, _subscriber) = build_test_guard(Level::Info, Style::default());

        // First, set a more complex filter.
        guard
            .set_filter(Filter::from_directive("info,my_crate=debug"))
            .expect("set_filter should succeed");

        // Now replace it with a simple level using the level API.
        guard
            .set_level(Level::Warn)
            .expect("set_level(Level::Warn) should succeed");

        // After calling set_level, the directive should be a simple level directive.
        assert_eq!(
            guard.filter.as_directive(),
            "warn",
            "set_level should replace the filter with a simple level directive"
        );
    }

    #[test]
    fn set_target_level_after_raw_directive_adds_per_target_override() {
        let (mut guard, _subscriber) = build_test_guard(Level::Info, Style::default());

        guard
            .set_filter(Filter::from_directive("info,my_crate=debug"))
            .expect("initial set_filter should succeed");

        // Add a per-target override after the raw directive has been applied.
        guard
            .set_target_level("demo", Level::Trace)
            .expect("set_target_level should succeed after set_filter");

        let directive = guard.filter.as_directive();

        // The overall directive should still contain the base level and original override.
        assert!(
            directive.contains("info"),
            "directive should still include base level after set_target_level: {directive}"
        );
        assert!(
            directive.contains("my_crate=debug"),
            "directive should retain existing per-target override after set_target_level: {directive}"
        );
        assert!(
            directive.contains("demo=trace"),
            "directive should include the new per-target override added via set_target_level: {directive}"
        );
    }

```

The replacement above assumes:
1. The snippet you shared is the full body of `build_test_guard`. If there is additional code in that function, reinsert it between the `let layers: Vec<BoxedLayer> = vec![ ... ];` line and the closing `}` so the helper continues to build and return a valid `(TracingGuard, TestSubscriber)`.
2. `Style` implements `Default`. If it does not, replace `Style::default()` in the tests with whatever style constructor you use elsewhere in your test suite (for example, `Style::Pretty` or a crate-specific default).
3. `Level` uses `CamelCase` variants (`Level::Info`, `Level::Warn`, `Level::Trace`) as implied by your review comment. If instead you re-export `tracing::Level`, adjust the variants to `Level::INFO`, `Level::WARN`, and `Level::TRACE`.
4. `Filter::from_directive(&str)` and `guard.filter` are available as shown in your comment. If `filter` is not public, replace direct field access with the appropriate accessor (for example, `guard.filter().as_directive()`).
</issue_to_address>

### Comment 5
<location path="src/color/style.rs" line_range="10" />
<code_context>
-    let r = rgb.0;
-    let g = rgb.1;
-    let b = rgb.2;
+#[derive(Clone, Copy, Debug)]
+pub struct Styled {
+    rgb: Rgb,
</code_context>
<issue_to_address>
**issue (complexity):** 建议在 `Styled` 之上重新引入简单的公共辅助函数,并把 ANSI16 表提升为常量,这样既能让 API 意图更清晰,也能让实现更精简。

你可以保留现在新的共享转换逻辑,同时恢复更简单、更具意图表达能力的 API,并降低部分内部复杂度。

### 1. 在 `Styled` 之上重新引入小的辅助函数

暴露和之前相同形状的辅助函数,把 `Styled` + `From<Styled>` 作为内部实现细节。这可以保持调用点简单,避免泄露 `Styled` 这个抽象:

```rust
pub fn rgb_to_owo(rgb: (u8, u8, u8), depth: ColorDepth) -> OwoStyle {
    Styled::new(Rgb(rgb.0, rgb.1, rgb.2), depth).into()
}

pub fn rgb_to_owo_on(r: u8, g: u8, b: u8, depth: ColorDepth) -> OwoStyle {
    Styled::new(Rgb(r, g, b), depth).on().into()
}

/// Dims the foreground by dividing brightness by 4 to ensure high text legibility.
pub fn theme_fg_dimmed(rgb: (u8, u8, u8), depth: ColorDepth) -> OwoStyle {
    Styled::new(Rgb(rgb.0, rgb.1, rgb.2), depth).dimmed().into()
}
```

这样调用点就可以恢复成:

```rust
let style = rgb_to_owo(theme.text, self.color_depth);
// or
let style = rgb_to_owo_on(bg.0, bg.1, bg.2, self.color_depth);
// or
let style = theme_fg_dimmed(theme.text, self.color_depth);
```

同时仍然可以在 `From<Styled>` 内部复用更新后的映射逻辑。

如果不希望 `Styled` 成为公共类型,也可以将其设为 `pub(crate)`,并保留这些辅助函数作为公共 API。

### 2. 避免在每次调用时重建 ANSI16 颜色表

目前 `Ansi16` 分支在每次转换时都会分配一个 16 元素数组。你可以通过将其提升为常量来降低复杂度和开销:

```rust
const ANSI16_TABLE: [AnsiColors; 16] = [
    AnsiColors::Black,
    AnsiColors::Red,
    AnsiColors::Green,
    AnsiColors::Yellow,
    AnsiColors::Blue,
    AnsiColors::Magenta,
    AnsiColors::Cyan,
    AnsiColors::White,
    AnsiColors::BrightBlack,
    AnsiColors::BrightRed,
    AnsiColors::BrightGreen,
    AnsiColors::BrightYellow,
    AnsiColors::BrightBlue,
    AnsiColors::BrightMagenta,
    AnsiColors::BrightCyan,
    AnsiColors::BrightWhite,
];
```

然后:

```rust
ColorDepth::Ansi16 => {
    let idx = rgb_to_ansi(s.as_tuple().into(), Palette::default()) as usize;
    let ansi = ANSI16_TABLE
        .get(idx)
        .unwrap_or(&AnsiColors::White);

    if s.on {
        return style.on_color(ansi);
    }
    style.color(ansi)
}
```

这些改动可以保留所有新的行为(变暗处理、背景色、更新后的映射),同时让公共使用方式更简单、内部实现也更精简。
</issue_to_address>

Sourcery 对开源项目免费 —— 如果你觉得我们的评审有帮助,欢迎分享 ✨
帮我变得更有用!请对每条评论点 👍 或 👎,我会根据反馈改进以后的评审。
Original comment in English

Hey - I've found 5 issues, and left some high level feedback:

  • The new Formatter::format_path implementation is quite hard to follow (multiple allocations, reverse-then-split logic, etc.); consider extracting the truncation logic into a small, documented helper and simplifying the directory tail calculation so the intended behavior is clearer and easier to reason about.
  • In color::style::Styled::from, the ANSI16 mapping allocates a fresh 16-element array and calls Palette::default() on every conversion; you could reduce overhead by reusing a static palette and color table (or a small helper function) instead of rebuilding them per call.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The new `Formatter::format_path` implementation is quite hard to follow (multiple allocations, reverse-then-split logic, etc.); consider extracting the truncation logic into a small, documented helper and simplifying the directory tail calculation so the intended behavior is clearer and easier to reason about.
- In `color::style::Styled::from`, the ANSI16 mapping allocates a fresh 16-element array and calls `Palette::default()` on every conversion; you could reduce overhead by reusing a static palette and color table (or a small helper function) instead of rebuilding them per call.

## Individual Comments

### Comment 1
<location path="src/builder.rs" line_range="18-27" />
<code_context>
-    }
-}
+pub(crate) type BoxedLayer = Box<dyn tracing_subscriber::Layer<Registry> + Send + Sync>;
+pub(crate) type InnerSubscriber = Layered<Vec<BoxedLayer>, Registry>;
+pub(crate) type ReloadHandle =
+    tracing_subscriber::reload::Handle<tracing_subscriber::EnvFilter, InnerSubscriber>;
</code_context>
<issue_to_address>
**issue (bug_risk):** Using `Vec<BoxedLayer>` as a `Layer` for `Registry` appears unsound and likely won’t compile due to orphan rules.

`InnerSubscriber` assumes `Vec<BoxedLayer>` implements `Layer<Registry>` so that `Registry::default().with(layers)` type-checks, but you can’t add `impl Layer<Registry> for Vec<BoxedLayer>` here due to orphan rules (both `Layer` and `Vec` are foreign). That makes it impossible for `Vec<BoxedLayer>` to satisfy the `Layer<S>` bound in this crate.

If this currently compiles, it likely means `InnerSubscriber` no longer matches the actual subscriber type, so `reload::Handle<EnvFilter, InnerSubscriber>` would be incorrect. Instead, consider a local newtype wrapper, e.g. `struct Layers(Vec<BoxedLayer>); impl<S> Layer<S> for Layers where S: Subscriber + LookupSpan<'_> { … }`, and use `Layers` both in `.with(...)` and in the `InnerSubscriber` alias.
</issue_to_address>

### Comment 2
<location path="src/fmt/mod.rs" line_range="135-144" />
<code_context>
+    pub(crate) fn format_path(&self, file: &str, line: u32) -> String {
</code_context>
<issue_to_address>
**issue (bug_risk):** The new path-compaction logic is hard to reason about and likely not producing the intended directory prefix.

The new implementation reverses `[..last_slash]` and then splits that reversed string on `'/'`. Because the directory separators are reversed along with the characters, the resulting segments don’t map cleanly to original path components and are unlikely to represent “the trailing directory name that still fits.” It also creates several intermediate `String`s and does extra `char`-level work on a hot path.

A simpler, safer approach is to keep the old behaviour conceptually: operate on the forward path bytes, trim from the left until `len <= max_width`, and prepend `` if trimming occurred. If you want to preserve filename + some directory context:
1. Build `file_with_line = format!("{filename}:{line}")`.
2. If it fits, right-align as you do now.
3. If not, compute how much of the *end* of `path_str` can be kept so that `format!("{tail}:{line}")` fits, or fall back to `` + suffix logic—without reversing the string.

This keeps the logic easier to reason about and avoids mangled directory names.
</issue_to_address>

### Comment 3
<location path="src/fmt/test.rs" line_range="9" />
<code_context>
 fn formatter_defaults() {
     let fmt = Formatter::new();
     assert_eq!(fmt.time_format, "%H:%M:%S");
-    assert_eq!(fmt.path_width, BUILD_PATH_WIDTH);
+    assert_eq!(fmt.path_width, DEFAULT_PATH_WIDTH);
     assert!(fmt.show_path);
     assert!(fmt.show_spans);
</code_context>
<issue_to_address>
**suggestion (testing):** Add focused tests for the new `Formatter::format_path` truncation and normalization logic.

The new `Formatter::format_path` logic (string-based truncation, `src/` vs `src\` handling, ellipsis, and directory-tail heuristics) isn’t covered beyond this default width check.

Please add focused tests that:
- Cover short paths (no truncation, right-aligned),
- Cover long `src/` paths that become relative and truncated,
- Verify Windows-style paths are normalized to `/`,
- Hit the `file_with_line.len() + 2 <= max_width` branch,
- Hit the final leading-ellipsis branch.

You can make these deterministic by setting `fmt.path_width` to a small value and asserting exact formatted strings.

Suggested implementation:

```rust
fn formatter_defaults() {
    let fmt = Formatter::new();
    assert_eq!(fmt.time_format, "%H:%M:%S");
    assert_eq!(fmt.path_width, DEFAULT_PATH_WIDTH);
    assert!(fmt.show_path);
    assert!(fmt.show_spans);
}

#[test]
fn format_path_short_no_truncation_right_aligned() {
    let mut fmt = Formatter::new();
    // Wide enough that no truncation happens
    fmt.path_width = 40;

    let formatted = fmt.format_path("src/lib.rs", 10);

    // No truncation markers
    assert!(!formatted.contains('…'), "expected no ellipsis: {formatted}");
    assert!(!formatted.starts_with("..."), "expected no ascii ellipsis: {formatted}");

    // Should end with the full file-with-line tail and be right-aligned within the field
    assert!(formatted.ends_with("src/lib.rs:10"), "expected to end with full path: {formatted}");
    assert!(
        formatted.len() <= fmt.path_width,
        "formatted length {} should not exceed path_width {}",
        formatted.len(),
        fmt.path_width
    );
}

#[test]
fn format_path_long_src_path_truncated_relative() {
    let mut fmt = Formatter::new();
    // Force truncation even after making it relative to src/
    fmt.path_width = 24;

    let formatted = fmt.format_path(
        "/home/user/project/src/module/submodule/deeply_nested_file_name.rs",
        42,
    );

    // Should be made relative to src/ and then truncated
    assert!(
        !formatted.contains("/home/user/project/"),
        "expected path to be made relative to src/: {formatted}",
    );
    assert!(
        formatted.contains("deeply_nested_file_name.rs:42") || formatted.contains("file_name.rs:42"),
        "expected file name with line to be present in truncated output: {formatted}",
    );

    // Truncation marker and width bound
    assert!(
        formatted.contains('…') || formatted.starts_with("..."),
        "expected some form of ellipsis/truncation marker: {formatted}",
    );
    assert!(
        formatted.len() <= fmt.path_width,
        "formatted length {} should not exceed path_width {}",
        formatted.len(),
        fmt.path_width
    );
}

#[test]
fn format_path_windows_style_paths_normalized() {
    let mut fmt = Formatter::new();
    fmt.path_width = 40;

    let formatted = fmt.format_path(r"C:\project\src\module\file.rs", 7);

    // Windows separators should be normalized to '/'
    assert!(
        !formatted.contains('\\'),
        "expected backslashes to be normalized out: {formatted}",
    );
    assert!(
        formatted.contains("src/module/file.rs:7"),
        "expected normalized src path with line: {formatted}",
    );
}

#[test]
fn format_path_hits_file_with_line_branch() {
    let mut fmt = Formatter::new();

    // Choose a width such that `file_with_line.len() + 2 <= max_width`
    // so that branch is exercised without needing truncation.
    let file_with_line = "src/main.rs:1";
    let max_width = file_with_line.len() + 2;
    fmt.path_width = max_width as u16;

    let formatted = fmt.format_path("src/main.rs", 1);

    // We expect the full file-with-line to appear without truncation markers
    assert!(
        formatted.contains(file_with_line),
        "expected full file_with_line in output: {formatted}",
    );
    assert!(
        !formatted.contains('…') && !formatted.starts_with("..."),
        "expected no ellipsis for file_with_line branch: {formatted}",
    );
    assert!(
        formatted.len() <= fmt.path_width as usize,
        "formatted length {} should not exceed path_width {}",
        formatted.len(),
        fmt.path_width
    );
}

#[test]
fn format_path_leading_ellipsis_for_very_long_paths() {
    let mut fmt = Formatter::new();

    // Very narrow width to ensure we hit the final leading-ellipsis truncation branch
    fmt.path_width = 16;

    let formatted = fmt.format_path(
        "/very/long/prefix/that/is/not/src/and/should/be/trimmed/file.rs",
        99,
    );

    // Final branch should produce a leading ellipsis and keep just the tail
    assert!(
        formatted.starts_with('…') || formatted.starts_with("..."),
        "expected leading ellipsis for aggressively truncated path: {formatted}",
    );
    assert!(
        formatted.contains("file.rs:99"),
        "expected file name with line to be preserved in final-branch truncation: {formatted}",
    );
    assert!(
        formatted.len() <= fmt.path_width,
        "formatted length {} should not exceed path_width {}",
        formatted.len(),
        fmt.path_width
    );
}

```

These tests assume:
1. `Formatter::format_path(&self, path: &str, line: u32) -> String` exists and is accessible from this module.
2. `Formatter::path_width` is a public or otherwise settable numeric field (here treated as `u16`; adjust the cast or type if it differs).
3. The implementation normalizes `\` to `/`, makes absolute `src/`-containing paths relative to `src/`, and uses either the Unicode ellipsis `` or a `"..."` prefix for truncation.

If the actual behavior differs (e.g., different ellipsis marker, no right-alignment, different relativization rule), update the assertions to match the concrete formatting rules while keeping the test structure (short/no truncation, long `src/` + truncation, Windows normalization, `file_with_line.len() + 2 <= max_width` branch, and final leading-ellipsis branch) the same.
</issue_to_address>

### Comment 4
<location path="src/test.rs" line_range="18-27" />
<code_context>
+fn build_test_guard(level: Level, style: Style) -> (TracingGuard, TestSubscriber) {
</code_context>
<issue_to_address>
**suggestion (testing):** Add tests that exercise the new `TracingGuard::set_filter` and level/target APIs with raw directives.

The existing tests cover `with_style`, `set_target_level`, and `remove_target_level`, but the new `TracingGuard::set_filter` API and tighter `Level`/`Filter` semantics are untested.

Please add tests that, for example:
- Build a guard via `build_test_guard(Level::Info, ...)`, call `set_filter(Filter::from_directive("info,my_crate=debug"))`, and assert `guard.filter.as_directive()` matches and the call returns `Ok(())`.
- Call `set_level(Level::Warn)` and assert `guard.filter.as_directive()` becomes `"warn"` without error.
- Call `set_target_level("demo", Level::Trace)` after a `set_filter` based on a raw directive, asserting per-target overrides are correctly reflected and succeed.

These will help lock in the new public API behavior without depending on `EnvFilter` internals.

Suggested implementation:

```rust
fn build_test_guard(level: Level, style: Style) -> (TracingGuard, TestSubscriber) {
    let filter = Filter::new(level);
    let env_filter =
        tracing_subscriber::EnvFilter::try_new(filter.as_directive()).unwrap_or_default();
    let (env_layer, raw): (_, ReloadHandle) = tracing_subscriber::reload::Layer::new(env_filter);

    let layers: Vec<BoxedLayer> = vec![
        tracing_subscriber::fmt::Layer::default()
            .with_writer(io::sink)
            .boxed(),
    ];

    // NOTE: The rest of `build_test_guard` is assumed to remain unchanged.

}

#[cfg(test)]
mod tracing_guard_filter_tests {
    use super::*;

    #[test]
    fn set_filter_with_raw_directive_updates_guard() {
        // Start from an initial guard with a simple level-based filter.
        let (mut guard, _subscriber) = build_test_guard(Level::Info, Style::default());

        // Apply a raw directive that includes a per-target override.
        let filter = Filter::from_directive("info,my_crate=debug");

        guard
            .set_filter(filter)
            .expect("set_filter with raw directive should succeed");

        // The guard's filter should now match the directive we provided.
        assert_eq!(
            guard.filter.as_directive(),
            "info,my_crate=debug",
            "guard.filter.as_directive() should reflect the raw directive applied via set_filter"
        );
    }

    #[test]
    fn set_level_after_filter_replaces_with_simple_level_directive() {
        let (mut guard, _subscriber) = build_test_guard(Level::Info, Style::default());

        // First, set a more complex filter.
        guard
            .set_filter(Filter::from_directive("info,my_crate=debug"))
            .expect("set_filter should succeed");

        // Now replace it with a simple level using the level API.
        guard
            .set_level(Level::Warn)
            .expect("set_level(Level::Warn) should succeed");

        // After calling set_level, the directive should be a simple level directive.
        assert_eq!(
            guard.filter.as_directive(),
            "warn",
            "set_level should replace the filter with a simple level directive"
        );
    }

    #[test]
    fn set_target_level_after_raw_directive_adds_per_target_override() {
        let (mut guard, _subscriber) = build_test_guard(Level::Info, Style::default());

        guard
            .set_filter(Filter::from_directive("info,my_crate=debug"))
            .expect("initial set_filter should succeed");

        // Add a per-target override after the raw directive has been applied.
        guard
            .set_target_level("demo", Level::Trace)
            .expect("set_target_level should succeed after set_filter");

        let directive = guard.filter.as_directive();

        // The overall directive should still contain the base level and original override.
        assert!(
            directive.contains("info"),
            "directive should still include base level after set_target_level: {directive}"
        );
        assert!(
            directive.contains("my_crate=debug"),
            "directive should retain existing per-target override after set_target_level: {directive}"
        );
        assert!(
            directive.contains("demo=trace"),
            "directive should include the new per-target override added via set_target_level: {directive}"
        );
    }

```

The replacement above assumes:
1. The snippet you shared is the full body of `build_test_guard`. If there is additional code in that function, reinsert it between the `let layers: Vec<BoxedLayer> = vec![ ... ];` line and the closing `}` so the helper continues to build and return a valid `(TracingGuard, TestSubscriber)`.
2. `Style` implements `Default`. If it does not, replace `Style::default()` in the tests with whatever style constructor you use elsewhere in your test suite (for example, `Style::Pretty` or a crate-specific default).
3. `Level` uses `CamelCase` variants (`Level::Info`, `Level::Warn`, `Level::Trace`) as implied by your review comment. If instead you re-export `tracing::Level`, adjust the variants to `Level::INFO`, `Level::WARN`, and `Level::TRACE`.
4. `Filter::from_directive(&str)` and `guard.filter` are available as shown in your comment. If `filter` is not public, replace direct field access with the appropriate accessor (for example, `guard.filter().as_directive()`).
</issue_to_address>

### Comment 5
<location path="src/color/style.rs" line_range="10" />
<code_context>
-    let r = rgb.0;
-    let g = rgb.1;
-    let b = rgb.2;
+#[derive(Clone, Copy, Debug)]
+pub struct Styled {
+    rgb: Rgb,
</code_context>
<issue_to_address>
**issue (complexity):** Consider reintroducing simple public helper functions over `Styled` and hoisting the ANSI16 table into a constant to keep the API intention‑revealing and the implementation leaner.

You can keep the new shared conversion logic while restoring the simpler, intention‑revealing API and reducing some internal complexity.

### 1. Reintroduce small helper functions on top of `Styled`

Expose the same shape of helpers as before and keep `Styled` + `From<Styled>` as an internal detail. This keeps call sites simple and avoids leaking the `Styled` abstraction:

```rust
pub fn rgb_to_owo(rgb: (u8, u8, u8), depth: ColorDepth) -> OwoStyle {
    Styled::new(Rgb(rgb.0, rgb.1, rgb.2), depth).into()
}

pub fn rgb_to_owo_on(r: u8, g: u8, b: u8, depth: ColorDepth) -> OwoStyle {
    Styled::new(Rgb(r, g, b), depth).on().into()
}

/// Dims the foreground by dividing brightness by 4 to ensure high text legibility.
pub fn theme_fg_dimmed(rgb: (u8, u8, u8), depth: ColorDepth) -> OwoStyle {
    Styled::new(Rgb(rgb.0, rgb.1, rgb.2), depth).dimmed().into()
}
```

Call sites then go back to:

```rust
let style = rgb_to_owo(theme.text, self.color_depth);
// or
let style = rgb_to_owo_on(bg.0, bg.1, bg.2, self.color_depth);
// or
let style = theme_fg_dimmed(theme.text, self.color_depth);
```

while still benefiting from the updated mapping logic inside `From<Styled>`.

If `Styled` is not meant to be public, you can also make it `pub(crate)` and keep these helpers as the public API.

### 2. Avoid rebuilding the ANSI16 color table on every call

The `Ansi16` arm currently allocates a 16‑element array on every conversion. You can reduce both complexity and overhead by hoisting it to a constant:

```rust
const ANSI16_TABLE: [AnsiColors; 16] = [
    AnsiColors::Black,
    AnsiColors::Red,
    AnsiColors::Green,
    AnsiColors::Yellow,
    AnsiColors::Blue,
    AnsiColors::Magenta,
    AnsiColors::Cyan,
    AnsiColors::White,
    AnsiColors::BrightBlack,
    AnsiColors::BrightRed,
    AnsiColors::BrightGreen,
    AnsiColors::BrightYellow,
    AnsiColors::BrightBlue,
    AnsiColors::BrightMagenta,
    AnsiColors::BrightCyan,
    AnsiColors::BrightWhite,
];
```

and then:

```rust
ColorDepth::Ansi16 => {
    let idx = rgb_to_ansi(s.as_tuple().into(), Palette::default()) as usize;
    let ansi = ANSI16_TABLE
        .get(idx)
        .unwrap_or(&AnsiColors::White);

    if s.on {
        return style.on_color(ansi);
    }
    style.color(ansi)
}
```

These changes preserve all new behavior (dimming, background, updated mapping) while making the public usage simpler and the internal implementation slightly leaner.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread src/builder.rs
Comment thread src/fmt/mod.rs
Comment thread src/fmt/test.rs
Comment thread src/test.rs
Comment thread src/color/style.rs
@Sn0wo2 Sn0wo2 marked this pull request as ready for review May 21, 2026 01:01
@Sn0wo2

Sn0wo2 commented May 21, 2026

Copy link
Copy Markdown
Owner Author

/gemini review

@Sn0wo2

Sn0wo2 commented May 21, 2026

Copy link
Copy Markdown
Owner Author

@sourcery-ai review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request refactors the library's configuration and initialization API, replacing the Level field with a more robust Filter system and simplifying the subscriber layer management. It also reduces external dependencies and refactors the path formatting logic. Review feedback identifies a bug in how source paths are stripped when multiple 'src/' directories exist and points out an alignment issue in the path formatter that breaks log column consistency. A cleanup of redundant variables in the initialization logic was also suggested.

Comment thread src/fmt/mod.rs
Comment thread src/fmt/mod.rs Outdated
Comment thread src/builder.rs Outdated

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - 我发现了 1 个问题,并且有一些整体性的反馈:

  • 新的 Formatter::format_path 逻辑依赖通过在字符串中按 "src/" / "src\\" 分割并手动切片,这在非标准目录结构或存在多个 src 片段时会比较脆弱;建议改用基于 Path / Path::strip_prefix 的规范化方式,这样前缀剥离和分隔符规范化会更不易出错、也更具可移植性。
  • acta-build::walk_src_max_width 中,目前对 strip_prefix 的处理是对字符串化后的路径使用 find(strip_prefix);如果该前缀出现多次(或分隔符混用),可能会导致路径被错误地剥离——在可能的情况下使用 Path::strip_prefix,仅在必要时回退到字符串操作,可以让计算出来的宽度在不同平台上更加准确。
给 AI Agent 的提示
Please address the comments from this code review:

## Overall Comments
- The new `Formatter::format_path` logic relies on string splitting on `"src/"` / `"src\\"` and manual slicing, which can be brittle for non-standard layouts or multiple `src` segments; consider switching to `Path` / `Path::strip_prefix`-based normalization so prefix-stripping and separator normalization are less error-prone and more portable.
- In `acta-build::walk_src_max_width`, the `strip_prefix` handling currently uses `find(strip_prefix)` on a stringified path; if the prefix appears more than once (or with mixed separators), this may mis-strip paths—using `Path::strip_prefix` where possible and only falling back to string operations would make the computed width more accurate across platforms.

## Individual Comments

### Comment 1
<location path="src/fmt/test.rs" line_range="145-152" />
<code_context>
+}
+
+#[test]
+fn format_path_leading_ellipsis_for_very_long_path() {
+    let mut fmt = Formatter::new();
+    // Narrow enough that file_with_line alone exceeds width,
+    // forcing the leading-ellipsis fallback branch.
+    fmt.path_width = 11;
+    let result = fmt.format_path("/very/long/prefix/deep/nested/dirs/file.rs", 99);
+    assert!(result.starts_with('\u{2026}'), "expected leading ellipsis: {result}");
+    assert!(result.contains("file.rs:99"), "expected filename preserved: {result}");
+}
+
</code_context>
<issue_to_address>
**suggestion (testing):** Add a test covering multi-byte (Unicode) paths to exercise the char-boundary-safe truncation logic in `format_path`

Given `format_path` now enforces UTF-8 char boundaries, please add a test with a multi-byte path segment (e.g. `"src/模块/文件.rs"`) and a narrow `path_width` that forces truncation. The test should verify the output remains valid UTF-8 (no split characters around the ellipsis) and still preserves the `file.rs:line` suffix. This helps catch off-by-one errors in the boundary-adjustment loop.
</issue_to_address>

Sourcery 对开源项目是免费的 —— 如果你觉得这些 Review 有帮助,欢迎分享 ✨
帮我变得更有用!请在每条评论上点 👍 或 👎,我会根据你的反馈来改进后续的代码审查。
Original comment in English

Hey - I've found 1 issue, and left some high level feedback:

  • The new Formatter::format_path logic relies on string splitting on "src/" / "src\\" and manual slicing, which can be brittle for non-standard layouts or multiple src segments; consider switching to Path / Path::strip_prefix-based normalization so prefix-stripping and separator normalization are less error-prone and more portable.
  • In acta-build::walk_src_max_width, the strip_prefix handling currently uses find(strip_prefix) on a stringified path; if the prefix appears more than once (or with mixed separators), this may mis-strip paths—using Path::strip_prefix where possible and only falling back to string operations would make the computed width more accurate across platforms.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The new `Formatter::format_path` logic relies on string splitting on `"src/"` / `"src\\"` and manual slicing, which can be brittle for non-standard layouts or multiple `src` segments; consider switching to `Path` / `Path::strip_prefix`-based normalization so prefix-stripping and separator normalization are less error-prone and more portable.
- In `acta-build::walk_src_max_width`, the `strip_prefix` handling currently uses `find(strip_prefix)` on a stringified path; if the prefix appears more than once (or with mixed separators), this may mis-strip paths—using `Path::strip_prefix` where possible and only falling back to string operations would make the computed width more accurate across platforms.

## Individual Comments

### Comment 1
<location path="src/fmt/test.rs" line_range="145-152" />
<code_context>
+}
+
+#[test]
+fn format_path_leading_ellipsis_for_very_long_path() {
+    let mut fmt = Formatter::new();
+    // Narrow enough that file_with_line alone exceeds width,
+    // forcing the leading-ellipsis fallback branch.
+    fmt.path_width = 11;
+    let result = fmt.format_path("/very/long/prefix/deep/nested/dirs/file.rs", 99);
+    assert!(result.starts_with('\u{2026}'), "expected leading ellipsis: {result}");
+    assert!(result.contains("file.rs:99"), "expected filename preserved: {result}");
+}
+
</code_context>
<issue_to_address>
**suggestion (testing):** Add a test covering multi-byte (Unicode) paths to exercise the char-boundary-safe truncation logic in `format_path`

Given `format_path` now enforces UTF-8 char boundaries, please add a test with a multi-byte path segment (e.g. `"src/模块/文件.rs"`) and a narrow `path_width` that forces truncation. The test should verify the output remains valid UTF-8 (no split characters around the ellipsis) and still preserves the `file.rs:line` suffix. This helps catch off-by-one errors in the boundary-adjustment loop.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread src/fmt/test.rs Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 9

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/builder.rs (1)

362-379: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

失败的过滤器更新会把 TracingGuard 留在未提交状态。

Line 367 先修改了 self.filter,Line 377-378 才去构造并 reload EnvFilter。如果 target 生成了无效 directive,这个方法虽然返回 Err,但 self.filter 已经变成新值了,之后的 Debugremove_target_level 和后续更新都会基于一个并未真正生效的过滤器状态。先在临时 Filter 上验证成功后再回写会更稳妥。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/builder.rs` around lines 362 - 379, The current flow mutates self.filter
(in set_target_level and remove_target_level) before attempting to construct and
reload an EnvFilter in apply_current_filter, leaving self.filter changed even if
EnvFilter::try_new fails; instead, perform the mutation on a temporary Filter
copy, validate by calling
tracing_subscriber::EnvFilter::try_new(temp.as_directive()) (or call
apply_current_filter-like validation) and only when that succeeds assign the
validated temp back to self.filter and then update the raw guard (i.e., change
set_target_level and remove_target_level to operate on a cloned filter, validate
the directive, then commit the clone to self.filter and call the existing reload
logic).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@build.rs`:
- Around line 7-9: The write failure to the build artifact is currently only
logged as a warning; change the branch handling the std::fs::write(&path,
width.to_string()) Err(e) case to fail the build immediately (e.g., panic! or
call std::process::exit with non-zero) so the build stops on write errors;
update the error handling around std::fs::write (referencing path, width, and
the Err(e) match arm) to include the error details in the failure message so the
build aborts with a clear diagnostic.

In `@crates/acta-build/src/lib.rs`:
- Around line 60-61: The current code uses display.find(strip_prefix) which
matches the first occurrence anywhere instead of only at the start; replace this
with prefix semantics by using display.strip_prefix(strip_prefix) (or
starts_with + slicing) so you only strip when strip_prefix is at the beginning.
Concretely, change the .find(strip_prefix).map_or(display.len(), |i| display[i +
strip_prefix.len()..].len()) logic to use
display.strip_prefix(strip_prefix).map_or(display.len(), |s| s.len()) so the
computed length is based on a true prefix removal of strip_prefix.

In `@README.md`:
- Around line 112-115: Add a single blank line between the table ending with
"`Format::Json(LayerConfig)`" and the following "## File logging" heading so the
table and heading are separated (this prevents MD058/MD022 warnings); simply
insert one empty line after the table row for Format::Json.

In `@src/builder.rs`:
- Around line 141-143: build_layer currently lets WriterTarget::File be routed
to std::io::sink() via build_layer_with while remaining pub, causing callers who
use build_layer directly to silently drop file logs; change the API so callers
can't silently lose logs — either make build_layer return Result<BoxedLayer,
Error> and propagate an error when WriterTarget::File would use sink, or split
file-target construction into a separate non-public helper and keep build_layer
public only for safe targets; update build_layer, build_layer_with, and any code
that constructs WriterTarget::File to use the new error-returning or separate
constructor and ensure no path returns a BoxedLayer that wraps std::io::sink for
file targets.
- Around line 270-272: Current code creates a single shared ArcSwap
(shared_style) and passes it into build_formatter for multiple non-file writers,
so each writer's call to with_style_config(writer.style) overwrites the same
ArcSwap and later writers' styles win. Fix by giving each non-file writer its
own ArcSwap initialized from that writer's style instead of reusing
shared_style: inside the loop that constructs writers (the code that checks for
file vs non-file writers and calls build_formatter(..., shared_handle)), create
a new Arc::new(arc_swap::ArcSwap::new(Arc::new(writer.style))) per writer and
pass that per-writer ArcSwap into build_formatter; remove or stop using the
single shared_style for non-file writers so styles don't clobber each other.
- Around line 277-279: The current implementation in init stores only a single
file_guard and log_path (variables file_guard and log_path) and overwrites them
each time a WriterTarget::File is encountered, losing previous guards and paths;
either reject multiple WriterTarget::File entries in init (validate targets and
return an error if more than one file target is present) or change file_guard
and log_path to collections (e.g., Vec or HashMap) and push/store a guard/path
for each WriterTarget::File encountered and adjust any uses of log_path() and
the guard drop semantics accordingly so all file writers are retained; update
code paths that assume a single file (references around handling of file_guard,
log_path, and log_path()) to iterate over the collection.

In `@src/color/style.rs`:
- Around line 76-78: The code casts the anstyle::AnsiColor returned by
rgb_to_ansi to usize and indexes ANSI16_TABLE (ColorDepth::Ansi16 branch), which
relies on the enum's discriminant values and can break if upstream changes;
replace that fragile indexing with an explicit exhaustive mapping from each
anstyle::AnsiColor variant to the corresponding table entry (or to the desired
AnsiColors value) — locate where rgb_to_ansi(...) is used and instead of "as
usize" + ANSI16_TABLE.get(...).copied().unwrap_or(AnsiColors::White) implement a
match on the rgb_to_ansi(...) result (covering all AnsiColor variants) that
returns the correct entry and remove the silent White fallback so behavior is
explicit.

In `@src/test.rs`:
- Around line 137-189: Update the tests to assert actual filtering behavior via
the returned subscriber instead of only inspecting guard.filter; for example, in
set_filter_with_raw_directive_updates_guard (and/or
set_target_level_after_raw_directive_adds_per_target_override) use the
subscriber returned from build_test_guard to construct Metadata for different
targets/levels and call the subscriber's enabled check to assert that the base
level ("info") blocks lower levels and that the per-target override
("my_crate=debug" and later "demo=trace") allows the expected levels; ensure you
call build_test_guard(...) to get the subscriber and add assertions that the
subscriber.enabled(...) (or the equivalent API) returns true/false for the
specific target+level combinations that prove the filter is actually enforced at
runtime.

In `@src/writer/test.rs`:
- Around line 1-5: Remove the module-level allow for clippy::unnecessary_map_or
and rewrite the specific Option/Iterator map_or usages in this file to avoid
suppressing future warnings: search for occurrences of Option::map_or or
.map_or(...) in tests within src/writer/test.rs and replace them with more
explicit checks (e.g., use is_some_and for boolean predicates or match/if let to
handle values) so the linter rule can remain enabled; keep the other clippy
allows intact and only remove the clippy::unnecessary_map_or attribute.

---

Outside diff comments:
In `@src/builder.rs`:
- Around line 362-379: The current flow mutates self.filter (in set_target_level
and remove_target_level) before attempting to construct and reload an EnvFilter
in apply_current_filter, leaving self.filter changed even if EnvFilter::try_new
fails; instead, perform the mutation on a temporary Filter copy, validate by
calling tracing_subscriber::EnvFilter::try_new(temp.as_directive()) (or call
apply_current_filter-like validation) and only when that succeeds assign the
validated temp back to self.filter and then update the raw guard (i.e., change
set_target_level and remove_target_level to operate on a cloned filter, validate
the directive, then commit the clone to self.filter and call the existing reload
logic).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 1ea57346-f0d9-4895-93a1-203e971ae813

📥 Commits

Reviewing files that changed from the base of the PR and between 25a4836 and e195c63.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (23)
  • .gitignore
  • Cargo.toml
  • README.md
  • build.rs
  • crates/acta-build/Cargo.toml
  • crates/acta-build/src/lib.rs
  • crates/acta-debug/src/main.rs
  • src/builder.rs
  • src/color/mapping.rs
  • src/color/mod.rs
  • src/color/style.rs
  • src/color/tests.rs
  • src/config/depth.rs
  • src/config/mod.rs
  • src/config/test.rs
  • src/fmt/mod.rs
  • src/fmt/test.rs
  • src/lib.rs
  • src/prelude.rs
  • src/test.rs
  • src/writer/custom.rs
  • src/writer/mod.rs
  • src/writer/test.rs
💤 Files with no reviewable changes (3)
  • src/color/mapping.rs
  • Cargo.toml
  • src/config/depth.rs
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Build / Build
🧰 Additional context used
🪛 markdownlint-cli2 (0.22.1)
README.md

[warning] 114-114: Tables should be surrounded by blank lines

(MD058, blanks-around-tables)


[warning] 115-115: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Above

(MD022, blanks-around-headings)

🔇 Additional comments (7)
src/writer/custom.rs (1)

24-27: LGTM!

Also applies to: 60-60

src/writer/mod.rs (1)

13-13: LGTM!

src/lib.rs (1)

14-18: LGTM!

Also applies to: 26-26

src/prelude.rs (1)

1-1: LGTM!

Also applies to: 4-7, 12-12

crates/acta-debug/src/main.rs (1)

5-7: 本文件改动主要是演示代码与新 API 对齐,当前未发现需要在此处新增的问题评论。

Also applies to: 43-43, 270-276, 291-291, 320-322, 343-362

src/config/mod.rs (2)

570-575: 🏗️ Heavy lift

Request missing inputs to rewrite review comment: No <review_comment> content (and no verification results) was provided, so I can’t rewrite it. Paste the original review comment and any relevant command outputs/logs you want used.


425-428: ⚡ Quick win

Missing review comment context: Paste the original <review_comment>...</review_comment> (and any related diff/snippet it refers to) so I can rewrite it accurately.

Comment thread build.rs
Comment thread crates/acta-build/src/lib.rs Outdated
Comment thread README.md
Comment thread src/builder.rs Outdated
Comment thread src/builder.rs
Comment thread src/builder.rs Outdated
Comment thread src/color/style.rs Outdated
Comment thread src/test.rs
Comment thread src/writer/test.rs Outdated
@Sn0wo2

Sn0wo2 commented May 31, 2026

Copy link
Copy Markdown
Owner Author

@sourcery-ai review

@Sn0wo2

Sn0wo2 commented May 31, 2026

Copy link
Copy Markdown
Owner Author

/gemini review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request refactors the acta logging library to simplify its architecture, improve performance, and clean up the public API. Key changes include replacing the stacked optional layers with a vector of boxed layers, introducing a Filter struct to handle both structured levels and raw EnvFilter directives, refactoring color mapping using a new Styled helper, and migrating internal string representations to CompactString to reduce heap allocations. Additionally, several dependencies like arrayvec, derive_more, and smart-default have been removed. Feedback on these changes highlights a correctness regression in the path truncation logic of Formatter::format_path due to the switch from find to rfind, as well as a performance regression from using heap-allocated String instead of CompactString on the hot path. A refactored version of format_path is suggested to resolve these issues.

Comment thread src/fmt/mod.rs

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - 我发现了 3 个问题,并给出了一些高层次的反馈:

  • 围绕 LevelLabelsFormatter 的公共 API 变更(例如 LevelLabels::DEFAULTFormatter::with_path_width)在 README 示例中没有得到一致体现,按当前写法示例代码将无法编译;要么恢复原来的构建器风格方法/别名,要么更新示例以使用当前的常量和直接的 path_width 字段。
  • WriterTarget::File 的结构已经更改为包裹 FileConfig,但至少有一个 README 示例仍然使用旧的结构形式(WriterTarget::File { path, rotation });请更新这些代码片段,以匹配新的 WriterTarget::File(FileConfig::new(...).with_rotation(...)) API。
  • 现在 TracingGuard 可以管理多个文件写入器,log_path 只返回第一个路径可能会让用户感到意外;可以考虑要么暴露一个返回所有日志路径的方法,要么在文档中说明 log_path 只代表主/第一个文件。
供 AI Agent 使用的提示
Please address the comments from this code review:

## Overall Comments
- The public API changes around `LevelLabels` and `Formatter` (e.g. `LevelLabels::DEFAULT`, `Formatter::with_path_width`) are not reflected consistently in the README examples and will not compile as written; either restore the builder-style methods/aliases or update the examples to use the current constants and direct `path_width` field.
- The `WriterTarget::File` shape has changed to wrap `FileConfig`, but at least one README example still uses the old struct form (`WriterTarget::File { path, rotation }`); update those snippets to match the new `WriterTarget::File(FileConfig::new(...).with_rotation(...))` API.
- Now that `TracingGuard` can manage multiple file writers, `log_path` returning only the first path may be surprising to users; consider either exposing a method that returns all log paths or documenting that `log_path` is the primary/first file only.

## Individual Comments

### Comment 1
<location path="src/fmt/mod.rs" line_range="50-59" />
<code_context>
+///
+/// Returns [`FALLBACK_WIDTH`] (40) if `dir` does not exist or contains no
+/// `.rs` files — safe to call unconditionally from a `build.rs`.
+#[must_use]
+pub fn walk_src_max_width(dir: impl AsRef<Path>, strip_prefix: &str) -> usize {
+    let entries: Vec<_> = WalkDir::new(dir.as_ref())
</code_context>
<issue_to_address>
**issue (bug_risk):** Removing `with_path_width` leaves no way to use the build-time `path_width` and diverges from `acta-build` docs.

`Formatter::with_path_width` was removed while `Formatter` still exposes `path_width`, and `acta-build`’s README still shows `Formatter::new().with_path_width(width)`. This breaks the documented usage and prevents callers from applying the computed width.

Either reintroduce a public way to set `path_width` (e.g., `with_path_width` or equivalent) so `acta-build` remains usable, or update the docs to reflect the new intended usage without that method.
</issue_to_address>

### Comment 2
<location path="src/test.rs" line_range="137-146" />
<code_context>
 }
+
+#[test]
+fn set_filter_with_raw_directive_updates_guard() {
+    let (mut guard, subscriber) = build_test_guard(Level::Info, Style::default());
+    let filter = Filter::from_directive("info,my_crate=debug");
+    guard
+        .set_filter(filter)
+        .expect("set_filter with raw directive should succeed");
+    assert_eq!(
+        guard.filter.as_directive(),
+        "info,my_crate=debug",
+        "guard.filter should reflect the raw directive applied via set_filter"
+    );
+
+    // Verify actual tracing behavior reflects the filter
+    tracing::subscriber::with_default(subscriber, || {
+        assert!(
+            tracing::enabled!(tracing::Level::INFO),
</code_context>
<issue_to_address>
**suggestion (testing):** Add a negative test case for `set_filter` to cover invalid directives.

Since `TracingGuard::set_filter` now propagates `EnvFilter::try_new` errors, add a test that passes an invalid directive (e.g. `"=invalid"` or `"foo::bar::"`) and asserts that `set_filter` returns `Err`, to ensure invalid configurations aren’t silently accepted or misreported.
</issue_to_address>

### Comment 3
<location path="src/color/style.rs" line_range="35" />
<code_context>
-        ColorDepth::Ansi16 => {
-            let c = rgb_to_ansi16(r, g, b);
-            OwoStyle::new().color(DynColors::Ansi(c))
+impl Styled {
+    pub(crate) const fn new(rgb: Rgb, depth: ColorDepth) -> Self {
+        Self {
</code_context>
<issue_to_address>
**issue (complexity):** Consider replacing the public `Styled` builder type with a small set of helper functions that directly return `OwoStyle`, keeping the ANSI16/palette logic internal while simplifying call sites.

You’ve increased call‑site and type complexity without a clear benefit. You can keep all the new behavior (palette + `rgb_to_ansi` + `ANSI16_TABLE`) while hiding the extra knobs behind a small set of helper functions, instead of pushing a builder type into all callers.

### Suggested refactor

Keep the core mapping logic, but move it into an internal helper that returns `OwoStyle` directly, and expose simple, focused functions. You likely don’t need `Styled` at all.

```rust
const ANSI16_TABLE: [AnsiColors; 16] = [ /* unchanged */ ];

fn style_for(rgb: Rgb, depth: ColorDepth, on: bool, dimmed: bool) -> OwoStyle {
    let mut rgb = rgb;
    if dimmed {
        rgb = Rgb(rgb.0 >> 2, rgb.1 >> 2, rgb.2 >> 2);
    }

    let style = OwoStyle::new();
    match depth {
        ColorDepth::TrueColor => {
            if on {
                style.on_truecolor(rgb.0, rgb.1, rgb.2)
            } else {
                style.truecolor(rgb.0, rgb.1, rgb.2)
            }
        }
        ColorDepth::Ansi256 => {
            let ansi =
                XtermColors::from(ansi_colours::ansi256_from_rgb((rgb.0, rgb.1, rgb.2)));
            if on {
                style.on_color(ansi)
            } else {
                style.color(ansi)
            }
        }
        ColorDepth::Ansi16 => {
            let ansi = ANSI16_TABLE
                .get(rgb_to_ansi((rgb.0, rgb.1, rgb.2).into(), Palette::default()) as usize)
                .copied()
                .unwrap_or(AnsiColors::White);
            if on {
                style.on_color(ansi)
            } else {
                style.color(ansi)
            }
        }
        ColorDepth::NoColor => style,
    }
}
```

Then define small helpers that callers actually use:

```rust
pub fn fg_style(rgb: Rgb, depth: ColorDepth) -> OwoStyle {
    style_for(rgb, depth, false, false)
}

pub fn fg_dimmed_style(rgb: Rgb, depth: ColorDepth) -> OwoStyle {
    style_for(rgb, depth, false, true)
}

pub fn bg_style(rgb: Rgb, depth: ColorDepth) -> OwoStyle {
    style_for(rgb, depth, true, false)
}

// add variants as needed, e.g. bg_dimmed_style(...)
```

Call sites then become:

```rust
let style = fg_style(Rgb(r, g, b), depth);
// or
let style = fg_dimmed_style(Rgb(r, g, b), depth);
// or
let style = bg_style(Rgb(r, g, b), depth);
```

This:

- Preserves all new behavior (16‑color mapping, palette logic, dimming, `on` handling).
- Removes the need for the `Styled` type and its `From` impl.
- Shortens and simplifies usage at every call site, which was the main complexity complaint.
</issue_to_address>

Sourcery 对开源项目免费——如果你觉得这些评审有帮助,请考虑分享它们 ✨
帮我变得更有用!请在每条评论上点 👍 或 👎,我会根据这些反馈改进后续评审。
Original comment in English

Hey - I've found 3 issues, and left some high level feedback:

  • The public API changes around LevelLabels and Formatter (e.g. LevelLabels::DEFAULT, Formatter::with_path_width) are not reflected consistently in the README examples and will not compile as written; either restore the builder-style methods/aliases or update the examples to use the current constants and direct path_width field.
  • The WriterTarget::File shape has changed to wrap FileConfig, but at least one README example still uses the old struct form (WriterTarget::File { path, rotation }); update those snippets to match the new WriterTarget::File(FileConfig::new(...).with_rotation(...)) API.
  • Now that TracingGuard can manage multiple file writers, log_path returning only the first path may be surprising to users; consider either exposing a method that returns all log paths or documenting that log_path is the primary/first file only.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The public API changes around `LevelLabels` and `Formatter` (e.g. `LevelLabels::DEFAULT`, `Formatter::with_path_width`) are not reflected consistently in the README examples and will not compile as written; either restore the builder-style methods/aliases or update the examples to use the current constants and direct `path_width` field.
- The `WriterTarget::File` shape has changed to wrap `FileConfig`, but at least one README example still uses the old struct form (`WriterTarget::File { path, rotation }`); update those snippets to match the new `WriterTarget::File(FileConfig::new(...).with_rotation(...))` API.
- Now that `TracingGuard` can manage multiple file writers, `log_path` returning only the first path may be surprising to users; consider either exposing a method that returns all log paths or documenting that `log_path` is the primary/first file only.

## Individual Comments

### Comment 1
<location path="src/fmt/mod.rs" line_range="50-59" />
<code_context>
+///
+/// Returns [`FALLBACK_WIDTH`] (40) if `dir` does not exist or contains no
+/// `.rs` files — safe to call unconditionally from a `build.rs`.
+#[must_use]
+pub fn walk_src_max_width(dir: impl AsRef<Path>, strip_prefix: &str) -> usize {
+    let entries: Vec<_> = WalkDir::new(dir.as_ref())
</code_context>
<issue_to_address>
**issue (bug_risk):** Removing `with_path_width` leaves no way to use the build-time `path_width` and diverges from `acta-build` docs.

`Formatter::with_path_width` was removed while `Formatter` still exposes `path_width`, and `acta-build`’s README still shows `Formatter::new().with_path_width(width)`. This breaks the documented usage and prevents callers from applying the computed width.

Either reintroduce a public way to set `path_width` (e.g., `with_path_width` or equivalent) so `acta-build` remains usable, or update the docs to reflect the new intended usage without that method.
</issue_to_address>

### Comment 2
<location path="src/test.rs" line_range="137-146" />
<code_context>
 }
+
+#[test]
+fn set_filter_with_raw_directive_updates_guard() {
+    let (mut guard, subscriber) = build_test_guard(Level::Info, Style::default());
+    let filter = Filter::from_directive("info,my_crate=debug");
+    guard
+        .set_filter(filter)
+        .expect("set_filter with raw directive should succeed");
+    assert_eq!(
+        guard.filter.as_directive(),
+        "info,my_crate=debug",
+        "guard.filter should reflect the raw directive applied via set_filter"
+    );
+
+    // Verify actual tracing behavior reflects the filter
+    tracing::subscriber::with_default(subscriber, || {
+        assert!(
+            tracing::enabled!(tracing::Level::INFO),
</code_context>
<issue_to_address>
**suggestion (testing):** Add a negative test case for `set_filter` to cover invalid directives.

Since `TracingGuard::set_filter` now propagates `EnvFilter::try_new` errors, add a test that passes an invalid directive (e.g. `"=invalid"` or `"foo::bar::"`) and asserts that `set_filter` returns `Err`, to ensure invalid configurations aren’t silently accepted or misreported.
</issue_to_address>

### Comment 3
<location path="src/color/style.rs" line_range="35" />
<code_context>
-        ColorDepth::Ansi16 => {
-            let c = rgb_to_ansi16(r, g, b);
-            OwoStyle::new().color(DynColors::Ansi(c))
+impl Styled {
+    pub(crate) const fn new(rgb: Rgb, depth: ColorDepth) -> Self {
+        Self {
</code_context>
<issue_to_address>
**issue (complexity):** Consider replacing the public `Styled` builder type with a small set of helper functions that directly return `OwoStyle`, keeping the ANSI16/palette logic internal while simplifying call sites.

You’ve increased call‑site and type complexity without a clear benefit. You can keep all the new behavior (palette + `rgb_to_ansi` + `ANSI16_TABLE`) while hiding the extra knobs behind a small set of helper functions, instead of pushing a builder type into all callers.

### Suggested refactor

Keep the core mapping logic, but move it into an internal helper that returns `OwoStyle` directly, and expose simple, focused functions. You likely don’t need `Styled` at all.

```rust
const ANSI16_TABLE: [AnsiColors; 16] = [ /* unchanged */ ];

fn style_for(rgb: Rgb, depth: ColorDepth, on: bool, dimmed: bool) -> OwoStyle {
    let mut rgb = rgb;
    if dimmed {
        rgb = Rgb(rgb.0 >> 2, rgb.1 >> 2, rgb.2 >> 2);
    }

    let style = OwoStyle::new();
    match depth {
        ColorDepth::TrueColor => {
            if on {
                style.on_truecolor(rgb.0, rgb.1, rgb.2)
            } else {
                style.truecolor(rgb.0, rgb.1, rgb.2)
            }
        }
        ColorDepth::Ansi256 => {
            let ansi =
                XtermColors::from(ansi_colours::ansi256_from_rgb((rgb.0, rgb.1, rgb.2)));
            if on {
                style.on_color(ansi)
            } else {
                style.color(ansi)
            }
        }
        ColorDepth::Ansi16 => {
            let ansi = ANSI16_TABLE
                .get(rgb_to_ansi((rgb.0, rgb.1, rgb.2).into(), Palette::default()) as usize)
                .copied()
                .unwrap_or(AnsiColors::White);
            if on {
                style.on_color(ansi)
            } else {
                style.color(ansi)
            }
        }
        ColorDepth::NoColor => style,
    }
}
```

Then define small helpers that callers actually use:

```rust
pub fn fg_style(rgb: Rgb, depth: ColorDepth) -> OwoStyle {
    style_for(rgb, depth, false, false)
}

pub fn fg_dimmed_style(rgb: Rgb, depth: ColorDepth) -> OwoStyle {
    style_for(rgb, depth, false, true)
}

pub fn bg_style(rgb: Rgb, depth: ColorDepth) -> OwoStyle {
    style_for(rgb, depth, true, false)
}

// add variants as needed, e.g. bg_dimmed_style(...)
```

Call sites then become:

```rust
let style = fg_style(Rgb(r, g, b), depth);
// or
let style = fg_dimmed_style(Rgb(r, g, b), depth);
// or
let style = bg_style(Rgb(r, g, b), depth);
```

This:

- Preserves all new behavior (16‑color mapping, palette logic, dimming, `on` handling).
- Removes the need for the `Styled` type and its `From` impl.
- Shortens and simplifies usage at every call site, which was the main complexity complaint.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread src/fmt/mod.rs
Comment thread src/test.rs
Comment thread src/color/style.rs

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
src/writer/custom.rs (1)

49-63: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

避免 async_writer_forcapacity==0 或无 Tokio runtime 时直接 panic

  • async_writer_for 内部调用 tokio::sync::mpsc::channel(capacity)capacity==0 会 panic;而 AsyncMode::Custom { buffer_size: usize } 在配置层未见 >0 校验,可能在 init/构建阶段直接崩溃。
  • tokio::spawn(...) 也要求当前线程处于 Tokio runtime 上下文;当前没有保护(README 虽提示需要 Tokio runtime),仍建议在这里用 tokio::runtime::Handle::try_current() 获取句柄并在失败时向上传递错误。
  • 建议把 async_writer_for 改成返回 Result<AsyncWriter>:先校验 capacity > 0,再检查 runtime,不满足则返回错误而非 panic。
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/writer/custom.rs` around lines 49 - 63, The async_writer_for function
must not panic on capacity==0 or when there's no Tokio runtime: change
async_writer_for to return Result<AsyncWriter, Error>, validate that capacity >
0 and return a clear error if not, and check for an active Tokio runtime using
tokio::runtime::Handle::try_current() (return an error when none is available)
instead of calling tokio::spawn unconditionally; if a handle exists, use
handle.spawn(...) to start the background task. Keep references to
AsyncWriterTarget and the async_writer_for function name so callers can be
updated to propagate the Result and handle errors accordingly.
README.md (2)

123-135: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

文件日志示例仍在使用已删除的 WriterTarget::File { ... } 变体。

当前 API 已改成 WriterTarget::File(FileConfig),这里的示例现在会直接编译失败。

♻️ 建议修改
 use acta::{
-    init, Format, LayerConfig, Level, Config, Result, Rotation, Writer, WriterTarget,
+    init, Config, FileConfig, Format, LayerConfig, Level, Result, Rotation, Writer, WriterTarget,
 };
-use std::path::PathBuf;
 
 fn main() -> Result<()> {
     let config = Config {
         filter: Level::Info.into(),
         writers: vec![Writer {
             format: Format::Compact(LayerConfig::compact()),
-            target: WriterTarget::File {
-                path: PathBuf::from("logs/app.log"),
-                rotation: Rotation::Rename,
-            },
+            target: WriterTarget::File(
+                FileConfig::new("logs/app.log").with_rotation(Rotation::Rename),
+            ),
             ..Default::default()
         }],
         ..Default::default()
     };
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@README.md` around lines 123 - 135, The README example uses the removed enum
variant WriterTarget::File { ... }; update it to the new shape
WriterTarget::File(FileConfig) by constructing a FileConfig instance and passing
it to WriterTarget::File (e.g., create a FileConfig with the same path and
rotation values); ensure the example imports/uses the FileConfig type alongside
Writer, WriterTarget, Rotation, and PathBuf and replace the old inline struct
literal with the FileConfig construction so the example compiles.

204-210: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

文档示例仍引用已移除的 LevelLabels::DEFAULT 常量。

LevelLabels::DEFAULT 已不存在,当前示例会编译失败。这里应改成现有常量,比如 LevelLabels::LONG

♻️ 建议修改
 let formatter = Formatter::new()
     .with_theme(Theme::tokyo_night())
     .with_icons(Icons::UNICODE)
-    .with_labels(LevelLabels::DEFAULT)
+    .with_labels(LevelLabels::LONG)
     .with_time_format("%H:%M:%S")
     .with_show_path(true)
     .with_show_spans(true);
 let unicode_icons = Icons::UNICODE;
 let short_labels = LevelLabels::SHORT;
-let long_labels = LevelLabels::DEFAULT;
+let long_labels = LevelLabels::LONG;

Also applies to: 271-276

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@README.md` around lines 204 - 210, Update the README examples that reference
the removed constant LevelLabels::DEFAULT to use an existing constant (e.g.,
LevelLabels::LONG); specifically, in the Formatter setup example (the chain
using Formatter::new(), .with_theme(...), .with_icons(...), .with_labels(...),
.with_time_format(...)) replace LevelLabels::DEFAULT with LevelLabels::LONG and
make the same replacement for the other occurrence later in the file so the
example compiles.
♻️ Duplicate comments (2)
src/color/style.rs (1)

74-80: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

不要把 anstyle::AnsiColor 的判别值当成稳定索引。

这里用 as usize 去索引本地表,把颜色映射正确性绑定到了上游枚举布局;一旦上游调整判别值,结果会静默退回到 White。改成对 rgb_to_ansi(...) 结果做穷举匹配会更稳。

For anstyle-lossy 1.1.0 / anstyle, is the discriminant ordering or numeric value of `anstyle::AnsiColor` documented as stable and intended to be used for array indexing via `as usize`?
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/color/style.rs` around lines 74 - 80, The code uses rgb_to_ansi(... ) as
usize to index ANSI16_TABLE, which relies on the numeric discriminants of
anstyle::AnsiColor; instead change the code to match on the rgb_to_ansi(...)
return value and map each AnsiColor variant to the corresponding ANSI16_TABLE
entry (or directly to the desired AnsiColors value), e.g. call
rgb_to_ansi((s.rgb.0, s.rgb.1, s.rgb.2).into(), Palette::default()) and then
match its variants to select the proper value rather than casting to usize;
update the block referencing ANSI16_TABLE, rgb_to_ansi, Palette::default(), and
AnsiColors::White accordingly so the mapping is explicit and not dependent on
enum discriminants.
crates/acta-build/src/lib.rs (1)

58-59: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

统一规范化 strip_prefix,否则 Windows 风格前缀不会被剥离。

这里把 display 归一化成了 /,但 strip_prefix 仍按原样比较;调用方若传入 src\,会退回整条路径长度,导致宽度计算偏大。

♻️ 建议修改
         .iter()
         .map(|e| {
             let display = e.path().to_string_lossy().replace('\\', "/");
-            display.strip_prefix(strip_prefix).unwrap_or(&display).len()
+            let normalized_prefix = strip_prefix.replace('\\', "/");
+            display
+                .strip_prefix(&normalized_prefix)
+                .unwrap_or(&display)
+                .len()
         })
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/acta-build/src/lib.rs` around lines 58 - 59, The code normalizes the
file path into `display` (replacing '\\' with '/'), but still calls
`display.strip_prefix(strip_prefix)` using the original `strip_prefix`, so
Windows-style prefixes like `src\` won't match; normalize `strip_prefix` the
same way before calling `strip_prefix` (e.g., create a `normalized_prefix` by
replacing '\\' with '/' on `strip_prefix` and use that in
`display.strip_prefix(&normalized_prefix)`), ensuring the `display` and
`strip_prefix` use the same path form.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/fmt/mod.rs`:
- Around line 104-107: with_time_format 不应收窄为 String——请把签名改回接受更宽的类型(例如 pub fn
with_time_format<T: Into<String>>(mut self, fmt: T) -> Self 或使用 impl
Into<String>),并在函数体中用 self.time_format = fmt.into(); 保持函数名 with_time_format 和字段
time_format 不变以兼容现有调用者。

---

Outside diff comments:
In `@README.md`:
- Around line 123-135: The README example uses the removed enum variant
WriterTarget::File { ... }; update it to the new shape
WriterTarget::File(FileConfig) by constructing a FileConfig instance and passing
it to WriterTarget::File (e.g., create a FileConfig with the same path and
rotation values); ensure the example imports/uses the FileConfig type alongside
Writer, WriterTarget, Rotation, and PathBuf and replace the old inline struct
literal with the FileConfig construction so the example compiles.
- Around line 204-210: Update the README examples that reference the removed
constant LevelLabels::DEFAULT to use an existing constant (e.g.,
LevelLabels::LONG); specifically, in the Formatter setup example (the chain
using Formatter::new(), .with_theme(...), .with_icons(...), .with_labels(...),
.with_time_format(...)) replace LevelLabels::DEFAULT with LevelLabels::LONG and
make the same replacement for the other occurrence later in the file so the
example compiles.

In `@src/writer/custom.rs`:
- Around line 49-63: The async_writer_for function must not panic on capacity==0
or when there's no Tokio runtime: change async_writer_for to return
Result<AsyncWriter, Error>, validate that capacity > 0 and return a clear error
if not, and check for an active Tokio runtime using
tokio::runtime::Handle::try_current() (return an error when none is available)
instead of calling tokio::spawn unconditionally; if a handle exists, use
handle.spawn(...) to start the background task. Keep references to
AsyncWriterTarget and the async_writer_for function name so callers can be
updated to propagate the Result and handle errors accordingly.

---

Duplicate comments:
In `@crates/acta-build/src/lib.rs`:
- Around line 58-59: The code normalizes the file path into `display` (replacing
'\\' with '/'), but still calls `display.strip_prefix(strip_prefix)` using the
original `strip_prefix`, so Windows-style prefixes like `src\` won't match;
normalize `strip_prefix` the same way before calling `strip_prefix` (e.g.,
create a `normalized_prefix` by replacing '\\' with '/' on `strip_prefix` and
use that in `display.strip_prefix(&normalized_prefix)`), ensuring the `display`
and `strip_prefix` use the same path form.

In `@src/color/style.rs`:
- Around line 74-80: The code uses rgb_to_ansi(... ) as usize to index
ANSI16_TABLE, which relies on the numeric discriminants of anstyle::AnsiColor;
instead change the code to match on the rgb_to_ansi(...) return value and map
each AnsiColor variant to the corresponding ANSI16_TABLE entry (or directly to
the desired AnsiColors value), e.g. call rgb_to_ansi((s.rgb.0, s.rgb.1,
s.rgb.2).into(), Palette::default()) and then match its variants to select the
proper value rather than casting to usize; update the block referencing
ANSI16_TABLE, rgb_to_ansi, Palette::default(), and AnsiColors::White accordingly
so the mapping is explicit and not dependent on enum discriminants.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: c96bba23-215d-4892-8dd4-82fd140719ef

📥 Commits

Reviewing files that changed from the base of the PR and between e195c63 and 21e7583.

📒 Files selected for processing (14)
  • README.md
  • crates/acta-build/src/lib.rs
  • crates/acta-debug/src/main.rs
  • src/builder.rs
  • src/color/style.rs
  • src/config/mod.rs
  • src/config/test.rs
  • src/fmt/mod.rs
  • src/fmt/test.rs
  • src/fmt/visitor.rs
  • src/lib.rs
  • src/test.rs
  • src/writer/custom.rs
  • src/writer/test.rs
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Build / Build
🔇 Additional comments (1)
src/writer/test.rs (1)

1-1: LGTM!

Also applies to: 124-124

Comment thread src/fmt/mod.rs
@Sn0wo2 Sn0wo2 merged commit 82f47c0 into main Jun 13, 2026
16 checks passed
@Sn0wo2 Sn0wo2 deleted the refactor/acta branch June 13, 2026 08:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant