在过来的几个月,社区用户为 Apache APISIX 增加了许多插件,丰盛了 Apache APISIX 的生态。从使用者的角度而言,更多样化的插件呈现无疑是一件坏事,它们在欠缺 Apache APISIX 高性能和低提早的根底之上,满足了使用者对于网关的更多冀望,即 “一站式”和“多功能”。
社区的贡献者们是如何为 Apache APISIX 开发插件的呢?Apache APISIX 博客上的文章仿佛都没有具体讲述过开发插件的流程。那么这次咱们换一个视角,从插件开发者的角度登程,一起来看看一款插件诞生的全过程吧!
本篇文章记录了一个没有后端教训的前端工程师开发 file-logger
插件的过程。在具体阐明实现过程之前,先向大家简略介绍下 file-logger
的性能。
性能介绍
file-logger
反对应用 Apache APISIX 插件元数据生成自定义的日志格局,用户能够通过 file-logger
插件将 JSON 格局的申请和响应数据附加到日志文件中,也能够将 Log 数据流推送到指定地位。
试想,在监控某一个路由的拜访日志时,很多时候咱们关注的不仅是某些申请和响应数据的值,还心愿将日志数据独自写入到指定的文件中。这时就能够应用 file-logger
插件来帮忙实现这些需要。
不反对在 Docs 外粘贴 block
具体实现过程中咱们能够通过 file-logger
将日志数据独自写入指定的日志文件,简化监控和调试的过程。
开发实现过程
介绍完 file-logger
的性能之后,大家也算对这个插件有了更多的意识。上面为大家具体解说一下,没有服务端教训的我是如何从 0 开始为 Apache APISIX 实现该插件并增加相应测试的。
确定插件名称及优先级
关上 Apache APISIX 插件开发指南,依照先后顺序须要确定以下几项:
- 确定插件分类。
- 确定插件优先级,并更新
conf/config-default.yaml
文件。
因为此次开发的 file-logger
属于日志类型的插件,所以我参考了Apache APISIX 已有的日志插件的名称和排序,将 file-logger
放到了这里:
征询了其余插件的作者和社区的热心成员之后,最终确认了该插件的名称 file-logger
和优先级 399
。
须要留神,插件的优先级与执行程序无关,优先级的值越大,执行越靠前。而插件名称的排序与执行程序无关。
创立最小可执行插件文件
确认好插件名称和优先级后,便可在 apisix/plugins/
目录下创立咱们的插件代码文件,这里有两点须要留神下:
- 如果是间接在
apisix/plugins/
目录下间接创立插件代码文件,就无需更改Makefile
文件。 - 如果你的插件有新建本人的代码目录,那么就须要更新
Makefile
文件,具体步骤可参考 Apache APISIX 插件开发指南。 - 上面咱们在
apisix/plugins/
目录下创立file-logger.lua
文件。 - 而后依据官网给出的
example-plugin
的例子做参考,来实现一个初始化版本。
-- 在头部引入咱们所须要的模块local core = require("apisix.core")local plugin = require("apisix.plugin")local ngx = ngx-- 申明插件名称local plugin_name = "file-logger"-- 定义插件 schema 格局local schema = { type = "object", properties = { path = { type = "string" }, }, required = {"path"}}-- 插件元数据 schemalocal metadata_schema = { type = "object", properties = { log_format = log_util.metadata_schema_log_format }}local _M = { version = 0.1, priority = 399, name = plugin_name, schema = schema, metadata_schema = metadata_schema}-- 查看插件配置是否正确function _M.check_schema(conf, schema_type) if schema_type == core.schema.TYPE_METADATA then return core.schema.check(metadata_schema, conf) end return core.schema.check(schema, conf)end-- 日志阶段function _M.log(conf, ctx) core.log.warn("conf: ", core.json.encode(conf)) core.log.warn("ctx: ", core.json.encode(ctx, true))endreturn _M
通过 example-plugin
的例子实现了一个最小的可用插件文件后,便可通过 core.log.warn(core.json.encode(conf))
和 core.log.warn("ctx: ", core.json.encode(ctx, true))
将插件的配置数据和申请相干的数据信息输入到 error.log
文件中去。
启用插件并测试
上面通过创立一条测试路由,来测试插件是否能胜利将咱们为其配置的插件数据和申请相干的数据信息,打印到谬误日志文件中去。
- 在本地筹备一个测试上游(本文中应用的测试上游是我在本地创立的
127.0.0.1:3030/api/hello
)。 - 通过
curl
命令创立一条路由并启用咱们新增的插件。
curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '{ "plugins": { "file-logger": { "path": "logs/file.log" } }, "upstream": { "type": "roundrobin", "nodes": { "127.0.0.1:3030": 1 } }, "uri": "/api/hello"}'
接着就会看到一个 200
的状态码,表明已胜利创立了路由。
- 运行
curl
命令向该路由发送申请,测试file-logeer
插件是否曾经启动。
$ curl -i http://127.0.0.1:9080/api/helloHTTP/1.1 200 OK...hello, world
- 在
logs/error.log
文件中会有一条这样的记录:
能够看到, 在 conf 参数中咱们为插件配置的 path: logs/file.log
曾经胜利的保留了。到此咱们曾经胜利创立了一个最小可用的插件,在日志阶段打印了 conf
和 ctx
参数的数据。
之后,咱们能够间接在该 file-logger.lua
插件代码文件中,为它编写外围性能。这里咱们能够间接运行 apisix reload
命令来从新加载最新的插件代码,而无需重启 Apache APISIX。
为 file-logger 插件编写外围性能
file-logger
插件的次要性能是写日志数据。在通过询问和查阅材料后,我理解到 Lua 的 IO 库:https://www.tutorialspoint.co...,于是确认了该插件的性能逻辑大抵为以下几步:
每次承受申请之后,将日志数据输入到插件配置的
path
中去。
- 首先,在日志阶段通过
conf
拿到file-logger
中path
的值。 - 而后,通过 Lua IO 库来实现文件创建、关上、写、刷新缓存、敞开的操作。
- 首先,在日志阶段通过
- 解决关上文件失败、创立文件失败等谬误。
local function write_file_data(conf, log_message) local msg, err = core.json.encode(log_message) if err then return core.log.error("message json serialization failed, error info : ", err) end local file, err = io_open(conf.path, 'a+') if not file then core.log.error("failed to open file: ", conf.path, ", error info: ", err) else local ok, err = file:write(msg, '\n') if not ok then core.log.error("failed to write file: ", conf.path, ", error info: ", err) else file:flush() end file:close() endend
- 参考
http-logger
插件源码,实现了将日记数据传给写日志数据的办法和 metadata 的一些判断和解决。
function _M.log(conf, ctx) local metadata = plugin.plugin_metadata(plugin_name) local entry if metadata and metadata.value.log_format and core.table.nkeys(metadata.value.log_format) > 0 then entry = log_util.get_custom_format_log(ctx, metadata.value.log_format) else entry = log_util.get_full_log(ngx, conf) end write_file_data(conf, entry)end
验证及增加测试
验证收集日志记录
因为在创立测试路由时曾经启用了 file-logger
插件,并且配置了 path
为 logs/file.log
,所以此时咱们只需给测试路由发送申请,以验证日志收集的后果:
curl -i http://127.0.0.1:9080/api/hello
在对应的 logs/file.log
中咱们能够看到每条记录都是以 JSON 的格局保留下来。将其中一条数据格式化之后如下所示:
{ "server":{ "hostname":"....", "version":"2.11.0" }, "client_ip":"127.0.0.1", "upstream":"127.0.0.1:3030", "route_id":"1", "start_time":1641285122961, "latency":13.999938964844, "response":{ "status":200, "size":252, "headers":{ "server":"APISIX\/2.11.0", "content-type":"application\/json; charset=utf-8", "date":"Tue, 04 Jan 2022 08:32:02 GMT", "vary":"Accept-Encoding", "content-length":"19", "connection":"close", "etag":"\"13-5j0ZZR0tI549fSRsYxl8c9vAU78\"" } }, "service_id":"", "request":{ "querystring":{ }, "size":87, "method":"GET", "headers":{ "host":"127.0.0.1:9080", "accept":"*\/*", "user-agent":"curl\/7.77.0" }, "url":"http:\/\/127.0.0.1:9080\/api\/hello", "uri":"\/api\/hello" }}
至此验证收集日志记录的环节完结了,验证后果阐明插件胜利启动并返回了应有的数据
为插件增加测试
对于 add_block_preprocessor
局部的代码,因为我自己没有学过 Perl ,所以在刚开始编写时比拟困惑。在询问之后才理解到它的正确应用形式:如果咱们在数据局部没有编写相干的 request
断言和 no_error_log
断言时,那么默认断言如下:
--- requestGET /t--- no_error_log[error]
综合参考了一些其余的日志测试文件当前,我在 t/plugin/
目录下创立 file-logger.t
文件。
每一份测试文件都由 __DATA__
分为序言局部和数据局部。因为目前官网测试相干文档没有明确归档分类,更多具体内容可参考文末的相干材料。上面我为大家列出参考相干材料后实现的其中一个测试用例:
use t::APISIX 'no_plan';no_long_string();no_root_location();add_block_preprocessor(sub { my ($block) = @_; if (! $block->request) { $block->set_value("request", "GET /t"); } if (! $block->no_error_log && ! $block->error_log) { $block->set_value("no_error_log", "[error]"); }});run_tests;__DATA__=== TEST 1: sanity--- config location /t { content_by_lua_block { local configs = { -- full configuration { path = "file.log" }, -- property "path" is required { path = nil } } local plugin = require("apisix.plugins.file-logger") for i = 1, #configs do ok, err = plugin.check_schema(configs[i]) if err then ngx.say(err) else ngx.say("done") end end } }--- response_body_likedoneproperty "path" is required
至此插件增加测试环节完结。
总结
以上就是我作为一个后端老手,从 0 开始实现一款 Apache APISIX 插件的全过程。在开发插件的过程中的确碰到了很多坑,比拟侥幸的是 Apache APISIX 社区外面有很多热心的大佬帮我解惑,使得 file-logger
插件的开发和测试全程都比拟顺畅。如果你对这个插件感兴趣,或想要查看插件详情, 能够参考 Apache APISIX 官网文档。
目前,Apache APISIX 也在开发其余插件以反对集成更多服务,如果您对此感兴趣,欢送随时在 GitHub Discussion 中发动探讨,也可通过 邮件列表 进行交换探讨。