Ruby Fluentd插件开发:父类及上层模块继承的正确实现方式
问题根因
你遇到的报错由三个错误叠加导致,和Ruby继承机制本身无关:
- 笔误:
hello_world方法中引用了未定义的实例变量@parent_var,实际通过config_param定义的参数对应实例变量是@parent_param - 生命周期顺序错误:Fluentd的
config_param定义的配置项,是在configure方法执行阶段(调用基类super之后)才会完成赋值,你在initialize方法里就调用依赖配置参数的hello_world,此时参数还未初始化,必然为nil - 实例初始化流程缺失:你在父类
configure中直接Child.new()创建的是全新的独立实例,既没有传入配置对象,也没有执行该实例的configure初始化流程,该实例的配置参数、部分基类能力不会完成初始化。你能正常使用log对象,仅仅是因为log在initialize阶段就完成了初始化,不代表整个实例初始化完成。
正确实现规则
1. 父类变量与配置参数的继承
Ruby的实例变量是和具体对象实例绑定的,不存在跨实例的变量共享,子类对父类变量和配置参数的继承遵守以下规则:
config_param是类级别的声明,只要子类继承了父类,就会自动继承父类定义的所有配置项,不需要重复声明- 配置项对应的实例变量,只有在实例执行完
configure(conf)方法(且方法内第一行调用了super)之后才会被赋值,initialize阶段所有配置项的值都是nil,不要在initialize里写依赖配置参数的逻辑 - 如果需要拆分业务创建子类/辅助类实例,必须把当前的配置对象
conf传入新实例,并且调用新实例的configure(conf)方法完成配置加载,不能直接new完就执行业务逻辑 - 注意变量名拼写,
config_param :parent_param对应的实例变量固定是@parent_param,不要随意更改变量名。
2. 父类方法的继承与调用
Ruby的普通方法默认支持继承,不需要额外声明即可在子类中直接调用,注意几个约束:
- 重写Fluentd插件标准生命周期方法(
initialize/configure/start/shutdown等)时,必须在方法第一行调用super,保证父类的初始化、配置加载等基础逻辑先执行完,否则会出现依赖资源未初始化的问题 - 子类扩展父类方法逻辑时,可以在重写的方法中通过
super调用父类的原始实现,再追加自定义逻辑,不需要重复实现父类已有功能 - 方法调用默认作用于当前实例,不要尝试在A实例中直接访问B实例的实例变量,所有状态访问都要通过当前实例的方法完成。
3. 上层基类/模块能力的继承
Fluentd插件基类提供的log日志对象、router事件路由、配置解析等能力,只要遵守两个规则即可正常使用:
- 所有自定义的插件类、业务拆分的子类,必须直接或间接继承对应类型的Fluentd插件基类(比如输入插件继承
Fluent::Plugin::Input,业务拆分的子类直接继承你自己实现的父类插件即可,会自动透传基类能力) - 所有重写的生命周期方法必须调用
super,不能跳过父类实现。比如你在initialize中不调用super,基类的日志对象初始化逻辑就不会执行,log就会是nil - 手动创建的插件类实例必须走完
initialize -> configure(conf) -> start完整生命周期,才能正常使用所有基类能力,不要跳过任何步骤直接执行业务方法。
修正后可运行代码示例
require "fluent/plugin/input" module Fluent::Plugin class ParentInput < Fluent::Plugin::Input Fluent::Plugin.register_input("parent", self) config_param :parent_param, :string, default: "Hello" def initialize super log.info "Inside Parent initialize()" end def configure(conf) super # 必须先调用super完成配置加载 log.info "Inside Parent configure()" log.info "@parent_param=", @parent_param # 初始化子类实例,传入配置并走完configure流程 @child = Child.new @child.configure(conf) @child.main end def start super log.info "Inside Parent start" hello_world_val = hello_world() log.info "hello_world", hello_world_val end def hello_world log.info "Inside Parent hello_world()" # 修正变量名,使用正确的配置参数对应实例变量 @parent_param + " World" end end class Child < ParentInput def initialize super log.info "Inside Child initialize()" end def configure(conf) super # 调用父类configure完成配置和基类能力初始化 # 配置加载完成后再调用依赖配置的方法 @hello_world = hello_world() end def main log.info "Inside Child main()" log.info @hello_world end end end
修正后的执行流程符合预期,不会再出现nil报错:
- 父类
initialize执行,完成基础资源初始化- 父类
configure执行,加载配置参数,初始化子类实例- 子类
initialize执行- 子类
configure执行,加载配置参数,调用hello_world拿到正确的Hello World值- 子类
main执行,正常打印@hello_world- 父类
start执行,正常调用hello_world打印结果
内容的提问来源于stack exchange,提问作者Dante
相关产品推荐
相关产品推荐

