资讯专栏INFORMATION COLUMN

openresty 日志输出的处理

BingqiChen / 3879人阅读

摘要:我处理的方式是使用每一个请求,都会有自己独立的这个会贯穿整个请求的始终,简单的函数如下到了阶段要把追踪日志写入到硬盘里,处理代码如下小于秒的请求不记录可以用在模块,也可以用在模块,也能直接精确到模块,即只到某个请求。

最近出了个故障,有个接口的请求居然出现了长达几十秒的处理时间,由于日志缺乏,网络故障也解除了,就没法再重现这个故障了。为了可以在下次出现问题的时候能追查到问题,所以需要添加一些追踪日志。
添加这些追踪日志,我希望能够达到如下几点:

1、只有请求超过一定时间才记录,不然请求太多,系统扛不住

2、添加的代码可以尽量的少

3、对接口的影响尽量小,比如不影响实际时延,甚至记录日志时出现了错误,也不影响系统正常运行

openresty这套工具,可以在nginx处理请求的每一个阶段介入,编写代码进行逻辑处理。其可介入的流程如下图:

log Phase这个阶段,就是openresty能处理的最后阶段。到这个阶段的时候,实际上请求的响应已经发送给客户端了。所以使用 log_by_lua (知乎真特么蛋疼啊,左右下划线就自动斜体,还没提供转义功能)

log Phase这个阶段,就是openresty能处理的最后阶段。到这个阶段的时候,实际上请求的响应已经发送给客户端了。另外我也测试过了,即使在这个阶段发生了错误,如 io 错误,也不会影响接口的正常响应,所以使用 log_by_lua 很是符合需求。

好处不止如此, log_by_lua是一个请求的最后处理阶段,那么只要请求正常进行,比如会走到这一步,因此,在这一步,我们就知道了这个请求的耗时了。另外,则是我们的代码里有不少的 ngx.exit ,如果是在业务逻辑处理的时候就记录日志,那么每个出现 ngx.exit 的地方,都需要插入写日志到硬盘的操作,大大增加了代码量。

写日志到硬盘的这一步操作,可以在 log_by_lua 这个阶段来完成,剩下的另一个问题就是每一步记录的日志如何传递到 log_by_lua 这一阶段来了。

我处理的方式是使用ngx.ctx, 每一个请求,都会有自己独立的 ngx.ctx, 这个 ngx.ctx 会贯穿整个请求的始终,简单的log函数如下:

</>复制代码

  1. logger.lua
  2. --------------------------
  3. local _M = {}
  4. function _M.log(format, ...)
  5. if ngx.ctx.log_slot == nil then
  6. ngx.ctx.log_slot = {}
  7. end
  8. arg = {...}
  9. local logstr = ""
  10. if arg == nil then
  11. logstr = format
  12. else
  13. logstr = string.format(format, unpack(arg))
  14. end
  15. logstr = logstr .. "
  16. " .. ngx.now()
  17. table.insert(ngx.ctx.log_slot, logstr)
  18. end
  19. return _M

到了 log_by_lua 阶段要把追踪日志写入到硬盘里,处理代码如下:

