-
Notifications
You must be signed in to change notification settings - Fork 4
前端错误日志按状态码区分存放 #80
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
前端错误日志按状态码区分存放 #80
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,121 @@ | ||
| <!-- START doctoc generated TOC please keep comment here to allow auto update --> | ||
| <!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE --> | ||
| # Table of Content | ||
|
|
||
| - [Name](#name) | ||
| - [Status](#status) | ||
| - [Description](#description) | ||
| - [Synopsis](#synopsis) | ||
| - [Methods](#methods) | ||
| - [new](#new) | ||
| - [write_log](#write_log) | ||
| - [Author](#author) | ||
| - [Copyright and License](#copyright-and-license) | ||
|
|
||
| <!-- END doctoc generated TOC please keep comment here to allow auto update --> | ||
|
|
||
| # Name | ||
|
|
||
| acid.category_log | ||
|
|
||
| # Status | ||
|
|
||
| This library is considered production ready. | ||
|
|
||
| # Description | ||
|
|
||
| This lua module is used to write error log of different requst to | ||
| different log file. Error logs can be separated by response status | ||
| or something else. | ||
|
|
||
| It is implementd by wrapping the ngx.log function, | ||
| besides logging to the original log file normally, it also save logs | ||
| in memory, and at nginx log phase, it finally write to a proper log file. | ||
|
|
||
| # Synopsis | ||
|
|
||
| ```lua | ||
| # nginx.conf | ||
|
|
||
| http { | ||
| init_worker_by_lua_block { | ||
| local category_log = require('acid.category_log') | ||
|
|
||
| local get_category_file = function() | ||
| return string.format('front_%d_error.log', ngx.status) | ||
| end | ||
|
|
||
| local opts = { | ||
| get_category_file = get_category_file, | ||
| max_repeat_n = 64, | ||
| max_entry_n = 256, | ||
| } | ||
|
|
||
| category_log.wrap_log(opts) | ||
| } | ||
|
|
||
| server { | ||
| ... | ||
|
|
||
| log_by_lua_block { | ||
| local category_log = require('acid.category_log') | ||
| category_log.write_log() | ||
| } | ||
|
|
||
| location = /t { | ||
| rewrite_by_lua_block { | ||
| ... | ||
| ngx.log(ngx.ERR, 'test_error_log') | ||
| ngx.status = 500 | ||
| ngx.exit(ngx.HTTP_OK) | ||
| } | ||
| } | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| # Methods | ||
|
|
||
| ## new | ||
|
|
||
| **syntax**: | ||
| `category_log.wrap_log(opts)` | ||
|
|
||
| **arguments**: | ||
|
|
||
| - `opts`: | ||
| The options table should contain the following fields: | ||
|
|
||
| - `get_category_file`: a callback function, have no argument, | ||
| should return the log file to which the logs of this request | ||
| should write, return nil indicate not write to any log file. | ||
|
|
||
| - `max_repeat_n`: set how many logs on same source file and same | ||
| line number will be saved. | ||
|
|
||
| - `max_entry_n`: set the max total number of logs to save. | ||
|
|
||
| - `log_level`: set level of logging, the default is `ngx.INFO`. | ||
|
|
||
| **return**: | ||
| do not have return value | ||
|
|
||
| ## write_log | ||
|
|
||
| **syntax**: | ||
| `category_log:write_log()` | ||
|
|
||
| write logs saved in memory to proper log file. | ||
|
|
||
| **return**: | ||
| do not have return value | ||
|
|
||
| # Author | ||
|
|
||
| Renzhi (任稚) <zhi.ren@baishancloud.com> | ||
|
|
||
| # Copyright and License | ||
|
|
||
| The MIT License (MIT) | ||
|
|
||
| Copyright (c) 2015 Renzhi (任稚) <zhi.ren@baishancloud.com> | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,199 @@ | ||
| local strutil = require('acid.strutil') | ||
| local time = require('acid.time') | ||
|
|
||
|
|
||
| local to_str = strutil.to_str | ||
|
|
||
|
|
||
| local _M = {} | ||
|
|
||
|
|
||
| local level_to_str = { | ||
| [ngx.DEBUG] = '[debug]', | ||
| [ngx.INFO] = '[info]', | ||
| [ngx.NOTICE] = '[notice]', | ||
| [ngx.WARN] = '[warn]', | ||
| [ngx.ERR] = '[error]', | ||
| [ngx.CRIT] = '[crit]', | ||
| [ngx.ALERT] = '[alert]', | ||
| [ngx.EMERG] = '[emerg]', | ||
| } | ||
|
|
||
|
|
||
| local function write_to_file(log_path, log_ctx) | ||
| if log_ctx == nil then | ||
| return | ||
| end | ||
|
|
||
| table.insert(log_ctx.log_entry, to_str(log_ctx.counter) .. '\n') | ||
|
|
||
| local data = table.concat(log_ctx.log_entry, '') | ||
|
|
||
| local f = io.open(log_path, 'a+') | ||
| f:write(data) | ||
| f:close() | ||
| end | ||
|
|
||
|
|
||
| local function feed_log_entry(level, repeat_n, prefix, args) | ||
| local log_ctx = ngx.ctx.category_log | ||
| local log_entry = log_ctx.log_entry | ||
|
|
||
| if #log_entry >= _M.max_entry_n then | ||
| return | ||
| end | ||
|
|
||
| if repeat_n > _M.max_repeat_n then | ||
| return | ||
| end | ||
|
|
||
| local time_str = time.format(ngx.time(), 'nginxerrorlog') | ||
| local level_str = level_to_str[level] | ||
|
|
||
| local parts = {time_str, level_str, ngx.worker.pid(), prefix} | ||
|
|
||
| for _, v in ipairs(args) do | ||
| table.insert(parts, tostring(v)) | ||
| end | ||
|
|
||
| local log_line = table.concat(parts, ', ') .. '\n' | ||
|
|
||
| table.insert(log_entry, log_line) | ||
|
|
||
| return | ||
| end | ||
|
|
||
|
|
||
| local function _get_request_id() | ||
| local request_id = ngx.var.requestid | ||
| return request_id | ||
| end | ||
|
|
||
|
|
||
| local function get_request_id() | ||
| local ok, request_id = pcall(_get_request_id) | ||
| if not ok then | ||
| return | ||
| end | ||
|
|
||
| return request_id | ||
| end | ||
|
|
||
|
|
||
| local function log_by_category(level, src_file_name, line_number, prefix, args) | ||
| local log_ctx = ngx.ctx.category_log | ||
|
|
||
| local counter = log_ctx.counter | ||
|
|
||
| local ident = string.format('%s_%d', src_file_name, line_number) | ||
| counter[ident] = (counter[ident] or 0) + 1 | ||
|
|
||
| local repeat_n = counter[ident] | ||
|
|
||
| feed_log_entry(level, repeat_n, prefix, args) | ||
|
|
||
| return | ||
| end | ||
|
|
||
|
|
||
| local function _category_log(level, ...) | ||
| if ngx.ctx.category_log == nil then | ||
| ngx.ctx.category_log = { | ||
| -- contain log entries. | ||
| log_entry = {}, | ||
| -- save how many times a log on same source file | ||
| -- and same line nubmer have repeated. | ||
| counter = {}, | ||
| } | ||
| end | ||
|
|
||
| local log_ctx = ngx.ctx.category_log | ||
|
|
||
| if log_ctx.request_id == nil then | ||
| log_ctx.request_id = get_request_id() | ||
| end | ||
|
|
||
| -- get info of function at level 4 of the call stack, which is the | ||
| -- function called ngx.log | ||
| local debug_info = debug.getinfo(4) | ||
| local path_parts = strutil.rsplit(debug_info.short_src, '/', | ||
| {plain=true, maxsplit=1}) | ||
| local src_file_name = path_parts[#path_parts] | ||
| local line_number = debug_info.currentline | ||
| local func_name = debug_info.name | ||
|
|
||
| local prefix = string.format('%s %s:%d %s() ', log_ctx.request_id, | ||
| src_file_name, line_number, func_name) | ||
|
|
||
| _M.origin_log(level, prefix, ...) | ||
|
|
||
| if level > _M.log_level then | ||
| return | ||
| end | ||
|
|
||
| log_by_category(level, src_file_name, line_number, prefix, {...}) | ||
|
|
||
| return | ||
| end | ||
|
|
||
|
|
||
| local function category_log(level, ...) | ||
| local ok, err = pcall(_category_log, level, ...) | ||
| if not ok then | ||
| _M.origin_log(ngx.ERR, 'failed to do category log: %s' .. err) | ||
| end | ||
| end | ||
|
|
||
|
|
||
| function _M.wrap_log(opts) | ||
| if _M.origin_log ~= nil then | ||
| ngx.log(ngx.ERR, 'ngx.log can be wrapped only once') | ||
| return | ||
| end | ||
|
|
||
| _M.origin_log = ngx.log | ||
|
|
||
| if opts == nil then | ||
| opts = {} | ||
| end | ||
|
|
||
| _M.get_category_file = opts.get_category_file | ||
| _M.max_repeat_n = opts.max_repeat_n | ||
| _M.max_entry_n = opts.max_entry_n | ||
| _M.log_level = opts.log_level or ngx.INFO | ||
|
|
||
| ngx.log = category_log | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 有个情况是timer里的日志怎么打印出来...我看timer里也可以使用ngx.ctx
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. timer里的日志好像不能按状态码区分,timer里的ctx和request中的ctx好像不是同一个,在timer里应该得不到返回状态码,不过所有的日志包括timer中的日志都会正常写到front.error.log,只有指定状态码的日志(不包括timer中的)会被写到另外的文件中,这些日志在两个文件中都有的。 |
||
|
|
||
| return | ||
| end | ||
|
|
||
|
|
||
| local function write_log_timer(premature, log_path, log_ctx) | ||
| if premature then | ||
| _M.origin_log(ngx.WARN, 'write log timer premature') | ||
| return | ||
| end | ||
|
|
||
| write_to_file(log_path, log_ctx) | ||
| end | ||
|
|
||
|
|
||
| function _M.write_log() | ||
| local log_ctx = ngx.ctx.category_log | ||
| if log_ctx == nil then | ||
| return | ||
| end | ||
|
|
||
| local log_path = _M.get_category_file() | ||
| if log_path == nil then | ||
| return | ||
| end | ||
|
|
||
| local ok, err = ngx.timer.at(0, write_log_timer, log_path, log_ctx) | ||
| if not ok then | ||
| _M.origin_log(ngx.ERR, 'failed to add write log timer: ' .. err) | ||
| end | ||
| end | ||
|
|
||
|
|
||
| return _M | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
简单说明下这个函数干啥的吧..
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
嗯