From 8dc9ed8a49488d5f1c918cbcd50b9abcc449b979 Mon Sep 17 00:00:00 2001 From: xiaojin Date: Wed, 30 Jun 2021 20:17:08 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=94=A7=20build:=20=E8=B0=83=E6=95=B4=20?= =?UTF-8?q?=E5=BA=93=20=E7=9B=AE=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- framework/lualib/3rd/zeus/middleclass.lua | 25 +- framework/lualib/zeus/skynet/gd.lua | 41 ++ framework/lualib/zeus/skynet/redlock.lua | 31 ++ framework/lualib/zeus/skynet/share.lua | 78 +++ framework/lualib/zeus/storage/entity_pool.lua | 99 ++++ framework/lualib/zeus/storage/initdb.lua | 6 + .../lualib/zeus/storage/multi_entity.lua | 177 +++++++ .../lualib/zeus/storage/single_entity.lua | 133 +++++ framework/lualib/zeus/storage/store.lua | 40 ++ framework/lualib/zeus/storage/store_util.lua | 149 ++++++ framework/lualib/zeus/zenv/init.lua | 5 + framework/lualib/zeus/zenv/redlock_conf.lua | 42 ++ .../lualib/zeus/zlog/formatter/concat.lua | 16 + .../lualib/zeus/zlog/formatter/default.lua | 9 + .../lualib/zeus/zlog/formatter/format.lua | 9 + framework/lualib/zeus/zlog/formatter/mix.lua | 24 + .../lualib/zeus/zlog/formatter/pformat.lua | 93 ++++ framework/lualib/zeus/zlog/log.lua | 223 ++++++++ .../lualib/zeus/zlog/logformat/default.lua | 18 + .../lualib/zeus/zlog/logformat/proxy.lua | 11 + .../lualib/zeus/zlog/logformat/proxy/pack.lua | 36 ++ .../lualib/zeus/zlog/logformat/syslog.lua | 148 ++++++ .../zlog/writer/async/_private/server.lua | 101 ++++ .../lualib/zeus/zlog/writer/async/lane.lua | 22 + .../zeus/zlog/writer/async/server/lane.lua | 71 +++ .../zeus/zlog/writer/async/server/udp.lua | 10 + .../zeus/zlog/writer/async/server/zmq.lua | 42 ++ .../lualib/zeus/zlog/writer/async/udp.lua | 18 + .../lualib/zeus/zlog/writer/async/zmq.lua | 21 + framework/lualib/zeus/zlog/writer/console.lua | 1 + .../lualib/zeus/zlog/writer/console/color.lua | 334 ++++++++++++ framework/lualib/zeus/zlog/writer/file.lua | 17 + .../lualib/zeus/zlog/writer/file/by_day.lua | 17 + .../zeus/zlog/writer/file/private/impl.lua | 484 ++++++++++++++++++ .../lualib/zeus/zlog/writer/file/roll.lua | 18 + framework/lualib/zeus/zlog/writer/filter.lua | 1 + .../lualib/zeus/zlog/writer/filter/lvl/eq.lua | 14 + .../lualib/zeus/zlog/writer/filter/lvl/le.lua | 14 + framework/lualib/zeus/zlog/writer/format.lua | 9 + .../lualib/zeus/zlog/writer/freeswitch.lua | 42 ++ framework/lualib/zeus/zlog/writer/list.lua | 13 + .../zeus/zlog/writer/net/server/udp.lua | 29 ++ .../zeus/zlog/writer/net/server/zmq.lua | 55 ++ .../lualib/zeus/zlog/writer/net/smtp.lua | 26 + framework/lualib/zeus/zlog/writer/net/udp.lua | 19 + framework/lualib/zeus/zlog/writer/net/zmq.lua | 1 + .../zlog/writer/net/zmq/_private/compat.lua | 150 ++++++ .../zlog/writer/net/zmq/_private/impl.lua | 74 +++ .../lualib/zeus/zlog/writer/net/zmq/pub.lua | 1 + .../lualib/zeus/zlog/writer/net/zmq/push.lua | 1 + .../zeus/zlog/writer/net/zmq/srv/pub.lua | 1 + framework/lualib/zeus/zlog/writer/prefix.lua | 9 + framework/lualib/zeus/zlog/writer/stderr.lua | 11 + framework/lualib/zeus/zlog/writer/stdout.lua | 11 + framework/service/dbmgr.lua | 78 +++ framework/service/gdd.lua | 34 ++ framework/service/guild_generator.lua | 52 ++ framework/service/mysqld.lua | 50 ++ framework/service/redisd.lua | 42 ++ framework/service/redlockd.lua | 198 +++++++ framework/service/template.lua | 48 +- framework/service/zeus.lua | 61 ++- 62 files changed, 3586 insertions(+), 27 deletions(-) create mode 100644 framework/lualib/zeus/skynet/gd.lua create mode 100644 framework/lualib/zeus/skynet/redlock.lua create mode 100644 framework/lualib/zeus/skynet/share.lua create mode 100644 framework/lualib/zeus/storage/entity_pool.lua create mode 100644 framework/lualib/zeus/storage/initdb.lua create mode 100644 framework/lualib/zeus/storage/multi_entity.lua create mode 100644 framework/lualib/zeus/storage/single_entity.lua create mode 100644 framework/lualib/zeus/storage/store.lua create mode 100644 framework/lualib/zeus/storage/store_util.lua create mode 100644 framework/lualib/zeus/zenv/redlock_conf.lua create mode 100644 framework/lualib/zeus/zlog/formatter/concat.lua create mode 100644 framework/lualib/zeus/zlog/formatter/default.lua create mode 100644 framework/lualib/zeus/zlog/formatter/format.lua create mode 100644 framework/lualib/zeus/zlog/formatter/mix.lua create mode 100644 framework/lualib/zeus/zlog/formatter/pformat.lua create mode 100644 framework/lualib/zeus/zlog/log.lua create mode 100644 framework/lualib/zeus/zlog/logformat/default.lua create mode 100644 framework/lualib/zeus/zlog/logformat/proxy.lua create mode 100644 framework/lualib/zeus/zlog/logformat/proxy/pack.lua create mode 100644 framework/lualib/zeus/zlog/logformat/syslog.lua create mode 100644 framework/lualib/zeus/zlog/writer/async/_private/server.lua create mode 100644 framework/lualib/zeus/zlog/writer/async/lane.lua create mode 100644 framework/lualib/zeus/zlog/writer/async/server/lane.lua create mode 100644 framework/lualib/zeus/zlog/writer/async/server/udp.lua create mode 100644 framework/lualib/zeus/zlog/writer/async/server/zmq.lua create mode 100644 framework/lualib/zeus/zlog/writer/async/udp.lua create mode 100644 framework/lualib/zeus/zlog/writer/async/zmq.lua create mode 100644 framework/lualib/zeus/zlog/writer/console.lua create mode 100644 framework/lualib/zeus/zlog/writer/console/color.lua create mode 100644 framework/lualib/zeus/zlog/writer/file.lua create mode 100644 framework/lualib/zeus/zlog/writer/file/by_day.lua create mode 100644 framework/lualib/zeus/zlog/writer/file/private/impl.lua create mode 100644 framework/lualib/zeus/zlog/writer/file/roll.lua create mode 100644 framework/lualib/zeus/zlog/writer/filter.lua create mode 100644 framework/lualib/zeus/zlog/writer/filter/lvl/eq.lua create mode 100644 framework/lualib/zeus/zlog/writer/filter/lvl/le.lua create mode 100644 framework/lualib/zeus/zlog/writer/format.lua create mode 100644 framework/lualib/zeus/zlog/writer/freeswitch.lua create mode 100644 framework/lualib/zeus/zlog/writer/list.lua create mode 100644 framework/lualib/zeus/zlog/writer/net/server/udp.lua create mode 100644 framework/lualib/zeus/zlog/writer/net/server/zmq.lua create mode 100644 framework/lualib/zeus/zlog/writer/net/smtp.lua create mode 100644 framework/lualib/zeus/zlog/writer/net/udp.lua create mode 100644 framework/lualib/zeus/zlog/writer/net/zmq.lua create mode 100644 framework/lualib/zeus/zlog/writer/net/zmq/_private/compat.lua create mode 100644 framework/lualib/zeus/zlog/writer/net/zmq/_private/impl.lua create mode 100644 framework/lualib/zeus/zlog/writer/net/zmq/pub.lua create mode 100644 framework/lualib/zeus/zlog/writer/net/zmq/push.lua create mode 100644 framework/lualib/zeus/zlog/writer/net/zmq/srv/pub.lua create mode 100644 framework/lualib/zeus/zlog/writer/prefix.lua create mode 100644 framework/lualib/zeus/zlog/writer/stderr.lua create mode 100644 framework/lualib/zeus/zlog/writer/stdout.lua create mode 100644 framework/service/dbmgr.lua create mode 100644 framework/service/gdd.lua create mode 100644 framework/service/guild_generator.lua create mode 100644 framework/service/mysqld.lua create mode 100644 framework/service/redisd.lua create mode 100644 framework/service/redlockd.lua diff --git a/framework/lualib/3rd/zeus/middleclass.lua b/framework/lualib/3rd/zeus/middleclass.lua index ae603af..57f702c 100644 --- a/framework/lualib/3rd/zeus/middleclass.lua +++ b/framework/lualib/3rd/zeus/middleclass.lua @@ -1,17 +1,5 @@ -local type = type -local pairs = pairs -local rawget = rawget -local setmetatable = setmetatable -local assert = assert -local tostring = tostring -local ipairs = ipairs -------------------------------------------------------------- - -local middleclass = { - _VERSION = 'middleclass v4.1.1', - _DESCRIPTION = 'Object Orientation for Lua', - _URL = 'https://github.com/kikito/middleclass', -} +-- https://github.com/jojo59516/middleclass +local middleclass = {} local function _createIndexWrapper(aClass, f) if f == nil then @@ -72,7 +60,7 @@ local function _createClass(name, super) dict.__index = dict local aClass = { - __cname = name, + name = name, super = super, static = {}, __instanceDict = dict, @@ -139,9 +127,8 @@ local DefaultMixin = { isInstanceOf = function(self, aClass) return type(aClass) == 'table' and type(self) == 'table' and - (self.class == aClass or type(self.class) == 'table' and - type(self.class.isSubclassOf) == 'function' and - self.class:isSubclassOf(aClass)) + (self.class == aClass or type(self.class) == 'table' and type(self.class.isSubclassOf) == 'function' and + self.class:isSubclassOf(aClass)) end, static = { @@ -185,7 +172,7 @@ local DefaultMixin = { isSubclassOf = function(self, other) return type(other) == 'table' and type(self.super) == 'table' and - (self.super == other or self.super:isSubclassOf(other)) + (self.super == other or self.super:isSubclassOf(other)) end, include = function(self, ...) diff --git a/framework/lualib/zeus/skynet/gd.lua b/framework/lualib/zeus/skynet/gd.lua new file mode 100644 index 0000000..e341df9 --- /dev/null +++ b/framework/lualib/zeus/skynet/gd.lua @@ -0,0 +1,41 @@ +local skynet = require "skynet" +local mc = require "skynet.multicast" +local st = require "skynet.sharetable" + +local shared +local channel + +local M + +local function init() + assert(not shared) + assert(not channel) + shared = skynet.uniqueservice "shared" + local name = skynet.call(shared, "lua", "channel") + channel = mc.new { + channel = name, + dispatch = function(_, _, filenames) + for _, filename in pairs(filenames) do + if M[filename] then + M[filename] = nil + end + end + end, + } +end + +M = setmetatable({}, { + __index = function(self, filename) + if not shared then + init() + end + + local obj = st.query(filename) + if obj then + self[filename] = obj + end + return obj + end, +}) + +return M diff --git a/framework/lualib/zeus/skynet/redlock.lua b/framework/lualib/zeus/skynet/redlock.lua new file mode 100644 index 0000000..7202fb1 --- /dev/null +++ b/framework/lualib/zeus/skynet/redlock.lua @@ -0,0 +1,31 @@ +local skynet = require "skynet" +local redlock = {} + +local addr +local make_uuid + +local function default_cb() +end + +local function _lock(lockname, func, cb, timeout, ...) + cb = cb or default_cb + local uuid = make_uuid() + local ok, data = skynet.call(addr, "lua", "lock", lockname, uuid, timeout) + if not ok then + return cb(ok, data) + end + cb(pcall(func, ...)) + skynet.call(addr, "lua", "unlock", lockname, uuid) +end + +function redlock.lock(lockname, func, cb, timeout, ...) + skynet.fork(_lock, lockname, func, cb, timeout, ...) +end + +skynet.init(function() + local conf = require "redlock_conf" + make_uuid = conf.make_uuid + addr = skynet.uniqueservice("redlockd") +end) + +return redlock diff --git a/framework/lualib/zeus/skynet/share.lua b/framework/lualib/zeus/skynet/share.lua new file mode 100644 index 0000000..bf4b0e4 --- /dev/null +++ b/framework/lualib/zeus/skynet/share.lua @@ -0,0 +1,78 @@ +local lfs = require "lfs" +-------------------------------------------------------------------------------- +-------------------------------------------------------------------------------- +local Share = class("Share") + +function Share:initialize(root) + self._root = root +end + +function Share:get_paths(rootpath, paths) + paths = paths or {} + for entry in lfs.dir(rootpath) do + if entry ~= "." and entry ~= ".." then + local path = string.format("%s/%s", rootpath, entry) + + local attr = lfs.attributes(path) + assert(type(attr) == "table") + + if attr.mode == "directory" then + self:get_paths(path, paths) + else + -- 获取文件 + entry = entry:match("(.+)%.lua$") + if entry then + paths[entry] = path + end + end + end + end + return paths +end + +function Share:read_file(fileName) + local f, errMsg = io.open(fileName, "r") + if not f then + return false, errMsg + end + + local source = f:read("a") + io.close(f) + return true, source +end + +function Share:load_file(fileName, ...) + local ok, source = self:read_file(fileName) + if ok then + local chunk, err = load(source) + if "function" == type(chunk) then + return pcall(chunk, ...) + else + return false, err + end + else + local err = source + return false, err + end +end + +function Share:get_share_data() + local ret = {} + if not self._root or "table" ~= type(self._root) then + return ret + end + + local files = {} + for _, rootPath in pairs(self._root) do + self:get_paths(rootPath, files) + end + for fileName, filePath in pairs(files) do + local ok, data = self:load_file(filePath) + assert(ok, string.format("%s err: %s", filePath, data)) + ret[fileName] = data + end + return ret +end + +return Share + diff --git a/framework/lualib/zeus/storage/entity_pool.lua b/framework/lualib/zeus/storage/entity_pool.lua new file mode 100644 index 0000000..03bd953 --- /dev/null +++ b/framework/lualib/zeus/storage/entity_pool.lua @@ -0,0 +1,99 @@ +local single_entity = require "single_entity" +local multi_entity = require "multi_entity" +local util = require "store_util" + +local traceback = debug.traceback + +local mt = {} +mt.__index = mt + +function mt:ctor(opts) + self.conf = util.config(opts) + self.entities = {} +end + +function mt:load(id) + local conf = self.conf + local entity + if conf.multi then + entity = multi_entity.new(id, conf) + else + entity = single_entity.new(id, conf) + end + assert(entity) + self.entities[id] = entity + return entity +end + +function mt:unload(id) + local entity = self.entities[id] + if not entity then + return + end + entity:flush() + self.entities[id] = nil +end + +function mt:unload_all() + self:flush() + self.entities = {} +end + +function mt:entity(id) + return self.entities[id] +end + +function mt:get(id, ...) + local entity = self.entities[id] + assert(entity, id) + if self.conf.multi then + local subid = ... + return entity:get(subid) + else + return entity:get() + end +end + +function mt:set(id, ...) + local data1, data2 = ... + local entity = self.entities[id] + assert(entity, id) + if self.conf.multi then + entity:set(data1, data2) + else + entity:set(data1) + end +end + +function mt:flush() + local rediscmds = {} + local sqls = {} + + for _, entity in pairs(self.entities) do + entity:collect_dirty(rediscmds, sqls) + end + + for addr, cmds in pairs(rediscmds) do + xpcall(util.redis_execute, traceback, addr, cmds) + end + + for addr, sqls in pairs(sqls) do + xpcall(util.mysql_execute, traceback, addr, sqls) + end +end + +function mt:dump() + local obj = {} + for key, entity in pairs(self.entities) do + obj[key] = entity.rs + end + return util.encode(obj) +end + +return { + new = function(opts) + local obj = setmetatable({}, mt) + obj:ctor(opts) + return obj + end, +} diff --git a/framework/lualib/zeus/storage/initdb.lua b/framework/lualib/zeus/storage/initdb.lua new file mode 100644 index 0000000..21f0cd8 --- /dev/null +++ b/framework/lualib/zeus/storage/initdb.lua @@ -0,0 +1,6 @@ +local skynet = require "skynet" + +return function(dbconf) + local addr = skynet.uniqueservice("dbmgr") + return skynet.call(addr, "lua", "init", dbconf) +end diff --git a/framework/lualib/zeus/storage/multi_entity.lua b/framework/lualib/zeus/storage/multi_entity.lua new file mode 100644 index 0000000..2977635 --- /dev/null +++ b/framework/lualib/zeus/storage/multi_entity.lua @@ -0,0 +1,177 @@ +local util = require "store_util" + +local strformat = string.format +local tinsert = table.insert +local mysql_execute = util.mysql_execute +local redis_execute = util.redis_execute +local redis_key_field = util.redis_execute +local quote_sql_str = util.quote_sql_str +local decode = util.decode +local encode = util.encode +local guid = util.guid +local traceback = debug.traceback + +local mt = {} +mt.__index = mt + +function mt:ctor(id, conf) + assert(conf) + assert(type(id) == "number") + self.conf = conf + self.id = id + + local redisd_addr = conf.redisd_addr + local mysqld_addr = conf.mysqld_addr + + -- load from redis + if redisd_addr then + local key, _ = redis_key_field(conf, 0) + local res = redis_execute(redisd_addr, "hgetall", key) + local sz = #res + if sz > 0 then + local rs = {} + for i = 1, sz, 2 do + local id = tonumber(res[i]) + rs[id] = decode(res[i + 1]) + end + self.rs = rs + end + end + + -- load from mysql + if not self.rs and mysqld_addr then + local mysql_idname = conf.mysql_idname + local mysql_valname = conf.mysql_valname + local sql = strformat([[SELECT `%s`, `%s` FROM `%s` where `%s` = %d]], mysql_idname, mysql_valname, conf.tbl, + conf.mysql_owner_idname, self.id) + local res = mysql_execute(mysqld_addr, sql) + local sz = #res + if sz > 0 then + local rs = {} + for i = 1, sz do + local data = res[i] + local id = data[mysql_idname] + assert(type(id) == "number", id) + rs[id] = data[mysql_valname] + end + self.rs = rs + end + end + + if not self.rs then + self.rs = {} + end + + -- dirty id + self.dirty = {} +end + +function mt:guid() + return guid() +end + +function mt:get(id) + return self.rs[id] +end + +function mt:set(id, val) + local conf = self.conf + if self.rs[id] then + if val then + -- update + self.dirty[id] = true + else + -- delete + self.dirty[id] = nil + if conf.mysqld_addr then + local sql = strformat([[delete from `%s` where `%s` = %d and `%s` = %d limit 1]], conf.tbl, + conf.mysql_idname, id, conf.mysql_owner_idname, self.id) + mysql_execute(conf.mysqld_addr, sql) + end + if conf.redisd_addr then + local key, field = redis_key_field(conf, id) + redis_execute(conf.redisd_addr, "hdel", key, field) + end + end + elseif val then + -- insert + local sval = encode(val) + assert(sval) + if conf.mysqld_addr then + local sql = strformat([[insert into `%s`(`%s`,`%s`,`%s`) values (%d,%d,%s)]], conf.tbl, conf.mysql_idname, + conf.mysql_owner_idname, conf.mysql_valname, id, self.id, quote_sql_str(sval)) + mysql_execute(conf.mysqld_addr, sql) + end + if conf.redisd_addr then + local key, field = redis_key_field(conf, id) + redis_execute(conf.redisd_addr, "hset", key, field, sval) + end + end + + self.rs[id] = val +end + +function mt:_collect_dirty(id, rediscmds, sqls) + local val = self.rs[id] + val = encode(val) + assert(val) + local conf = self.conf + local redisd_addr = conf.redid_addr + local mysqld_addr = conf.mysqld_addr + + if redisd_addr then + local key, field = redis_key_field(conf, id) + local list = rediscmds[redisd_addr] + if not list then + list = {} + rediscmds[redisd_addr] = list + end + tinsert(list, {"HSET", key, field, val}) + end + + if mysqld_addr then + local sql = strformat([[UPDATE `%s` SET `%s` = %s where `%s` = %d and `%s` = %d limit 1]], conf.tbl, + conf.mysql_valname, quote_sql_str(val), conf.mysql_idname, id, conf.mysql_owner_idname, self.id) + + local list = sqls[mysqld_addr] + if not list then + list = {} + sqls[mysqld_addr] = list + end + tinsert(list, sql) + end +end + +function mt:collect_dirty(rediscmds, sqls) + if not next(self.dirty) then + return + end + for id, _ in pairs(self.dirty) do + xpcall(self._collect_dirty, traceback, self, id, rediscmds, sqls) + end + self.dirty = {} +end + +function mt:flush() + local conf = self.conf + local rediscmds = {} + local sqls = {} + + self:collect_dirty(rediscmds, sqls) + + if #rediscmds > 0 then + xpcall(redis_execute, traceback, conf.redisd_addr, rediscmds) + end + + if #sqls > 0 then + mysql_execute(conf.mysqld_addr, sqls) + end +end + +return { + new = function(...) + local obj = setmetatable({}, mt) + obj:ctor(...) + return obj + end, +} diff --git a/framework/lualib/zeus/storage/single_entity.lua b/framework/lualib/zeus/storage/single_entity.lua new file mode 100644 index 0000000..fd56fdd --- /dev/null +++ b/framework/lualib/zeus/storage/single_entity.lua @@ -0,0 +1,133 @@ +local util = require "store_util" + +local strformat = string.format +local tinsert = table.insert +local mysql_execute = util.mysql_execute +local redis_execute = util.redis_execute +local redis_key_field = util.redis_execute +local quote_sql_str = util.quote_sql_str +local decode = util.decode +local encode = util.encode +local traceback = debug.traceback + +local mt = {} +mt.__index = mt + +function mt:ctor(id, conf) + assert(conf) + assert(type(id) == "number") + self.conf = conf + self.id = id + -- load record + if conf.redisd_addr then + local key, field = redis_key_field(conf) + local ret = redis_execute(conf.redisd_addr, "hget", key, field) + self.rs = decode(ret) + end + + if not self.rs and conf.mysqld_addr then + local sql = strformat("select `%s` from `%s` where `%s` = %d limit 1", conf.mysql_valname, conf.tbl, + conf.mysql_idname, self.id) + + local ret = mysql_execute(conf.mysqld_addr, sql) + if ret then + self.rs = decode(ret[1][conf.mysql_valname]) + end + end + + if not self.rs and not conf.readonly then + self:set({}) + end + + self.dirty = false +end + +function mt:get() + return self.rs +end + +function mt:set(val) + local conf = self.conf + assert(not conf.readonly, conf.tbl) + + local sval = encode(val) + if not sval then + assert(false, ("[tbl] %s [id] %s"):format(conf.tbl, self.id)) + return + end + + if not self.rs then + -- insert + if conf.mysqld_addr then + local sql = strformat([[insert into `%s`(`%s`, `%s`) values (%d, %s)]], conf.tbl, conf.mysql_idname, + conf.mysql_valname, self.id, quote_sql_str(sval)) + + mysql_execute(conf.mysqld_addr, sql) + end + + if conf.redisd_addr then + local key, field = redis_key_field(conf) + redis_execute(conf.redisd_addr, "hset", key, field, sval) + end + else + self.dirty = true + end + self.rs = val +end + +function mt:collect_dirty(rediscmds, sqls) + local conf = self.conf + if not self.dirty then + return + end + local val = encode(self.rs) + local redisd_addr = conf.redisd_addr + local mysqld_addr = conf.mysqld_addr + + if redisd_addr then + local key, field = redis_key_field(conf) + local list = rediscmds[redisd_addr] + if not list then + list = {} + rediscmds[redisd_addr] = list + end + tinsert(list, {"hset", key, field, val}) + end + + if mysqld_addr then + local sql = strformat([[update `%s` set `%s` = %s where `%s` = %d limit 1]], conf.tbl, conf.mysql_valname, + quote_sql_str(val), conf.mysql_idname, self.id) + local list = sqls[mysqld_addr] + if not list then + list = {} + sqls[mysqld_addr] = list + end + tinsert(list, sql) + end + + self.dirty = false +end + +function mt:flush() + local conf = self.conf + local rediscmds = {} + local sqls = {} + + self:collect_dirty(rediscmds, sqls) + + if #rediscmds > 0 then + xpcall(redis_execute, traceback, conf.redisd_addr, rediscmds) + end + + if #sqls > 0 then + mysql_execute(conf.mysqld_addr, sqls) + end +end + +return { + new = function(...) + local obj = setmetatable({}, mt) + obj:ctor(...) + return obj + end, +} diff --git a/framework/lualib/zeus/storage/store.lua b/framework/lualib/zeus/storage/store.lua new file mode 100644 index 0000000..c6fcf27 --- /dev/null +++ b/framework/lualib/zeus/storage/store.lua @@ -0,0 +1,40 @@ +local skynet = require "skynet" +local entity_pool = requier "entity_pool" +local _store = {} + +local inited = false + +local function add_pool(name, opts) + if _store[name] then + error(("entity pool [%d] has exist"):format(name)) + end + _store[name] = entity_pool.new(opts) +end + +local function open(flush_interval) + if flush_interval then + skynet.fork(function() + while inited do + for _, pool in pairs(_store) do + pool:flush() + skynet.yield() + end + skynet.sleep(flush_interval*100) + end + end) + end +end + +local function close() + for _, pool in pairs(_store) do + pool:unload_all() + end + _store = {} + inited = false +end + +local store = setmetatable(_store, {__index = function(self, name) + error(("can not find entity pool [%s]"):format(name)) +end}) + +return store, add_pool, open, close diff --git a/framework/lualib/zeus/storage/store_util.lua b/framework/lualib/zeus/storage/store_util.lua new file mode 100644 index 0000000..1515018 --- /dev/null +++ b/framework/lualib/zeus/storage/store_util.lua @@ -0,0 +1,149 @@ +local skynet = require "skynet" +local json = require "json" + +local strformat = string.format +local strgsub = string.gsub +local random = math.random +local traceback = debug.traceback + +local mysqld_service_num = {} +local redisd_service_num = {} +local guid_generator_addrs + +local M = {} + +function M.config(opts) + local schema = opts.schema + assert(type(schema.tbl) == "string", schema.tbl) + + local conf = {} + conf.mysqld_addr = opts.mysqld_addr + conf.redisd_addr = opts.redisd_addr + conf.tbl = schema.tbl + conf.readonly = schema.readonly + conf.multi = false + if schema.multi then + conf.multi = true + end + if conf.mysqld then + local field = schema.field or {} + conf.mysql_valname = field.data or "data" + conf.mysql_idname = field.id or "id" + if conf.multi then + conf.mysql_owner_idname = field.owner_id or "owner_id" + end + end + return conf +end + +function M.mysql_sname(dbkey, index) + return ".mysqld." .. dbkey .. "." .. index +end + +function M.redis_sname(dbkey, index) + return ".redisd." .. dbkey .. "." .. index +end + +function M.balance_mysqld_addr(dbkey, id) + local num = mysqld_service_num[dbkey] + if not num then + num = skynet.call(".dbmgr", "lua", "mysql_service_num", dbkey) + mysqld_service_num[dbkey] = num + end + assert(num) + local sname = M.mysqld_sname(dbkey, (id % num) + 1) + return skynet.localname(sname) +end + +function M.balance_redisd_addr(dbkey, id) + local num = redisd_service_num[dbkey] + if not num then + num = skynet.call(".dbmgr", "lua", "redis_service_num", dbkey) + redisd_service_num[dbkey] = num + end + assert(num) + local sname = M.redis_sname(dbkey, (id % num) + 1) + return skynet.localname(sname) +end + +function M.guid() + if not guid_generator_addrs then + guid_generator_addrs = skynet.call(".dbmgr", "lua", "guid_generators") + end + assert(guid_generator_addrs, ".dbmgr must be init") + local num = #guid_generator_addrs + return skynet.call(guid_generator_addrs[random(1, num)], "lua") +end + +function M.encode(val) + if not val then + return + end + local ok, ret = xpcall(json.encode, traceback, val) + if not ok then + assert(false, strformat("encode error. val=[%s] ret=[%s]", tostring(val), tostring(ret))) + return + end + return ret +end + +function M.decode(val) + if not val then + return + end + local ok, ret = xpcall(json.decode, traceback, val) + if not ok then + assert(false, strformat("decode error. val=[%s] ret=[%s]", tostring(val), tostring(ret))) + return + end + return ret +end + +local escape_map = { + ['\0'] = "\\0", + ['\b'] = "\\b", + ['\n'] = "\\n", + ['\r'] = "\\r", + ['\t'] = "\\t", + ['\26'] = "\\Z", + ['\\'] = "\\\\", + ["'"] = "\\'", + ['"'] = '\\"', +} + +function M.quote_sql_str(str) + return strformat("'%s'", strgsub(str, "[\0\b\n\r\t\26\\\'\"]", escape_map)) +end + +function M.redis_key_field(conf, id) + if not conf.redisd_addr then + return + end + local key, field + if id then + key = conf.tbl .. ":" .. conf.id + field = tostring(id) + else + key = conf.tbl + field = conf.id + end + return key, field +end + +function M.mysql_execute(addr, sqls) + if type(sqls) == "table" then + return skynet.call(addr, "lua", "exec", sqls) + elseif type(sqls) == "string" then + return skynet.call(addr, "lua", "exec_one", sqls) + end +end + +function M.redis_execute(addr, cmd, ...) + if type(cmd) == "table" then + return skynet.call(addr, "lua", "exec", cmd) + elseif type(cmd) == "string" then + return skynet.call(addr, "lua", "exec_one", cmd, ...) + end +end + +return M diff --git a/framework/lualib/zeus/zenv/init.lua b/framework/lualib/zeus/zenv/init.lua index 8b74b3c..1a2d37a 100644 --- a/framework/lualib/zeus/zenv/init.lua +++ b/framework/lualib/zeus/zenv/init.lua @@ -17,6 +17,11 @@ M.Nodes = { nodeid = 100, name = "game", }, + + [110] = { + nodeid = 110, + name = "storage", + }, } function M.get_node_conf(nodeid) diff --git a/framework/lualib/zeus/zenv/redlock_conf.lua b/framework/lualib/zeus/zenv/redlock_conf.lua new file mode 100644 index 0000000..9ac884c --- /dev/null +++ b/framework/lualib/zeus/zenv/redlock_conf.lua @@ -0,0 +1,42 @@ +local conf = { + + -- redis服务器信息, redis服务器数为奇数 + -- 最好将奇数台reids部署在不同机器, 达到容错、高可用目的 + servers = {{ + host = "127.0.0.1", + port = 6379, + db = 0, + auth = "123456", + }}, + + -- uuid生成器, 请替换成真uuid(可使用uuid库uuid_generate) + make_uuid = function() + local fn = function(x) + local r = math.random(16) - 1 + r = (x == "x") and (r + 1) or (r % 4) + 9 + return ("0123456789abcdef"):sub(r, r) + end + return (("xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx"):gsub("[xy]", fn)) + end, + + -- 默认锁过期时间 + timeout = 3000, + + -- 用计算服务器漂移时间 (http://redis.io/topics/distlock) + drift_factor = 0.01, + + -- 重试获得锁最大次数 + retry_count = 10, + + -- 重试获得锁间隔(毫秒) + retry_delay = 200, + + -- 重试获得锁抖动时间(毫秒) https://www.awsarchitectureblog.com/2015/03/backoff.html + retry_jitter = 100, + + -- 请求redis超时时间(毫秒) + request_timeout = 500, +} + +return conf + diff --git a/framework/lualib/zeus/zlog/formatter/concat.lua b/framework/lualib/zeus/zlog/formatter/concat.lua new file mode 100644 index 0000000..c18b204 --- /dev/null +++ b/framework/lualib/zeus/zlog/formatter/concat.lua @@ -0,0 +1,16 @@ +local M = {} + +function M.new(sep) + sep = sep or ' ' + + return function(...) + local argc, argv = select('#', ...), {...} + for i = 1, argc do + argv[i] = tostring(argv[i]) + end + return (table.concat(argv, sep)) + end +end + +return M + diff --git a/framework/lualib/zeus/zlog/formatter/default.lua b/framework/lualib/zeus/zlog/formatter/default.lua new file mode 100644 index 0000000..268c456 --- /dev/null +++ b/framework/lualib/zeus/zlog/formatter/default.lua @@ -0,0 +1,9 @@ +local M = {} + +function M.new() + return function(msg) + return msg + end +end + +return M diff --git a/framework/lualib/zeus/zlog/formatter/format.lua b/framework/lualib/zeus/zlog/formatter/format.lua new file mode 100644 index 0000000..41340aa --- /dev/null +++ b/framework/lualib/zeus/zlog/formatter/format.lua @@ -0,0 +1,9 @@ +local string = require "string" + +local M = {} + +function M.new() + return string.format +end + +return M diff --git a/framework/lualib/zeus/zlog/formatter/mix.lua b/framework/lualib/zeus/zlog/formatter/mix.lua new file mode 100644 index 0000000..76177ab --- /dev/null +++ b/framework/lualib/zeus/zlog/formatter/mix.lua @@ -0,0 +1,24 @@ +--- +-- compatiable with lualogging +-- +local M = {} + +function M.new(default) + if not default then + default = require"log.formatter.format".new() + end + + return function(...) + if type((...)) == 'function' then + return (...)(select(2, ...)) + end + + if select('#', ...) < 2 then + return tostring((...)) + end + + return default(...) + end +end + +return M diff --git a/framework/lualib/zeus/zlog/formatter/pformat.lua b/framework/lualib/zeus/zlog/formatter/pformat.lua new file mode 100644 index 0000000..452c40b --- /dev/null +++ b/framework/lualib/zeus/zlog/formatter/pformat.lua @@ -0,0 +1,93 @@ +local lpeg = require "lpeg" +local table = require "table" +local string = require "string" + +local unpack = unpack or table.unpack + +local HAS_A_FORMAT = pcall(string.format, '%a', 10) + +local P, C, Cs, Ct, Cp, S, R = lpeg.P, lpeg.C, lpeg.Cs, lpeg.Ct, lpeg.Cp, lpeg.S, lpeg.R + +local any = P(1) +local empty = P(0) + +local esc = P '%%' +local flags = S '-+ #0' +local digit = R '09' + +local fsym = S('cdiouxXeEfgGqs' .. (HAS_A_FORMAT and 'aA' or '')) +local width = digit * digit + digit +local precision = P '.' * (digit * digit + digit) +local format = (flags + empty) * (width + empty) * (precision + empty) * (P '.' + empty) +local valid_format = P '%' * format * fsym +local valid_format_capture = Cs(valid_format) + +local any_fsym = any - (flags + digit + P '.') +local any_format = P '%' * (flags + digit + P '.') ^ 0 * any_fsym + +local types = { + c = 'number', + d = 'number', + i = 'number', + o = 'number', + u = 'number', + x = 'number', + X = 'number', + a = 'number', + A = 'number', + e = 'number', + E = 'number', + f = 'number', + g = 'number', + G = 'number', + q = 'string', + s = 'string', +} + +local function safe_format(protect_only_args, fmt, ...) + local n, args = 0, {...} + + local function fix_fmt(f) + local fmt = valid_format_capture:match(f) + + if not fmt then + if protect_only_args then + return + end + return '%' .. f + end + + local typ = string.sub(fmt, -1) + + n = n + 1 + + if types[typ] ~= type(args[n]) then + args[n], fmt = tostring(args[n]), '%s' + end + + return fmt + end + + local pattern = Cs((esc + any_format / fix_fmt + any) ^ 0) + fmt = pattern:match(fmt) + + return string.format(fmt, unpack(args, 1, n)) +end + +local function buld_formatter(protect_only_args, no_warning) + return function(...) + local ok, msg = pcall(string.format, ...) + if not ok then + local err = msg + msg = safe_format(protect_only_args, ...) + if not no_warning then + msg = msg .. ' - ' .. 'WARNING: Error formatting log message: ' .. err + end + end + return msg + end +end + +return { + new = buld_formatter, +} diff --git a/framework/lualib/zeus/zlog/log.lua b/framework/lualib/zeus/zlog/log.lua new file mode 100644 index 0000000..83668a7 --- /dev/null +++ b/framework/lualib/zeus/zlog/log.lua @@ -0,0 +1,223 @@ +--- +-- @module log +-- +local _COPYRIGHT = "Copyright (C) 2013-2016 Alexey Melnichuk"; +local _VERSION = "0.1.7-dev" + +local table = require "table" +local date = require "date" + +local destroy_list = {} +local loggers_list = setmetatable({}, { + __mode = 'k', +}) +local emptyfn = function() +end + +local LOG_LVL = { + EMERG = 1, + ALERT = 2, + FATAL = 3, + ERROR = 4, + WARNING = 5, + NOTICE = 6, + INFO = 7, + DEBUG = 8, + TRACE = 9, +} + +local writer_names = {} +local LOG_LVL_NAMES = {} +for k, v in pairs(LOG_LVL) do + LOG_LVL_NAMES[v] = k + writer_names[v] = k:lower() +end + +local LOG_LVL_COUNT = #LOG_LVL_NAMES + +local function lvl2number(lvl) + if type(lvl) == 'number' then + return lvl + end + if type(lvl) == 'string' then + lvl = lvl:upper() + local n + if lvl == 'NONE' then + n = 0 + else + n = LOG_LVL[lvl] + end + if not n then + return nil, "unknown log level: '" .. lvl .. "'" + end + return n + end + + return nil, 'unsupported log leve type: ' .. type(lvl) +end + +local Log = {} +Log._COPYRIGHT = _COPYRIGHT +Log._NAME = "log" +Log._VERSION = _VERSION +Log._LICENSE = "MIT" + +Log.LVL = LOG_LVL +Log.LVL_NAMES = LOG_LVL_NAMES + +Log.lvl2number = lvl2number + +function Log.new(max_lvl, writer, formatter, logformat) + if max_lvl and type(max_lvl) ~= 'number' and type(max_lvl) ~= 'string' then + max_lvl, writer, formatter, logformat = nil, max_lvl, writer, formatter + end + + max_lvl = assert(lvl2number(max_lvl or LOG_LVL.INFO)) + + if not writer then + writer = require"log.writer.stdout".new() + end + + if not formatter then + formatter = require"log.formatter.default".new() + end + + if not logformat then + logformat = require"log.logformat.default".new() + end + + local write = function(lvl, ...) + local now = date() + writer(logformat, formatter(...), lvl, now) + end; + + local dump = function(lvl, fn, ...) + local now = date() + writer(logformat, (fn(...) or ''), lvl, now) + end + + local logger = {} + + function logger.writer() + return writer + end + + function logger.formatter() + return formatter + end + + function logger.format() + return logformat + end + + function logger.lvl() + return max_lvl + end + + function logger.set_writer(value) + assert(value) + writer, value = value, writer + return value + end + + function logger.set_formatter(value) + assert(value) + formatter, value = value, formatter + return value + end + + function logger.set_format(value) + assert(value) + logformat, value = value, logformat + return value + end + + function logger.log(lvl, ...) + local err + lvl, err = lvl2number(lvl) + if not lvl then + return nil, err + end + return write(lvl, ...) + end + + function logger.dump(lvl, ...) + local err + lvl, err = lvl2number(lvl) + if not lvl then + return nil, err + end + return dump(lvl, ...) + end + + function logger.set_lvl(lvl) + local err + lvl, err = lvl2number(lvl) + if not lvl then + return nil, err + end + max_lvl = lvl + for i = 1, max_lvl do + logger[writer_names[i]] = function(...) + write(i, ...) + end + end + for i = 1, max_lvl do + logger[writer_names[i] .. '_dump'] = function(...) + dump(i, ...) + end + end + for i = max_lvl + 1, LOG_LVL_COUNT do + logger[writer_names[i]] = emptyfn + end + for i = max_lvl + 1, LOG_LVL_COUNT do + logger[writer_names[i] .. '_dump'] = emptyfn + end + return true + end + + assert(logger.set_lvl(max_lvl)) + + loggers_list[logger] = true; + + return logger +end + +function Log.add_cleanup(fn) + assert(type(fn) == 'function') + for k, v in ipairs(destroy_list) do + if v == fn then + return + end + end + table.insert(destroy_list, 1, fn) + return fn +end + +function Log.remove_cleanup(fn) + for k, v in ipairs(destroy_list) do + if v == fn then + table.remove(destroy_list, k) + break + end + end +end + +function Log.close() + for _, fn in ipairs(destroy_list) do + pcall(fn) + end + for logger in pairs(loggers_list) do + logger.fotal = emptyfn; + logger.error = emptyfn; + logger.warning = emptyfn; + logger.info = emptyfn; + logger.notice = emptyfn; + logger.debug = emptyfn; + logger.closed = true; + loggers_list[logger] = nil + end + destroy_list = {} +end + +return Log diff --git a/framework/lualib/zeus/zlog/logformat/default.lua b/framework/lualib/zeus/zlog/logformat/default.lua new file mode 100644 index 0000000..34e15ab --- /dev/null +++ b/framework/lualib/zeus/zlog/logformat/default.lua @@ -0,0 +1,18 @@ +local string = require "string" +local Log = require "log" + +local sformat = string.format +local function date_fmt(now) + local Y, M, D = now:getdate() + return sformat("%.4d-%.2d-%.2d %.2d:%.2d:%.2d", Y, M, D, now:gettime()) +end + +local M = {} + +function M.new() + return function(msg, lvl, now) + return date_fmt(now) .. ' [' .. Log.LVL_NAMES[lvl] .. '] ' .. msg + end +end + +return M diff --git a/framework/lualib/zeus/zlog/logformat/proxy.lua b/framework/lualib/zeus/zlog/logformat/proxy.lua new file mode 100644 index 0000000..a24931b --- /dev/null +++ b/framework/lualib/zeus/zlog/logformat/proxy.lua @@ -0,0 +1,11 @@ +local packer = require "log.logformat.proxy.pack" + +local M = {} + +function M.new() + return function(now, lvl, msg) + return packer.pack(now, lvl, msg) + end +end + +return M diff --git a/framework/lualib/zeus/zlog/logformat/proxy/pack.lua b/framework/lualib/zeus/zlog/logformat/proxy/pack.lua new file mode 100644 index 0000000..d43066b --- /dev/null +++ b/framework/lualib/zeus/zlog/logformat/proxy/pack.lua @@ -0,0 +1,36 @@ +local Log = require "log" +local date = require "date" +local string = require "string" +local schar = string.char +local sbyte = string.byte +local sformat = string.format +local ssub = string.sub +local tn = tonumber + +local M = {} + +function M.pack(msg, lvl, now) + local Y, M, D = now:getdate() + local h, m, s = now:gettime() + local now_s = sformat("%.4d-%.2d-%.2d %.2d:%.2d:%.2d", Y, M, D, h, m, s) + + return schar(lvl) .. now_s .. msg +end + +function M.unpack(str) + local lvl = sbyte(ssub(str, 1, 1)) + if not Log.LVL_NAMES[lvl] then + return + end + local now_s = ssub(str, 2, 20) + local Y, M, D = ssub(str, 2, 5), ssub(str, 7, 8), ssub(str, 10, 11) + local h, m, s = ssub(str, 13, 14), ssub(str, 16, 17), ssub(str, 19, 20) + Y, M, D, h, m, s = tn(Y), tn(M), tn(D), tn(h), tn(m), tn(s) + if not (Y and M and D and h and m and s) then + return + end + + return ssub(str, 21), lvl, date(Y, M, D, h, m, s) +end + +return M diff --git a/framework/lualib/zeus/zlog/logformat/syslog.lua b/framework/lualib/zeus/zlog/logformat/syslog.lua new file mode 100644 index 0000000..b06845d --- /dev/null +++ b/framework/lualib/zeus/zlog/logformat/syslog.lua @@ -0,0 +1,148 @@ +local string = require "string" +local math = require "math" +local Log = require "log" + +local mod, floor, ceil, abs, pow = math.fmod, math.floor, math.ceil, math.abs, math.pow +local fmt = string.format + +-- removes the decimal part of a number +local function fix(n) + n = tonumber(n) + return n and ((n > 0 and floor or ceil)(n)) +end + +local function lshift(a, b) + if math.pow then + return a * math.pow(2, b) + else + return a << b + end +end + +--[[ RFC5424 + 0 Emergency: system is unusable + 1 Alert: action must be taken immediately + 2 Critical: critical conditions + 3 Error: error conditions + 4 Warning: warning conditions + 5 Notice: normal but significant condition + 6 Informational: informational messages + 7 Debug: debug-level messages +--]] +local SEVERITY = { + EMERG = 0, + ALERT = 1, + CRIT = 2, + ERR = 3, + WARNING = 4, + NOTICE = 5, + INFO = 6, + DEBUG = 7, +} + +--[[ RFC5424 + 0 kernel messages + 1 user-level messages + 2 mail system + 3 system daemons + 4 security/authorization messages + 5 messages generated internally by syslogd + 6 line printer subsystem + 7 network news subsystem + 8 UUCP subsystem + 9 clock daemon + 10 security/authorization messages + 11 FTP daemon + 12 NTP subsystem + 13 log audit + 14 log alert + 15 clock daemon (note 2) + 16 local use 0 (local0) + 17 local use 1 (local1) + 18 local use 2 (local2) + 19 local use 3 (local3) + 20 local use 4 (local4) + 21 local use 5 (local5) + 22 local use 6 (local6) + 23 local use 7 (local7) +--]] +local FACILITY = { + KERN = lshift(0, 3), + USER = lshift(1, 3), + MAIL = lshift(2, 3), + DAEMON = lshift(3, 3), + AUTH = lshift(4, 3), + SYSLOG = lshift(5, 3), + LPR = lshift(6, 3), + NEWS = lshift(7, 3), + UUCP = lshift(8, 3), + CRON = lshift(9, 3), + CLKD = lshift(9, 3), + AUTHPRIV = lshift(10, 3), + FTP = lshift(11, 3), + NTP = lshift(12, 3), + SECURITY = lshift(13, 3), + AUDIT = lshift(13, 3), + CONSOLE = lshift(14, 3), + ALERT = lshift(14, 3), + CLKD2 = lshift(15, 3), + LOCAL0 = lshift(16, 3), + LOCAL1 = lshift(17, 3), + LOCAL2 = lshift(18, 3), + LOCAL3 = lshift(19, 3), + LOCAL4 = lshift(20, 3), + LOCAL5 = lshift(21, 3), + LOCAL6 = lshift(22, 3), + LOCAL7 = lshift(23, 3), +} + +local LVL2SYSLOG = { + [Log.LVL.EMERG] = SEVERITY.EMERG, + [Log.LVL.ALERT] = SEVERITY.ALERT, + [Log.LVL.FATAL] = SEVERITY.CRIT, + [Log.LVL.ERROR] = SEVERITY.ERR, + [Log.LVL.WARNING] = SEVERITY.WARNING, + [Log.LVL.NOTICE] = SEVERITY.NOTICE, + [Log.LVL.INFO] = SEVERITY.INFO, + [Log.LVL.DEBUG] = SEVERITY.DEBUG, + [Log.LVL.TRACE] = SEVERITY.DEBUG, +} + +local function Date2SysLog(now) + local Y, M, D = now:getdate() + local h, m, s = now:gettime() + + local b = -now:getbias(); + local x = abs(b); + + return fmt("%.4d-%.2d-%.2dT%.2d:%.2d:%.2d%s%.2d:%.2d", Y, M, D, h, m, s, b < 0 and "-" or "+", fix(x / 60), + floor(mod(x, 60))) +end + +local M = {} + +function M.new(facility, host_name, app_name, procid, msgid) + if not facility then + facility = FACILITY.USER + else + facility = FACILITY[facility:upper()] + end + host_name = host_name or '-' + app_name = app_name or '-' + procid = procid or '-' + msgid = msgid or '-' + + -- HOSTNAME APP-NAME PROCID MSGID + local header = host_name .. ' ' .. app_name .. ' ' .. procid .. ' ' .. msgid + + return function(msg, lvl, now) + local slvl = assert(LVL2SYSLOG[lvl]) + return -- HEADER + -- PRI VERSION TIMESTAMP + '<' .. slvl + facility .. '>1 ' .. Date2SysLog(now) .. ' ' .. -- HEADER STRUCTURED-DATA MSG + header .. ' - ' .. msg + + end +end + +return M diff --git a/framework/lualib/zeus/zlog/writer/async/_private/server.lua b/framework/lualib/zeus/zlog/writer/async/_private/server.lua new file mode 100644 index 0000000..c20687a --- /dev/null +++ b/framework/lualib/zeus/zlog/writer/async/_private/server.lua @@ -0,0 +1,101 @@ +local function prequire(...) + local ok, mod = pcall(require, ...) + return ok and mod, mod or nil +end + +local llthreads = prequire "llthreads2" +if not llthreads then + llthreads = require "llthreads" +end + +local runstring = function(code, ...) + code = [[do + local string = require "string" + local os = require "os" + local loadstring = loadstring or load + local lua_init = os.getenv("lua_init") + if lua_init and #lua_init > 0 then + if lua_init:sub(1,1) == '@' then dofile((lua_init:sub(2))) + else assert(loadstring(lua_init))() end + end + end;]] .. code + return llthreads.new(code, ...) +end + +local sleep +do + repeat + local socket = prequire "socket" + if socket then + sleep = function(ms) + socket.sleep(ms / 1000) + end + break + end + + local ztimer = prequire "lzmq.timer" + if ztimer then + sleep = ztimer.sleep + break + end + + -- @todo find another way (use os.execute("sleep " .. ms)) on *nix + + sleep = function() + end + break + until true +end + +local Worker = [=[ +(function(server, maker, logformat, ...) + local Log = require "log" + local logformat = require(logformat).new() + + local loadstring = loadstring or load + local writer = assert(loadstring(maker))() + + require(server).run(writer, Log.close, logformat, ...) +end)(...) +]=] + +local function run_server(server, maker, logformat, ...) + if type(maker) == 'function' then + maker = string.dump(maker) + end + + assert(type(server) == 'string') + assert(type(maker) == 'string') + assert(type(logformat) == 'string') + + local child_thread = assert(runstring(Worker, server, maker, logformat, ...)) + child_thread:start(true, true) + sleep(500) + return +end + +local Z +local function run_zserver(server, maker, logformat, ctx, ...) + Z = Z or require "log.writer.net.zmq._private.compat" + + if type(maker) == 'function' then + maker = string.dump(maker) + end + + assert(type(server) == 'string') + assert(type(maker) == 'string') + assert(type(logformat) == 'string') + assert(Z.is_ctx(ctx)) + + local zthreads = assert(Z.threads) + local child_thread = assert((zthreads.run or zthreads.runstring)(ctx, Worker, server, maker, logformat, ...)) + child_thread:start(true) + return +end + +local M = {} + +M.run = run_server +M.zrun = run_zserver + +return M diff --git a/framework/lualib/zeus/zlog/writer/async/lane.lua b/framework/lualib/zeus/zlog/writer/async/lane.lua new file mode 100644 index 0000000..be1516e --- /dev/null +++ b/framework/lualib/zeus/zlog/writer/async/lane.lua @@ -0,0 +1,22 @@ +local server = require "log.writer.async.server.lane" +local packer = require "log.logformat.proxy.pack" + +local function create_writer(channel, maker) + if maker then + server.run(channel, maker, "log.logformat.default") + end + + local queue = server.channel() + local pack = packer.pack + + return function(fmt, ...) + local msg = pack(...) + queue:send(channel, msg) + end +end + +local M = {} + +M.new = create_writer + +return M diff --git a/framework/lualib/zeus/zlog/writer/async/server/lane.lua b/framework/lualib/zeus/zlog/writer/async/server/lane.lua new file mode 100644 index 0000000..d9b21ae --- /dev/null +++ b/framework/lualib/zeus/zlog/writer/async/server/lane.lua @@ -0,0 +1,71 @@ +local on_lane_create = function() + local loadstring = loadstring or load + local lua_init = os.getenv("lua_init") + if lua_init and #lua_init > 0 then + if lua_init:sub(1, 1) == '@' then + dofile(lua_init:sub(2)) + else + assert(loadstring(lua_init))() + end + end +end + +local LOG = require "log" +local lanes = require"lanes".configure { + with_timers = false, + on_state_create = on_lane_create, +} +local pack = require"log.logformat.proxy.pack".pack + +local queue + +local function context() + queue = queue or assert(lanes.linda()) + return queue +end + +local function log_thread_fn(maker, logformat, channel) + local log_packer = require "log.logformat.proxy.pack" + local logformat = require(logformat).new() + local unpack = log_packer.unpack + + local loadstring = loadstring or load + local writer = assert(assert(loadstring(maker))()) + while (true) do + local key, val = queue:receive(1.0, channel) + -- print(maker, channel, key, val) + if not (key and val) then + key, val = nil, 'timeout' + end + if key then + local msg, lvl, now = unpack(val) + if msg and lvl and now then + writer(logformat, msg, lvl, now) + end + else + if val ~= 'timeout' then + io.stderror:write('lane_logger: ', val) + end + end + end +end + +local function start_log_thread(...) + local fn = assert(lanes.gen("*", log_thread_fn)) + return assert(fn(...)) +end + +local M = {} + +M.run = function(channel, maker, logformat) + logformat = logformat or "log.logformat.default" + context() -- init context + local child_thread = start_log_thread(maker, logformat, channel) + LOG.add_cleanup(function() + child_thread:cancel(60) + end) +end + +M.channel = context + +return M diff --git a/framework/lualib/zeus/zlog/writer/async/server/udp.lua b/framework/lualib/zeus/zlog/writer/async/server/udp.lua new file mode 100644 index 0000000..88d8aac --- /dev/null +++ b/framework/lualib/zeus/zlog/writer/async/server/udp.lua @@ -0,0 +1,10 @@ +local server = require "log.writer.async._private.server" + +local M = {} + +M.run = function(host, port, maker, logformat) + logformat = logformat or "log.logformat.default" + server.run("log.writer.net.server.udp", maker, logformat, host, port) +end + +return M diff --git a/framework/lualib/zeus/zlog/writer/async/server/zmq.lua b/framework/lualib/zeus/zlog/writer/async/server/zmq.lua new file mode 100644 index 0000000..3a42277 --- /dev/null +++ b/framework/lualib/zeus/zlog/writer/async/server/zmq.lua @@ -0,0 +1,42 @@ +local Z = require "log.writer.net.zmq._private.compat" +local IMPL = require "log.writer.net.zmq._private.impl" +local server = require "log.writer.async._private.server" + +local zmq, ETERM, zstrerror, zassert, zrecv = Z.zmq, Z.ETERM, Z.strerror, Z.assert, Z.recv + +local function rand_str(n) + math.randomseed(os.time()) + local str = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ' + local res = '' + for i = 1, n do + local n = math.random(1, #str) + res = res .. str:sub(n, n) + end + return res +end + +local function create_server(ctx, addr, maker, logformat) + if ctx and not Z.is_ctx(ctx) then + ctx, addr, maker, logformat = nil, ctx, addr, maker + end + logformat = logformat or "log.logformat.default" + + ctx = IMPL.context(ctx) + + if maker then + local addr_sync = 'inproc://' .. rand_str(15) + local skt_sync = zassert(ctx:socket(zmq.PAIR)) + zassert(skt_sync:bind(addr_sync)) + server.zrun("log.writer.net.server.zmq", maker, logformat, ctx, false, 'PULL', addr, addr_sync) + zassert(skt_sync:recv()) + skt_sync:close() + end +end + +local M = {} + +M.run = create_server + +M.context = IMPL.context + +return M diff --git a/framework/lualib/zeus/zlog/writer/async/udp.lua b/framework/lualib/zeus/zlog/writer/async/udp.lua new file mode 100644 index 0000000..a1f0c72 --- /dev/null +++ b/framework/lualib/zeus/zlog/writer/async/udp.lua @@ -0,0 +1,18 @@ +local server = require "log.writer.async.server.udp" + +local function create_writer(host, port, maker) + if maker then + server.run(host, port, maker, "log.logformat.default") + end + + local writer = require"log.writer.format".new(require"log.logformat.proxy".new(), + require"log.writer.net.udp".new(host, port)) + + return writer +end + +local M = {} + +M.new = create_writer + +return M diff --git a/framework/lualib/zeus/zlog/writer/async/zmq.lua b/framework/lualib/zeus/zlog/writer/async/zmq.lua new file mode 100644 index 0000000..785223a --- /dev/null +++ b/framework/lualib/zeus/zlog/writer/async/zmq.lua @@ -0,0 +1,21 @@ +local Z = require "log.writer.net.zmq._private.compat" +local server = require "log.writer.async.server.zmq" + +local function create_writer(ctx, addr, maker) + if ctx and not Z.is_ctx(ctx) then + ctx, addr, maker = nil, ctx, addr + end + + if maker then + server.run(ctx, addr, maker, "log.logformat.default") + end + + return require"log.writer.format".new(require"log.logformat.proxy".new(), + require"log.writer.net.zmq.push".new(ctx, addr)) +end + +local M = {} + +M.new = create_writer + +return M diff --git a/framework/lualib/zeus/zlog/writer/console.lua b/framework/lualib/zeus/zlog/writer/console.lua new file mode 100644 index 0000000..09a6aa0 --- /dev/null +++ b/framework/lualib/zeus/zlog/writer/console.lua @@ -0,0 +1 @@ +return require "log.writer.stdout" diff --git a/framework/lualib/zeus/zlog/writer/console/color.lua b/framework/lualib/zeus/zlog/writer/console/color.lua new file mode 100644 index 0000000..fc18bcb --- /dev/null +++ b/framework/lualib/zeus/zlog/writer/console/color.lua @@ -0,0 +1,334 @@ +local io = require "io" +local os = require "os" +local Log = require "log" + +local make_attr, color_writeln, COLORS + +if not COLORS then -- afx cio + local ok, cio = pcall(require, "cio") + if ok then + COLORS = { + BLACK = 'n', + BLUE = 'b', + GREEN = 'g', + CYAN = 'c', + RED = 'r', + MAGENTA = 'm', + BROWN = 'y', + LIGHTGRAY = 'w', + DARKGRAY = 'n+', + LIGHTBLUE = 'b+', + LIGHTGREEN = 'g+', + LIGHTCYAN = 'c+', + LIGHTRED = 'r+', + LIGHTMAGENTA = 'm+', + YELLOW = 'y+', + WHITE = 'w+', + } + make_attr = function(F, B) + return F .. '/' .. B + end + color_writeln = function(attr, text) + cio.textattr(attr) + cio.writeln(text or "") + end + end +end + +if not COLORS then -- conio + local ok, conio = pcall(require, "conio") + if ok then + COLORS = { + BLACK = conio.COLOR_BLACK, + BLUE = conio.COLOR_BLUE, + GREEN = conio.COLOR_GREEN, + CYAN = conio.COLOR_CYAN, + RED = conio.COLOR_RED, + MAGENTA = conio.COLOR_MAGENTA, + BROWN = conio.COLOR_BROWN, + LIGHTGRAY = conio.COLOR_LIGHTGRAY, + DARKGRAY = conio.COLOR_DARKGRAY, + LIGHTBLUE = conio.COLOR_LIGHTBLUE, + LIGHTGREEN = conio.COLOR_LIGHTGREEN, + LIGHTCYAN = conio.COLOR_LIGHTCYAN, + LIGHTRED = conio.COLOR_LIGHTRED, + LIGHTMAGENTA = conio.COLOR_LIGHTMAGENTA, + YELLOW = conio.COLOR_YELLOW, + WHITE = conio.COLOR_WHITE, + } + make_attr = function(F, B) + return {F, B} + end + color_writeln = function(attr, text) + conio.textcolor(attr[1]) + conio.textbackground(attr[2]) + conio.cputs((text or "") .. '\n') + end + end +end + +if not COLORS then -- ansicolors + local IS_WINDOWS = (package.config:sub(1, 1) == '\\') + if IS_WINDOWS and os.getenv("ANSICON") then + IS_WINDOWS = nil + end + local ok, c + if not IS_WINDOWS then + ok, c = pcall(require, "ansicolors") + end + if ok and (type(c) == 'table') then + if c.black then -- version above 1.0.2 + COLORS = { + BLACK = 1, + BLUE = 2, + GREEN = 3, + CYAN = 4, + RED = 5, + MAGENTA = 6, + BROWN = 7, + LIGHTGRAY = 8, + DARKGRAY = 9, + LIGHTBLUE = 10, + LIGHTGREEN = 11, + LIGHTCYAN = 12, + LIGHTRED = 13, + LIGHTMAGENTA = 14, + YELLOW = 15, + WHITE = 16, + } + local reset = tostring(c.reset) + local fore = { + [COLORS.BLACK] = c.black, + [COLORS.BLUE] = c.blue, + [COLORS.GREEN] = c.green, + [COLORS.CYAN] = c.cyan, + [COLORS.RED] = c.red, + [COLORS.MAGENTA] = c.magenta, + [COLORS.BROWN] = c.yellow, + [COLORS.LIGHTGRAY] = c.white, + [COLORS.DARKGRAY] = c.black .. c.bright, + [COLORS.LIGHTBLUE] = c.blue .. c.bright, + [COLORS.LIGHTGREEN] = c.green .. c.bright, + [COLORS.LIGHTCYAN] = c.cyan .. c.bright, + [COLORS.LIGHTRED] = c.red .. c.bright, + [COLORS.LIGHTMAGENTA] = c.magenta .. c.bright, + [COLORS.YELLOW] = c.yellow .. c.bright, + [COLORS.WHITE] = c.white .. c.bright, + } + + local back = { + [COLORS.BLACK] = c.onblack, + [COLORS.BLUE] = c.onblue, + [COLORS.GREEN] = c.ongreen, + [COLORS.CYAN] = c.oncyan, + [COLORS.RED] = c.onred, + [COLORS.MAGENTA] = c.onmagenta, + [COLORS.BROWN] = c.onyellow, + [COLORS.LIGHTGRAY] = c.onwhite, + [COLORS.DARKGRAY] = c.onblack .. c.bright, + [COLORS.LIGHTBLUE] = c.onblue .. c.bright, + [COLORS.LIGHTGREEN] = c.ongreen .. c.bright, + [COLORS.LIGHTCYAN] = c.oncyan .. c.bright, + [COLORS.LIGHTRED] = c.onred .. c.bright, + [COLORS.LIGHTMAGENTA] = c.onmagenta .. c.bright, + [COLORS.YELLOW] = c.onyellow .. c.bright, + [COLORS.WHITE] = c.onwhite .. c.bright, + } + + make_attr = function(F, B) + return fore[F] .. back[B] + end + color_writeln = function(attr, text) + io.write(attr, text, reset, '\n') + end + elseif c.noReset then -- 1.0.2 + COLORS = { + BLACK = 'black', + BLUE = 'blue', + GREEN = 'green', + CYAN = 'cyan', + RED = 'red', + MAGENTA = 'magenta', + BROWN = 'yellow', + LIGHTGRAY = 'white', + DARKGRAY = 'bright black', + LIGHTBLUE = 'bright blue', + LIGHTGREEN = 'bright green', + LIGHTCYAN = 'bright cyan', + LIGHTRED = 'bright red', + LIGHTMAGENTA = 'bright magenta', + YELLOW = 'bright yellow', + WHITE = 'bright white', + } + + make_attr = function(F, B) + return c.noReset("%{" .. F .. " " .. B .. "bg}") + end + local RESET = c.noReset("%{reset}") + color_writeln = function(attr, text) + io.write(attr, (text), RESET, '\n') + end + end + end + if ok and (type(c) == 'function') then + COLORS = { + BLACK = 1, + BLUE = 2, + GREEN = 3, + CYAN = 4, + RED = 5, + MAGENTA = 6, + BROWN = 7, + LIGHTGRAY = 8, + DARKGRAY = 9, + LIGHTBLUE = 10, + LIGHTGREEN = 11, + LIGHTCYAN = 12, + LIGHTRED = 13, + LIGHTMAGENTA = 14, + YELLOW = 15, + WHITE = 16, + } + local fore = { + [COLORS.BLACK] = "%{black}", + [COLORS.BLUE] = "%{blue}", + [COLORS.GREEN] = "%{green}", + [COLORS.CYAN] = "%{cyan}", + [COLORS.RED] = "%{red}", + [COLORS.MAGENTA] = "%{magenta}", + [COLORS.BROWN] = "%{yellow}", + [COLORS.LIGHTGRAY] = "%{white}", + [COLORS.DARKGRAY] = "%{black}" .. "%{bright}", + [COLORS.LIGHTBLUE] = "%{blue}" .. "%{bright}", + [COLORS.LIGHTGREEN] = "%{green}" .. "%{bright}", + [COLORS.LIGHTCYAN] = "%{cyan}" .. "%{bright}", + [COLORS.LIGHTRED] = "%{red}" .. "%{bright}", + [COLORS.LIGHTMAGENTA] = "%{magenta}" .. "%{bright}", + [COLORS.YELLOW] = "%{yellow}" .. "%{bright}", + [COLORS.WHITE] = "%{white}" .. "%{bright}", + } + local back = { + [COLORS.BLACK] = "%{blackbg}", + [COLORS.BLUE] = "%{bluebg}", + [COLORS.GREEN] = "%{greenbg}", + [COLORS.CYAN] = "%{cyanbg}", + [COLORS.RED] = "%{redbg}", + [COLORS.MAGENTA] = "%{magentabg}", + [COLORS.BROWN] = "%{yellowbg}", + [COLORS.LIGHTGRAY] = "%{whitebg}", + [COLORS.DARKGRAY] = "%{blackbg}" .. "%{bright}", + [COLORS.LIGHTBLUE] = "%{bluebg}" .. "%{bright}", + [COLORS.LIGHTGREEN] = "%{greenbg}" .. "%{bright}", + [COLORS.LIGHTCYAN] = "%{cyanbg}" .. "%{bright}", + [COLORS.LIGHTRED] = "%{redbg}" .. "%{bright}", + [COLORS.LIGHTMAGENTA] = "%{magentabg}" .. "%{bright}", + [COLORS.YELLOW] = "%{yellowbg}" .. "%{bright}", + [COLORS.WHITE] = "%{whitebg}" .. "%{bright}", + } + make_attr = function(F, B) + return fore[F] .. back[B] + end + color_writeln = function(attr, text) + io.write(c(attr .. text), '\n') + end + end +end + +if not COLORS then + local ok, term = pcall(require, "term") + if ok then + COLORS = { + BLACK = 1, + BLUE = 2, + GREEN = 3, + CYAN = 4, + RED = 5, + MAGENTA = 6, + BROWN = 7, + LIGHTGRAY = 8, + DARKGRAY = 9, + LIGHTBLUE = 10, + LIGHTGREEN = 11, + LIGHTCYAN = 12, + LIGHTRED = 13, + LIGHTMAGENTA = 14, + YELLOW = 15, + WHITE = 16, + } + + local c = term.colors + local reset = c.reset + local fore = { + [COLORS.BLACK] = c.black, + [COLORS.BLUE] = c.blue, + [COLORS.GREEN] = c.green, + [COLORS.CYAN] = c.cyan, + [COLORS.RED] = c.red, + [COLORS.MAGENTA] = c.magenta, + [COLORS.BROWN] = c.yellow, + [COLORS.LIGHTGRAY] = c.white, + [COLORS.DARKGRAY] = c.black .. c.bright, + [COLORS.LIGHTBLUE] = c.blue .. c.bright, + [COLORS.LIGHTGREEN] = c.green .. c.bright, + [COLORS.LIGHTCYAN] = c.cyan .. c.bright, + [COLORS.LIGHTRED] = c.red .. c.bright, + [COLORS.LIGHTMAGENTA] = c.magenta .. c.bright, + [COLORS.YELLOW] = c.yellow .. c.bright, + [COLORS.WHITE] = c.white .. c.bright, + } + + local back = { + [COLORS.BLACK] = c.onblack, + [COLORS.BLUE] = c.onblue, + [COLORS.GREEN] = c.ongreen, + [COLORS.CYAN] = c.oncyan, + [COLORS.RED] = c.onred, + [COLORS.MAGENTA] = c.onmagenta, + [COLORS.BROWN] = c.onyellow, + [COLORS.LIGHTGRAY] = c.onwhite, + [COLORS.DARKGRAY] = c.onblack .. c.bright, + [COLORS.LIGHTBLUE] = c.onblue .. c.bright, + [COLORS.LIGHTGREEN] = c.ongreen .. c.bright, + [COLORS.LIGHTCYAN] = c.oncyan .. c.bright, + [COLORS.LIGHTRED] = c.onred .. c.bright, + [COLORS.LIGHTMAGENTA] = c.onmagenta .. c.bright, + [COLORS.YELLOW] = c.onyellow .. c.bright, + [COLORS.WHITE] = c.onwhite .. c.bright, + } + + make_attr = function(F, B) + return fore[F] .. back[B] + end + color_writeln = function(attr, text) + io.write(attr .. text .. reset, '\n') + end + end +end + +if not COLORS then -- fallback to console + return require "log.writer.console" +end + +local colors = { + [Log.LVL.EMERG] = make_attr(COLORS.WHITE, COLORS.LIGHTRED), + [Log.LVL.ALERT] = make_attr(COLORS.BLUE, COLORS.LIGHTRED), + [Log.LVL.FATAL] = make_attr(COLORS.BLACK, COLORS.LIGHTRED), + [Log.LVL.ERROR] = make_attr(COLORS.LIGHTRED, COLORS.BLACK), + [Log.LVL.WARNING] = make_attr(COLORS.LIGHTMAGENTA, COLORS.BLACK), + [Log.LVL.NOTICE] = make_attr(COLORS.LIGHTCYAN, COLORS.BLACK), + [Log.LVL.INFO] = make_attr(COLORS.WHITE, COLORS.BLACK), + [Log.LVL.DEBUG] = make_attr(COLORS.YELLOW, COLORS.BLACK), + [Log.LVL.TRACE] = make_attr(COLORS.LIGHTGREEN, COLORS.BLACK), +} + +local function console_writer(fmt, msg, lvl, now) + color_writeln(colors[lvl], fmt(msg, lvl, now)) +end + +local M = {} + +function M.new() + return console_writer +end + +return M diff --git a/framework/lualib/zeus/zlog/writer/file.lua b/framework/lualib/zeus/zlog/writer/file.lua new file mode 100644 index 0000000..c8ad509 --- /dev/null +++ b/framework/lualib/zeus/zlog/writer/file.lua @@ -0,0 +1,17 @@ +local LOG = require "log" +local file_logger = require "log.writer.file.private.impl" + +local M = {} + +function M.new(opt) + local logger = file_logger:new(opt) + LOG.add_cleanup(function() + logger:close() + end) + + return function(fmt, msg, lvl, now) + logger:write((fmt(msg, lvl, now))) + end +end + +return M diff --git a/framework/lualib/zeus/zlog/writer/file/by_day.lua b/framework/lualib/zeus/zlog/writer/file/by_day.lua new file mode 100644 index 0000000..10fb3f1 --- /dev/null +++ b/framework/lualib/zeus/zlog/writer/file/by_day.lua @@ -0,0 +1,17 @@ +local file = require "log.writer.file" + +local M = {} + +function M.new(log_dir, log_name, max_rows) + return file.new { + log_dir = log_dir, + log_name = log_name, + max_rows = max_rows, + by_day = true, + close_file = false, + flush_interval = 1, + } +end + +return M + diff --git a/framework/lualib/zeus/zlog/writer/file/private/impl.lua b/framework/lualib/zeus/zlog/writer/file/private/impl.lua new file mode 100644 index 0000000..714a6c7 --- /dev/null +++ b/framework/lualib/zeus/zlog/writer/file/private/impl.lua @@ -0,0 +1,484 @@ +local Log = require "log" +local io = require "io" +local os = require "os" +local string = require "string" +local date = require "date" +local lfs = require "lfs" + +local DIR_SEP = package.config:sub(1, 1) +local IS_WINDOWS = DIR_SEP == '\\' + +local function remove_dir_end(str) + return (string.gsub(str, '[\\/]+$', '')) +end + +local function ensure_dir_end(str) + return remove_dir_end(str) .. DIR_SEP +end + +local function path_normolize_sep(P) + return (string.gsub(P, '\\', DIR_SEP):gsub('/', DIR_SEP)) +end + +local function path_fullpath(P) + P = path_normolize_sep(P) + local ch1, ch2 = P:sub(1, 1), P:sub(2, 2) + if IS_WINDOWS then + if ch1 == DIR_SEP then -- \temp => c:\temp + local cwd = lfs.currentdir() + local disk = cwd:sub(1, 2) + P = disk .. P + elseif ch1 == '~' then -- ~\temp + local base = os.getenv('USERPROFILE') or (os.getenv('HOMEDRIVE') .. os.getenv('HOMEPATH')) + P = ((ch2 == DIR_SEP) and remove_dir_end(base) or ensure_dir_end(base)) .. string.sub(P, 2) + elseif ch2 ~= ':' then + P = ensure_dir_end(lfs.currentdir()) .. P + end + else + if ch1 == '~' then -- ~/temp + local base = os.getenv('HOME') + P = ((ch2 == DIR_SEP) and remove_dir_end(base) or ensure_dir_end(base)) .. string.sub(P, 2) + else + if P:sub(1, 1) ~= '/' then + P = ensure_dir_end(lfs.currentdir()) .. P + end + end + end + + P = string.gsub(P, DIR_SEP .. '%.' .. DIR_SEP, DIR_SEP):gsub(DIR_SEP .. DIR_SEP, DIR_SEP) + while true do + local first, last = string.find(P, DIR_SEP .. "[^" .. DIR_SEP .. "]+" .. DIR_SEP .. '%.%.' .. DIR_SEP) + if not first then + break + end + P = string.sub(P, 1, first) .. string.sub(P, last + 1) + end + + return P +end + +local function attrib(P, ...) + if IS_WINDOWS then + if #P < 4 and P:sub(2, 2) == ':' then + P = ensure_dir_end(P) -- c: => c:\ + else + P = remove_dir_end(P) -- c:\temp\ => c:\temp + end + end + return lfs.attributes(P, ...) +end + +local function path_exists(P) + return attrib(P, 'mode') ~= nil and P +end + +local function path_isdir(P) + return attrib(P, 'mode') == 'directory' and P +end + +local function path_mkdir(P) + local P = path_fullpath(P) + local p = '' + + for str in string.gmatch(ensure_dir_end(P), '.-' .. DIR_SEP) do + p = p .. str + if path_exists(p) then + if not path_isdir(p) then + return nil, 'can not create ' .. p + end + else + local ok, err = lfs.mkdir(remove_dir_end(p)) + if not ok then + return nil, err .. ' ' .. p + end + end + end + + return true +end + +local function path_getctime(P) + return attrib(P, 'change') +end + +local function path_getmtime(P) + return attrib(P, 'modification') +end + +local function path_getatime(P) + return attrib(P, 'access') +end + +local function path_getsize(P) + return attrib(P, 'size') +end + +local function path_getrows(P) + local f, err = io.open(P, "r") + if not f then + return 0 + end + local count = 0 + for _ in f:lines() do + count = count + 1 + end + f:close() + return count +end + +local function path_remove(P) + return os.remove(P) +end + +local function path_rename(from, to) + path_remove(to) + return os.rename(from, to) +end + +local function reset_out(FileName, rewrite) + local END_OF_LINE = '\n' + local FILE_APPEND = 'a' + + if rewrite then + local FILE_REWRITE = 'w+' + local f, err = io.open(FileName, FILE_REWRITE); + if not f then + return nil, err + end + f:close(); + end + + return function(msg) + local f, err = io.open(FileName, FILE_APPEND) + if not f then + return nil, err + end + f:write(msg, END_OF_LINE) + f:close() + end +end + +local function make_no_close_reset(flush_interval) + return function(FileName, rewrite) + local END_OF_LINE = '\n' + local FILE_APPEND = 'a' + + if rewrite then + local FILE_REWRITE = 'w+' + local f, err = io.open(FileName, FILE_REWRITE); + if not f then + return nil, err + end + f:close() + end + + local f, err = io.open(FileName, FILE_APPEND); + if not f then + return nil, err + end + + local writer + if flush_interval then + local flush_interval, counter = flush_interval, 0 + writer = function(msg) + f:write(msg, END_OF_LINE) + counter = counter + 1 + if counter >= flush_interval then + f:flush() + counter = 0 + end + end + else + writer = function(msg) + f:write(msg, END_OF_LINE) + end + end + return writer, function() + f:close() + end + end +end + +local function split_ext(fname) + local s1, s2 = string.match(fname, '([^\\/]*)([.][^.\\/]*)$') + if s1 then + return s1, s2 + end + s1 = string.match(fname, '([^\\/]+)$') + if s1 then + return s1, '' + end +end + +local function assert_2(f1, f2, v1, v2) + assert(f1 == v1, string.format("Expected '%s' got '%s'", tostring(f1), tostring(v1))) + assert(f2 == v2, string.format("Expected '%s' got '%s'", tostring(f2), tostring(v2))) +end + +assert_2("events", ".log", split_ext("events.log")) +assert_2("events", '', split_ext("events")) +assert_2(nil, nil, split_ext("events\\")) +assert_2('', '.log', split_ext("events\\.log")) +assert_2('log', '', split_ext("events\\log")) + +local file_logger = {} + +local FILE_LOG_DATE_FMT = "%Y%m%d" +local EOL_SIZE = IS_WINDOWS and 2 or 1 + +local function get_file_date(fname) + local mdate = path_getmtime(fname) + if mdate then + mdate = date(mdate):tolocal() + else + mdate = date() + end + return mdate:fmt(FILE_LOG_DATE_FMT) +end + +function file_logger:close() + if self.private_.logger and self.private_.logger_close then + self.private_.logger_close() + end + self.private_.logger = nil + self.private_.logger_close = nil +end + +function file_logger:open() + local full_name = self:current_name() + + local logger, err = self.private_.reset_out(full_name) + if not logger then + return nil, string.format("can not create logger for file '%s':", full_name, err) + end + + self.private_.logger = logger + self.private_.logger_close = err + self.private_.log_date = os.date(FILE_LOG_DATE_FMT) + self.private_.log_rows = 0 + self.private_.log_size = 0 + + return true +end + +function file_logger:current_name() + return self.private_.log_dir .. self.private_.log_name +end + +function file_logger:archive_roll_name(i) + return self.private_.log_dir .. string.format("%s.%.5d.log", self.private_.arc_pfx, i) +end + +function file_logger:archive_date_name(d, i) + return self.private_.log_dir .. string.format("%s.%s.%.5d.log", self.private_.arc_pfx, d, i) +end + +function file_logger:reset_log_by_roll() + self:close() + + local full_name = self:current_name() + local first_name = self:archive_roll_name(1) + + -- we must "free" space for current file + if path_exists(first_name) then + for i = self.private_.roll_count - 1, 1, -1 do + local fname1 = self:archive_roll_name(i) + local fname2 = self:archive_roll_name(i + 1) + path_rename(fname1, fname2) + end + end + + if path_exists(full_name) then + local ok, err = path_rename(full_name, first_name) + if not ok then + return nil, string.format("can not rename '%s' to '%s' : %s", full_name, first_name, err or '') + end + end + + return self:open() +end + +function file_logger:next_date_name(log_date) + local id = self.private_.id + + local fname = self:archive_date_name(log_date, id) + while path_exists(fname) do + id = id + 1 + fname = self:archive_date_name(log_date, id) + end + + self.private_.id = id + return fname +end + +function file_logger:reset_log_by_date(log_date) + self:close() + + local full_name = self:current_name() + if path_exists(full_name) then -- previews file + log_date = log_date or get_file_date(full_name) + local next_fname = self:next_date_name(log_date) + local ok, err = path_rename(full_name, next_fname) + if not ok then + return nil, string.format("can not rename '%s' to '%s' : ", full_name, next_fname, err or '') + end + end + + return self:open() +end + +function file_logger:reset_log(...) + if self.private_.roll_count then + return self:reset_log_by_roll(...) + end + return self:reset_log_by_date(...) +end + +function file_logger:check() + if self.private_.by_day then + local now = os.date(FILE_LOG_DATE_FMT) + if self.private_.log_date ~= now then + local ok, err = self:reset_log_by_date(self.private_.log_date) + self.private_.id = 1 + return ok, err + end + end + + if self.private_.max_rows and (self.private_.log_rows >= self.private_.max_rows) then + return self:reset_log() + end + + if self.private_.max_size and (self.private_.log_size >= self.private_.max_size) then + return self:reset_log() + end + + return true +end + +function file_logger:write(msg) + local ok, err = self:check() + if not ok then + io.stderr:write("logger error: ", err, '\n') + return + end + + self.private_.logger(msg) + self.private_.log_rows = self.private_.log_rows + 1 + self.private_.log_size = self.private_.log_size + #msg + EOL_SIZE +end + +function file_logger:init(opt) + + if (opt.by_day or opt.roll_count) then + assert(not (opt.by_day and opt.roll_count), "Can not set 'by_day' and 'roll_count' fields at the same time!") + end + assert(opt.log_name, 'field log_name is required') + + local log_dir = path_fullpath(opt.log_dir or '.') + + if path_exists(log_dir) then + assert(path_isdir(log_dir)) + else + assert(path_mkdir(log_dir)) + end + + local log_name, log_ext = string.match(opt.log_name, '([^\\/]+)([.][^.\\/]+)$') + assert(log_name and log_ext) + + log_dir = ensure_dir_end(log_dir) + local full_name = log_dir .. log_name .. log_ext + local current_size = path_getsize(full_name) + if 0 == current_size then + -- prevent rename zero size logfile + path_remove(full_name) + end + + local flush_interval = opt.flush_interval and + assert(tonumber(opt.flush_interval), 'flush_interval must be a number') or 1 + self.private_ = { + -- options + log_dir = log_dir, + log_name = log_name .. log_ext, + max_rows = opt.max_rows or math.huge, + max_size = opt.max_size or math.huge, + reset_out = opt.close_file and reset_out or make_no_close_reset(flush_interval), + arc_pfx = opt.archive_prefix or log_name, + roll_count = opt.roll_count and assert(tonumber(opt.roll_count), 'roll_count must be a number'), + by_day = not not opt.by_day, + + -- state + -- log_date = ; -- date when current log file was create + -- log_rows = 0; -- how many lines in current log file + -- log_size = 0; + id = 1, -- numbers of file in current log_date + } + if self.private_.roll_count then + assert(self.private_.roll_count > 0) + end + + local reuse_log = opt.reuse + + if reuse_log and current_size and (current_size > 0) then + self.private_.log_date = get_file_date(full_name) + + if opt.max_rows then + self.private_.log_rows = path_getrows(full_name) or 0 + else + self.private_.log_rows = 0 + end + + if opt.max_size then + self.private_.log_size = path_getsize(full_name) or 0 + else + self.private_.log_size = 0 + end + + local logger, err = self.private_.reset_out(full_name) + if not logger then + error(string.format("can not create logger for file '%s':", full_name, err)) + end + + self.private_.logger = logger + self.private_.logger_close = err + + else + assert(self:reset_log()) + end + + return self +end + +function file_logger:new(...) + local o = setmetatable({}, { + __index = self, + }):init(...) + Log.add_cleanup(function() + o:close() + end) + return o +end + +local function do_profile() + require"profiler".start() + + local logger = file_logger:new{ + log_dir = './logs', + log_name = "events.log", + max_rows = 1000, + max_size = 70, + roll_count = 11, + -- by_day = true; + close_file = false, + flush_interval = 1, + reuse = true, + } + + for i = 1, 10000 do + local msg = string.format("%5d", i) + logger:write(msg) + end + + logger:close() +end + +return file_logger diff --git a/framework/lualib/zeus/zlog/writer/file/roll.lua b/framework/lualib/zeus/zlog/writer/file/roll.lua new file mode 100644 index 0000000..91f706a --- /dev/null +++ b/framework/lualib/zeus/zlog/writer/file/roll.lua @@ -0,0 +1,18 @@ +local file = require "log.writer.file" + +local M = {} + +function M.new(log_dir, log_name, roll_count, max_size) + return file.new { + log_dir = log_dir, + log_name = log_name, + max_size = max_size or 10 * 1024 * 1024, + roll_count = assert(roll_count), + close_file = false, + flush_interval = 1, + reuse = true, + } +end + +return M + diff --git a/framework/lualib/zeus/zlog/writer/filter.lua b/framework/lualib/zeus/zlog/writer/filter.lua new file mode 100644 index 0000000..0400f10 --- /dev/null +++ b/framework/lualib/zeus/zlog/writer/filter.lua @@ -0,0 +1 @@ +return require "log.writer.filter.lvl.le" diff --git a/framework/lualib/zeus/zlog/writer/filter/lvl/eq.lua b/framework/lualib/zeus/zlog/writer/filter/lvl/eq.lua new file mode 100644 index 0000000..53b8abc --- /dev/null +++ b/framework/lualib/zeus/zlog/writer/filter/lvl/eq.lua @@ -0,0 +1,14 @@ +local Log = require "log" + +local M = {} + +function M.new(max_lvl, writer) + max_lvl = assert(Log.lvl2number(max_lvl)) + return function(fmt, msg, lvl, now) + if lvl == max_lvl then + writer(fmt, msg, lvl, now) + end + end +end + +return M diff --git a/framework/lualib/zeus/zlog/writer/filter/lvl/le.lua b/framework/lualib/zeus/zlog/writer/filter/lvl/le.lua new file mode 100644 index 0000000..599b012 --- /dev/null +++ b/framework/lualib/zeus/zlog/writer/filter/lvl/le.lua @@ -0,0 +1,14 @@ +local Log = require "log" + +local M = {} + +function M.new(max_lvl, writer) + max_lvl = assert(Log.lvl2number(max_lvl)) + return function(fmt, msg, lvl, now) + if lvl <= max_lvl then + writer(fmt, msg, lvl, now) + end + end +end + +return M diff --git a/framework/lualib/zeus/zlog/writer/format.lua b/framework/lualib/zeus/zlog/writer/format.lua new file mode 100644 index 0000000..50a7572 --- /dev/null +++ b/framework/lualib/zeus/zlog/writer/format.lua @@ -0,0 +1,9 @@ +local M = {} + +function M.new(newfmt, writer) + return function(oldfmt, msg, lvl, now) + writer(newfmt, msg, lvl, now) + end +end + +return M diff --git a/framework/lualib/zeus/zlog/writer/freeswitch.lua b/framework/lualib/zeus/zlog/writer/freeswitch.lua new file mode 100644 index 0000000..025a1a8 --- /dev/null +++ b/framework/lualib/zeus/zlog/writer/freeswitch.lua @@ -0,0 +1,42 @@ +local log = require "log" + +local freeswitch = assert(freeswitch) + +local FS_LVL = { + console = 0, + alert = 1, + crit = 2, + err = 3, + warning = 4, + notice = 5, + info = 6, + debug = 7, +} + +local LOG2FS = { + [log.LVL.EMERG] = FS_LVL.alert, + [log.LVL.ALERT] = FS_LVL.alert, + [log.LVL.FATAL] = FS_LVL.crit, + [log.LVL.ERROR] = FS_LVL.err, + [log.LVL.WARNING] = FS_LVL.warning, + [log.LVL.NOTICE] = FS_LVL.notice, + [log.LVL.INFO] = FS_LVL.info, + [log.LVL.DEBUG] = FS_LVL.debug, + [log.LVL.TRACE] = FS_LVL.debug, +} + +local M = {} + +function M.new(session) + if session then + return function(fmt, msg, lvl, now) + session:consoleLog(LOG2FS[lvl], msg .. '\n') + end + else + return function(fmt, msg, lvl, now) + freeswitch.consoleLog(LOG2FS[lvl], msg .. '\n') + end + end +end + +return M diff --git a/framework/lualib/zeus/zlog/writer/list.lua b/framework/lualib/zeus/zlog/writer/list.lua new file mode 100644 index 0000000..77147d4 --- /dev/null +++ b/framework/lualib/zeus/zlog/writer/list.lua @@ -0,0 +1,13 @@ +local M = {} + +function M.new(...) + local writers = {...} + return function(...) + for i = 1, #writers do + writers[i](...) + end + end +end + +return M + diff --git a/framework/lualib/zeus/zlog/writer/net/server/udp.lua b/framework/lualib/zeus/zlog/writer/net/server/udp.lua new file mode 100644 index 0000000..9e9ce59 --- /dev/null +++ b/framework/lualib/zeus/zlog/writer/net/server/udp.lua @@ -0,0 +1,29 @@ +local socket = require "socket" +local log_packer = require "log.logformat.proxy.pack" + +local _M = {} + +function _M.run(writer, final, logformat, host, port) + local uskt = assert(socket.udp()) + assert(uskt:setsockname(host, port)) + local unpack = log_packer.unpack + + while true do + local msg, err = uskt:receivefrom() + if msg then + local msg, lvl, now = unpack(msg) + if msg and lvl and now then + writer(logformat, msg, lvl, now) + end + else + if err ~= 'timeout' then + io.stderr:write('log.writer.net.udp.server: ', err, '\n') + end + end + end + + -- @todo + -- if final then final() end +end + +return _M diff --git a/framework/lualib/zeus/zlog/writer/net/server/zmq.lua b/framework/lualib/zeus/zlog/writer/net/server/zmq.lua new file mode 100644 index 0000000..24c0855 --- /dev/null +++ b/framework/lualib/zeus/zlog/writer/net/server/zmq.lua @@ -0,0 +1,55 @@ +local Z = require "log.writer.net.zmq._private.compat" +local IMPL = require "log.writer.net.zmq._private.impl" + +local zmq, ETERM, zstrerror, zassert, zrecv = Z.zmq, Z.ETERM, Z.strerror, Z.assert, Z.recv +local zerrcode = Z.errcode + +local log_packer = require "log.logformat.proxy.pack" + +local _M = {} + +function _M.run(writer, final, logformat, ctx, stype, address, addr_sync) + -- print(writer, logformat, ctx, stype, address, addr_sync) + local stypes = { + SUB = zmq.SUB, + PULL = zmq.PULL, + } + stype = assert(stypes[stype], 'Unsupported socket type') + + ctx = IMPL.context(ctx) + + local skt = zassert(ctx:socket(stype)) + zassert(skt:bind(address)) + + if addr_sync then + local skt_sync = zassert(ctx:socket(zmq.PAIR)) + zassert(skt_sync:connect(addr_sync)) + skt_sync:send("") + skt_sync:close() + end + + local unpack = log_packer.unpack + + while (true) do + local msg, err = zrecv(skt) + if msg then + local msg, lvl, now = unpack(msg) + if msg and lvl and now then + writer(logformat, msg, lvl, now) + end + else + if zerrcode(err) == ETERM then + break + end + io.stderr:write('log.writer.net.zmq.server: ', tostring(err), zstrerror(err), '\n') + end + end + + if final then + final() + end + + skt:close() +end + +return _M diff --git a/framework/lualib/zeus/zlog/writer/net/smtp.lua b/framework/lualib/zeus/zlog/writer/net/smtp.lua new file mode 100644 index 0000000..994adea --- /dev/null +++ b/framework/lualib/zeus/zlog/writer/net/smtp.lua @@ -0,0 +1,26 @@ +local Log = require "log" +local sendmail = require "sendmail" + +local M = {} + +function M.new(from, to, server, subject) + assert(to, "'to' parameter is required") + assert(from, "'from' parameter is required") + assert(server, "'server' parameter is required") + + subject = subject or '' + + return function(fmt, msg, lvl, now) + msg = fmt(msg, lvl, now) + sendmail(from, to, server, { + subject = now:fmt("%F %T") .. ' [' .. Log.LVL_NAMES[lvl] .. '] ' .. subject, + file = { + name = 'message.txt', + data = msg, + }, + text = msg, + }) + end +end + +return M diff --git a/framework/lualib/zeus/zlog/writer/net/udp.lua b/framework/lualib/zeus/zlog/writer/net/udp.lua new file mode 100644 index 0000000..faf9561 --- /dev/null +++ b/framework/lualib/zeus/zlog/writer/net/udp.lua @@ -0,0 +1,19 @@ +local socket = require("socket") + +local function create_socket(host, port, timeout) + local skt = assert(socket.udp()) + assert(skt:settimeout(timeout or 0.1)) + assert(skt:setpeername(host, port)) + return skt +end + +local M = {} + +function M.new(host, port, timeout) + local skt = create_socket(host, port, timeout) + return function(fmt, ...) + skt:send((fmt(...))) + end +end + +return M diff --git a/framework/lualib/zeus/zlog/writer/net/zmq.lua b/framework/lualib/zeus/zlog/writer/net/zmq.lua new file mode 100644 index 0000000..19e6715 --- /dev/null +++ b/framework/lualib/zeus/zlog/writer/net/zmq.lua @@ -0,0 +1 @@ +return require "log.writer.net.zmq.pub" diff --git a/framework/lualib/zeus/zlog/writer/net/zmq/_private/compat.lua b/framework/lualib/zeus/zlog/writer/net/zmq/_private/compat.lua new file mode 100644 index 0000000..24a0e33 --- /dev/null +++ b/framework/lualib/zeus/zlog/writer/net/zmq/_private/compat.lua @@ -0,0 +1,150 @@ +local function prequire(...) + local ok, mod = pcall(require, ...) + return ok and mod, mod or nil +end + +local zmq, zthreads, zpoller +local zstrerror, zassert, ETERM +local zconnect, zbind +local zrecv_all, zrecv +local zerrcode + +local function has_member(t, key) + local ok, has + if type(key) == "table" then + ok, has = pcall(function() + for _, k in ipairs(key) do + if nil == t[k] then + return false + end + end + return true + end) + else + ok, has = pcall(function() + return nil ~= t[key] + end) + end + return ok and has +end + +local function is_ctx(ctx) + local tname = type(ctx) + if (tname ~= 'table') and (tname ~= 'userdata') then + return false + end + return has_member(ctx, {'socket', 'term'}) +end + +zmq = prequire "lzmq" +if zmq then + zpoller = prequire "lzmq.poller" + zthreads = prequire "lzmq.threads" + ETERM = zmq.errors.ETERM + zstrerror = function(err) + if type(err) == "number" then + return zmq.strerror(err) + end + if type(err) == "string" then + return err + end + return err:msg() + end + zerrcode = function(err) + if type(err) == "number" then + return err + end + if type(err) == "string" then + return err + end -- @todo extract no from string + return err:no() + end + zassert = zmq.assert + zrecv_all = function(skt) + return skt:recv_all() + end + zconnect = function(skt, addr) + return skt:connect(addr) + end + zbind = function(skt, addr) + return skt:bind(addr) + end +else + zmq = require "zmq" + zpoller = require "zmq.poller" + zthreads = prequire "zmq.threads" + ETERM = 'closed' + zstrerror = function(err) + return err + end + zerrcode = function(err) + return err + end + zassert = assert + zrecv_all = function(skt) + local t = {} + local r, err = skt:recv() + if not r then + return nil, err + end + table.insert(t, r) + while skt:rcvmore() == 1 do + r, err = skt:recv() + if not r then + return nil, err, t + end + table.insert(t, r) + end + return t + end + zconnect = function(skt, addr) + if type(addr) == 'table' then + for i, a in ipairs(addr) do + local ok, err = skt:connect(a) + if not ok then + return nil, err, i + end + end + return true + end + return skt:connect(addr) + end + zbind = function(skt, addr) + if type(addr) == 'table' then + for i, a in ipairs(addr) do + local ok, err = skt:bind(a) + if not ok then + return nil, err, i + end + end + return true + end + return skt:bind(addr) + end +end + +zrecv = function(skt) + local ok, err, t = zrecv_all(skt) + if not ok then + if t and t[1] then + return t[1] + end + return nil, err + end + return ok[1] +end + +return { + zmq = zmq, + threads = zthreads, + poller = zpoller, + connect = zconnect, + bind = zbind, + recv_all = zrecv_all, + recv = zrecv, + strerror = zstrerror, + errcode = zerrcode, + assert = zassert, + ETERM = ETERM, + is_ctx = is_ctx, +} diff --git a/framework/lualib/zeus/zlog/writer/net/zmq/_private/impl.lua b/framework/lualib/zeus/zlog/writer/net/zmq/_private/impl.lua new file mode 100644 index 0000000..a7a2cd2 --- /dev/null +++ b/framework/lualib/zeus/zlog/writer/net/zmq/_private/impl.lua @@ -0,0 +1,74 @@ +local Log = require "log" +local Z = require "log.writer.net.zmq._private.compat" + +local zmq, zthreads = Z.zmq, Z.threads +local zstrerror, zassert = Z.strerror, Z.assert +local ETERM = Z.ETERM +local zconnect, zbind = Z.connect, Z.bind + +local log_ctx + +local function context(ctx) + -- we have to use same context for all writers + if ctx and log_ctx then + assert(ctx == log_ctx) + end + + if log_ctx then + return log_ctx + end + + log_ctx = ctx or (zthreads and zthreads.get_parent_ctx()) or zassert(zmq.init(1)) + + return log_ctx +end + +local function socket(ctx, stype, is_srv, addr, timeout) + local stypes = { + PUSH = zmq.PUSH, + PUB = zmq.PUB, + } + stype = assert(stypes[stype], 'Unsupported socket type') + timeout = timeout or 100 + ctx = context(ctx) + + local skt = ctx:socket(stype) + if ctx.autoclose then + ctx:autoclose(skt) + end + skt:set_sndtimeo(timeout) + skt:set_linger(timeout) + if is_srv then + zassert(zbind(skt, addr)) + else + zassert(zconnect(skt, addr)) + end + if not ctx.autoclose then + Log.add_cleanup(function() + skt:close() + end) + end + return skt +end + +local function init(stype, is_srv) + local M = {} + + function M.new(ctx, addr, timeout) + if ctx and not Z.is_ctx(ctx) then + ctx, addr, timeout = nil, ctx, addr + end + + local skt = socket(ctx, stype, is_srv, addr, timeout) + return function(fmt, ...) + skt:send((fmt(...))) + end + end + + return M +end + +return { + init = init, + context = context, +} diff --git a/framework/lualib/zeus/zlog/writer/net/zmq/pub.lua b/framework/lualib/zeus/zlog/writer/net/zmq/pub.lua new file mode 100644 index 0000000..e365d7d --- /dev/null +++ b/framework/lualib/zeus/zlog/writer/net/zmq/pub.lua @@ -0,0 +1 @@ +return require"log.writer.net.zmq._private.impl".init('PUB', false) diff --git a/framework/lualib/zeus/zlog/writer/net/zmq/push.lua b/framework/lualib/zeus/zlog/writer/net/zmq/push.lua new file mode 100644 index 0000000..517fc43 --- /dev/null +++ b/framework/lualib/zeus/zlog/writer/net/zmq/push.lua @@ -0,0 +1 @@ +return require"log.writer.net.zmq._private.impl".init('PUSH', false) diff --git a/framework/lualib/zeus/zlog/writer/net/zmq/srv/pub.lua b/framework/lualib/zeus/zlog/writer/net/zmq/srv/pub.lua new file mode 100644 index 0000000..0ca1db8 --- /dev/null +++ b/framework/lualib/zeus/zlog/writer/net/zmq/srv/pub.lua @@ -0,0 +1 @@ +return require"log.writer.net.zmq._private.impl".init('PUB', true) diff --git a/framework/lualib/zeus/zlog/writer/prefix.lua b/framework/lualib/zeus/zlog/writer/prefix.lua new file mode 100644 index 0000000..9fb4202 --- /dev/null +++ b/framework/lualib/zeus/zlog/writer/prefix.lua @@ -0,0 +1,9 @@ +local M = {} + +function M.new(prefix, writer) + return function(fmt, msg, lvl, now) + writer(fmt, prefix .. msg, lvl, now) + end +end + +return M diff --git a/framework/lualib/zeus/zlog/writer/stderr.lua b/framework/lualib/zeus/zlog/writer/stderr.lua new file mode 100644 index 0000000..34a7ba1 --- /dev/null +++ b/framework/lualib/zeus/zlog/writer/stderr.lua @@ -0,0 +1,11 @@ +local io = require "io" + +local M = {} + +function M.new() + return function(fmt, ...) + io.stderr:write(fmt(...), '\n') + end +end + +return M diff --git a/framework/lualib/zeus/zlog/writer/stdout.lua b/framework/lualib/zeus/zlog/writer/stdout.lua new file mode 100644 index 0000000..f2a5f99 --- /dev/null +++ b/framework/lualib/zeus/zlog/writer/stdout.lua @@ -0,0 +1,11 @@ +local io = require "io" + +local M = {} + +function M.new() + return function(fmt, ...) + io.stdout:write(fmt(...), '\n') + end +end + +return M diff --git a/framework/service/dbmgr.lua b/framework/service/dbmgr.lua new file mode 100644 index 0000000..045511a --- /dev/null +++ b/framework/service/dbmgr.lua @@ -0,0 +1,78 @@ +local skynet = require "skynet" +local queue = require "skynet.queue" +local util = require "store_util" +require "skynet.manager" + +local dbconf +local lock = queue() +local guid_generator_addrs = {} + +local CMD = {} + +function CMD.init(conf) + assert(not dbconf, "dbmgr has been initialized.") + dbconf = conf -- init is allowed only once + + local uidconf = dbconf.guid_generator + assert(uidconf) + for _, worker_id in pairs(uidconf.worker_ids) do + local addr = skynet.newservice("guid_generator", worker_id) + table.insert(guid_generator_addrs, addr) + end + + local redisconf = dbconf.redis + for dbkey, conf in pairs(redisconf) do + for index = 1, conf.service_num do + local addr = skynet.newservice("redisd", dbkey, index) + local ok = skynet.call(addr, "lua", "init", conf) + if not ok then + assert(false, ("redisd init failed. [dbkey] %s [id] %d"):format(dbkey, index)) + end + end + end + + local mysqlconf = dbconf.mysql + for dbkey, conf in pairs(mysqlconf) do + for index = 1, conf.service_num do + local addr = skynet.newservice("mysqld", dbkey, index) + local ok = skynet.call(addr, "lua", "init", conf) + if not ok then + assert(false, ("mysqld init failed. [dbkey] %s [id] %d"):format(dbkey, index)) + end + end + end + + return true +end + +function CMD.mysql_service_num(dbkey) + if not dbconf then return end + local mysqlconf = dbconf.mysql + if not mysqlconf then return end + local conf = mysqlconf[dbkey] + if not conf then return end + return conf.service_num +end + +function CMD.redis_service_num(dbkey) + if not dbconf then return end + local redisconf = dbconf.redis + if not redisconf then return end + local conf = redisconf[dbkey] + if not conf then return end + return conf.service_num +end + +function CMD.guid_generators() + return guid_generator_addrs +end + +skynet.start(function() + skynet.dispatch("lua", function(_, _, cmd, ...) + local f = CMD[cmd] + assert(f, cmd) + skynet.retpack(f(...)) + end) +end) + +skynet.register(".dbmgr") \ No newline at end of file diff --git a/framework/service/gdd.lua b/framework/service/gdd.lua new file mode 100644 index 0000000..1be0d27 --- /dev/null +++ b/framework/service/gdd.lua @@ -0,0 +1,34 @@ +local skynet = require "skynet" +local st = require "skynet.sharetable" +local mc = require "skynet.multicast" + +local channel + +local CMD = {} + +function CMD.query(filename) + assert(channel) + return st.query(filename) +end + +function CMD.loadfiles(filenames) + assert(type(filenames) == "table") + for _, filename in pairs(filenames) do + st.loadfile(filename) + end + channel:publish(filenames) +end + +function CMD.channel() + return channel.channel +end + +skynet.start(function() + channel = mc.new() + skynet.dispatch("lua", function(_, _, cmd, ...) + local f = CMD[cmd] + assert(f, cmd) + skynet.retpack(f(...)) + end) +end) + diff --git a/framework/service/guild_generator.lua b/framework/service/guild_generator.lua new file mode 100644 index 0000000..28a8d61 --- /dev/null +++ b/framework/service/guild_generator.lua @@ -0,0 +1,52 @@ +local skynet = require "skynet" +local time = skynet.time +local floor = math.floor +local ceil = math.ceil + +--[[ +sign | delta seconds | worker id | sequence +1bit | 30bits | 20bits | 13bits + +sign - 0为正数 1为负数 +delta seconds - 2020/01/01到当前时间时间差, 30bits可以用到2054-01-09 +worker id - 0~1048575 每个服务worker id都应该不一样,才能保证唯一性, worker_id需使用者自行规划 +sequence - 0~8191 当前时刻的id序列 +]] + +local DELTA_SECS_BITS = 30 +local WORKER_ID_BITS = 20 +local SEQ_BITS = 13 +local TIME_LEFT_SHIFT_BITS = WORKER_ID_BITS + SEQ_BITS + +local START_TIME = 1577808000 -- 2020/01/01 +local MAX_WORKER_ID = (1<= 0 and worker_id <= MAX_WORKER_ID) + +local seq = 0 +local last_delta_time = 0 +local gen_guid + +gen_guid = function() + local now = time() + local floor_now = floor(now) + local delta_time = floor_now - START_TIME + if last_delta_time ~= delta_time then + last_delta_time = delta_time + seq = 0 + elseif seq < MAX_SEQ then + seq = seq + 1 + else + skynet.sleep(100 - ceil(now - floor_now)*100) + return gen_guid() + end + return (delta_time<= QUORUM and s.endtime > now then + local ti = s.timeout / 3 - (now - s.starttime) + ti = math_floor(ti / 10) + if ti < 0 then + ti = 0 + end + skynet.timeout(ti, function() + if s.endtime == 0 then + return + end + s.attempts = 0 + calc_time(s) + attempt(s, true) + end) + return true + else + unlock(s) + -- retry + if conf.retry_count == -1 or s.attempts <= conf.retry_count then + local t = conf.retry_delay + math_floor((math_random() * 2 - 1) * conf.retry_jitter) + skynet.sleep(math_ceil(t / 10)) + calc_time(s) + return attempt(s) + end + -- failed + sessions[s.uuid] = nil + return false, "timeout" + end +end + +local CMD = {} + +function CMD.lock(lockname, uuid, timeout) + timeout = timeout or conf.timeout + local s = sessions[uuid] + if s then + return false, "session exist" + end + s = make_session(lockname, uuid, timeout) + sessions[uuid] = s + + return attempt(s) +end + +function CMD.unlock(_, uuid) + local s = sessions[uuid] + if not s then + return false, "session not exist." + end + sessions[uuid] = nil + return unlock(s) +end + +skynet.init(function() + conf = require "redlock_conf" + for _, client in ipairs(conf.servers) do + table.insert(dbs, redis.connect(client)) + end + QUORUM = math_floor(#conf.servers / 2) + 1 +end) + +skynet.start(function() + skynet.dispatch("lua", function(_, _, cmd, ...) + local f = CMD[cmd] + assert(f, cmd) + skynet.retpack(f(...)) + end) +end) + diff --git a/framework/service/template.lua b/framework/service/template.lua index 2deb775..0bf4574 100644 --- a/framework/service/template.lua +++ b/framework/service/template.lua @@ -1,16 +1,16 @@ -- 服务处理模版 +local skynet = require "skynet" local fsm = require "zeus.fsm" +local shiftimer = require "skynet.shiftimer" + +--------------------------------------------------------------- +--------------------------------------------------------------- local Template = class("Template"):include(singleton) function Template:initialize() self._cmds = {} -- 已注册的命令 - self._exceptions = {} -- 已注册异常处理 - self._updaters = {} -- 定时器 - self._newday = { -- newday只允许注册一次且不可删除 - curDay = os.date("%d", os.time()), -- 当前天 - handle = nil, -- newday updater的句柄 - } + self._shiftimer = shiftimer.new() -- 定时器 self._fsm = fsm.create({ initial = "none", @@ -38,6 +38,7 @@ function Template:initialize() on_exit = handler("_on_exit", self), }, }) + end function Template:get_fsm() @@ -48,15 +49,48 @@ function Template:_on_initial() end function Template:_on_running() + self._shiftimer:star() end function Template:_on_stop() end function Template:_on_exit() + self._shiftimer:stop() +end + +function Template:reg_cmd(cmd) + if self.m_cmds[cmd] then + skynet.error(cmd, "was registered already") + return + end + + self.m_cmds[cmd] = cmd +end + +function Template:reg_cmd_ex(obj, cmd, ...) + assert(not self[cmd], "reg cmd handler exsit") + self[cmd] = function(_, ...) + return obj[cmd](obj, ...) + end + self:reg_cmd(cmd, ...) +end + +function Template:get_cmd_func(cmd) + local func = self[cmd] + if func then + return func + end + + return self.m_cmds[cmd] +end + +function Template:add_timer(ti, func, args) + return self._shiftimer:add(ti * 100, func, args) end -function Template:reg_cmd() +function Template:remove_timer(tid) + return self._shiftimer:delete(tid) end return Template diff --git a/framework/service/zeus.lua b/framework/service/zeus.lua index 645ca61..2d59719 100644 --- a/framework/service/zeus.lua +++ b/framework/service/zeus.lua @@ -1 +1,60 @@ ---统一的服务基础逻辑 \ No newline at end of file +-- 统一的服务基础逻辑 +local _service_name, _service_path = ... +assert(_service_name, "_service_name nil") +assert(_service_path, "_service_path nil") + +SERVICE_NAME = _service_name +local _arguments = table.pack(...) + +local skynet = require "skynet" +require "skynet.manager" + +require "sharetable" +require "cluster" +require "multicast" +require "datacenter" + +local function _exception(e) + skynet.error(e) + return e +end + +local _xpcall = function(f, ...) + return xpcall(f, _exception, ...) +end + +local single -- 服务逻辑单例对象 + +local _info = function() +end + +local _dispatch = function(session, source, cmd, ...) + local func = single:get_cmd_func(cmd) + if not func then + skynet.retpack({ + ok = false, + msg = string.format("[%s]UNKNOWN_CMD[%s]FROM[%s]", _service_name, cmd, tostring(source)), + }) + return + end + + if session > 0 then + skynet.retpack(_xpcall(func, single, ...)) + else + _xpcall(func, single, ...) + end +end + +skynet.start(function() + single = require(_service_path):instance(table.unpack(_arguments, 2, _arguments.n)) + assert(single.dispatch, "zeus service dispatch function nil") + + skynet.dispatch("lua", _dispatch) + skynet.info_func(_info) + + local fsm = single:get_fsm() + fsm.initial() + fsm.running() + + single:add_timer(_info, 3600) +end)