mirror of
https://gitee.com/johng/gf
synced 2026-06-06 16:21:40 +08:00
完成Web Server路由表打印/获取特性
This commit is contained in:
@ -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
|
||||
|
||||
1
g/g.go
1
g/g.go
@ -21,4 +21,3 @@ type List = []Map
|
||||
// 常用slice数据结构(使用别名)
|
||||
type Slice = []interface{}
|
||||
type Array = Slice
|
||||
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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("<autogenerated>", 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
|
||||
|
||||
@ -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 ...)
|
||||
}
|
||||
|
||||
@ -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(`<autogenerated>`, cfile) {
|
||||
if (goroot == "" || !gregex.IsMatchString("^" + goroot, cfile)) && !gregex.IsMatchString(`<autogenerated>`, cfile) {
|
||||
backtrace += fmt.Sprintf(`%d. %s:%d%s`, index, cfile, cline, ln)
|
||||
index++
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
@ -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)
|
||||
}
|
||||
|
||||
|
||||
@ -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())
|
||||
}
|
||||
|
||||
|
||||
@ -7,6 +7,9 @@ import (
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
//g.Server().SetDumpRouteMap(false)
|
||||
g.Server().SetPort(8199)
|
||||
g.Server().Run()
|
||||
|
||||
}
|
||||
|
||||
@ -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()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user