- 云原生
- 虚拟化
- 存储
- 后端
- 高可用
【免费下载链接】harvester
Open source hyperconverged infrastructure (HCI) software
结构化日志(Structured Logging)是 Harvester 日志可观测性建设的关键一环:它要求 Harvester 进程产出的每一条日志都以结构化的键值对形式呈现,并统一使用标准字段名,从而让日志能够被自动化工具批量消费、过滤与告警。本文以 Harvester 官方增强提案 enhancements/20240327-structured-logging.md 为主线,结合当前仓库源码中的实际落地形态,讲解 Harvester 结构化日志的目标、标准字段规范、从旧式格式化日志迁移到 Logrus 结构化日志的具体手法,以及 CI 防护与开发者指南等配套措施。读完本文,你将掌握在 Harvester 及其相关 Go 控制器、API 处理器中写出规范结构化日志的完整方法,并能独立把仓库中残留的旧式日志改造成可被工具自动分析的 JSON 日志。
一、背景与动机:为什么 Harvester 需要结构化日志
Harvester 定位为开源超融合基础设施(HCI)软件,其控制面由大量 Go 编写的 API 处理器与 controller-runtime 控制器构成,日志输出量巨大。在未引入结构化日志之前,代码中大量使用logrus.Errorf("something went wrong with %s/%s", obj.Namespace, obj.Name)这类拼接文本。
非结构化日志对人工阅读很友好,但缺少结构使得工具难以介入:
- 无法对大量日志做自动化的分析、故障检测与过滤;
- 字段只能靠正则去"猜",字段名拼写稍有出入(例如一处用
namespace、另一处用k8s_ns)就无法统一检索; - Harvester 用户在排查问题时依赖支持包(support bundle)中的日志,Harvester 开发者与支持工程师同样需要借助工具分析支持包,日志不可机器消费会显著拖慢排障效率。
因此提案的核心结论是:凡是 Harvester 产出的日志消息都应当是结构化的,并通过统一字段名、最小化新依赖、配套迁移策略与 CI 拦截四件事来系统性落地。
二、目标与非目标
提案明确了本次改造的范围边界,防止工程无限膨胀:
Goals(目标)
- Harvester 产出的所有日志消息均应为结构化日志;
- 结构字段名应标准化,以支持跨模块统一消费;
- 实现过程中引入的新依赖应尽可能少;
- 为存量代码库制定一套把旧日志迁移到结构化日志的迁移策略。
Non-Goals(非目标)
- 不涉及日志分析工具的集成(例如与 Loki、Elasticsearch 等对接不在本提案范围内);
- 不涉及日志的存储、访问、查看与保留策略;
- 不讨论何时该打日志、以及每条日志应使用何种严重级别——这些属于工程判断,交由开发者自行决策。
从当前仓库看,Harvester 的日志收集与路由另有基于 logging-operator 的 rancher-logging 方案(其 Flow/Output 校验逻辑见 pkg/util/logging/logging.go),本提案专注的是"日志产出端的格式",两者互补:产出端保证结构化,采集端保证可路由、可存储。
三、技术选型:复用 Logrus 的内建结构化能力
提案明确:Harvester 使用 Golang 库Logrus来输出日志,而 Logrus 本身就内建了结构化日志支持,无需引入新的日志库。
- 依赖确认:仓库 go.mod 声明
github.com/sirupsen/logrus v1.9.4; - 使用方法:通过
logrus.WithFields(logrus.Fields{...})携带结构化字段,再调用Error/Info/Warn等无格式化版本输出; - 输出形态:当 Logrus 配置为 JSON Formatter 时,每条日志会输出为
{"namespace":"...","name":"...","msg":"...","level":"error","time":"..."}形式的 JSON 行,天然可被 jq、Loki、Logstash 等工具直接解析。
仓库中已落地的典型结构化日志写法(来自 pkg/api/volume/handler.go):
image, err := h.images.Create(vmImage) if err != nil { logrus.WithFields(logrus.Fields{ "namespace": pvcNamespace, "name": pvcName, "apiVersion": "v1", "kind": "PersistentVolumeClaim", "err": err, }).Error("failed to create image from PVC") return nil, err }这里err字段直接放入error值,由 Logrus 渲染为字符串;apiVersion、kind明确标注了日志主体的 K8s 资源类型,使日志自描述能力更强。
四、标准结构字段规范
让日志可被自动化消费的前提,是全程序统一字段名。提案给出了一个非常直观的反例——下面两条日志都包含"命名空间、名称、消息"三个信息,但字段名不一致,导致无法用一条过滤条件同时命中两者:
{"namespace": "default", "name": "test", "msg": "creating VM"} {"k8s_ns": "default", "vm_display_name": "test", "message": "could not initialize"}而统一字段名之后,同样的两条日志变成:
{"namespace": "default", "name": "test", "msg": "creating VM"} {"namespace": "default", "name": "test", "msg": "could not initialize"}此时无论是建过滤器、做聚合还是写告警规则,都可以基于同一组字段完成。提案推荐的标准字段名如下(字段与 K8s 资源字段的映射关系必须完整继承):
| 字段名 | K8s 资源字段 | 说明 |
|---|---|---|
namespace | .metadata.namespace | 日志主体(subject)所在的 K8s 命名空间 |
name | .metadata.name | 作为日志主体的 K8s 资源名称 |
kind | .kind | 作为日志主体的 K8s 资源类型(Kind) |
err | 不适用 | builtin.error值的字符串表示 |
apiVersion | .apiVersion | 日志主体所属 K8s API 版本与 API 组信息 |
这些字段在仓库中已经有成规模的应用,可作为"标准写法"的活教材:
- pkg/api/volume/handler.go 在卷克隆失败时输出
name、namespace、apiVersion、kind、err五个标准字段; - pkg/api/vm/template.go 在创建 VM 模板失败时输出
namespace、name; - pkg/api/node/formatter.go 在清理节点维护模式条件时输出
node、preCheckErrorOnly等业务语义字段,与标准字段配合使用。
值得强调的是,字段名一经发布就应保持稳定,避免在迁移过程中"顺手改名"造成新旧日志字段不一致——这正是标准化字段表存在的意义。
五、迁移策略:把%s/%s改造成 WithFields
存量代码中典型的旧式日志(提案原文示例):
logrus.Errorf("something went wrong with %s/%s", obj.Namespace, obj.Name)改造后的结构化版本,利用 Logrus 内建设施:
logrus.WithFields(logrus.Fields{ "namespace": obj.Namespace, "name": obj.Name, }).Error("something went wrong")迁移要点:
- 把动态信息从消息模板中剥离:
%s/%s这类占位符从消息文本移入Fields,消息本身只保留静态描述("something went wrong"); - 统一调用无格式化变体:
Errorf/Infof/Warnf等带f的变体用于旧式拼接,迁移后应使用Error/Info/Warn并配合WithFields; - 错误对象直接传值:
"err": err直接传入error,不要预先err.Error()拼进消息,保证工具能拿到完整错误语义; - 补全主体信息:涉及 K8s 资源操作时,尽量补齐
kind与apiVersion,如 pkg/api/volume/handler.go 所示。
从当前仓库源码结构看,迁移工作处于"新旧共存"的中间态:
- 已迁移的结构化日志分布广泛,覆盖 API 层(pkg/api/vm/template.go、pkg/api/volume/handler.go)、备份引擎(pkg/backup/common/vshelper.go)、控制器(如 pkg/controller/master/backup/backup_metadata.go、pkg/controller/master/node/cpu_manager_controller.go)等数十个文件;
- 仍残留的旧式写法可作"待改造清单"的样例:例如 pkg/api/kubeconfig/generate_handler.go 的
logrus.Infof("kubeconfig for service account %s/%s is created", req.Namespace, req.SaName)、pkg/api/vm/formatter.go 的logrus.Errorf("Can't get PVC %s/%s, err: %+v", ...)、pkg/api/vm/handler.go 的logrus.Errorf("failed to update vmi %s/%s ...")等。
将这些残留点按"主体字段入 Fields、消息变静态描述"的规则逐个迁移,即可收敛到全结构化状态。
六、CI 防护与开发者指南
仅有迁移策略还不够——如果没有拦截机制,新代码很容易重新引入非结构化日志。提案给出两条配套措施:
- 更新开发者指南:在 DEVELOPER_GUIDE 中新增日志开发规范章节,说明应使用哪个库(Logrus)、如何实现结构化日志,以及标准字段与约定。当前仓库的 DEVELOPER_GUIDE.md 中仅提及 rancher-logging 附加组件文档(见其依赖列表),尚缺少结构化日志专章,这一缺口正是提案预期要补齐的部分。
- 引入 CI 检查:使用 Kubernetes SIG 维护的 [Logcheck] 工具(来自 kubernetes-sigs/logtools 项目)作为静态检查,阻止新的非结构化日志进入代码库。从当前仓库的 scripts、ci 与 pkg 目录源码看,尚未发现 Logcheck 的配置文件或 CI 集成痕迹,可以推断该检查可能部署在仓库外部的 CI 流水线中,或作为后续工程落地项推进——但"用工具拦截非结构化日志"这一防护方向是明确的。
对开发者而言,这条 CI 防线意味着:新写的logrus.Infof(...%s/%s...)会被工具标记为不合格,代码审查时也应优先要求使用logrus.WithFields形态。
七、API 变更与配套参考
本提案不引入任何 API 变更(API Changes: None),纯属工程内部的日志输出改造,不影响 Harvester 对外暴露的 API 契约、CRD 结构或升级兼容性,因此可以随常规版本迭代渐进式推进,无需协调客户端升级。
在设计思路上,提案参考了 Kubernetes 官方的结构化日志 KEP(sig-instrumentation 下的 1602-structured-logging),其核心思想一致:日志字段标准化 + 结构化输出,是支撑大规模日志自动分析与故障定位的前提。Harvester 在此框架下基于 Logrus 落地了更贴合自身资源模型的字段集合。
小结:如何验证与持续保持结构化
- 自查:新写日志一律使用
logrus.WithFields(logrus.Fields{...}),优先带上namespace、name、kind、apiVersion、err五个标准字段; - 排障:将 Logrus 配置为 JSON 输出后,可直接用
jq类工具对支持包日志按namespace/kind/err做过滤聚合; - 回归:把残留的
logrus.Errorf("...%s/%s...")视为待办项,按第五节规则逐个迁移,并依托 CI 的 Logcheck 类检查防止回潮。
结构化日志不是银弹,但它为 Harvester 的日志可观测性打下了机器可消费的地基——这正是本提案最核心的工程价值。
- 云原生
- 虚拟化
- 存储
- 后端
- 高可用
【免费下载链接】harvester
Open source hyperconverged infrastructure (HCI) software
相关推荐
SQLFlow 日志体系设计解析:基于 Logrus 与 Lumberjack 的结构化日志实践
SQLFlow 日志体系设计解析:基于 Logrus 与 Lumberjack 的结构化日志实践 导读 本文以 SQLFlow 的设计文档《Logs in SQ
人工智能机器学习后端编译器如何快速部署Hashtopolis:从安装到启动的简明教程
如何快速部署Hashtopolis:从安装到启动的简明教程 Hashtopolis是一款功能强大的Hashcat分布式密码恢复工具,它能够帮助用户轻松管理和分发
后端网络安全任务调度告别混乱日志:Logrus结构化日志最佳实践与迁移指南
告别混乱日志:Logrus结构化日志最佳实践与迁移指南 你是否还在为Go项目中的日志管理烦恼?面对一堆杂乱无章的文本日志,排查问题时如同大海捞针?本文将带你掌握
开发工具
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考