</>复制代码

  1. log_slot.lua
  2. ---------------------
  3. local request_time = ngx.var.request_time
  4. if request_time < 1 then
  5. return --- 小于1秒的请求不记录
  6. end
  7. local slot = ngx.ctx.log_slot
  8. if slot == nil or type(slot) ~= "table" then
  9. return
  10. end
  11. local logs = table.concat(slot, "
  12. ")
  13. local f = assert(io.open("/logs/trace", "a"))
  14. f:write(logs .. "
  15. ")
  16. f:close()

log_by_lua 可以用在 http 模块,也可以用在server模块,也能直接精确到location模块,即只到某个请求。所以你可以在nginx.conf 里的http里添加:

</>复制代码

  1. http{
  2. log_by_lua_file "/code/log_slot.lua";
  3. }

也可以在server的配置里添加:

</>复制代码

  1. server {
  2. log_by_lua_file "/code/log_slot.lua";
  3. }

更能直接在某个接口里添加:

</>复制代码

  1. /v1/test {
  2. content_by_lua_file "/code/v1/test.lua";
  3. log_by_lua_file "/code/log_slot.lua";
  4. }

http里添加,则对所有的server; server里添加,则只针对此server;location里添加,就只针对这个接口。

但是,比较坑爹的是,log_by_lua 不像 access log,可以多层级使用。log_by_lua 在某层使用了之后,上层的 log_by_lua 就对此一层无效了。比如 /v1/test 接口添加了 log_by_lua, 那么 http 或者 server 里添加的 log_by_lua 在接受/v1/test接口的请求时都不会被用到。

正是因为这个坑,浪费了我不少的时间来解决。我们的系统里,http 模块是配置了 log_by_lua 的,用来做接口监控,监控返回的错误码,处理的时延等。如果我在 /v1/test 里添加了只针对 /v1/test 的追踪日志,那么接口监控就无法正常运行了。

不过天无绝人之路,我想到了一个处理方法如下:

</>复制代码

  1. monitor_log.lua
  2. ---------------------
  3. local _M = {}
  4. function _M.monitor_log()
  5. local f = _M.api_monitor_log_func
  6. if f == nil then
  7. f, err = loadfile("/code/monitor.lua")
  8. if f == nil then
  9. ngx.log(ngx.ERR, "/code/monitor.lua, ", err)
  10. --- 如果不存在接口监控,直接给一个空函数
  11. f = function() end
  12. end
  13. _M.api_monitor_log_func = f
  14. end
  15. local status, err = pcall(f)
  16. if not status then
  17. ngx.log(ngx.ERR, "run api monitor /code/monitor.lua failed", err)
  18. end
  19. end
  20. return _M

修改log_slot.lua代码如下:

</>复制代码

  1. local logger = require "code.monitor"
  2. local request_time = ngx.var.request_time
  3. logger.monitor_log()
  4. if request_time < 1 then
  5. return --- 小于1秒的请求不记录
  6. end
  7. local slot = ngx.ctx.log_slot
  8. if slot == nil or type(slot) ~= "table" then
  9. return
  10. end
  11. local logs = table.concat(slot, "
  12. ")
  13. local f = assert(io.open("/logs/trace", "a"))
  14. f:write(logs .. "
  15. ")
  16. f:close()

如上,就可以进行其他层级的 log_by_lua 代码运行了,皆大欢喜,问题解决了。
当系统并发请求较低的时候,worker够用,则使用 log_by_lua 可以说是毫无坏处。当然,一旦 log_by_lua 出现了故障,如死循环,则会长时间占用worker,造成整个系统崩溃掉。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/39987.html

相关文章

  • 日志平台(网关层) - 基于Openresty+ELKF+Kafka

    摘要:现在用方式调用接口,中使用方式输入内容日志平台网关层基于。日志平台网关层基于到此为止,提取经过网关的接口信息,并将其写入日志文件就完成了,所有的接口日志都写入了文件中。 背景介绍 1、问题现状与尝试 没有做日志记录的线上系统,绝对是给系统运维人员留下的坑。尤其是前后端分离的项目,后端的接口日志可以解决对接、测试和运维时的很多问题。之前项目上发布的接口都是通过Oracle Service...

    xumenger 评论0 收藏0
  • 由一条OpenResty Error log谈谈ngx.exit与ngx.eof区别

    摘要:一看果然是在响应发出后报的错,但日志没有反应出报错的具体位置。而我期望的当前请求直接终止,不应该使用而是。自起,执行成功返回,失败则返回和错误描述信息。 事由 我们基于Vanilla开发了一个类似于一个网关的流量分发服务,在原来的业务线上对不同的业务使用不同的后端(PHP、Python、Lua...)进行处理,最近在紧锣密鼓的测试(当然这里咱们主要看问题),在扫荡日志的过程中发现有这样...

    wslongchen 评论0 收藏0
  • OpenResty安装、配置与使用

    摘要:用于方便地搭建能够处理超高并发扩展性极高的动态应用服务和动态网关。安装安装依赖库下载及安装激活组件被用于构建。大部组件默认是激活的,也有部件不是。您需要通过以下选项在编译的时候将它们各自激活,和。 OpenResty简介 OpenResty 是一个基于 Nginx 与 Lua 的高性能 Web 平台,其内部集成了大量精良的 Lua 库、第三方模块以及大多数的依赖项。用于方便地搭建能够处...

    stackfing 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<