diff --git a/cmd/gameserver/agent.go b/cmd/gameserver/agent.go index 60663d3..cac84ce 100644 --- a/cmd/gameserver/agent.go +++ b/cmd/gameserver/agent.go @@ -5,9 +5,9 @@ import ( "math" "pro2d/common" "pro2d/common/components" + "pro2d/common/logger" "pro2d/models" "pro2d/utils" - "pro2d/utils/logger" "sync/atomic" ) @@ -43,7 +43,7 @@ func (c *Agent) OnConnection(conn components.IConnection) { func (c *Agent) OnMessage(msg components.IMessage) { atomic.StoreInt64(&c.lastHeartCheckTime, utils.Timex()) - md := c.Server.GetPlugin().GetAction(msg.GetHeader().GetMsgID()) + md := c.Server.GetAction(msg.GetHeader().GetMsgID()) if md == nil { logger.Debug("cmd: %d, md is nil", msg.GetHeader().GetMsgID()) return @@ -52,17 +52,17 @@ func (c *Agent) OnMessage(msg components.IMessage) { logger.Debug("protocode handler: %d", msg.GetHeader().GetMsgID()) //fmt.Printf("errCode: %d, protomsg:%v\n", errCode, protomsg) - f := md.(func (conn components.IConnection, msg components.IMessage) (int32, interface{})) - errCode, protomsg := f(c, msg) + f := md.(func (msg components.IMessage) (int32, interface{})) + errCode, protomsg := f(msg) rsp, err := proto.Marshal(protomsg.(proto.Message)) if err != nil { - conn := c.Server.GetIConnection(msg.GetSessId()) + conn := msg.GetSession() if conn != nil { conn.Send(-100, msg.GetHeader().GetMsgID(), nil) } return } - conn := c.Server.GetIConnection(msg.GetSessId()) + conn := msg.GetSession() if conn != nil { conn.Send(errCode, msg.GetHeader().GetMsgID(), rsp) } diff --git a/cmd/gameserver/game.go b/cmd/gameserver/game.go index 6c8ce02..a573029 100644 --- a/cmd/gameserver/game.go +++ b/cmd/gameserver/game.go @@ -1,21 +1,15 @@ package main import ( - "context" "fmt" - "net/http" _ "net/http/pprof" - "os" - "os/signal" + "pro2d/common" "pro2d/common/components" - "pro2d/conf" + "pro2d/common/db" "pro2d/models" - //_ "pro2d/cmd/gameserver/plugin" - "pro2d/utils/db" - "pro2d/utils/etcd" - "pro2d/utils/logger" + + "pro2d/common/etcd" "sync" - "syscall" ) type GameServer struct { @@ -25,12 +19,12 @@ type GameServer struct { Agents *sync.Map } -func NewGameServer(sconf *conf.SConf) (*GameServer, error) { +func NewGameServer(sconf *common.SConf) (*GameServer, error) { s := &GameServer{ Agents: new(sync.Map), } - options := []components.Option{ + options := []components.ServerOption{ components.WithPlugin(components.NewPlugin(sconf.PluginPath)), components.WithSplitter(components.NewPBSplitter()), components.WithConnCbk(s.OnConnection), @@ -42,21 +36,34 @@ func NewGameServer(sconf *conf.SConf) (*GameServer, error) { iserver := components.NewServer(sconf.Port, options...) s.IServer = iserver - //mongo 初始化 - db.MongoDatabase = db.MongoClient.Database(sconf.DBName) - models.InitGameServerModels() + //mgo init + err := db.ConnectMongo(sconf.MongoConf) + if err != nil { + return nil, err + } + models.InitModels() //Etcd 初始化 - var err error - s.EtcdClient, err = etcd.NewEtcdClient(conf.GlobalConf.Etcd) + s.EtcdClient, err = etcd.NewEtcdClient(common.GlobalConf.Etcd) if err != nil { return nil, err } - s.EtcdClient.PutWithLeasePrefix(conf.GlobalConf.GameConf.Name, conf.GlobalConf.GameConf.ID, fmt.Sprintf("%s:%d", conf.GlobalConf.GameConf.IP, conf.GlobalConf.GameConf.Port), 5) + s.EtcdClient.PutWithLeasePrefix(common.GlobalConf.GameConf.Name, common.GlobalConf.GameConf.ID, fmt.Sprintf("%s:%d", common.GlobalConf.GameConf.IP, common.GlobalConf.GameConf.Port), 5) return s, nil } +func (s *GameServer) Start() error { + return s.IServer.Start() +} + +func (s *GameServer) Stop() { + s.IServer.Stop() + + db.CloseMongo() + s.EtcdClient.Close() +} + func (s *GameServer) OnConnection(conn components.IConnection) { agent := NewAgent(s) agent.OnConnection(conn) @@ -64,7 +71,7 @@ func (s *GameServer) OnConnection(conn components.IConnection) { } func (s *GameServer) OnMessage(msg components.IMessage) { - agent, ok := s.Agents.Load(msg.GetSessId()) + agent, ok := s.Agents.Load(msg.GetSession().GetID()) if !ok { return } @@ -87,50 +94,3 @@ func (s *GameServer) OnClose(conn components.IConnection) { agent.(*Agent).OnClose() s.Agents.Delete(conn.GetID()) } - -func (s *GameServer) Stop() { - s.IServer.Stop() - - db.MongoClient.Disconnect(context.TODO()) -} - - -func main() { - err := make(chan error) - stopChan := make(chan os.Signal) - signal.Notify(stopChan, syscall.SIGTERM, syscall.SIGINT, syscall.SIGKILL) - - userChan := make(chan os.Signal) - signal.Notify(userChan, syscall.SIGUSR1, syscall.SIGUSR2) - - s,err1 := NewGameServer(conf.GlobalConf.GameConf) - if err1 != nil { - fmt.Errorf(err1.Error()) - return - } - go func() { - err <- http.ListenAndServe("localhost:6061", nil) - }() - - go func() { - err <- s.Start() - }() - - for { - select { - case e := <- err: - logger.Error("game server error: %v", e) - return - case <-stopChan: - s.Stop() - logger.Debug("game stop...") - return - case u := <-userChan: - logger.Debug("userChan .. %v",u.String()) - e := s.IServer.GetPlugin().LoadPlugin() - if e != nil { - logger.Error("err: ", e.Error()) - } - } - } -} diff --git a/cmd/gameserver/main.go b/cmd/gameserver/main.go new file mode 100644 index 0000000..7339cfb --- /dev/null +++ b/cmd/gameserver/main.go @@ -0,0 +1,51 @@ +package main + +import ( + "fmt" + "net/http" + "os" + "os/signal" + "pro2d/common" + "pro2d/common/logger" + "syscall" +) + +func main() { + err := make(chan error) + stopChan := make(chan os.Signal) + signal.Notify(stopChan, syscall.SIGTERM, syscall.SIGINT, syscall.SIGKILL) + + userChan := make(chan os.Signal) + signal.Notify(userChan, syscall.SIGUSR1, syscall.SIGUSR2) + + s,err1 := NewGameServer(common.GlobalConf.GameConf) + if err1 != nil { + fmt.Errorf(err1.Error()) + return + } + go func() { + err <- http.ListenAndServe(fmt.Sprintf("localhost:%d", common.GlobalConf.GameConf.DebugPort), nil) + }() + + go func() { + err <- s.Start() + }() + + for { + select { + case e := <- err: + logger.Error("game server error: %v", e) + return + case <-stopChan: + logger.Debug("game stop...") + s.Stop() + return + case u := <-userChan: + logger.Debug("userChan .. %v",u.String()) + e := s.IServer.GetPlugin().LoadPlugin() + if e != nil { + logger.Error("err: ", e.Error()) + } + } + } +} diff --git a/cmd/gameserver/plugin/RolePlugin.go b/cmd/gameserver/plugin/RolePlugin.go index 8303dce..ea5de95 100644 --- a/cmd/gameserver/plugin/RolePlugin.go +++ b/cmd/gameserver/plugin/RolePlugin.go @@ -2,11 +2,11 @@ package main import ( "github.com/golang/protobuf/proto" + "pro2d/common" "pro2d/common/components" - "pro2d/conf" + "pro2d/common/logger" "pro2d/models" "pro2d/pb" - "pro2d/utils/logger" ) func HeartRpc(msg components.IMessage) (int32, interface{}) { @@ -25,7 +25,7 @@ func CreateRpc(msg components.IMessage) (int32, interface{}) { return 2, nil } - roleId := conf.SnowFlack.NextValStr() + roleId := common.SnowFlack.NextValStr() role = models.NewRole(roleId) if err := role.Create(); err != nil { logger.Error("CreateRpc role create err: %v", err) diff --git a/cmd/gameserver/plugin/protocode.go b/cmd/gameserver/plugin/protocode.go index 8ca8e1e..a135bc5 100644 --- a/cmd/gameserver/plugin/protocode.go +++ b/cmd/gameserver/plugin/protocode.go @@ -1,8 +1,8 @@ package main import ( + "pro2d/common/logger" "pro2d/pb" - "pro2d/utils/logger" ) func GetActionMap() map[interface{}]interface{} { diff --git a/cmd/httpserver/AccountAction.go b/cmd/httpserver/AccountAction.go index 27ae06d..9079eb6 100644 --- a/cmd/httpserver/AccountAction.go +++ b/cmd/httpserver/AccountAction.go @@ -2,7 +2,7 @@ package main import ( "github.com/gin-gonic/gin" - "pro2d/conf" + "pro2d/common" "pro2d/models" "pro2d/pb" "pro2d/utils" @@ -23,7 +23,7 @@ func (h *AccountAction) Register(c *gin.Context) (int, interface{}){ return -2 , "account exists: " + register.Phone } - account.Uid = conf.SnowFlack.NextValStr() + account.Uid = common.SnowFlack.NextValStr() account.Password = utils.Md5V(register.Password) if err := account.Create(); err != nil{ return -3, "account register err: " + err.Error() @@ -47,10 +47,10 @@ func (h *AccountAction) Login(c *gin.Context) (int,interface{}) { } var gs []*pb.ServiceInfo - for k, v := range h.HttpServer.EtcdClient.GetByPrefix(conf.GlobalConf.GameConf.Name) { + for k, v := range h.HttpServer.EtcdClient.GetByPrefix(common.GlobalConf.GameConf.Name) { gs = append(gs, &pb.ServiceInfo{ Id: k, - Name: conf.GlobalConf.GameConf.Name, + Name: common.GlobalConf.GameConf.Name, Address: v, }) } diff --git a/cmd/httpserver/http.go b/cmd/httpserver/http.go index 37a0041..3400cbb 100644 --- a/cmd/httpserver/http.go +++ b/cmd/httpserver/http.go @@ -4,13 +4,11 @@ import ( "fmt" "os" "os/signal" + "pro2d/common" "pro2d/common/components" - "pro2d/conf" - _ "pro2d/conf" - "pro2d/models" - "pro2d/utils/db" - "pro2d/utils/etcd" - "pro2d/utils/logger" + "pro2d/common/db" + "pro2d/common/etcd" + "pro2d/common/logger" "syscall" ) @@ -24,17 +22,21 @@ func NewAccountServer(version string, port ...string) *AccountServer { } func (s *AccountServer) Init() error { - //mongo 初始化 - db.MongoDatabase = db.MongoClient.Database(conf.GlobalConf.AccountConf.DBName) - models.InitAccountServerModels() + //mgo init + err := db.ConnectMongo(common.GlobalConf.AccountConf.MongoConf) //Etcd 初始化 - var err error - s.EtcdClient, err = etcd.NewEtcdClient(conf.GlobalConf.Etcd) + s.EtcdClient, err = etcd.NewEtcdClient(common.GlobalConf.Etcd) if err != nil { return err } - s.EtcdClient.PutWithLeasePrefix(conf.GlobalConf.AccountConf.Name, conf.GlobalConf.AccountConf.ID, fmt.Sprintf("%s:%d", conf.GlobalConf.AccountConf.IP, conf.GlobalConf.AccountConf.Port), 5) + + //Etcd 初始化 + s.EtcdClient, err = etcd.NewEtcdClient(common.GlobalConf.Etcd) + if err != nil { + return err + } + s.EtcdClient.PutWithLeasePrefix(common.GlobalConf.AccountConf.Name, common.GlobalConf.AccountConf.ID, fmt.Sprintf("%s:%d", common.GlobalConf.AccountConf.IP, common.GlobalConf.AccountConf.Port), 5) return nil } @@ -51,7 +53,7 @@ func main() { stopChan := make(chan os.Signal) signal.Notify(stopChan, syscall.SIGTERM, syscall.SIGINT, syscall.SIGKILL) - web := NewAccountServer("v1", fmt.Sprintf(":%d", conf.GlobalConf.AccountConf.Port)) + web := NewAccountServer("v1", fmt.Sprintf(":%d", common.GlobalConf.AccountConf.Port)) web.BindHandler(&AccountAction{HttpServer: web}) go func() { err <- web.Start() diff --git a/cmd/test/client.go b/cmd/test/client.go index 3cf0ded..02377a5 100644 --- a/cmd/test/client.go +++ b/cmd/test/client.go @@ -7,8 +7,8 @@ import ( "github.com/golang/protobuf/proto" "net" "pro2d/common/components" + "pro2d/common/logger" "pro2d/pb" - "pro2d/utils/logger" "time" ) diff --git a/common/components/conn.go b/common/components/conn.go index c12b7c1..a9e518c 100644 --- a/common/components/conn.go +++ b/common/components/conn.go @@ -5,7 +5,7 @@ import ( "fmt" "net" "pro2d/common" - "pro2d/utils/logger" + "pro2d/common/logger" "sync/atomic" "time" ) @@ -84,18 +84,16 @@ func (c *Connection) Start() { c.handleTimeOut() } -func (c *Connection) Stop() { - logger.Debug("ID: %d close", c.Id) - closed := atomic.LoadUint32(&c.Status) - if closed == 0 { +func (c *Connection) Stop() { + sendTimeout := time.NewTimer(5 * time.Millisecond) + defer sendTimeout.Stop() + // 发送超时 + select { + case <-sendTimeout.C: + return + case c.Quit <- c: return } - atomic.StoreUint32(&c.Status, 0) - - close(c.WBuffer) - close(c.Quit) - c.Conn.Close() - c.closeCallback(c) } func (c *Connection) Send(errCode int32, cmd uint32, data []byte) error{ @@ -128,7 +126,7 @@ func (c *Connection) defaultTimerCallback(conn IConnection) { } func (c *Connection) write() { - defer c.Stop() + defer c.quitting() for msg := range c.WBuffer { n, err := c.writer.Write(msg) @@ -144,7 +142,7 @@ func (c *Connection) write() { } func (c *Connection) read() { - defer c.Quitting() + defer c.quitting() c.scanner.Split(c.Server.GetSplitter().ParseMsg) for c.scanner.Scan() { @@ -153,7 +151,7 @@ func (c *Connection) read() { return } - req.SetSessId(c.Id) + req.SetSession(c) c.readFunc <- func() { c.messageCallback(req) } @@ -167,7 +165,7 @@ func (c *Connection) read() { //此设计目的是为了让网络数据与定时器处理都在一条协程里处理。不想加锁。。。 func (c *Connection) listen(){ - defer c.Stop() + defer c.quitting() for { select { @@ -188,6 +186,16 @@ func (c *Connection) handleTimeOut() { TimeOut(1*time.Second, c.handleTimeOut) } -func (c *Connection) Quitting() { - c.Quit <- c +func (c *Connection) quitting() { + closed := atomic.LoadUint32(&c.Status) + if closed == 0 { + return + } + atomic.StoreUint32(&c.Status, 0) + + logger.Debug("ID: %d close", c.Id) + close(c.WBuffer) + close(c.Quit) + c.Conn.Close() + c.closeCallback(c) } diff --git a/common/components/icompontents.go b/common/components/icompontents.go index c88b381..2ba1830 100644 --- a/common/components/icompontents.go +++ b/common/components/icompontents.go @@ -1,77 +1,117 @@ package components -//网络包头 -type IHead interface { - GetDataLen() uint32 //获取消息数据段长度 - GetMsgID() uint32 //获取消息ID - GetErrCode() int32 //获取消息错误码 - GetPreserve() uint32 //获取预留数据 -} - -//网络包 -type IMessage interface { - IHead - GetHeader() IHead //获取消息头 - SetHeader(header IHead) //设置消息头 - - GetData() []byte //获取消息内容 - SetData([]byte) //设置消息内容 - - SetSessId(int) //设置连接id - GetSessId() int //获取连接id -} - -//网络拆包解包器 -type ISplitter interface { - UnPack([]byte) (IMessage, error) - Pack(cmd uint32, data []byte, errcode int32, preserve uint32) ([]byte, error) - ParseMsg (data []byte, atEOF bool) (advance int, token []byte, err error) - GetHeadLen() uint32 -} - -type ConnectionCallback func(IConnection) -type CloseCallback func(IConnection) -type MessageCallback func(IMessage) -type TimerCallback func(IConnection) - -//链接 -type IConnection interface { - GetID() int - Start() - Stop() - Send(code int32, cmd uint32, b []byte) error - - SetConnectionCallback(ConnectionCallback) - SetMessageCallback(MessageCallback) - SetCloseCallback(CloseCallback) - SetTimerCallback(TimerCallback) -} - -//server -type IServer interface { - Start() error - Stop() - - GetSplitter() ISplitter - GetIConnection(id int) IConnection - GetPlugin() IPlugin - - SetConnectionCallback(ConnectionCallback) - SetMessageCallback(MessageCallback) - SetCloseCallback(CloseCallback) - SetTimerCallback(TimerCallback) -} - -//httpserver -type IHttp interface { - Start() error - Stop() - BindHandler(interface{}) -} - -type ActionHandler func (conn IConnection, msg IMessage) (int32, interface{}) -//用于热更逻辑的插件接口 -type IPlugin interface { - LoadPlugin() error - GetAction(uint32) interface{} -} \ No newline at end of file +//----------------- +//----net start---- +//----------------- +type ( + //网络包头 + IHead interface { + GetDataLen() uint32 //获取消息数据段长度 + GetMsgID() uint32 //获取消息ID + GetErrCode() int32 //获取消息错误码 + GetPreserve() uint32 //获取预留数据 + } + //网络包 + IMessage interface { + IHead + GetHeader() IHead //获取消息头 + SetHeader(header IHead) //设置消息头 + + GetData() []byte //获取消息内容 + SetData([]byte) //设置消息内容 + + SetSession(IConnection) //设置连接 + GetSession() IConnection //获取连接 + } + //网络拆包解包器 + ISplitter interface { + UnPack([]byte) (IMessage, error) + Pack(cmd uint32, data []byte, errcode int32, preserve uint32) ([]byte, error) + ParseMsg(data []byte, atEOF bool) (advance int, token []byte, err error) + GetHeadLen() uint32 + } + ConnectionCallback func(IConnection) + CloseCallback func(IConnection) + MessageCallback func(IMessage) + TimerCallback func(IConnection) + //链接 + IConnection interface { + GetID() int + Start() + Stop() + Send(code int32, cmd uint32, b []byte) error + + SetConnectionCallback(ConnectionCallback) + SetMessageCallback(MessageCallback) + SetCloseCallback(CloseCallback) + SetTimerCallback(TimerCallback) + } + //server + IServer interface { + Start() error + Stop() + + GetSplitter() ISplitter + GetIConnection(id int) IConnection + GetPlugin() IPlugin + GetAction(uint32) interface{} + + SetConnectionCallback(ConnectionCallback) + SetMessageCallback(MessageCallback) + SetCloseCallback(CloseCallback) + SetTimerCallback(TimerCallback) + } + + //httpserver + IHttp interface { + Start() error + Stop() + BindHandler(interface{}) + } + ActionHandler func(conn IConnection, msg IMessage) (int32, interface{}) + //用于热更逻辑的插件接口 + IPlugin interface { + LoadPlugin() error + SetActions(map[interface{}]interface{}) + GetAction(uint32) interface{} + } + + +) + +//----------------- +//-----db start---- +//----------------- +type ( + IDB interface { + CreateTable() error + + Create() (interface{}, error) + Load() error + FindOne() error + UpdateProperty(key string, val interface{}) error + UpdateProperties(properties map[string]interface{}) error + + SetUnique(key string) (string, error) + } + + ISchema interface { + Init() + GetDB() IDB + + GetPri() interface{} + GetSchema() interface{} + GetSchemaName() string + + Load() error + Create() error + Update() + + SetProperty(key string, val interface{}) + SetProperties(properties map[string]interface{}) + } +) + +//----------------- +//-----db end------ +//----------------- diff --git a/common/components/pbsplitter.go b/common/components/pbsplitter.go index 50d316d..4f44377 100644 --- a/common/components/pbsplitter.go +++ b/common/components/pbsplitter.go @@ -35,7 +35,7 @@ type PBMessage struct { Head IHead Body []byte - SessionID int + conn IConnection } @@ -54,12 +54,12 @@ func (m *PBMessage) SetData(b []byte) { m.Body = b } -func (m *PBMessage) SetSessId(id int) { - m.SessionID = id +func (m *PBMessage) SetSession(connection IConnection) { + m.conn = connection } -func (m *PBMessage) GetSessId() int { - return m.SessionID +func (m *PBMessage) GetSession() IConnection { + return m.conn } diff --git a/common/components/plugin.go b/common/components/plugin.go index 5c3aa1f..fa5706c 100644 --- a/common/components/plugin.go +++ b/common/components/plugin.go @@ -2,8 +2,8 @@ package components import ( "plugin" + "pro2d/common/logger" "pro2d/pb" - "pro2d/utils/logger" "sync" ) @@ -43,11 +43,7 @@ func (p *Plugin) LoadPlugin() error { if x, ok := f.(func()map[interface{}]interface{}); ok { logger.Debug("func LoadPlugin GetActionMap success...") - am := x() - for k, v := range am { - p.Actions.Delete(k) - p.Actions.Store(k, v) - } + p.SetActions(x()) } return nil @@ -60,3 +56,11 @@ func (p *Plugin) GetAction(cmd uint32) interface{} { } return f } + +func (p *Plugin) SetActions(am map[interface{}]interface{}) { + for k, v := range am { + cmd := k.(pb.ProtoCode) + p.Actions.Delete(cmd) + p.Actions.Store(cmd, v) + } +} diff --git a/common/components/server.go b/common/components/server.go index ed8279a..3833f8d 100644 --- a/common/components/server.go +++ b/common/components/server.go @@ -3,43 +3,43 @@ package components import ( "fmt" "net" - "pro2d/utils/logger" + "pro2d/common/logger" "sync" ) -type Option func(*Server) +type ServerOption func(*Server) -func WithPlugin(iPlugin IPlugin) Option { +func WithPlugin(iPlugin IPlugin) ServerOption { return func(server *Server) { server.plugins = iPlugin } } -func WithSplitter(splitter ISplitter) Option { +func WithSplitter(splitter ISplitter) ServerOption { return func(server *Server) { server.splitter = splitter } } -func WithConnCbk(cb ConnectionCallback) Option { +func WithConnCbk(cb ConnectionCallback) ServerOption { return func(server *Server) { server.connectionCallback = cb } } -func WithMsgCbk(cb MessageCallback) Option { +func WithMsgCbk(cb MessageCallback) ServerOption { return func(server *Server) { server.messageCallback = cb } } -func WithCloseCbk(cb CloseCallback) Option { +func WithCloseCbk(cb CloseCallback) ServerOption { return func(server *Server) { server.closeCallback = cb } } -func WithTimerCbk(cb TimerCallback) Option { +func WithTimerCbk(cb TimerCallback) ServerOption { return func(server *Server) { server.timerCallback = cb } @@ -50,7 +50,6 @@ type Server struct { PluginPath string plugins IPlugin splitter ISplitter - actionHandlers sync.Map connectionCallback ConnectionCallback messageCallback MessageCallback @@ -61,7 +60,7 @@ type Server struct { Clients *sync.Map } -func NewServer(port int, options ...Option) IServer { +func NewServer(port int, options ...ServerOption) IServer { s := &Server{ port: port, Clients: new(sync.Map), @@ -89,6 +88,10 @@ func (s *Server) GetPlugin() IPlugin { return s.plugins } +func (s *Server) GetAction(cmd uint32) interface{} { + return s.plugins.GetAction(cmd) +} + func (s *Server) SetConnectionCallback(cb ConnectionCallback) { s.connectionCallback = cb } @@ -105,22 +108,6 @@ func (s *Server) SetTimerCallback(cb TimerCallback) { s.timerCallback = cb } -func (s *Server) newConnection(conn IConnection) { - s.Clients.Store(conn.GetID(), conn) - - conn.SetConnectionCallback(s.connectionCallback) - conn.SetCloseCallback(s.removeConnection) - conn.SetMessageCallback(s.messageCallback) - conn.SetTimerCallback(s.timerCallback) - - go conn.Start() -} - -func (s *Server) removeConnection(conn IConnection) { - s.closeCallback(conn) - s.Clients.Delete(conn.GetID()) -} - func (s *Server) Start() error { if err := s.plugins.LoadPlugin(); err != nil { return err @@ -148,9 +135,26 @@ func (s *Server) Start() error { func (s *Server)Stop() { StopTimer() + s.Clients.Range(func(key, value interface{}) bool { client := value.(IConnection) client.Stop() return true }) } + +func (s *Server) newConnection(conn IConnection) { + s.Clients.Store(conn.GetID(), conn) + + conn.SetConnectionCallback(s.connectionCallback) + conn.SetCloseCallback(s.removeConnection) + conn.SetMessageCallback(s.messageCallback) + conn.SetTimerCallback(s.timerCallback) + + go conn.Start() +} + +func (s *Server) removeConnection(conn IConnection) { + s.closeCallback(conn) + s.Clients.Delete(conn.GetID()) +} diff --git a/common/conf.go b/common/conf.go new file mode 100644 index 0000000..711428a --- /dev/null +++ b/common/conf.go @@ -0,0 +1,117 @@ +package common + +import ( + "encoding/json" + "fmt" + "gopkg.in/yaml.v3" + "io/ioutil" + "pro2d/common/logger" + "strings" +) + +type RedisConf struct { + Address string `json:"address"` + Auth string `json:"auth"` + DB int `json:"db"` +} + +type Etcd struct { + Endpoints []string `json:"endpoints"` + DialTimeout int `json:"dialtimeout"` +} + +type MongoConf struct { + User string `yaml:"user"` + Password string `yaml:"password"` + Host string `yaml:"host"` + Port int `yaml:"port"` + TimeOut int `yaml:"timeout"` + MaxNum int `yaml:"maxnum"` + DBName string `yaml:"dbname"` +} + +type SConf struct { + ID string `yaml:"id"` + Name string `yaml:"name"` + IP string `yaml:"ip"` + Port int `yaml:"port"` + DebugPort int `yaml:"debugport"` + MongoConf *MongoConf `yaml:"mongo"` + WorkerPoolSize int `yaml:"pool_size"` + PluginPath string `yaml:"plugin_path"` +} + +type LogConsole struct { + Level string `yaml:"level" json:"level"` + Color bool `yaml:"color" json:"color"` +} + +type LogFile struct { + Level string `yaml:"level" json:"level"` + Daily bool `yaml:"daily" json:"daily"` + Maxlines int `yaml:"maxlines" json:"maxlines"` + Maxsize int `yaml:"maxsize" json:"maxsize"` + Maxdays int `yaml:"maxdays" json:"maxdays"` + Append bool `yaml:"append" json:"append"` + Permit string `yaml:"permit" json:"permit"` +} + +type LogConn struct { + Net string `yaml:"net" json:"net"` + Addr string `yaml:"addr" json:"addr"` + Level string `yaml:"level" json:"level"` + Reconnect bool `yaml:"reconnect" json:"reconnect"` + ReconnectOnMsg bool `yaml:"reconnectOnMsg" json:"reconnectOnMsg"` +} + +type LogConf struct { + TimeFormat string `yaml:"TimeFormat" json:"TimeFormat"` + LogConsole *LogConsole `yaml:"Console" json:"Console"` + LogFile *LogFile `yaml:"File" json:"File"` + LogConn *LogConn `yaml:"Conn" json:"Conn"` +} + +type ServerConf struct { + ID string `yaml:"id"` + Name string `yaml:"name"` + WorkerID int64 `yaml:"workerid"` + DatacenterID int64 `yaml:"datacenterid"` + AccountConf *SConf `yaml:"server_account"` + GameConf *SConf `yaml:"server_game"` + RedisConf *RedisConf `yaml:"redis"` + LogConf *LogConf `yaml:"logconf" json:"logconf"` + Etcd *Etcd `yaml:"etcd"` +} + +var( + GlobalConf ServerConf + SnowFlack *Snowflake +) + +func init() { + configFile, err := ioutil.ReadFile("conf/conf.yaml") + if err != nil { + fmt.Printf("conf faild: %v", err) + return + } + //初始化配置 + if err = yaml.Unmarshal(configFile, &GlobalConf); err != nil { + fmt.Printf("yaml unmarshal faild: %v", err) + return + } + + c, err := json.Marshal(&GlobalConf.LogConf) + if err != nil { + fmt.Errorf("log conf %v", err) + return + } + //初始化日志 + err = logger.SetLogger(string(c), strings.ToLower(GlobalConf.GameConf.Name)) + if err != nil { + fmt.Errorf("log conf %v", err) + return + } + + //初始化雪花算法 + SnowFlack = NewSnowflake(GlobalConf.WorkerID, GlobalConf.DatacenterID) +} \ No newline at end of file diff --git a/common/db/mongo.go b/common/db/mongo.go new file mode 100644 index 0000000..8a9b1d1 --- /dev/null +++ b/common/db/mongo.go @@ -0,0 +1,104 @@ +package db + +import ( + "context" + "fmt" + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" + "go.mongodb.org/mongo-driver/x/bsonx" + "pro2d/common/components" + "sort" + "strings" +) + +var ( + mongoClient *mongo.Client + mongoDatabase *mongo.Database +) + +type MgoColl struct { + components.IDB + Schema components.ISchema + + dbname string + coll *mongo.Collection +} + +func NewMongoColl(dbname string, schema components.ISchema) *MgoColl { + m := &MgoColl{ + dbname: dbname, + coll: DB().Collection(dbname), + Schema: schema, + } + return m +} + +func (m *MgoColl) CreateTable() error { + colls, _ := DB().ListCollectionNames(context.TODO(), bson.D{}) + pos := sort.SearchStrings(colls, m.dbname) + if pos != len(colls) { + if m.dbname == colls[pos] { + return DB().CreateCollection(context.TODO(), m.dbname) + } + } + return DB().CreateCollection(context.TODO(), m.dbname) +} + +func (m *MgoColl) Create() (interface{}, error){ + return m.coll.InsertOne(context.TODO(), m.Schema.GetSchema()) +} + +func (m *MgoColl) Load() error{ + r := m.coll.FindOne(context.TODO(), m.Schema.GetPri()) + err := r.Decode(m.Schema.GetSchema()) + if err != nil { + return err + } + return nil +} + +// 查询单个 +func (m *MgoColl) FindOne() error { + singleResult := m.coll.FindOne(context.TODO(), m.Schema.GetPri()) + return singleResult.Decode(m.Schema.GetSchema()) +} + +func (m *MgoColl) UpdateOne(filter interface{}, update interface{})*mongo.UpdateResult { + res, err := m.coll.UpdateOne(context.TODO(), filter, bson.D{{"$set", update}}) + if err != nil { + return nil + } + return res +} + +func (m *MgoColl) UpdateProperty(key string, val interface{}) error { + _, err := m.coll.UpdateOne(context.TODO(), m.Schema.GetPri(), bson.D{{"$set", bson.M{strings.ToLower(key): val}}}) + return err +} + +func (m *MgoColl) UpdateProperties(properties map[string]interface{}) error { + _, err := m.coll.UpdateOne(context.TODO(), m.Schema.GetPri(), properties) + return err +} + +//索引 +func (m *MgoColl) SetUnique(key string) (string, error){ + return m.coll.Indexes().CreateOne( + context.Background(), + mongo.IndexModel{ + Keys : bsonx.Doc{{key, bsonx.Int32(1)}}, + Options: options.Index().SetUnique(true), + }, + ) +} + +func (m *MgoColl) Delete(key string, value interface{}) int64 { + filter := bson.D{ {key, value}} + count, err := m.coll.DeleteOne(context.TODO(), filter, nil) + if err != nil { + fmt.Println(err) + } + return count.DeletedCount + +} diff --git a/common/db/mongoplugin.go b/common/db/mongoplugin.go new file mode 100644 index 0000000..c4de4ff --- /dev/null +++ b/common/db/mongoplugin.go @@ -0,0 +1,148 @@ +package db + +import ( + "context" + "fmt" + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" + "go.mongodb.org/mongo-driver/mongo/readpref" + "go.mongodb.org/mongo-driver/x/bsonx" + "pro2d/common" + "pro2d/common/logger" + "reflect" + "sort" + "strings" + "time" +) + +func DB() *mongo.Database { + return mongoDatabase +} + +func ConnectMongo(conf *common.MongoConf) error { + var uri string + if conf.User!= "" { + //uri = fmt.Sprintf("mongodb://%s:%s@%s:%d/%s?w=majority", conf.User, conf.Password, conf.Host, conf.Port, conf.DBName) + uri = fmt.Sprintf("mongodb://%s:%s@%s:%d/?w=majority", conf.User, conf.Password, conf.Host, conf.Port) + }else { + //uri = fmt.Sprintf("mongodb://%s:%d/%s?w=majority", conf.Host, conf.Port, conf.DBName) + uri = fmt.Sprintf("mongodb://%s:%d/?w=majority", conf.Host, conf.Port) + } + // 设置连接超时时间 + ctx, cancel := context.WithTimeout(context.Background(), time.Duration(conf.TimeOut) * time.Second) + defer cancel() + // 通过传进来的uri连接相关的配置 + o := options.Client().ApplyURI(uri) + // 设置最大连接数 - 默认是100 ,不设置就是最大 max 64 + o.SetMaxPoolSize(uint64(conf.MaxNum)) + // 发起链接 + var err error + mongoClient, err = mongo.Connect(ctx, o) + if err != nil { + return err + } + // 判断服务是不是可用 + if err = mongoClient.Ping(context.Background(), readpref.Primary()); err != nil { + return err + } + + mongoDatabase = mongoClient.Database(conf.DBName) + return nil +} + +func CloseMongo(){ + mongoClient.Disconnect(context.TODO()) +} + +func CreateTable(tb string) error { + colls, _ := DB().ListCollectionNames(context.TODO(), bson.D{}) + pos := sort.SearchStrings(colls, tb) + if pos != len(colls) { + if tb == colls[pos] { + return DB().CreateCollection(context.TODO(), tb) + } + } + return DB().CreateCollection(context.TODO(), tb) +} + +func FindOne(pri interface{}, schema interface{}) error { + r := mongoDatabase.Collection(GetCollName(schema)).FindOne(context.TODO(), pri) + return r.Decode(schema) +} + +func GetBsonD(key string, value interface{}) interface{} { + return bson.D{ {key, value}} +} + +func GetBsonM(key string, value interface{}) interface{} { + return bson.M{key: value} +} + +func GetSchemaType(schema interface{}) reflect.Type { + s := reflect.TypeOf(schema) + if s.Kind() == reflect.Ptr { + s = reflect.TypeOf(schema).Elem() + } + return s +} + +func GetCollName(schema interface{}) string { + return strings.ToLower(GetSchemaType(schema).Name()) +} + +func GetPriKey(schema interface{}) string { + s := GetSchemaType(schema) + + var pri string + for i := 0; i < s.NumField(); i++ { + if s.Field(i).Tag.Get("pri") == "1" { + pri = strings.ToLower(s.Field(i).Name) + break + } + } + return pri +} + +func FindIndex(schema interface{}) (string, []string){ + s := GetSchemaType(schema) + + var index []string + for i := 0; i < s.NumField(); i++ { + if s.Field(i).Tag.Get("index") != "" { + js := strings.Split(s.Field(i).Tag.Get("json"), ",") + if len(js) == 0 { + continue + } + index = append(index, js[0]) + } + } + return strings.ToLower(s.Name()), index +} + +func SetUnique(coll, key string) (string, error){ + return DB().Collection(coll).Indexes().CreateOne( + context.TODO(), + mongo.IndexModel{ + Keys : bsonx.Doc{{key, bsonx.Int32(1)}}, + Options: options.Index().SetUnique(true), + }, + ) +} + +func InitDoc(schema ...interface{}) { + for _, s := range schema { + coll, keys := FindIndex(s) + CreateTable(coll) + for _, index := range keys { + + logger.Debug("InitDoc collect: %v, createIndex: %s", coll, index) + res, err := SetUnique(coll, index) + if err != nil { + logger.Error("InitDoc unique: %s, err: %v", res, err) + continue + } + } + } + +} \ No newline at end of file diff --git a/common/db/redis.go b/common/db/redis.go new file mode 100644 index 0000000..76de78a --- /dev/null +++ b/common/db/redis.go @@ -0,0 +1,43 @@ +package db + +import ( + "github.com/garyburd/redigo/redis" + "time" +) +var RedisPool *redis.Pool + +//conf *conf.ServerConf +func ConnectRedis(db int, auth, address string) error { + RedisPool = &redis.Pool{ + //最大活跃连接数,0代表无限 + MaxActive: 888, + MaxIdle: 20, + //闲置连接的超时时间 + IdleTimeout: time.Second * 100, + //定义拨号获得连接的函数 + Dial: func() (redis.Conn, error) { + option := []redis.DialOption{redis.DialDatabase(db)} + if auth != "" { + option = append(option, redis.DialPassword(auth)) + } + return redis.Dial("tcp",address, option...) + }, + } + return nil +} + +func CloseRedis() { + RedisPool.Close() +} + +func HKEYS(args ...interface{}) (reply interface{}, err error) { + conn := RedisPool.Get() + defer conn.Close() + return conn.Do("HKEYS", args) +} + +func HMSET(args ...interface{}) (reply interface{}, err error) { + conn := RedisPool.Get() + defer conn.Close() + return conn.Do("HMSET", args) +} \ No newline at end of file diff --git a/common/etcd/etcd.go b/common/etcd/etcd.go new file mode 100644 index 0000000..0f5aba8 --- /dev/null +++ b/common/etcd/etcd.go @@ -0,0 +1,106 @@ +package etcd + +import ( + "context" + "fmt" + clientv3 "go.etcd.io/etcd/client/v3" + "pro2d/common" + "pro2d/common/logger" + "time" +) + +type EtcdClient struct { + etcd *clientv3.Client +} + +func NewEtcdClient(conf *common.Etcd) (*EtcdClient, error) { + cli, err := clientv3.New(clientv3.Config{ + Endpoints: conf.Endpoints, + DialTimeout: time.Duration(conf.DialTimeout) * time.Second, + }) + if err != nil { + logger.Error("etcd init err: %v", err) + return nil, err + } + return &EtcdClient{ + etcd: cli, + }, nil +} + +func (e *EtcdClient)PutWithPrefix(prefix, key, val string) { + _, err := e.etcd.Put(context.TODO(), fmt.Sprintf("/%s/%s/", prefix, key), val) + if err != nil { + logger.Error("PutWithPrefix err: %v", err) + return + } +} + +func (e *EtcdClient)PutWithLeasePrefix(prefix, key, val string, ttl int64) error { + lease := clientv3.NewLease(e.etcd) + leaseResp, err := lease.Grant(context.TODO(), ttl) + if err != nil { + logger.Error("PutWithLeasePrefix 设置租约时间失败:%v\n", err) + return err + } + + _, err = e.etcd.Put(context.TODO(), fmt.Sprintf("/%s/%s/", prefix, key), val, clientv3.WithLease(leaseResp.ID)) + if err != nil { + logger.Error("PutWithLeasePrefix err: %v", err) + return err + } + + keepRespChan, err := lease.KeepAlive(context.TODO(), leaseResp.ID) + if err != nil { + logger.Error("keepalive err: %v", err) + return err + } + go func() { + for { + select { + case _ = <-keepRespChan: + if keepRespChan == nil { + fmt.Println("租约已经失效") + goto END + } else { //每秒会续租一次,所以就会受到一次应答 + //fmt.Println("收到自动续租应答:", keepResp.ID) + } + } + } + END: + }() + return nil +} + +func (e *EtcdClient)Get(key string) map[string]string { + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + resp, err := e.etcd.Get(ctx, fmt.Sprintf("/%s/", key)) + cancel() + if err != nil { + logger.Error("etcd get key: %s, err: %v", key, err) + return nil + } + m := make(map[string]string) + for _, v := range resp.Kvs { + m[string(v.Key)] = string(v.Value) + } + return m +} + +func (e *EtcdClient)GetByPrefix(prefix string) map[string]string { + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + resp, err := e.etcd.Get(ctx, fmt.Sprintf("/%s/", prefix), clientv3.WithPrefix()) + cancel() + if err != nil { + logger.Error("etcd get prefix: %s, err: %v", prefix, err) + return nil + } + m := make(map[string]string) + for _, v := range resp.Kvs { + m[string(v.Key)] = string(v.Value) + } + return m +} + +func (e *EtcdClient)Close() { + e.etcd.Close() +} \ No newline at end of file diff --git a/common/etcd/etcd_test.go b/common/etcd/etcd_test.go new file mode 100644 index 0000000..9abfa10 --- /dev/null +++ b/common/etcd/etcd_test.go @@ -0,0 +1,50 @@ +package etcd + +import ( + "context" + "fmt" + "go.etcd.io/etcd/api/v3/mvccpb" + clientv3 "go.etcd.io/etcd/client/v3" + "pro2d/common" + "pro2d/common/logger" + "testing" +) + +func TestEtcdClient_GetByPrefix(t *testing.T) { + etcd, err := NewEtcdClient(common.GlobalConf.Etcd) + if err != nil { + logger.Error(err) + return + } + gameInfo := etcd.GetByPrefix(common.GlobalConf.AccountConf.Name) + for k, v := range gameInfo { + logger.Debug("game info key: %v val: %v", k, v) + } + + rch := etcd.etcd.Watch(context.Background(), fmt.Sprintf("/%s/", common.GlobalConf.AccountConf.Name), clientv3.WithPrefix()) + go func() { + for wresp := range rch { + for _, ev := range wresp.Events { + switch ev.Type { + case mvccpb.PUT: //修改或者新增 + logger.Debug("account put key: %s val: %s", ev.Kv.Key, ev.Kv.Value) + case mvccpb.DELETE: //删除 + logger.Debug("account delete key: %s val: %s", ev.Kv.Key, ev.Kv.Value) + } + } + } + }() + + game := etcd.etcd.Watch(context.Background(), fmt.Sprintf("/%s/", common.GlobalConf.GameConf.Name), clientv3.WithPrefix()) + for wresp := range game { + for _, ev := range wresp.Events { + switch ev.Type { + case mvccpb.PUT: //修改或者新增 + logger.Debug("game put key: %s val: %s", ev.Kv.Key, ev.Kv.Value) + case mvccpb.DELETE: //删除 + logger.Debug("game delete key: %s val: %s", ev.Kv.Key, ev.Kv.Value) + } + } + } + +} diff --git a/common/logger/README.md b/common/logger/README.md new file mode 100644 index 0000000..43d62bd --- /dev/null +++ b/common/logger/README.md @@ -0,0 +1,143 @@ +# logger +convenient log package + +# 1. 使用说明 +```go + import "github.com/wonderivan/logger" + + // 配置logger,如果不配置时默认为控制台输出,等级为DEBG + logger.SetLogger(`{"Console": {"level": "DEBG"}`) + // 配置说明见下文 + + // 设置完成后,即可在控制台和日志文件app.log中看到如下输出 + logger.Trace("this is Trace") + logger.Debug("this is Debug") + logger.Info("this is Info") + logger.Warn("this is Warn") + logger.Error("this is Error") + logger.Crit("this is Critical") + logger.Alert("this is Alert") + logger.Emer("this is Emergency") +``` +输出结果: + +![](images/output1.png) + +# 2. 日志等级 + +当前日志输出等级共8种,从0-7对应的等级由高到底,当配置为某个输出等级时,只有大于等于该等级的日志才会输出。不同的输出适配器支持不同的日志等级配置: + +| 等级 | 配置 | 释义 | 控制台颜色 | +| ---- | ---- | ------------------------------------------------ | ---------- | +| 0 | EMER | 系统级紧急,比如磁盘出错,内存异常,网络不可用等 | 红色底 | +| 1 | ALRT | 系统级警告,比如数据库访问异常,配置文件出错等 | 紫色 | +| 2 | CRIT | 系统级危险,比如权限出错,访问异常等 | 蓝色 | +| 3 | EROR | 用户级错误 | 红色 | +| 4 | WARN | 用户级警告 | 黄色 | +| 5 | INFO | 用户级重要 | 天蓝色 | +| 6 | DEBG | 用户级调试 | 绿色 | +| 7 | TRAC | 用户级基本输出 | 绿色 | + + +# 3. 配置说明 +logger当前支持控制台、文件、网络3种方式适配器输出,可以通过各自的参数进行设置,该logger支持多个方式同时输出,如果未配置某项适配器时,则不初始化也不会输出到该适配器。 + +通过调用logger.SetLogger(config string)方法设置参数,config支持json配置,也支持指定内容为json配置的文件路径,例如: +```go + // 通过配置参数直接配置 + logger.SetLogger(`{"Console": {"level": "DEBG"}}`) + // 通过配置文件配置 + logger.SetLogger("/home/log.json") + +``` + +```json +{ + "TimeFormat":"2006-01-02 15:04:05", // 输出日志开头时间格式 + "Console": { // 控制台日志配置 + "level": "TRAC", // 控制台日志输出等级 + "color": true // 控制台日志颜色开关 + }, + "File": { // 文件日志配置 + "filename": "app.log", // 初始日志文件名 + "level": "TRAC", // 日志文件日志输出等级 + "daily": true, // 跨天后是否创建新日志文件,当append=true时有效 + "maxlines": 1000000, // 日志文件最大行数,当append=true时有效 + "maxsize": 1, // 日志文件最大大小,当append=true时有效 + "maxdays": -1, // 日志文件有效期 + "append": true, // 是否支持日志追加 + "permit": "0660" // 新创建的日志文件权限属性 + }, + "Conn": { // 网络日志配置 + "net":"tcp", // 日志传输模式 + "addr":"10.1.55.10:1024", // 日志接收服务器 + "level": "Warn", // 网络日志输出等级 + "reconnect":true, // 网络断开后是否重连 + "reconnectOnMsg":false, // 发送完每条消息后是否断开网络 + } +} +``` + +- 时间格式 + +| 时间类型 | 时间格式 | +| ------------ | ----------------------------------------- | +| ANSIC | "Mon Jan _2 15:04:05 2006" | +| UnixDate | "Mon Jan _2 15:04:05 MST 2006" | +| RubyDate | "Mon Jan 02 15:04:05 -0700 2006" | +| RFC822 | "02 Jan 06 15:04 MST" | +| RFC822Z | "02 Jan 06 15:04 -0700" | +| RFC850 | "Monday, 02-Jan-06 15:04:05 MST" | +| RFC1123 | "Mon, 02 Jan 2006 15:04:05 MST" | +| RFC1123Z | "Mon, 02 Jan 2006 15:04:05 -0700" | +| RFC3339 | "2006-01-02T15:04:05Z07:00" | +| RFC3339Nano | "2006-01-02T15:04:05.999999999Z07:00" | +| Kitchen | "3:04PM" | +| Stamp | "Jan _2 15:04:05" | +| StampMilli | "Jan _2 15:04:05.000" | +| StampMicro | "Jan _2 15:04:05.000000" | +| StampNano | "Jan _2 15:04:05.000000000" | +| RFC3339Nano1 | "2006-01-02 15:04:05.999999999 -0700 MST" | +| DEFAULT | "2006-01-02 15:04:05" | + +- 时间格式打印: +``` +========RFC1123Z time format======== +Thu, 02 Aug 2018 18:48:04 +0800 [DEBG] [github.com/wonderivan/logger/log_test.go:115] Debug RFC1123Z +========Stamp time format======== +Aug 2 18:48:04 [DEBG] [github.com/wonderivan/logger/log_test.go:115] Debug Stamp +========StampMilli time format======== +Aug 2 18:48:04.489 [DEBG] [github.com/wonderivan/logger/log_test.go:115] Debug StampMilli +========StampNano time format======== +Aug 2 18:48:04.490002155 [DEBG] [github.com/wonderivan/logger/log_test.go:115] Debug StampNano +========RubyDate time format======== +Thu Aug 02 18:48:04 +0800 2018 [DEBG] [github.com/wonderivan/logger/log_test.go:115] Debug RubyDate +========RFC822 time format======== +02 Aug 18 18:48 CST [DEBG] [github.com/wonderivan/logger/log_test.go:115] Debug RFC822 +========RFC822Z time format======== +02 Aug 18 18:48 +0800 [DEBG] [github.com/wonderivan/logger/log_test.go:115] Debug RFC822Z +========RFC1123 time format======== +Thu, 02 Aug 2018 18:48:04 CST [DEBG] [github.com/wonderivan/logger/log_test.go:115] Debug RFC1123 +========RFC3339 time format======== +2018-08-02T18:48:04+08:00 [DEBG] [github.com/wonderivan/logger/log_test.go:115] Debug RFC3339 +========RFC3339Nano time format======== +2018-08-02T18:48:04.490377325+08:00 [DEBG] [github.com/wonderivan/logger/log_test.go:115] Debug RFC3339Nano +========ANSIC time format======== +Thu Aug 2 18:48:04 2018 [DEBG] [github.com/wonderivan/logger/log_test.go:115] Debug ANSIC +========UnixDate time format======== +Thu Aug 2 18:48:04 CST 2018 [DEBG] [github.com/wonderivan/logger/log_test.go:115] Debug UnixDate +========RFC850 time format======== +Thursday, 02-Aug-18 18:48:04 CST [DEBG] [github.com/wonderivan/logger/log_test.go:115] Debug RFC850 +========Kitchen time format======== +6:48PM [DEBG] [github.com/wonderivan/logger/log_test.go:115] Debug Kitchen +========StampMicro time format======== +Aug 2 18:48:04.490662 [DEBG] [github.com/wonderivan/logger/log_test.go:115] Debug StampMicro +``` + +# 4. 其他 + +1. logger默认是控制台输出,输出等级为DEBG,默认是支持颜色区分的。 +2. 日志文件append为true时,当写入的日志文件发生跨天(daily为true)或超过最大限制时,会创建一个新文件,原有文件格式被重命名为: ****.xxxx-xx-xx.xxx.xxx 格式,例如:当向app.log写入日志时,触发了创建新文件操作,则将app.log重命名为 app.2018-01-01.001.log, 如果此时app.2018-01-01.001.log已经存在,则将刚才的app.log重命名为 app.2018-01-01.002.log,以此类推。 +3. logger package默认初始化了全局的defaultLogger,直接调用logger包的Debug方法时,会默认调用defaultLogger.Debug,所以普通调用时,仅需要import logger即可使用。 +4. 网络配置中的reconnectOnMsg为每条消息都重连一次网络日志中心,适用于写日志频率极低的情况下的服务调用,避免长时间连接,占用资源。但强烈不建议普通使用时设置为true,这将会导致调用方反复的网络重连,极大增加资源消耗和延迟。 +5. conn网络输出适配器经过ELK集成环境的测试验证,通过该方式发送的日志,能够正常通过Elecsearch和Kibana检索和分析 \ No newline at end of file diff --git a/common/logger/conn.go b/common/logger/conn.go new file mode 100644 index 0000000..0c26efa --- /dev/null +++ b/common/logger/conn.go @@ -0,0 +1,115 @@ +package logger + +import ( + "encoding/json" + "fmt" + "io" + "net" + "strings" + "sync" + "time" +) + +type connLogger struct { + sync.Mutex + innerWriter io.WriteCloser + Net string `json:"net"` + Addr string `json:"addr"` + Level string `json:"level"` + LogLevel int + illNetFlag bool //网络异常标记 +} + +func (c *connLogger) Init(jsonConfig string, appName string) error { + if len(jsonConfig) == 0 { + return nil + } + //fmt.Printf("consoleWriter Init:%s\n", jsonConfig) + err := json.Unmarshal([]byte(jsonConfig), c) + if err != nil { + return err + } + if l, ok := LevelMap[c.Level]; ok { + c.LogLevel = l + } + if c.innerWriter != nil { + c.innerWriter.Close() + c.innerWriter = nil + } + + go func() { + for { + c.connect() + time.Sleep(10*time.Millisecond) + } + }() + + return nil +} + +func (c *connLogger) LogWrite(when time.Time, msgText interface{}, level int) (err error) { + if level > c.LogLevel { + return nil + } + + msg, ok := msgText.(*loginfo) + if !ok { + return + } + + if c.innerWriter != nil { + err = c.println(when, msg) + //网络异常,通知处理网络的go程自动重连 + if err != nil { + c.innerWriter.Close() + c.innerWriter = nil + } + } + + return +} + +func (c *connLogger) Destroy() { + if c.innerWriter != nil { + c.innerWriter.Close() + } +} + +func (c *connLogger) connect() error { + if c.innerWriter != nil { + return nil + } + addrs := strings.Split(c.Addr, ";") + for _, addr := range addrs { + conn, err := net.DialTimeout(c.Net, addr, 1 * time.Second) + if err != nil { + fmt.Printf("net.Dial error:%v\n", err) + //continue + return err + } + + if tcpConn, ok := conn.(*net.TCPConn); ok { + tcpConn.SetKeepAlive(true) + } + c.innerWriter = conn + return nil + } + return fmt.Errorf("hava no valid logs service addr:%v", c.Addr) +} + +func (c *connLogger) println(when time.Time, msg *loginfo) error { + c.Lock() + defer c.Unlock() + ss, err := json.Marshal(msg) + if err != nil { + return err + } + _, err = c.innerWriter.Write(append(ss, '\n')) + + //返回err,解决日志系统网络异常后的自动重连 + return err +} + +func init() { + Register(AdapterConn, &connLogger{LogLevel: LevelTrace}) +} diff --git a/common/logger/console.go b/common/logger/console.go new file mode 100644 index 0000000..691d308 --- /dev/null +++ b/common/logger/console.go @@ -0,0 +1,92 @@ +package logger + +import ( + "encoding/json" + //"fmt" + "os" + "runtime" + "sync" + "time" +) + +type brush func(string) string + +func newBrush(color string) brush { + pre := "\033[" + reset := "\033[0m" + return func(text string) string { + return pre + color + "m" + text + reset + } +} + +//鉴于终端的通常使用习惯,一般白色和黑色字体是不可行的,所以30,37不可用, +var colors = []brush{ + newBrush("1;41"), // Emergency 红色底 + newBrush("1;35"), // Alert 紫色 + newBrush("1;34"), // Critical 蓝色 + newBrush("1;31"), // Error 红色 + newBrush("1;33"), // Warn 黄色 + newBrush("1;36"), // Informational 天蓝色 + newBrush("1;32"), // Debug 绿色 + newBrush("1;32"), // Trace 绿色 +} + +type consoleLogger struct { + sync.Mutex + Level string `json:"level"` + Colorful bool `json:"color"` + LogLevel int +} + +func (c *consoleLogger) Init(jsonConfig string, appName string) error { + if len(jsonConfig) == 0 { + return nil + } + if jsonConfig != "{}" { + //fmt.Fprintf(os.Stdout, "consoleLogger Init:%s\n", jsonConfig) + } + + err := json.Unmarshal([]byte(jsonConfig), c) + if runtime.GOOS == "windows" { + c.Colorful = false + } + + if l, ok := LevelMap[c.Level]; ok { + c.LogLevel = l + return nil + } + + return err +} + +func (c *consoleLogger) LogWrite(when time.Time, msgText interface{}, level int) error { + if level > c.LogLevel { + return nil + } + msg, ok := msgText.(string) + if !ok { + return nil + } + if c.Colorful { + msg = colors[level](msg) + } + c.printlnConsole(when, msg) + return nil +} + +func (c *consoleLogger) Destroy() { + +} + +func (c *consoleLogger) printlnConsole(when time.Time, msg string) { + c.Lock() + defer c.Unlock() + os.Stdout.Write(append([]byte(msg), '\n')) +} + +func init() { + Register(AdapterConsole, &consoleLogger{ + LogLevel: LevelDebug, + Colorful: runtime.GOOS != "windows", + }) +} diff --git a/common/logger/file.go b/common/logger/file.go new file mode 100644 index 0000000..e2a94e9 --- /dev/null +++ b/common/logger/file.go @@ -0,0 +1,288 @@ +package logger + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "io" + "os" + "path/filepath" + "strconv" + "strings" + "sync" + "time" +) + +type fileLogger struct { + sync.RWMutex + fileWriter *os.File + + Filename string //`json:"filename"` + Append bool `json:"append"` + MaxLines int `json:"maxlines"` + MaxSize int `json:"maxsize"` + Daily bool `json:"daily"` + MaxDays int64 `json:"maxdays"` + Level string `json:"level"` + PermitMask string `json:"permit"` + + LogLevel int + maxSizeCurSize int + maxLinesCurLines int + dailyOpenDate int + dailyOpenTime time.Time + fileNameOnly, suffix string +} + +// Init file logger with json config. +// jsonConfig like: +// { +// "filename":"log/app.log", +// "maxlines":10000, +// "maxsize":1024, +// "daily":true, +// "maxdays":15, +// "rotate":true, +// "permit":"0600" +// } +func (f *fileLogger) Init(jsonConfig string, appName string) error { + //fmt.Printf("fileLogger Init:%s\n", jsonConfig) + if len(jsonConfig) == 0 { + return nil + } + err := json.Unmarshal([]byte(jsonConfig), f) + if err != nil { + return err + } + f.Filename = appName + if len(f.Filename) == 0 { + return errors.New("jsonconfig must have filename") + } + f.suffix = filepath.Ext(f.Filename) + f.fileNameOnly = strings.TrimSuffix(f.Filename, f.suffix) + f.MaxSize *= 1024 * 1024 // 将单位转换成MB + if f.suffix == "" { + f.suffix = ".log" + f.Filename += f.suffix + } + if l, ok := LevelMap[f.Level]; ok { + f.LogLevel = l + } + err = f.newFile() + return err +} + +func (f *fileLogger) needCreateFresh(size int, day int) bool { + return (f.MaxLines > 0 && f.maxLinesCurLines >= f.MaxLines) || + (f.MaxSize > 0 && f.maxSizeCurSize+size >= f.MaxSize) || + (f.Daily && day != f.dailyOpenDate) + +} + +// WriteMsg write logger message into file. +func (f *fileLogger) LogWrite(when time.Time, msgText interface{}, level int) error { + msg, ok := msgText.(string) + if !ok { + return nil + } + if level > f.LogLevel { + return nil + } + + day := when.Day() + msg += "\n" + if f.Append { + f.RLock() + if f.needCreateFresh(len(msg), day) { + f.RUnlock() + f.Lock() + if f.needCreateFresh(len(msg), day) { + if err := f.createFreshFile(when); err != nil { + fmt.Fprintf(os.Stdout, "createFreshFile(%q): %s\n", f.Filename, err) + } + } + f.Unlock() + } else { + f.RUnlock() + } + } + + f.Lock() + _, err := f.fileWriter.Write([]byte(msg)) + if err == nil { + f.maxLinesCurLines++ + f.maxSizeCurSize += len(msg) + } + f.Unlock() + return err +} + +func (f *fileLogger) createLogFile() (*os.File, error) { + // Open the log file + perm, err := strconv.ParseInt(f.PermitMask, 8, 64) + if err != nil { + return nil, err + } + fd, err := os.OpenFile(f.Filename, os.O_WRONLY|os.O_APPEND|os.O_CREATE, os.FileMode(perm)) + if err == nil { + // Make sure file perm is user set perm cause of `os.OpenFile` will obey umask + os.Chmod(f.Filename, os.FileMode(perm)) + } + return fd, err +} + +func (f *fileLogger) newFile() error { + file, err := f.createLogFile() + if err != nil { + return err + } + if f.fileWriter != nil { + f.fileWriter.Close() + } + f.fileWriter = file + + fInfo, err := file.Stat() + if err != nil { + return fmt.Errorf("get stat err: %s", err) + } + f.maxSizeCurSize = int(fInfo.Size()) + f.dailyOpenTime = time.Now() + f.dailyOpenDate = f.dailyOpenTime.Day() + f.maxLinesCurLines = 0 + if f.maxSizeCurSize > 0 { + count, err := f.lines() + if err != nil { + return err + } + f.maxLinesCurLines = count + } + return nil +} + +func (f *fileLogger) lines() (int, error) { + fd, err := os.Open(f.Filename) + if err != nil { + return 0, err + } + defer fd.Close() + + buf := make([]byte, 32768) // 32k + count := 0 + lineSep := []byte{'\n'} + + for { + c, err := fd.Read(buf) + if err != nil && err != io.EOF { + return count, err + } + + count += bytes.Count(buf[:c], lineSep) + + if err == io.EOF { + break + } + } + + return count, nil +} + +// new file name like xx.2013-01-01.001.log +func (f *fileLogger) createFreshFile(logTime time.Time) error { + // file exists + // Find the next available number + num := 1 + fName := "" + rotatePerm, err := strconv.ParseInt(f.PermitMask, 8, 64) + if err != nil { + return err + } + + _, err = os.Lstat(f.Filename) + if err != nil { + // 初始日志文件不存在,无需创建新文件 + goto RESTART_LOGGER + } + // 日期变了, 说明跨天,重命名时需要保存为昨天的日期 + if f.dailyOpenDate != logTime.Day() { + for ; err == nil && num <= 999; num++ { + fName = f.fileNameOnly + fmt.Sprintf(".%s.%03d%s", f.dailyOpenTime.Format("2006-01-02"), num, f.suffix) + _, err = os.Lstat(fName) + } + } else { //如果仅仅是文件大小或行数达到了限制,仅仅变更后缀序号即可 + for ; err == nil && num <= 999; num++ { + fName = f.fileNameOnly + fmt.Sprintf(".%s.%03d%s", logTime.Format("2006-01-02"), num, f.suffix) + _, err = os.Lstat(fName) + } + } + + if err == nil { + return fmt.Errorf("Cannot find free log number to rename %s", f.Filename) + } + f.fileWriter.Close() + + // 当创建新文件标记为true时 + // 当日志文件超过最大限制行 + // 当日志文件超过最大限制字节 + // 当日志文件隔天更新标记为true时 + // 将旧文件重命名,然后创建新文件 + err = os.Rename(f.Filename, fName) + if err != nil { + fmt.Fprintf(os.Stdout, "os.Rename %s to %s err:%s\n", f.Filename, fName, err.Error()) + goto RESTART_LOGGER + } + + err = os.Chmod(fName, os.FileMode(rotatePerm)) + +RESTART_LOGGER: + + startLoggerErr := f.newFile() + go f.deleteOldLog() + + if startLoggerErr != nil { + return fmt.Errorf("Rotate StartLogger: %s", startLoggerErr) + } + if err != nil { + return fmt.Errorf("Rotate: %s", err) + } + return nil +} + +func (f *fileLogger) deleteOldLog() { + dir := filepath.Dir(f.Filename) + filepath.Walk(dir, func(path string, info os.FileInfo, err error) (returnErr error) { + defer func() { + if r := recover(); r != nil { + fmt.Fprintf(os.Stdout, "Unable to delete old log '%s', error: %v\n", path, r) + } + }() + + if info == nil { + return + } + + if f.MaxDays != -1 && !info.IsDir() && info.ModTime().Add(24*time.Hour*time.Duration(f.MaxDays)).Before(time.Now()) { + if strings.HasPrefix(filepath.Base(path), filepath.Base(f.fileNameOnly)) && + strings.HasSuffix(filepath.Base(path), f.suffix) { + os.Remove(path) + } + } + return + }) +} + +func (f *fileLogger) Destroy() { + f.fileWriter.Close() +} + +func init() { + Register(AdapterFile, &fileLogger{ + Daily: true, + MaxDays: 7, + Append: true, + LogLevel: LevelDebug, + PermitMask: "0777", + MaxLines: 10, + MaxSize: 10 * 1024 * 1024, + }) +} diff --git a/common/logger/log.go b/common/logger/log.go new file mode 100644 index 0000000..79f1b15 --- /dev/null +++ b/common/logger/log.go @@ -0,0 +1,472 @@ +package logger + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "os" + "runtime" + "strings" + "sync" + "time" +) + +// 默认日志输出 +var defaultLogger *LocalLogger +var appName = "app" + +// 日志等级,从0-7,日优先级由高到低 +const ( + LevelEmergency = iota // 系统级紧急,比如磁盘出错,内存异常,网络不可用等 + LevelAlert // 系统级警告,比如数据库访问异常,配置文件出错等 + LevelCritical // 系统级危险,比如权限出错,访问异常等 + LevelError // 用户级错误 + LevelWarning // 用户级警告 + LevelInformational // 用户级信息 + LevelDebug // 用户级调试 + LevelTrace // 用户级基本输出 +) + +// 日志等级和描述映射关系 +var LevelMap = map[string]int{ + "EMER": LevelEmergency, + "ALRT": LevelAlert, + "CRIT": LevelCritical, + "EROR": LevelError, + "WARN": LevelWarning, + "INFO": LevelInformational, + "DEBG": LevelDebug, + "TRAC": LevelTrace, +} + +// 注册实现的适配器, 当前支持控制台,文件和网络输出 +var adapters = make(map[string]Logger) + +// 日志记录等级字段 +var levelPrefix = [LevelTrace + 1]string{ + "EMER", + "ALRT", + "CRIT", + "EROR", + "WARN", + "INFO", + "DEBG", + "TRAC", +} + +const ( + logTimeDefaultFormat = "2006-01-02 15:04:05" // 日志输出默认格式 + AdapterConsole = "console" // 控制台输出配置项 + AdapterFile = "file" // 文件输出配置项 + AdapterConn = "conn" // 网络输出配置项 +) + +// log provider interface +type Logger interface { + Init(config string, appName string) error + LogWrite(when time.Time, msg interface{}, level int) error + Destroy() +} + +// 日志输出适配器注册,log需要实现Init,LogWrite,Destroy方法 +func Register(name string, log Logger) { + if log == nil { + panic("logs: Register provide is nil") + } + if _, ok := adapters[name]; ok { + panic("logs: Register called twice for provider " + name) + } + adapters[name] = log +} + +type loginfo struct { + Time string + Level string + Path string + Name string + Content string +} + +type nameLogger struct { + Logger + name string + config string +} + +type LocalLogger struct { + lock sync.RWMutex + init bool + outputs []*nameLogger + appName string + callDepth int + timeFormat string + usePath string +} + +func NewLogger(depth ...int) *LocalLogger { + dep := append(depth, 2)[0] + l := new(LocalLogger) + // appName用于记录网络传输时标记的程序发送方, + // 通过环境变量APPSN进行设置,默认为NONE,此时无法通过网络日志检索区分不同服务发送方 + appSn := os.Getenv("APPSN") + if appSn == "" { + appSn = "NONE" + } + l.appName = "[" + appSn + "]" + l.callDepth = dep + l.SetLogger(AdapterConsole) + l.timeFormat = logTimeDefaultFormat + return l +} + +//配置文件 +type logConfig struct { + TimeFormat string `json:"TimeFormat"` + Console *consoleLogger `json:"Console,omitempty"` + File *fileLogger `json:"File,omitempty"` + Conn *connLogger `json:"Conn,omitempty"` +} + +func init() { + defaultLogger = NewLogger(3) +} + +func (this *LocalLogger) SetLogger(adapterName string, configs ...string) error { + this.lock.Lock() + defer this.lock.Unlock() + + if !this.init { + this.outputs = []*nameLogger{} + this.init = true + } + + config := append(configs, "{}")[0] + var num int = -1 + var i int + var l *nameLogger + for i, l = range this.outputs { + if l.name == adapterName { + if l.config == config { + //配置没有变动,不重新设置 + return fmt.Errorf("you have set same config for this adaptername %s", adapterName) + } + l.Logger.Destroy() + num = i + break + } + } + logger, ok := adapters[adapterName] + if !ok { + return fmt.Errorf("unknown adaptername %s (forgotten Register?)", adapterName) + } + + err := logger.Init(config, appName) + if err != nil { + fmt.Fprintf(os.Stdout, "logger Init <%s> err:%v, %s output ignore!\n", + adapterName, err, adapterName) + return err + } + if num >= 0 { + this.outputs[i] = &nameLogger{name: adapterName, Logger: logger, config: config} + return nil + } + this.outputs = append(this.outputs, &nameLogger{name: adapterName, Logger: logger, config: config}) + return nil +} + +func (this *LocalLogger) DelLogger(adapterName string) error { + this.lock.Lock() + defer this.lock.Unlock() + outputs := []*nameLogger{} + for _, lg := range this.outputs { + if lg.name == adapterName { + lg.Destroy() + } else { + outputs = append(outputs, lg) + } + } + if len(outputs) == len(this.outputs) { + return fmt.Errorf("logs: unknown adaptername %s (forgotten Register?)", adapterName) + } + this.outputs = outputs + return nil +} + +// 设置日志起始路径 +func (this *LocalLogger) SetLogPathTrim(trimPath string) { + this.usePath = trimPath +} + +func (this *LocalLogger) writeToLoggers(when time.Time, msg *loginfo, level int) { + this.lock.RLock() + defer this.lock.RUnlock() + for _, l := range this.outputs { + if l.name == AdapterConn { + //网络日志,使用json格式发送,此处使用结构体,用于类似ElasticSearch功能检索 + err := l.LogWrite(when, msg, level) + if err != nil { + fmt.Fprintf(os.Stdout, "unable to WriteMsg to adapter:%v,error:%v\n", l.name, err) + } + continue + } + + msgStr := when.Format(this.timeFormat) + " [" + msg.Level + "] " + "[" + msg.Path + "] " + msg.Content + err := l.LogWrite(when, msgStr, level) + if err != nil { + fmt.Fprintf(os.Stdout, "unable to WriteMsg to adapter:%v,error:%v\n", l.name, err) + } + } +} + +func (this *LocalLogger) writeMsg(logLevel int, msg string, v ...interface{}) error { + if !this.init { + this.SetLogger(AdapterConsole) + } + msgSt := new(loginfo) + src := "" + if len(v) > 0 { + msg = fmt.Sprintf(msg, v...) + } + when := time.Now() + _, file, lineno, ok := runtime.Caller(this.callDepth) + var strim string = "src/" + if this.usePath != "" { + strim = this.usePath + } + if ok { + + src = strings.Replace( + fmt.Sprintf("%s:%d", stringTrim(file, strim), lineno), "%2e", ".", -1) + } + + msgSt.Level = levelPrefix[logLevel] + msgSt.Path = src + msgSt.Content = msg + msgSt.Name = this.appName + msgSt.Time = when.Format(this.timeFormat) + this.writeToLoggers(when, msgSt, logLevel) + + return nil +} + +func (this *LocalLogger) Fatal(format string, args ...interface{}) { + this.Emer("###Exec Panic:"+format, args...) + os.Exit(1) +} + +func (this *LocalLogger) Panic(format string, args ...interface{}) { + this.Emer("###Exec Panic:"+format, args...) + panic(fmt.Sprintf(format, args...)) +} + +// Emer Log EMERGENCY level message. +func (this *LocalLogger) Emer(format string, v ...interface{}) { + this.writeMsg(LevelEmergency, format, v...) +} + +// Alert Log ALERT level message. +func (this *LocalLogger) Alert(format string, v ...interface{}) { + this.writeMsg(LevelAlert, format, v...) +} + +// Crit Log CRITICAL level message. +func (this *LocalLogger) Crit(format string, v ...interface{}) { + this.writeMsg(LevelCritical, format, v...) +} + +// Error Log ERROR level message. +func (this *LocalLogger) Error(format string, v ...interface{}) { + this.writeMsg(LevelError, format, v...) +} + +// Warn Log WARNING level message. +func (this *LocalLogger) Warn(format string, v ...interface{}) { + this.writeMsg(LevelWarning, format, v...) +} + +// Info Log INFO level message. +func (this *LocalLogger) Info(format string, v ...interface{}) { + this.writeMsg(LevelInformational, format, v...) +} + +// Debug Log DEBUG level message. +func (this *LocalLogger) Debug(format string, v ...interface{}) { + this.writeMsg(LevelDebug, format, v...) +} + +// Trace Log TRAC level message. +func (this *LocalLogger) Trace(format string, v ...interface{}) { + this.writeMsg(LevelTrace, format, v...) +} + +func (this *LocalLogger) Close() { + + for _, l := range this.outputs { + l.Destroy() + } + this.outputs = nil + +} + +func (this *LocalLogger) Reset() { + this.lock.Lock() + defer this.lock.Unlock() + for _, l := range this.outputs { + l.Destroy() + } + this.outputs = nil +} + +func (this *LocalLogger) SetCallDepth(depth int) { + this.callDepth = depth +} + +// GetlocalLogger returns the defaultLogger +func GetlocalLogger() *LocalLogger { + return defaultLogger +} + +// Reset will remove all the adapter +func Reset() { + defaultLogger.Reset() +} + +func SetLogPathTrim(trimPath string) { + defaultLogger.SetLogPathTrim(trimPath) +} + +// param 可以是log配置文件名,也可以是log配置内容,默认DEBUG输出到控制台 +func SetLogger(param ...string) error { + if 0 == len(param) { + //默认只输出到控制台 + defaultLogger.SetLogger(AdapterConsole) + return nil + } + + c := param[0] + if len(param) > 1 { + appName = param[1] + } + conf := new(logConfig) + err := json.Unmarshal([]byte(c), conf) + if err != nil { //不是json,就认为是配置文件,如果都不是,打印日志,然后退出 + // Open the configuration file + fd, err := os.Open(c) + if err != nil { + fmt.Fprintf(os.Stdout, "Could not open %s for configure: %s\n", c, err) + os.Exit(1) + return err + } + + contents, err := ioutil.ReadAll(fd) + if err != nil { + fmt.Fprintf(os.Stdout, "Could not read %s: %s\n", c, err) + os.Exit(1) + return err + } + err = json.Unmarshal(contents, conf) + if err != nil { + fmt.Fprintf(os.Stdout, "Could not Unmarshal %s: %s\n", contents, err) + os.Exit(1) + return err + } + } + if conf.TimeFormat != "" { + defaultLogger.timeFormat = conf.TimeFormat + } + if conf.Console != nil { + console, _ := json.Marshal(conf.Console) + defaultLogger.SetLogger(AdapterConsole, string(console)) + } + if conf.File != nil { + file, _ := json.Marshal(conf.File) + defaultLogger.SetLogger(AdapterFile, string(file)) + } + if conf.Conn != nil { + conn, _ := json.Marshal(conf.Conn) + defaultLogger.SetLogger(AdapterConn, string(conn)) + } + return nil +} + +// Painc logs a message at emergency level and panic. +func Painc(f interface{}, v ...interface{}) { + defaultLogger.Panic(formatLog(f, v...)) +} + +// Fatal logs a message at emergency level and exit. +func Fatal(f interface{}, v ...interface{}) { + defaultLogger.Fatal(formatLog(f, v...)) +} + +// Emer logs a message at emergency level. +func Emer(f interface{}, v ...interface{}) { + defaultLogger.Emer(formatLog(f, v...)) +} + +// Alert logs a message at alert level. +func Alert(f interface{}, v ...interface{}) { + defaultLogger.Alert(formatLog(f, v...)) +} + +// Crit logs a message at critical level. +func Crit(f interface{}, v ...interface{}) { + defaultLogger.Crit(formatLog(f, v...)) +} + +// Error logs a message at error level. +func Error(f interface{}, v ...interface{}) { + defaultLogger.Error(formatLog(f, v...)) +} + +// Warn logs a message at warning level. +func Warn(f interface{}, v ...interface{}) { + defaultLogger.Warn(formatLog(f, v...)) +} + +// Info logs a message at info level. +func Info(f interface{}, v ...interface{}) { + defaultLogger.Info(formatLog(f, v...)) +} + +// Notice logs a message at debug level. +func Debug(f interface{}, v ...interface{}) { + defaultLogger.Debug(formatLog(f, v...)) +} + +// Trace logs a message at trace level. +func Trace(f interface{}, v ...interface{}) { + defaultLogger.Trace(formatLog(f, v...)) +} + +func formatLog(f interface{}, v ...interface{}) string { + var msg string + switch f.(type) { + case string: + msg = f.(string) + if len(v) == 0 { + return msg + } + if strings.Contains(msg, "%") && !strings.Contains(msg, "%%") { + //format string + } else { + //do not contain format char + msg += strings.Repeat(" %v", len(v)) + } + default: + msg = fmt.Sprint(f) + if len(v) == 0 { + return msg + } + msg += strings.Repeat(" %v", len(v)) + } + return fmt.Sprintf(msg, v...) +} + +func stringTrim(s string, cut string) string { + ss := strings.SplitN(s, cut, 2) + if 1 == len(ss) { + return ss[0] + } + return ss[1] +} diff --git a/common/snowflake.go b/common/snowflake.go new file mode 100644 index 0000000..0d08509 --- /dev/null +++ b/common/snowflake.go @@ -0,0 +1,76 @@ +package common + +import ( + "fmt" + "github.com/golang/glog" + "sync" + "time" +) + +type Snowflake struct { + *sync.Mutex // 锁 + timestamp int64 // 时间戳 ,毫秒 + workerid int64 // 工作节点 + datacenterid int64 // 数据中心机房id + sequence int64 // 序列号 +} +const ( + epoch = int64(1577808000000) // 设置起始时间(时间戳/毫秒):2020-01-01 00:00:00,有效期69年 + timestampBits = uint(41) // 时间戳占用位数 + datacenteridBits = uint(2) // 数据中心id所占位数 + workeridBits = uint(7) // 机器id所占位数 + sequenceBits = uint(12) // 序列所占的位数 + timestampMax = int64(-1 ^ (-1 << timestampBits)) // 时间戳最大值 + datacenteridMax = int64(-1 ^ (-1 << datacenteridBits)) // 支持的最大数据中心id数量 + workeridMax = int64(-1 ^ (-1 << workeridBits)) // 支持的最大机器id数量 + sequenceMask = int64(-1 ^ (-1 << sequenceBits)) // 支持的最大序列id数量 + workeridShift = sequenceBits // 机器id左移位数 + datacenteridShift = sequenceBits + workeridBits // 数据中心id左移位数 + timestampShift = sequenceBits + workeridBits + datacenteridBits // 时间戳左移位数 +) + + + +func NewSnowflake(workerid, datacenterid int64) *Snowflake { + return &Snowflake{ + Mutex: new(sync.Mutex), + timestamp: time.Now().UnixNano() / 1000000, + workerid: workerid, + datacenterid: datacenterid, + sequence: 0, + } +} + +func (s *Snowflake) NextValStr() string { + return fmt.Sprintf("%d", s.NextVal()) +} + + +func (s *Snowflake) NextVal() int64 { + s.Lock() + now := time.Now().UnixNano() / 1000000 // 转毫秒 + if s.timestamp == now { + // 当同一时间戳(精度:毫秒)下多次生成id会增加序列号 + s.sequence = (s.sequence + 1) & sequenceMask + if s.sequence == 0 { + // 如果当前序列超出12bit长度,则需要等待下一毫秒 + // 下一毫秒将使用sequence:0 + for now <= s.timestamp { + now = time.Now().UnixNano() / 1000000 + } + } + } else { + // 不同时间戳(精度:毫秒)下直接使用序列号:0 + s.sequence = 0 + } + t := now - epoch + if t > timestampMax { + s.Unlock() + glog.Errorf("epoch must be between 0 and %d", timestampMax-1) + return 0 + } + s.timestamp = now + r := int64((t)< c.LogLevel { - return nil - } - - msg, ok := msgText.(*loginfo) - if !ok { - return - } - - if c.innerWriter != nil { - err = c.println(when, msg) - //网络异常,通知处理网络的go程自动重连 - if err != nil { - c.innerWriter.Close() - c.innerWriter = nil - } - } - - return -} - -func (c *connLogger) Destroy() { - if c.innerWriter != nil { - c.innerWriter.Close() - } -} - -func (c *connLogger) connect() error { - if c.innerWriter != nil { - return nil - } - addrs := strings.Split(c.Addr, ";") - for _, addr := range addrs { - conn, err := net.DialTimeout(c.Net, addr, 1 * time.Second) - if err != nil { - fmt.Printf("net.Dial error:%v\n", err) - //continue - return err - } - - if tcpConn, ok := conn.(*net.TCPConn); ok { - tcpConn.SetKeepAlive(true) - } - c.innerWriter = conn - return nil - } - return fmt.Errorf("hava no valid logs service addr:%v", c.Addr) -} - -func (c *connLogger) println(when time.Time, msg *loginfo) error { - c.Lock() - defer c.Unlock() - ss, err := json.Marshal(msg) - if err != nil { - return err - } - _, err = c.innerWriter.Write(append(ss, '\n')) - - //返回err,解决日志系统网络异常后的自动重连 - return err -} - -func init() { - Register(AdapterConn, &connLogger{LogLevel: LevelTrace}) -} diff --git a/utils/logger/console.go b/utils/logger/console.go deleted file mode 100644 index 691d308..0000000 --- a/utils/logger/console.go +++ /dev/null @@ -1,92 +0,0 @@ -package logger - -import ( - "encoding/json" - //"fmt" - "os" - "runtime" - "sync" - "time" -) - -type brush func(string) string - -func newBrush(color string) brush { - pre := "\033[" - reset := "\033[0m" - return func(text string) string { - return pre + color + "m" + text + reset - } -} - -//鉴于终端的通常使用习惯,一般白色和黑色字体是不可行的,所以30,37不可用, -var colors = []brush{ - newBrush("1;41"), // Emergency 红色底 - newBrush("1;35"), // Alert 紫色 - newBrush("1;34"), // Critical 蓝色 - newBrush("1;31"), // Error 红色 - newBrush("1;33"), // Warn 黄色 - newBrush("1;36"), // Informational 天蓝色 - newBrush("1;32"), // Debug 绿色 - newBrush("1;32"), // Trace 绿色 -} - -type consoleLogger struct { - sync.Mutex - Level string `json:"level"` - Colorful bool `json:"color"` - LogLevel int -} - -func (c *consoleLogger) Init(jsonConfig string, appName string) error { - if len(jsonConfig) == 0 { - return nil - } - if jsonConfig != "{}" { - //fmt.Fprintf(os.Stdout, "consoleLogger Init:%s\n", jsonConfig) - } - - err := json.Unmarshal([]byte(jsonConfig), c) - if runtime.GOOS == "windows" { - c.Colorful = false - } - - if l, ok := LevelMap[c.Level]; ok { - c.LogLevel = l - return nil - } - - return err -} - -func (c *consoleLogger) LogWrite(when time.Time, msgText interface{}, level int) error { - if level > c.LogLevel { - return nil - } - msg, ok := msgText.(string) - if !ok { - return nil - } - if c.Colorful { - msg = colors[level](msg) - } - c.printlnConsole(when, msg) - return nil -} - -func (c *consoleLogger) Destroy() { - -} - -func (c *consoleLogger) printlnConsole(when time.Time, msg string) { - c.Lock() - defer c.Unlock() - os.Stdout.Write(append([]byte(msg), '\n')) -} - -func init() { - Register(AdapterConsole, &consoleLogger{ - LogLevel: LevelDebug, - Colorful: runtime.GOOS != "windows", - }) -} diff --git a/utils/logger/file.go b/utils/logger/file.go deleted file mode 100644 index e2a94e9..0000000 --- a/utils/logger/file.go +++ /dev/null @@ -1,288 +0,0 @@ -package logger - -import ( - "bytes" - "encoding/json" - "errors" - "fmt" - "io" - "os" - "path/filepath" - "strconv" - "strings" - "sync" - "time" -) - -type fileLogger struct { - sync.RWMutex - fileWriter *os.File - - Filename string //`json:"filename"` - Append bool `json:"append"` - MaxLines int `json:"maxlines"` - MaxSize int `json:"maxsize"` - Daily bool `json:"daily"` - MaxDays int64 `json:"maxdays"` - Level string `json:"level"` - PermitMask string `json:"permit"` - - LogLevel int - maxSizeCurSize int - maxLinesCurLines int - dailyOpenDate int - dailyOpenTime time.Time - fileNameOnly, suffix string -} - -// Init file logger with json config. -// jsonConfig like: -// { -// "filename":"log/app.log", -// "maxlines":10000, -// "maxsize":1024, -// "daily":true, -// "maxdays":15, -// "rotate":true, -// "permit":"0600" -// } -func (f *fileLogger) Init(jsonConfig string, appName string) error { - //fmt.Printf("fileLogger Init:%s\n", jsonConfig) - if len(jsonConfig) == 0 { - return nil - } - err := json.Unmarshal([]byte(jsonConfig), f) - if err != nil { - return err - } - f.Filename = appName - if len(f.Filename) == 0 { - return errors.New("jsonconfig must have filename") - } - f.suffix = filepath.Ext(f.Filename) - f.fileNameOnly = strings.TrimSuffix(f.Filename, f.suffix) - f.MaxSize *= 1024 * 1024 // 将单位转换成MB - if f.suffix == "" { - f.suffix = ".log" - f.Filename += f.suffix - } - if l, ok := LevelMap[f.Level]; ok { - f.LogLevel = l - } - err = f.newFile() - return err -} - -func (f *fileLogger) needCreateFresh(size int, day int) bool { - return (f.MaxLines > 0 && f.maxLinesCurLines >= f.MaxLines) || - (f.MaxSize > 0 && f.maxSizeCurSize+size >= f.MaxSize) || - (f.Daily && day != f.dailyOpenDate) - -} - -// WriteMsg write logger message into file. -func (f *fileLogger) LogWrite(when time.Time, msgText interface{}, level int) error { - msg, ok := msgText.(string) - if !ok { - return nil - } - if level > f.LogLevel { - return nil - } - - day := when.Day() - msg += "\n" - if f.Append { - f.RLock() - if f.needCreateFresh(len(msg), day) { - f.RUnlock() - f.Lock() - if f.needCreateFresh(len(msg), day) { - if err := f.createFreshFile(when); err != nil { - fmt.Fprintf(os.Stdout, "createFreshFile(%q): %s\n", f.Filename, err) - } - } - f.Unlock() - } else { - f.RUnlock() - } - } - - f.Lock() - _, err := f.fileWriter.Write([]byte(msg)) - if err == nil { - f.maxLinesCurLines++ - f.maxSizeCurSize += len(msg) - } - f.Unlock() - return err -} - -func (f *fileLogger) createLogFile() (*os.File, error) { - // Open the log file - perm, err := strconv.ParseInt(f.PermitMask, 8, 64) - if err != nil { - return nil, err - } - fd, err := os.OpenFile(f.Filename, os.O_WRONLY|os.O_APPEND|os.O_CREATE, os.FileMode(perm)) - if err == nil { - // Make sure file perm is user set perm cause of `os.OpenFile` will obey umask - os.Chmod(f.Filename, os.FileMode(perm)) - } - return fd, err -} - -func (f *fileLogger) newFile() error { - file, err := f.createLogFile() - if err != nil { - return err - } - if f.fileWriter != nil { - f.fileWriter.Close() - } - f.fileWriter = file - - fInfo, err := file.Stat() - if err != nil { - return fmt.Errorf("get stat err: %s", err) - } - f.maxSizeCurSize = int(fInfo.Size()) - f.dailyOpenTime = time.Now() - f.dailyOpenDate = f.dailyOpenTime.Day() - f.maxLinesCurLines = 0 - if f.maxSizeCurSize > 0 { - count, err := f.lines() - if err != nil { - return err - } - f.maxLinesCurLines = count - } - return nil -} - -func (f *fileLogger) lines() (int, error) { - fd, err := os.Open(f.Filename) - if err != nil { - return 0, err - } - defer fd.Close() - - buf := make([]byte, 32768) // 32k - count := 0 - lineSep := []byte{'\n'} - - for { - c, err := fd.Read(buf) - if err != nil && err != io.EOF { - return count, err - } - - count += bytes.Count(buf[:c], lineSep) - - if err == io.EOF { - break - } - } - - return count, nil -} - -// new file name like xx.2013-01-01.001.log -func (f *fileLogger) createFreshFile(logTime time.Time) error { - // file exists - // Find the next available number - num := 1 - fName := "" - rotatePerm, err := strconv.ParseInt(f.PermitMask, 8, 64) - if err != nil { - return err - } - - _, err = os.Lstat(f.Filename) - if err != nil { - // 初始日志文件不存在,无需创建新文件 - goto RESTART_LOGGER - } - // 日期变了, 说明跨天,重命名时需要保存为昨天的日期 - if f.dailyOpenDate != logTime.Day() { - for ; err == nil && num <= 999; num++ { - fName = f.fileNameOnly + fmt.Sprintf(".%s.%03d%s", f.dailyOpenTime.Format("2006-01-02"), num, f.suffix) - _, err = os.Lstat(fName) - } - } else { //如果仅仅是文件大小或行数达到了限制,仅仅变更后缀序号即可 - for ; err == nil && num <= 999; num++ { - fName = f.fileNameOnly + fmt.Sprintf(".%s.%03d%s", logTime.Format("2006-01-02"), num, f.suffix) - _, err = os.Lstat(fName) - } - } - - if err == nil { - return fmt.Errorf("Cannot find free log number to rename %s", f.Filename) - } - f.fileWriter.Close() - - // 当创建新文件标记为true时 - // 当日志文件超过最大限制行 - // 当日志文件超过最大限制字节 - // 当日志文件隔天更新标记为true时 - // 将旧文件重命名,然后创建新文件 - err = os.Rename(f.Filename, fName) - if err != nil { - fmt.Fprintf(os.Stdout, "os.Rename %s to %s err:%s\n", f.Filename, fName, err.Error()) - goto RESTART_LOGGER - } - - err = os.Chmod(fName, os.FileMode(rotatePerm)) - -RESTART_LOGGER: - - startLoggerErr := f.newFile() - go f.deleteOldLog() - - if startLoggerErr != nil { - return fmt.Errorf("Rotate StartLogger: %s", startLoggerErr) - } - if err != nil { - return fmt.Errorf("Rotate: %s", err) - } - return nil -} - -func (f *fileLogger) deleteOldLog() { - dir := filepath.Dir(f.Filename) - filepath.Walk(dir, func(path string, info os.FileInfo, err error) (returnErr error) { - defer func() { - if r := recover(); r != nil { - fmt.Fprintf(os.Stdout, "Unable to delete old log '%s', error: %v\n", path, r) - } - }() - - if info == nil { - return - } - - if f.MaxDays != -1 && !info.IsDir() && info.ModTime().Add(24*time.Hour*time.Duration(f.MaxDays)).Before(time.Now()) { - if strings.HasPrefix(filepath.Base(path), filepath.Base(f.fileNameOnly)) && - strings.HasSuffix(filepath.Base(path), f.suffix) { - os.Remove(path) - } - } - return - }) -} - -func (f *fileLogger) Destroy() { - f.fileWriter.Close() -} - -func init() { - Register(AdapterFile, &fileLogger{ - Daily: true, - MaxDays: 7, - Append: true, - LogLevel: LevelDebug, - PermitMask: "0777", - MaxLines: 10, - MaxSize: 10 * 1024 * 1024, - }) -} diff --git a/utils/logger/log.go b/utils/logger/log.go deleted file mode 100644 index 79f1b15..0000000 --- a/utils/logger/log.go +++ /dev/null @@ -1,472 +0,0 @@ -package logger - -import ( - "encoding/json" - "fmt" - "io/ioutil" - "os" - "runtime" - "strings" - "sync" - "time" -) - -// 默认日志输出 -var defaultLogger *LocalLogger -var appName = "app" - -// 日志等级,从0-7,日优先级由高到低 -const ( - LevelEmergency = iota // 系统级紧急,比如磁盘出错,内存异常,网络不可用等 - LevelAlert // 系统级警告,比如数据库访问异常,配置文件出错等 - LevelCritical // 系统级危险,比如权限出错,访问异常等 - LevelError // 用户级错误 - LevelWarning // 用户级警告 - LevelInformational // 用户级信息 - LevelDebug // 用户级调试 - LevelTrace // 用户级基本输出 -) - -// 日志等级和描述映射关系 -var LevelMap = map[string]int{ - "EMER": LevelEmergency, - "ALRT": LevelAlert, - "CRIT": LevelCritical, - "EROR": LevelError, - "WARN": LevelWarning, - "INFO": LevelInformational, - "DEBG": LevelDebug, - "TRAC": LevelTrace, -} - -// 注册实现的适配器, 当前支持控制台,文件和网络输出 -var adapters = make(map[string]Logger) - -// 日志记录等级字段 -var levelPrefix = [LevelTrace + 1]string{ - "EMER", - "ALRT", - "CRIT", - "EROR", - "WARN", - "INFO", - "DEBG", - "TRAC", -} - -const ( - logTimeDefaultFormat = "2006-01-02 15:04:05" // 日志输出默认格式 - AdapterConsole = "console" // 控制台输出配置项 - AdapterFile = "file" // 文件输出配置项 - AdapterConn = "conn" // 网络输出配置项 -) - -// log provider interface -type Logger interface { - Init(config string, appName string) error - LogWrite(when time.Time, msg interface{}, level int) error - Destroy() -} - -// 日志输出适配器注册,log需要实现Init,LogWrite,Destroy方法 -func Register(name string, log Logger) { - if log == nil { - panic("logs: Register provide is nil") - } - if _, ok := adapters[name]; ok { - panic("logs: Register called twice for provider " + name) - } - adapters[name] = log -} - -type loginfo struct { - Time string - Level string - Path string - Name string - Content string -} - -type nameLogger struct { - Logger - name string - config string -} - -type LocalLogger struct { - lock sync.RWMutex - init bool - outputs []*nameLogger - appName string - callDepth int - timeFormat string - usePath string -} - -func NewLogger(depth ...int) *LocalLogger { - dep := append(depth, 2)[0] - l := new(LocalLogger) - // appName用于记录网络传输时标记的程序发送方, - // 通过环境变量APPSN进行设置,默认为NONE,此时无法通过网络日志检索区分不同服务发送方 - appSn := os.Getenv("APPSN") - if appSn == "" { - appSn = "NONE" - } - l.appName = "[" + appSn + "]" - l.callDepth = dep - l.SetLogger(AdapterConsole) - l.timeFormat = logTimeDefaultFormat - return l -} - -//配置文件 -type logConfig struct { - TimeFormat string `json:"TimeFormat"` - Console *consoleLogger `json:"Console,omitempty"` - File *fileLogger `json:"File,omitempty"` - Conn *connLogger `json:"Conn,omitempty"` -} - -func init() { - defaultLogger = NewLogger(3) -} - -func (this *LocalLogger) SetLogger(adapterName string, configs ...string) error { - this.lock.Lock() - defer this.lock.Unlock() - - if !this.init { - this.outputs = []*nameLogger{} - this.init = true - } - - config := append(configs, "{}")[0] - var num int = -1 - var i int - var l *nameLogger - for i, l = range this.outputs { - if l.name == adapterName { - if l.config == config { - //配置没有变动,不重新设置 - return fmt.Errorf("you have set same config for this adaptername %s", adapterName) - } - l.Logger.Destroy() - num = i - break - } - } - logger, ok := adapters[adapterName] - if !ok { - return fmt.Errorf("unknown adaptername %s (forgotten Register?)", adapterName) - } - - err := logger.Init(config, appName) - if err != nil { - fmt.Fprintf(os.Stdout, "logger Init <%s> err:%v, %s output ignore!\n", - adapterName, err, adapterName) - return err - } - if num >= 0 { - this.outputs[i] = &nameLogger{name: adapterName, Logger: logger, config: config} - return nil - } - this.outputs = append(this.outputs, &nameLogger{name: adapterName, Logger: logger, config: config}) - return nil -} - -func (this *LocalLogger) DelLogger(adapterName string) error { - this.lock.Lock() - defer this.lock.Unlock() - outputs := []*nameLogger{} - for _, lg := range this.outputs { - if lg.name == adapterName { - lg.Destroy() - } else { - outputs = append(outputs, lg) - } - } - if len(outputs) == len(this.outputs) { - return fmt.Errorf("logs: unknown adaptername %s (forgotten Register?)", adapterName) - } - this.outputs = outputs - return nil -} - -// 设置日志起始路径 -func (this *LocalLogger) SetLogPathTrim(trimPath string) { - this.usePath = trimPath -} - -func (this *LocalLogger) writeToLoggers(when time.Time, msg *loginfo, level int) { - this.lock.RLock() - defer this.lock.RUnlock() - for _, l := range this.outputs { - if l.name == AdapterConn { - //网络日志,使用json格式发送,此处使用结构体,用于类似ElasticSearch功能检索 - err := l.LogWrite(when, msg, level) - if err != nil { - fmt.Fprintf(os.Stdout, "unable to WriteMsg to adapter:%v,error:%v\n", l.name, err) - } - continue - } - - msgStr := when.Format(this.timeFormat) + " [" + msg.Level + "] " + "[" + msg.Path + "] " + msg.Content - err := l.LogWrite(when, msgStr, level) - if err != nil { - fmt.Fprintf(os.Stdout, "unable to WriteMsg to adapter:%v,error:%v\n", l.name, err) - } - } -} - -func (this *LocalLogger) writeMsg(logLevel int, msg string, v ...interface{}) error { - if !this.init { - this.SetLogger(AdapterConsole) - } - msgSt := new(loginfo) - src := "" - if len(v) > 0 { - msg = fmt.Sprintf(msg, v...) - } - when := time.Now() - _, file, lineno, ok := runtime.Caller(this.callDepth) - var strim string = "src/" - if this.usePath != "" { - strim = this.usePath - } - if ok { - - src = strings.Replace( - fmt.Sprintf("%s:%d", stringTrim(file, strim), lineno), "%2e", ".", -1) - } - - msgSt.Level = levelPrefix[logLevel] - msgSt.Path = src - msgSt.Content = msg - msgSt.Name = this.appName - msgSt.Time = when.Format(this.timeFormat) - this.writeToLoggers(when, msgSt, logLevel) - - return nil -} - -func (this *LocalLogger) Fatal(format string, args ...interface{}) { - this.Emer("###Exec Panic:"+format, args...) - os.Exit(1) -} - -func (this *LocalLogger) Panic(format string, args ...interface{}) { - this.Emer("###Exec Panic:"+format, args...) - panic(fmt.Sprintf(format, args...)) -} - -// Emer Log EMERGENCY level message. -func (this *LocalLogger) Emer(format string, v ...interface{}) { - this.writeMsg(LevelEmergency, format, v...) -} - -// Alert Log ALERT level message. -func (this *LocalLogger) Alert(format string, v ...interface{}) { - this.writeMsg(LevelAlert, format, v...) -} - -// Crit Log CRITICAL level message. -func (this *LocalLogger) Crit(format string, v ...interface{}) { - this.writeMsg(LevelCritical, format, v...) -} - -// Error Log ERROR level message. -func (this *LocalLogger) Error(format string, v ...interface{}) { - this.writeMsg(LevelError, format, v...) -} - -// Warn Log WARNING level message. -func (this *LocalLogger) Warn(format string, v ...interface{}) { - this.writeMsg(LevelWarning, format, v...) -} - -// Info Log INFO level message. -func (this *LocalLogger) Info(format string, v ...interface{}) { - this.writeMsg(LevelInformational, format, v...) -} - -// Debug Log DEBUG level message. -func (this *LocalLogger) Debug(format string, v ...interface{}) { - this.writeMsg(LevelDebug, format, v...) -} - -// Trace Log TRAC level message. -func (this *LocalLogger) Trace(format string, v ...interface{}) { - this.writeMsg(LevelTrace, format, v...) -} - -func (this *LocalLogger) Close() { - - for _, l := range this.outputs { - l.Destroy() - } - this.outputs = nil - -} - -func (this *LocalLogger) Reset() { - this.lock.Lock() - defer this.lock.Unlock() - for _, l := range this.outputs { - l.Destroy() - } - this.outputs = nil -} - -func (this *LocalLogger) SetCallDepth(depth int) { - this.callDepth = depth -} - -// GetlocalLogger returns the defaultLogger -func GetlocalLogger() *LocalLogger { - return defaultLogger -} - -// Reset will remove all the adapter -func Reset() { - defaultLogger.Reset() -} - -func SetLogPathTrim(trimPath string) { - defaultLogger.SetLogPathTrim(trimPath) -} - -// param 可以是log配置文件名,也可以是log配置内容,默认DEBUG输出到控制台 -func SetLogger(param ...string) error { - if 0 == len(param) { - //默认只输出到控制台 - defaultLogger.SetLogger(AdapterConsole) - return nil - } - - c := param[0] - if len(param) > 1 { - appName = param[1] - } - conf := new(logConfig) - err := json.Unmarshal([]byte(c), conf) - if err != nil { //不是json,就认为是配置文件,如果都不是,打印日志,然后退出 - // Open the configuration file - fd, err := os.Open(c) - if err != nil { - fmt.Fprintf(os.Stdout, "Could not open %s for configure: %s\n", c, err) - os.Exit(1) - return err - } - - contents, err := ioutil.ReadAll(fd) - if err != nil { - fmt.Fprintf(os.Stdout, "Could not read %s: %s\n", c, err) - os.Exit(1) - return err - } - err = json.Unmarshal(contents, conf) - if err != nil { - fmt.Fprintf(os.Stdout, "Could not Unmarshal %s: %s\n", contents, err) - os.Exit(1) - return err - } - } - if conf.TimeFormat != "" { - defaultLogger.timeFormat = conf.TimeFormat - } - if conf.Console != nil { - console, _ := json.Marshal(conf.Console) - defaultLogger.SetLogger(AdapterConsole, string(console)) - } - if conf.File != nil { - file, _ := json.Marshal(conf.File) - defaultLogger.SetLogger(AdapterFile, string(file)) - } - if conf.Conn != nil { - conn, _ := json.Marshal(conf.Conn) - defaultLogger.SetLogger(AdapterConn, string(conn)) - } - return nil -} - -// Painc logs a message at emergency level and panic. -func Painc(f interface{}, v ...interface{}) { - defaultLogger.Panic(formatLog(f, v...)) -} - -// Fatal logs a message at emergency level and exit. -func Fatal(f interface{}, v ...interface{}) { - defaultLogger.Fatal(formatLog(f, v...)) -} - -// Emer logs a message at emergency level. -func Emer(f interface{}, v ...interface{}) { - defaultLogger.Emer(formatLog(f, v...)) -} - -// Alert logs a message at alert level. -func Alert(f interface{}, v ...interface{}) { - defaultLogger.Alert(formatLog(f, v...)) -} - -// Crit logs a message at critical level. -func Crit(f interface{}, v ...interface{}) { - defaultLogger.Crit(formatLog(f, v...)) -} - -// Error logs a message at error level. -func Error(f interface{}, v ...interface{}) { - defaultLogger.Error(formatLog(f, v...)) -} - -// Warn logs a message at warning level. -func Warn(f interface{}, v ...interface{}) { - defaultLogger.Warn(formatLog(f, v...)) -} - -// Info logs a message at info level. -func Info(f interface{}, v ...interface{}) { - defaultLogger.Info(formatLog(f, v...)) -} - -// Notice logs a message at debug level. -func Debug(f interface{}, v ...interface{}) { - defaultLogger.Debug(formatLog(f, v...)) -} - -// Trace logs a message at trace level. -func Trace(f interface{}, v ...interface{}) { - defaultLogger.Trace(formatLog(f, v...)) -} - -func formatLog(f interface{}, v ...interface{}) string { - var msg string - switch f.(type) { - case string: - msg = f.(string) - if len(v) == 0 { - return msg - } - if strings.Contains(msg, "%") && !strings.Contains(msg, "%%") { - //format string - } else { - //do not contain format char - msg += strings.Repeat(" %v", len(v)) - } - default: - msg = fmt.Sprint(f) - if len(v) == 0 { - return msg - } - msg += strings.Repeat(" %v", len(v)) - } - return fmt.Sprintf(msg, v...) -} - -func stringTrim(s string, cut string) string { - ss := strings.SplitN(s, cut, 2) - if 1 == len(ss) { - return ss[0] - } - return ss[1] -} diff --git a/utils/snowflake.go b/utils/snowflake.go deleted file mode 100644 index 3b94ba5..0000000 --- a/utils/snowflake.go +++ /dev/null @@ -1,76 +0,0 @@ -package utils - -import ( - "fmt" - "github.com/golang/glog" - "sync" - "time" -) - -type Snowflake struct { - *sync.Mutex // 锁 - timestamp int64 // 时间戳 ,毫秒 - workerid int64 // 工作节点 - datacenterid int64 // 数据中心机房id - sequence int64 // 序列号 -} -const ( - epoch = int64(1577808000000) // 设置起始时间(时间戳/毫秒):2020-01-01 00:00:00,有效期69年 - timestampBits = uint(41) // 时间戳占用位数 - datacenteridBits = uint(2) // 数据中心id所占位数 - workeridBits = uint(7) // 机器id所占位数 - sequenceBits = uint(12) // 序列所占的位数 - timestampMax = int64(-1 ^ (-1 << timestampBits)) // 时间戳最大值 - datacenteridMax = int64(-1 ^ (-1 << datacenteridBits)) // 支持的最大数据中心id数量 - workeridMax = int64(-1 ^ (-1 << workeridBits)) // 支持的最大机器id数量 - sequenceMask = int64(-1 ^ (-1 << sequenceBits)) // 支持的最大序列id数量 - workeridShift = sequenceBits // 机器id左移位数 - datacenteridShift = sequenceBits + workeridBits // 数据中心id左移位数 - timestampShift = sequenceBits + workeridBits + datacenteridBits // 时间戳左移位数 -) - - - -func NewSnowflake(workerid, datacenterid int64) *Snowflake { - return &Snowflake{ - Mutex: new(sync.Mutex), - timestamp: time.Now().UnixNano() / 1000000, - workerid: workerid, - datacenterid: datacenterid, - sequence: 0, - } -} - -func (s *Snowflake) NextValStr() string { - return fmt.Sprintf("%d", s.NextVal()) -} - - -func (s *Snowflake) NextVal() int64 { - s.Lock() - now := time.Now().UnixNano() / 1000000 // 转毫秒 - if s.timestamp == now { - // 当同一时间戳(精度:毫秒)下多次生成id会增加序列号 - s.sequence = (s.sequence + 1) & sequenceMask - if s.sequence == 0 { - // 如果当前序列超出12bit长度,则需要等待下一毫秒 - // 下一毫秒将使用sequence:0 - for now <= s.timestamp { - now = time.Now().UnixNano() / 1000000 - } - } - } else { - // 不同时间戳(精度:毫秒)下直接使用序列号:0 - s.sequence = 0 - } - t := now - epoch - if t > timestampMax { - s.Unlock() - glog.Errorf("epoch must be between 0 and %d", timestampMax-1) - return 0 - } - s.timestamp = now - r := int64((t)<