diff --git a/g/frame/gins/gins.go b/g/frame/gins/gins.go index 6a963356b..4f62228f3 100644 --- a/g/frame/gins/gins.go +++ b/g/frame/gins/gins.go @@ -80,7 +80,7 @@ func View(name...string) *gview.View { } view := gview.Get(path) // 添加基于源码的搜索目录检索地址,常用于开发环境调试,只添加入口文件目录 - if p := gfile.MainPkgPath(); gfile.Exists(p) { + if p := gfile.MainPkgPath(); p != "" && gfile.Exists(p) { view.AddPath(p) } // 框架内置函数 @@ -107,7 +107,7 @@ func Config(file...string) *gcfg.Config { } config := gcfg.New(path, configFile) // 添加基于源码的搜索目录检索地址,常用于开发环境调试,只添加入口文件目录 - if p := gfile.MainPkgPath(); gfile.Exists(p) { + if p := gfile.MainPkgPath(); p != "" && gfile.Exists(p) { config.AddPath(p) } return config diff --git a/g/g.go b/g/g.go index 8ddabda0a..94a4d5601 100644 --- a/g/g.go +++ b/g/g.go @@ -21,4 +21,3 @@ type List = []Map // 常用slice数据结构(使用别名) type Slice = []interface{} type Array = Slice - diff --git a/g/net/ghttp/ghttp_server.go b/g/net/ghttp/ghttp_server.go index 1b1b6b963..cb39d3b1a 100644 --- a/g/net/ghttp/ghttp_server.go +++ b/g/net/ghttp/ghttp_server.go @@ -216,7 +216,7 @@ func (s *Server) Start() error { s.paths.Set(s.config.ServerRoot) } s.paths.Add(gfile.SelfDir()) - if p := gfile.MainPkgPath(); gfile.Exists(p) { + if p := gfile.MainPkgPath(); p != "" && gfile.Exists(p) { s.paths.Add(p) } @@ -271,11 +271,16 @@ func (s *Server) Start() error { // 打印展示路由表 func (s *Server) DumpRoutesMap() { - fmt.Println(s.GetRoutesMap()) + if s.config.DumpRouteMap { + // (等待一定时间后)当所有框架初始化信息打印完毕之后才打印路由表信息 + gtime.SetTimeout(50*time.Millisecond, func() { + glog.Header(false).Println(fmt.Sprintf("\n%s\n", s.GetRouteMap())) + }) + } } // 获得路由表(格式化字符串) -func (s *Server) GetRoutesMap() string { +func (s *Server) GetRouteMap() string { type tableItem struct { hook string domain string @@ -286,7 +291,7 @@ func (s *Server) GetRoutesMap() string { buf := bytes.NewBuffer(nil) table := tablewriter.NewWriter(buf) - table.SetHeader([]string{"DOMAIN", "METHOD", "ROUTE", "HANDLER", "HOOK"}) + table.SetHeader([]string{"SERVER", "ADDRESS", "DOMAIN", "METHOD", "ROUTE", "HANDLER", "HOOK"}) table.SetRowLine(true) table.SetBorder(false) table.SetCenterSeparator("|") @@ -319,15 +324,17 @@ func (s *Server) GetRoutesMap() string { m[item.domain].Add(item) } for _, a := range m { - s := make([]string, 5) + data := make([]string, 7) for _, v := range a.Slice() { item := v.(*tableItem) - s[0] = item.domain - s[1] = item.method - s[2] = item.route - s[3] = item.handler - s[4] = item.hook - table.Append(s) + data[0] = s.name + data[1] = s.config.Addr + data[2] = item.domain + data[3] = item.method + data[4] = item.route + data[5] = item.handler + data[6] = item.hook + table.Append(data) } } table.Render() diff --git a/g/net/ghttp/ghttp_server_config.go b/g/net/ghttp/ghttp_server_config.go index 30e2969ae..3b84a7652 100644 --- a/g/net/ghttp/ghttp_server_config.go +++ b/g/net/ghttp/ghttp_server_config.go @@ -76,6 +76,7 @@ type ServerConfig struct { // 其他设置 NameToUriType int // 服务注册时对象和方法名称转换为URI时的规则 GzipContentTypes []string // 允许进行gzip压缩的文件类型 + DumpRouteMap bool // 是否在程序启动时默认打印路由表信息 } // 默认HTTP Server @@ -102,6 +103,8 @@ var defaultServerConfig = ServerConfig { ErrorLogEnabled : true, GzipContentTypes : defaultGzipContentTypes, + + DumpRouteMap : true, } // 获取默认的http server设置 @@ -290,6 +293,14 @@ func (s *Server) SetNameToUriType(t int) { s.config.NameToUriType = t } +// 是否在程序启动时打印路由表信息 +func (s *Server) SetDumpRouteMap(enabled bool) { + if s.Status() == SERVER_STATUS_RUNNING { + glog.Error(gCHANGE_CONFIG_WHILE_RUNNING_ERROR) + } + s.config.DumpRouteMap = enabled +} + // 添加静态文件搜索目录,必须给定目录的绝对路径 func (s *Server) AddSearchPath(path string) error { return s.paths.Add(path) diff --git a/g/os/gfile/gfile.go b/g/os/gfile/gfile.go index 1c5a4c0fd..85bd511e8 100644 --- a/g/os/gfile/gfile.go +++ b/g/os/gfile/gfile.go @@ -20,11 +20,11 @@ import ( "os/user" "runtime" "path/filepath" - "gitee.com/johng/gf/g/util/gregex" "gitee.com/johng/gf/g/container/gtype" "sort" "gitee.com/johng/gf/g/util/gconv" "gitee.com/johng/gf/g/os/gfpool" + "gitee.com/johng/gf/g/util/gregex" ) // 封装了常用的文件操作方法,如需更详细的文件控制,请查看官方os包 @@ -530,7 +530,7 @@ func GetBinContentByTwoOffsets(file *os.File, start int64, end int64) []byte { } // 获取入口函数文件所在目录(main包文件目录), -// **仅对源码开发环境有效(即仅对生成该可执行文件的系统下有效)** +// **仅对源码开发环境有效(即仅对生成该可执行文件的系统下有效)** func MainPkgPath() string { path := mainPkgPath.Val() if path != "" { @@ -542,9 +542,12 @@ func MainPkgPath() string { if strings.EqualFold("", file) { // 如果是通过init包方法进入,那么无法得到准确的文件路径 f = "" - } else if !gregex.IsMatchString("^" + GoRootOfBuild(), file) { - // 不包含go源码路径 - f = file + } else { + goroot := GoRootOfBuild() + if goroot != "" && !gregex.IsMatchString("^" + GoRootOfBuild(), file) { + // 不包含go源码路径 + f = file + } } } else { break @@ -558,7 +561,8 @@ func MainPkgPath() string { return "" } -// 编译时环境的GOROOT数值 +// 编译时环境的GOROOT数值(对init初始化方法调用时无效,获取不了ROOT值) +// 注意:可能返回空 func GoRootOfBuild() string { if v := goRootOfBuild.Val(); v != "" { return v diff --git a/g/os/glog/glog.go b/g/os/glog/glog.go index 993bd31b5..0b33f2546 100644 --- a/g/os/glog/glog.go +++ b/g/os/glog/glog.go @@ -102,6 +102,10 @@ func StdPrint(enabled bool) *Logger { return logger.StdPrint(enabled) } +// 是否打印每行日志头信息(默认开启) +func Header(enabled bool) *Logger { + return logger.Header(enabled) +} func Print(v ...interface{}) { logger.Print(v ...) } diff --git a/g/os/glog/glog_logger.go b/g/os/glog/glog_logger.go index 67aa8ed05..e73c1532e 100644 --- a/g/os/glog/glog_logger.go +++ b/g/os/glog/glog_logger.go @@ -32,6 +32,7 @@ type Logger struct { level *gtype.Int // 日志输出等级 btSkip *gtype.Int // 错误产生时的backtrace回调信息skip条数 btEnabled *gtype.Bool // 是否当打印错误时同时开启backtrace打印 + printHeader *gtype.Bool // 是否不打印前缀信息(时间,级别等) alsoStdPrint *gtype.Bool // 控制台打印开关,当输出到文件/自定义输出时也同时打印到终端 } @@ -40,8 +41,12 @@ const ( gDEFAULT_FILE_POOL_FLAGS = os.O_CREATE|os.O_WRONLY|os.O_APPEND ) -// 默认的日志换行符 -var ln = "\n" +var ( + // 默认的日志换行符 + ln = "\n" + // 标准输出互斥锁,防止标准输出串日志 + stdMu = sync.RWMutex{} +) // 初始化日志换行符 func init() { @@ -59,6 +64,7 @@ func New() *Logger { level : gtype.NewInt(defaultLevel.Val()), btSkip : gtype.NewInt(), btEnabled : gtype.NewBool(true), + printHeader : gtype.NewBool(true), alsoStdPrint : gtype.NewBool(true), } } @@ -67,12 +73,13 @@ func New() *Logger { func (l *Logger) Clone() *Logger { return &Logger { pr : l, - io : l.GetIO(), + io : l.GetWriter(), path : l.path.Clone(), file : l.file.Clone(), level : l.level.Clone(), btSkip : l.btSkip.Clone(), btEnabled : l.btEnabled.Clone(), + printHeader : l.printHeader.Clone(), alsoStdPrint : l.alsoStdPrint.Clone(), } } @@ -106,14 +113,14 @@ func (l *Logger) SetBacktraceSkip(skip int) { } // 可自定义IO接口,IO可以是文件输出、标准输出、网络输出 -func (l *Logger) SetIO(w io.Writer) { +func (l *Logger) SetWriter(w io.Writer) { l.mu.Lock() l.io = w l.mu.Unlock() } // 返回自定义的IO,默认为nil -func (l *Logger) GetIO() io.Writer { +func (l *Logger) GetWriter() io.Writer { l.mu.RLock() r := l.io l.mu.RUnlock() @@ -166,35 +173,44 @@ func (l *Logger) SetStdPrint(enabled bool) { } // 这里的写锁保证统一时刻只会写入一行日志,防止串日志的情况 +// 注意这里的std io.Writer为值赋值,会造成标准输出串日志的情况 func (l *Logger) print(std io.Writer, s string) { // 优先使用自定义的IO输出 - str := l.format(s) - writer := l.GetIO() + if l.printHeader.Val() { + s = l.format(s) + } + writer := l.GetWriter() if writer == nil { - // 如果设置的IO为空,那么其次判断是否有文件输出设置 - // 内部使用了内存锁,保证在glog中对同一个日志文件的并发写入不会串日志 + // 如果设置的writer为空,那么其次判断是否有文件输出设置 + // 内部使用了内存锁,保证在glog中对同一个日志文件的并发写入不会串日志(并发安全) if f := l.getFilePointer(); f != nil { defer f.Close() key := l.path.Val() gmlock.Lock(key) - _, err := io.WriteString(f, str) + _, err := io.WriteString(f, s) gmlock.Unlock(key) if err != nil { fmt.Fprintln(os.Stderr, err.Error()) } } } else { - if _, err := io.WriteString(writer, str); err != nil { - fmt.Fprintln(os.Stderr, err.Error()) - } + l.doStdLockPrint(writer, s) } + // 是否允许输出到标准输出 if l.alsoStdPrint.Val() { - if _, err := io.WriteString(std, str); err != nil { - fmt.Fprintln(os.Stderr, err.Error()) - } + l.doStdLockPrint(std, s) } } +// 并发安全打印到标准输出 +func (l *Logger) doStdLockPrint(std io.Writer, s string) { + stdMu.Lock() + if _, err := std.Write([]byte(s)); err != nil { + fmt.Fprintln(os.Stderr, err.Error()) + } + stdMu.Unlock() +} + // 核心打印数据方法(标准输出) func (l *Logger) stdPrint(s string) { l.print(os.Stdout, s) @@ -214,7 +230,8 @@ func (l *Logger) errPrint(s string) { } } } - l.print(os.Stderr, s) + // 防止串日志情况,这里不使用stderr,而是使用stdout + l.print(os.Stdout, s) } // 直接打印回溯信息,参数skip表示调用端往上多少级开始回溯 @@ -241,10 +258,11 @@ func (l *Logger) GetBacktrace(skip...int) string { } } // 从业务文件开始位置根据自定义的skip开始backtrace + goroot := gfile.GoRootOfBuild() for i := from + customSkip + l.btSkip.Val(); i < 10000; i++ { - if _, cfile, cline, ok := runtime.Caller(i); ok { + if _, cfile, cline, ok := runtime.Caller(i); ok && cfile != "" { // 不打印出go源码路径及glog包文件路径,日志打印必须从业务源码文件开始,且从glog包文件开始检索 - if !gregex.IsMatchString("^" + gfile.GoRootOfBuild(), cfile) && !gregex.IsMatchString(``, cfile) { + if (goroot == "" || !gregex.IsMatchString("^" + goroot, cfile)) && !gregex.IsMatchString(``, cfile) { backtrace += fmt.Sprintf(`%d. %s:%d%s`, index, cfile, cline, ln) index++ } diff --git a/g/os/glog/glog_logger_linkop.go b/g/os/glog/glog_logger_linkop.go index cd0968a86..4840486b3 100644 --- a/g/os/glog/glog_logger_linkop.go +++ b/g/os/glog/glog_logger_linkop.go @@ -73,4 +73,16 @@ func (l *Logger) StdPrint(enabled bool) *Logger { } logger.SetStdPrint(enabled) return logger +} + +// 是否打印每行日志头信息(默认开启) +func (l *Logger) Header(enabled bool) *Logger { + logger := (*Logger)(nil) + if l.pr == nil { + logger = l.Clone() + } else { + logger = l + } + logger.printHeader.Set(enabled) + return logger } \ No newline at end of file diff --git a/g/os/gspath/gspath.go b/g/os/gspath/gspath.go index c92dd3c8e..4c762f71b 100644 --- a/g/os/gspath/gspath.go +++ b/g/os/gspath/gspath.go @@ -50,7 +50,7 @@ func (sp *SPath) Set(path string) error { glog.Debug("gspath.SetPath:", r) return nil } - glog.Debug("gspath.SetPath failed:", path) + glog.Warning("gspath.SetPath failed:", path) return errors.New("invalid path:" + path) } @@ -71,7 +71,7 @@ func (sp *SPath) Add(path string) error { glog.Debug("gspath.Add:", r) return nil } - glog.Debug("gspath.Add failed:", path) + glog.Warning("gspath.Add failed:", path) return errors.New("invalid path:" + path) } diff --git a/g/util/gutil/gutil.go b/g/util/gutil/gutil.go index 1b8ec0aa7..553190f04 100644 --- a/g/util/gutil/gutil.go +++ b/g/util/gutil/gutil.go @@ -13,6 +13,8 @@ import ( "encoding/json" "reflect" "gitee.com/johng/gf/g/util/gconv" + "runtime" + "gitee.com/johng/gf/g/os/glog" ) // 格式化打印变量(类似于PHP-vardump) @@ -48,3 +50,18 @@ func Dump(i...interface{}) { } } +// 打印完整的调用回溯信息 +func PrintBacktrace() { + index := 1 + buffer := bytes.NewBuffer(nil) + for i := 0; i < 10000; i++ { + if _, cfile, cline, ok := runtime.Caller(i); ok { + buffer.WriteString(fmt.Sprintf(`%d. %s:%d%s`, index, cfile, cline, "\n")) + index++ + } else { + break + } + } + glog.Header(false).Print(buffer.String()) +} + diff --git a/geg/frame/mvc/main.go b/geg/frame/mvc/main.go index 2aed9c843..78268202b 100644 --- a/geg/frame/mvc/main.go +++ b/geg/frame/mvc/main.go @@ -7,6 +7,9 @@ import ( ) func main() { + + //g.Server().SetDumpRouteMap(false) g.Server().SetPort(8199) g.Server().Run() + } diff --git a/geg/other/test.go b/geg/other/test.go index f03c18f32..1c468a41f 100644 --- a/geg/other/test.go +++ b/geg/other/test.go @@ -1,26 +1,14 @@ package main import ( - "github.com/olekukonko/tablewriter" - "os" + "fmt" + "gitee.com/johng/gf/g/os/gfile" ) +func init() { + fmt.Println(gfile.GoRootOfBuild()) +} func main() { - data := [][]string{ - []string{"1/1/2014", "Domain name", "2233", "$10.98"}, - []string{"1/1/2014", "January Hosting", "2233", "$54.95"}, - []string{"1/4/2014", "February Hosting", "2233", "$51.00"}, - []string{"1/4/2014", "February Extra Bandwidth1111111111111111111111111111111111", "2233", "$30.00"}, - } - table := tablewriter.NewWriter(os.Stdout) - table.SetHeader([]string{"Date", "Description", "CV2", "Amount"}) - table.SetFooter([]string{"", "", "Total", "$146.93"}) // Add Footer - table.SetCenterSeparator("|") - table.SetAutoMergeCells(true) - table.SetRowLine(true) - table.SetBorder(false) - table.AppendBulk(data) - table.Render() }