grthtrhthjhtyjytjytkergtrhtrjytjerhrfh4:24 29/09/2026-- vim: st=4 sts=4 sw=4 et: --- Main Sentry reporting module. -- This module contains the core of the reporting logic, it still depends on a -- network layer to actually send the data to the Sentry server. -- -- @module raven -- @copyright 2014-2017 CloudFlare, Inc. -- @license BSD 3-clause (see LICENSE file) local util = require 'raven.util' local cjson = require 'cjson' local _M = {} _M._VERSION = util._VERSION local debug_getinfo = debug.getinfo local table_insert = table.insert local unpack = unpack or table.unpack -- luacheck: ignore local generate_event_id = util.generate_event_id local iso8601 = util.iso8601 local json_encode = cjson.encode local catcher_trace_level = 4 --- Table describing main Sentry client settings. -- @field sender Object used to send message, see `rave.senders.*` modules to -- find a sender suitable with your application -- @field level Set the message level (string), defaults to `"error"` -- @field logger Sets the message logger (string), defaults to `"root"` -- @field tags Defaults tags for sent messages, defaults to `{}`. Example: -- `{ "foo"="bar", ... }` -- @field extra Default extra data sent with messages, defaults to `{}` -- @table sentry_conf local raven_mt = { } raven_mt.__index = raven_mt -- utility function to deal errors, xpcall and stack traces -- This metatable is associated with returned error object so the original -- error message is returned when tostring is invoked on it. This allows to use -- the error objects for logging purposes as well. This is mostly a workaround -- of xpcall error handlers having a single result. local err_mt = { __tostring = function(self) return self.message end, } -- return a string detailing the function running at a stack level local function get_culprit(level) local culprit level = level + 1 local info = debug_getinfo(level, "Snl") if info.name then culprit = info.name else culprit = info.short_src .. ":" .. info.linedefined end return culprit end local function backtrace(level) local frames = {} level = level + 1 while true do local info = debug_getinfo(level, "Snl") if not info then break end table_insert(frames, 1, { filename = info.short_src, ["function"] = info.name, lineno = info.currentline, }) level = level + 1 end return { frames = frames } end -- error_catcher: used to catch an error from xpcall and return a correct -- error message local function error_catcher(err) return { message = err, culprit = get_culprit(catcher_trace_level), exception = { { value = err, stacktrace = backtrace(catcher_trace_level), } }, } end -- a wrapper around error_catcher that will return something even if -- error_catcher itself crashes local function capture_error_handler(err) local ok, json_exception = pcall(error_catcher, err) if not ok then -- when failed, json_exception is error message util.errlog('failed to run exception catcher: ' .. tostring(json_exception)) -- try to return something anyway (error message with no culprit and -- no stacktrace json_exception = { message = err, culprit = '???', exception = { { value=err } }, } end return setmetatable(json_exception, err_mt) end _M.capture_error_handler = capture_error_handler --- Create a new Sentry client. -- It takes a @{sentry_conf} table tune its behavior. -- @param conf client configuration. -- @return a new raven instance -- @usage -- local raven = require "raven" -- local rvn = raven.new { -- sender = require("raven.senders.luasocket").new { -- dsn = "http://pub:secret@127.0.0.1:8080/sentry/proj-id", -- }, -- tags = { foo = "bar", abc = "def" }, -- logger = "foo", -- } function _M.new(conf) local obj = { sender = assert(conf.sender, "sender is required"), level = conf.level or "error", logger = conf.logger or "root", tags = conf.tags or nil, extra = conf.extra or nil, } return setmetatable(obj, raven_mt) end --- This method is reponsible to return the `server_name` field. -- The default implementation just returns `"undefined"`, users are encouraged -- to override this to something more sensible. function _M.get_server_name() return "undefined" end --- This table can be used to tune the message reporting. -- @field tags Tags for the message, they will be coalesced with the ones -- provided in the @{sentry_conf} table used in the constructor if any. In -- case of conflict, the message tags have precedence. -- -- @field extra Extra data for the message. Like tags, data is merged with -- the extra data passed to the constructor. -- -- @field trace_level Starting stack level for the report (can be used to skip -- useless frames. The internal raven frames are automatically skipped, so a -- level of `1` is means that the direct caller will be reported as culprit. -- -- @table report_conf --- A Raven client instance is responsible to collect events and send them -- using the associated sender object. -- @type Raven --- Send an exception to Sentry. -- See [reference](https://docs.sentry.io/clientdev/interfaces/exception/). -- Note that the stack trace will be filled automatically. -- -- Note that the `conf` table will be modified by the function, therefore it -- is not safe to reuse conf table across calls. Consider passing common -- attributes to the client constructor instead. -- -- @function Raven:captureException -- @param exception a table describing the exception conforming to the Sentry -- format described in the reference docs -- @param conf capture configuration table, see @{report_conf} -- @return On success, return event id. If not success, return nil and -- an error string. -- @usage -- local rvn = ravennew(...) -- local exception = { { -- beware, exceptions are arrays -- type = "SyntaxError", -- value = "Wattttt!", -- module = "mymodule", -- -- stacktrace is populated automatically -- } } -- local id, err = rvn:captureException(exception, -- { tags = { foo = "bar", abc = "def" }}) function raven_mt:captureException(exception, conf) local trace_level if not conf then conf = { trace_level = 2 } elseif not conf.trace_level then conf.trace_level = 2 else conf.trace_level = conf.trace_level + 1 end trace_level = conf.trace_level exception[1].stacktrace = backtrace(trace_level) local payload = { exception = exception, message = exception[1].value, culprit = get_culprit(trace_level), } -- because whether tail call will or will not appear in the stack back trace -- is different between PUC-lua or LuaJIT, so just avoid tail call local id, err = self:send_report(payload, conf) return id, err end --- Send a message to Sentry. -- See [reference](https://docs.sentry.io/clientdev/interfaces/message/). -- -- Note that the `conf` table will be modified by the function, therefore it -- is not safe to reuse conf table across calls. Consider passing common -- attributes to the client constructor instead. -- -- @function Raven:captureMessage -- @param message the message, usually a raw string -- @param conf capture configuration table, see @{report_conf} -- @return On success, return event id. If not success, return nil and -- an error string. -- @usage -- local rvn = ravennew(...) -- local id, err = rvn:captureMessage("simple message", -- { tags = { foo = "bar", abc = "def" }}) function raven_mt:captureMessage(message, conf) if not conf then conf = { trace_level = 2 } elseif not conf.trace_level then conf.trace_level = 2 else conf.trace_level = conf.trace_level + 1 end local payload = { message = message, culprit = get_culprit(conf.trace_level), } local id, err = self:send_report(payload, conf) return id, err end --- Alias of @{Raven:captureException}. -- @function Raven:capture_exception raven_mt.capture_exception = raven_mt.captureException --- Ailas of @{Raven:captureMessage}. -- @function Raven:capture_message raven_mt.capture_message = raven_mt.captureMessage local function merge_tables(msg, root) if not root then return msg elseif not msg then return root end -- both table exist, merge root into msg for k, v in pairs(root) do msg[k] = msg[k] or v end return msg end --- Send directly a report to Sentry. -- This is an internal function, you should not call it directly, use -- @{Raven:captureException} or @{Raven:captureMessage} instead. -- -- Note that the `conf` table will be modified by the function, therefore it -- is not safe to reuse conf table across calls. Consider passing common -- attributes to the client constructor instead. -- -- @function Raven:send_report -- @param json table to be sent. Don't need to fill `event_id`, `timestamp`, -- `tags` and `level`. -- @param conf capture configuration table, see @{report_conf} -- @return On success, return event id. If not success, return nil and an -- error string. function raven_mt:send_report(json, conf) local event_id = generate_event_id() if not json then json = self.json if not json then return end end json.event_id = event_id json.timestamp = iso8601() json.level = self.level json.platform = "lua" json.logger = self.logger if conf then json.tags = merge_tables(conf.tags, self.tags) json.extra = merge_tables(conf.extra, self.extra) if conf.level then json.level = conf.level end else json.tags = self.tags json.extra = self.extra end json.server_name = _M.get_server_name() local json_str = json_encode(json) local ok, err = self.sender:send(json_str) if not ok then util.errlog("Failed to send to Sentry: ", err, " ", json_str) return nil, err end return json.event_id end -- the above two function used to be exposed as method, but there is no reason -- to do so as they don't need self at the end. -- Get culprit using given level function raven_mt:get_culprit(level) -- luacheck: ignore self return get_culprit(level) end -- catcher: used to catch an error from xpcall. function raven_mt:catcher(err) -- luacheck: ignore self return error_catcher(err) end --- Call given function and report any errors to Sentry. -- @function Raven:call -- @param f function to be called -- @param ... function's arguments -- @return the same as @{xpcall} -- @usage -- function func(a, b, c) -- return a * b + c -- end -- return rvn:call(func, 1, 'foo', true) function raven_mt:call(f, ...) -- When used with ngx_lua, connecting a tcp socket in xpcall error handler -- will cause a "yield across C-call boundary" error. To avoid this, we -- move all the network operations outside of the xpcall error handler. local res = { xpcall(f, capture_error_handler, ...) } if not res[1] then self:send_report(res[2]) res[2] = res[2].message -- turn the error object back to its initial form end return unpack(res) end --- Return a error handler function to be used with @{xpcall} -- This is a high performance version of the @{call} method, but it demands -- some more work to be used. It return a function that will turn a Lua error -- into a Sentry exception table. It is meant to be used as a @{xpcall} error -- hander. -- -- The error handler returns that table, the should then be sent with the -- @{send_report} method when appropriate. This cannot be easily done in the -- error handler itself as Lua disallow yielding form an error handler. -- -- It has better performance than @{call} because it avoid creating temporary -- objects at each invocation. -- -- Note that the error table generated by the handler has a `__tostring` -- metamethod returning the original error message for logging purposes. -- -- @function Raven:gen_capture_err -- @usage -- local handler = rvn:gen_capture_err() -- local ok, err = xpcall(function() error("boom") end, handler) -- if not ok then -- rvn:send_report(err, { tags = { "foo"="bar" } }) -- end function raven_mt:gen_capture_err() -- luacheck: ignore self return capture_error_handler end return _M