综述
SGAME框架所使用的日志系统基于一个小的日志库slog,具体细节可以参考https://github.com/nmsoccer/slog 其安装也很简单,会在/usr/local/lib下生成一个slog.a静态库(主要免去到目标机器时各种部署动态库的麻烦,而且便于调试). slog有一个slog.go作为GO的API,在框架里已经包含
源码
SGAME的日志目录位于sgame/lib/log
,内容如下
sgame/lib/log/ |-- api.go `-- slog.go
- slog.go: 是slog项目本身提供的GO接口,我们将其包含进来
- api.go: 是基于slog与SGAME框架本身的封装层,框架实际使用的是api.go里的函数
类别
SGAME框架主要提供两类日志:
- 本地文件日志,方便记录进程运行时的各种事件
- 网络传输日志,因为游戏经常会有经分审计等需要,而且很多重要的功能节点也需要专门的信息保存,所以可以通过网络日志将重要信息发送到对端的UDP服务器用于保存和统计。因为是基于简单的UDP文本传输,接收端的开发也会很简单
本地日志
本地日志支持以下功能
- 日志分级
SGAME框架主要分为DEBUG,INFO,ERR,FATAL等四个级别。而且支持在进程运行时动态调整打印级别 - 日志滚动
SGAME框架支持日志文件的滚动,同时支持进程运行时的数目动态修改 - 时间粒度
支持秒,毫秒,微秒和纳秒四个粒度的时间,同时也支持进程运行时动态调整 - 协程支持
框架本身使用了多协程处理,所以支持多协程的日志打印
API
- 打开日志句柄
/*Open a Log handler * @filt_level:refer LOG_LV_XX. only print log above filt-level * @log_degree:refer LOG_DEGREE_XX * @rotate: log file max rotate num * @size: log file size */ func OpenLog(log_name string , filt_level int , log_degree int , rotate int , log_size int) *SLogPen
这里的参数就是日志文件名、过滤等级、日志粒度、日志滚动数以及单个日志文件的大小.
- 记录日志 在获得SLogPen句柄之后,主要提供了以下的方法:
-
DEBUG 打印debug级别日志
func (lp *SLogPen) Debug(format string, arg ...interface{})
-
INFO 打印info级别日志
func (lp *SLogPen) Info(format string, arg ...interface{})
-
ERR 打印err级别日志
func (lp *SLogPen) Err(format string, arg ...interface{})
-
FATAL 打印fatal级别日志
func (lp *SLogPen) Fatal(format string, arg ...interface{}
-
ATTR 动态修改句柄属性
func (lp *SLogPen) ChgAttr(attr int , value int) int
- 过滤等级 LOG_ATTR_LEVEL
- 日志粒度 LOG_ATTR_DEGREE
- 日志滚动 LOG_ATTR_ROTATE
- 日志大小 LOG_ATTR_SIZE
-
CLOSE 关闭句柄
func (lp *SLogPen) Close()
使用实例
我们以logic_serv为例来观看实际的代码使用.
- 打开日志已经做成通用的了,放置于comm/config.go里:
... //log lp := log.OpenLog(log_file , log.LOG_DEFAULT_FILT_LEVEL , log.LOG_DEFAULT_DEGREE , log.LOG_DEFAULT_ROTATE , log.LOG_DEFAULT_SIZE); if lp == nil { fmt.Printf("open log %s failed!\n" , log_file); return nil; } pconfig.Log = lp; ...
- 实际的打印 代码位于logic_serv/lib/base.go
... var log = pconfig.Comm.Log log.Info("%s starts---%v", pconfig.ProcName, os.Args) ...
会在进程运行之目录下生成相应的日志和记录:
-rw-rw-r-- 1 nmsoccer nmsoccer 159836 Aug 14 15:16 logic_serv.log.0 ... [2020-08-14 15:15:49.545 info] logic_serv-1 starts---[./logic_serv -N sgame -p 20001 -P logic_serv-1 -f conf/logic_serv.json -D] [2020-08-14 15:15:49.545 debug] <RecvHeartBeatReq> set heartbeat server:30001 1597389337 [2020-08-14 15:15:49.545 debug] <RecvHeartBeatReq> set heartbeat server:10001 1597389338 [2020-08-14 15:15:49.545 debug] <RecvHeartBeatReq> set heartbeat server:40001 1597389340 [2020-08-14 15:15:49.545 debug] <RecvHeartBeatReq> set heartbeat server:40002 1597389341 ...
- 进程退出时关闭
... //close log if pconfig.Comm.Log != nil { pconfig.Comm.Log.Info("%s", "server exit...") pconfig.Comm.Log.Close() } ...
网络日志
网络日志一般以结构化的文本方式进行发送,支持设置粒度和接收机器列表
API
- 打开日志句柄
支持设置目标机器地址列表peer_addr;发送到目标的方式method,日志粒度等. 返回句柄和解析错误的目标机器地址 - method有以下几种方式
- NETLOG_METHOD_SEQ = 1 直接选择第一个机器
- NETLOG_METHOD_ALL = 2 发给所有的目标机器
- NETLOG_METHOD_MOD = 3 对目标机器按hash_key取模;这里的hash_key就是打开句柄时填的参数,只有当method为这个选项时起效
- NETLOG_METHOD_RAND = 4 随机选择一台目标机器(默认)
/*Open a NetLog handler * @hash_key: if method is hash this is neccessary * @peer_addr: recv net-log servers * @method:refer NETLOG_METHOD_XX default:rand * @log_degree: LOG_DEGREE_XX. default:second * @return:*SNetLogPen , err_addr:which addr is invalid */ func OpenNetLog(hash_key int , peer_addr []string , method int , log_degree int) (*SNetLogPen, []string)
- 发送日志,将会使用sep对后面的作为字符串数组的arg进行整合并发送到对端
func (nlp *SNetLogPen) Log(sep string , arg ...string)
- 关闭
func (nlp *SNetLogPen) Close()
使用实例
我们仍然以logic_serv为例来观看实际的代码使用
- 打开网络日志句柄 示例代码位于logic_serv/lib/base.go
//NetLog var bad_addr []string pconfig.NetLog , bad_addr = llog.OpenNetLog(pconfig.ProcId , pconfig.FileConfig.NetLogAddr , pconfig.FileConfig.NetLogMethod , llog.NETLOG_DEFAULT_DEGREE) if pconfig.NetLog == nil { log.Err("%s OpenNetLog:%v Failed!" , _func_ , pconfig.FileConfig.NetLogAddr) return false } if len(bad_addr) > 0 { log.Err("%s OpenNetLog:%v some addr fail! failed_addr:%v" , _func_ , pconfig.FileConfig.NetLogAddr , bad_addr) return false } log.Info("%s OpenNetLog:%v Method:%d Degree:%d success!" , _func_ , pconfig.FileConfig.NetLogAddr , pconfig.FileConfig.NetLogMethod , llog.NETLOG_DEFAULT_DEGREE)
上面在配置里指定了对端机器NetLogAddr和方式,而粒度使用的是默认的秒级
cat sgame/servers/spush/tmpl/logic_serv.tmpl { "conn_serv":$conn_serv, "disp_serv_list":[40001,40002], "master_db":$master_db, "slave_db":$slave_db, "log_file":"logic_serv.log", "manage_addr":["$m_addr"], "net_log_addr":["127.0.0.1:8000" , "127.0.0.1:8001"], "net_log_method":2, "max_online":1500, "client_timeout":10, "monitor_inv":5 }
- 记录日志 我们在玩家登陆,登出时分别打印其登入与登出流水.代码截于logic_serv/lib/login.go
//登陆流水 log_content := fmt.Sprintf("%d|%s|LoginFlow|%d|%s|%s|%d" , pconfig.ProcId , pconfig.ProcName , uid , prsp.Name , prsp.UserInfo.BasicInfo.Addr,curr_ts) pconfig.NetLog.Log("|" , log_content) //登出流水 log_content := fmt.Sprintf("%d|%s|LogoutFlow|%d|%s|%d|%d" , pconfig.ProcId , pconfig.ProcName , uid , puser_info.BasicInfo.Name , reason , curr_ts) pconfig.NetLog.Log("|" , log_content)
然后使用nc工具监听于8000或8001端口,再使用客户端登陆sgame/client: ./game_cli -m 2 -p 18909 -c "login cs xxx"
nc -lu 8000 2020-08-14 18:48:39|20001|logic_serv-1|LoginFlow|10002|cs|shenzhen|1597402119 2020-08-14 18:48:50|20001|logic_serv-1|LogoutFlow|10002|cs|4|1597402130 nc -lu 8001 2020-08-14 18:48:39|20001|logic_serv-1|LoginFlow|10002|cs|shenzhen|1597394919 2020-08-14 18:48:50|20001|logic_serv-1|LogoutFlow|10002|cs|4|1597402130