mirror of
https://gitee.com/johng/gf.git
synced 2024-11-29 18:57:44 +08:00
add LevelPrint configuration for glog.Logger; add package internal/instance for grouped instance management feature; add default logger for panic message printing if no logger set in gcron.Cron (#2388)
* improve logging feature, add LevelPrint configuration for glog.Logger; add package internal/instance * improve command build * add default logger for panic message printing if no logger set * up * fix scheduler when timer triggers in less than one second for package gcron * up
This commit is contained in:
parent
5a8b33fa09
commit
ae4f14c2e2
@ -129,6 +129,9 @@ type cBuildInput struct {
|
||||
type cBuildOutput struct{}
|
||||
|
||||
func (c cBuild) Index(ctx context.Context, in cBuildInput) (out *cBuildOutput, err error) {
|
||||
// print used go env
|
||||
_, _ = Env.Index(ctx, cEnvInput{})
|
||||
|
||||
mlog.SetHeaderPrint(true)
|
||||
|
||||
mlog.Debugf(`build input: %+v`, in)
|
||||
|
@ -7,50 +7,11 @@
|
||||
// Package gins provides instances and core components management.
|
||||
package gins
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/v2/container/gmap"
|
||||
const (
|
||||
frameCoreComponentNameViewer = "gf.core.component.viewer"
|
||||
frameCoreComponentNameDatabase = "gf.core.component.database"
|
||||
frameCoreComponentNameHttpClient = "gf.core.component.httpclient"
|
||||
frameCoreComponentNameLogger = "gf.core.component.logger"
|
||||
frameCoreComponentNameRedis = "gf.core.component.redis"
|
||||
frameCoreComponentNameServer = "gf.core.component.server"
|
||||
)
|
||||
|
||||
var (
|
||||
// localInstances is the instance map for common used components.
|
||||
localInstances = gmap.NewStrAnyMap(true)
|
||||
)
|
||||
|
||||
// Get returns the instance by given name.
|
||||
func Get(name string) interface{} {
|
||||
return localInstances.Get(name)
|
||||
}
|
||||
|
||||
// Set sets an instance object to the instance manager with given name.
|
||||
func Set(name string, instance interface{}) {
|
||||
localInstances.Set(name, instance)
|
||||
}
|
||||
|
||||
// GetOrSet returns the instance by name,
|
||||
// or set instance to the instance manager if it does not exist and returns this instance.
|
||||
func GetOrSet(name string, instance interface{}) interface{} {
|
||||
return localInstances.GetOrSet(name, instance)
|
||||
}
|
||||
|
||||
// GetOrSetFunc returns the instance by name,
|
||||
// or sets instance with returned value of callback function `f` if it does not exist
|
||||
// and then returns this instance.
|
||||
func GetOrSetFunc(name string, f func() interface{}) interface{} {
|
||||
return localInstances.GetOrSetFunc(name, f)
|
||||
}
|
||||
|
||||
// GetOrSetFuncLock returns the instance by name,
|
||||
// or sets instance with returned value of callback function `f` if it does not exist
|
||||
// and then returns this instance.
|
||||
//
|
||||
// GetOrSetFuncLock differs with GetOrSetFunc function is that it executes function `f`
|
||||
// with mutex.Lock of the hash map.
|
||||
func GetOrSetFuncLock(name string, f func() interface{}) interface{} {
|
||||
return localInstances.GetOrSetFuncLock(name, f)
|
||||
}
|
||||
|
||||
// SetIfNotExist sets `instance` to the map if the `name` does not exist, then returns true.
|
||||
// It returns false if `name` exists, and `instance` would be ignored.
|
||||
func SetIfNotExist(name string, instance interface{}) bool {
|
||||
return localInstances.SetIfNotExist(name, instance)
|
||||
}
|
||||
|
@ -14,6 +14,7 @@ import (
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/internal/consts"
|
||||
"github.com/gogf/gf/v2/internal/instance"
|
||||
"github.com/gogf/gf/v2/internal/intlog"
|
||||
"github.com/gogf/gf/v2/os/gcfg"
|
||||
"github.com/gogf/gf/v2/os/glog"
|
||||
@ -21,10 +22,6 @@ import (
|
||||
"github.com/gogf/gf/v2/util/gutil"
|
||||
)
|
||||
|
||||
const (
|
||||
frameCoreComponentNameDatabase = "gf.core.component.database"
|
||||
)
|
||||
|
||||
// Database returns an instance of database ORM object with specified configuration group name.
|
||||
// Note that it panics if any error occurs duration instance creating.
|
||||
func Database(name ...string) gdb.DB {
|
||||
@ -37,7 +34,7 @@ func Database(name ...string) gdb.DB {
|
||||
group = name[0]
|
||||
}
|
||||
instanceKey := fmt.Sprintf("%s.%s", frameCoreComponentNameDatabase, group)
|
||||
db := localInstances.GetOrSetFuncLock(instanceKey, func() interface{} {
|
||||
db := instance.GetOrSetFuncLock(instanceKey, func() interface{} {
|
||||
// It ignores returned error to avoid file no found error while it's not necessary.
|
||||
var (
|
||||
configMap map[string]interface{}
|
||||
|
@ -9,17 +9,14 @@ package gins
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/gogf/gf/v2/internal/instance"
|
||||
"github.com/gogf/gf/v2/net/gclient"
|
||||
)
|
||||
|
||||
const (
|
||||
frameCoreComponentNameHttpClient = "gf.core.component.httpclient"
|
||||
)
|
||||
|
||||
// HttpClient returns an instance of http client with specified name.
|
||||
func HttpClient(name ...interface{}) *gclient.Client {
|
||||
var instanceKey = fmt.Sprintf("%s.%v", frameCoreComponentNameHttpClient, name)
|
||||
return localInstances.GetOrSetFuncLock(instanceKey, func() interface{} {
|
||||
return instance.GetOrSetFuncLock(instanceKey, func() interface{} {
|
||||
return gclient.New()
|
||||
}).(*gclient.Client)
|
||||
}
|
||||
|
@ -11,14 +11,11 @@ import (
|
||||
"fmt"
|
||||
|
||||
"github.com/gogf/gf/v2/internal/consts"
|
||||
"github.com/gogf/gf/v2/internal/instance"
|
||||
"github.com/gogf/gf/v2/os/glog"
|
||||
"github.com/gogf/gf/v2/util/gutil"
|
||||
)
|
||||
|
||||
const (
|
||||
frameCoreComponentNameLogger = "gf.core.component.logger"
|
||||
)
|
||||
|
||||
// Log returns an instance of glog.Logger.
|
||||
// The parameter `name` is the name for the instance.
|
||||
// Note that it panics if any error occurs duration instance creating.
|
||||
@ -31,7 +28,7 @@ func Log(name ...string) *glog.Logger {
|
||||
instanceName = name[0]
|
||||
}
|
||||
instanceKey := fmt.Sprintf("%s.%s", frameCoreComponentNameLogger, instanceName)
|
||||
return localInstances.GetOrSetFuncLock(instanceKey, func() interface{} {
|
||||
return instance.GetOrSetFuncLock(instanceKey, func() interface{} {
|
||||
logger := glog.Instance(instanceName)
|
||||
// To avoid file no found error while it's not necessary.
|
||||
var (
|
||||
|
@ -12,15 +12,12 @@ import (
|
||||
|
||||
"github.com/gogf/gf/v2/database/gredis"
|
||||
"github.com/gogf/gf/v2/internal/consts"
|
||||
"github.com/gogf/gf/v2/internal/instance"
|
||||
"github.com/gogf/gf/v2/internal/intlog"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
"github.com/gogf/gf/v2/util/gutil"
|
||||
)
|
||||
|
||||
const (
|
||||
frameCoreComponentNameRedis = "gf.core.component.redis"
|
||||
)
|
||||
|
||||
// Redis returns an instance of redis client with specified configuration group name.
|
||||
// Note that it panics if any error occurs duration instance creating.
|
||||
func Redis(name ...string) *gredis.Redis {
|
||||
@ -33,7 +30,7 @@ func Redis(name ...string) *gredis.Redis {
|
||||
group = name[0]
|
||||
}
|
||||
instanceKey := fmt.Sprintf("%s.%s", frameCoreComponentNameRedis, group)
|
||||
result := localInstances.GetOrSetFuncLock(instanceKey, func() interface{} {
|
||||
result := instance.GetOrSetFuncLock(instanceKey, func() interface{} {
|
||||
// If already configured, it returns the redis instance.
|
||||
if _, ok := gredis.GetConfig(group); ok {
|
||||
return gredis.Instance(group)
|
||||
|
@ -11,17 +11,13 @@ import (
|
||||
"fmt"
|
||||
|
||||
"github.com/gogf/gf/v2/internal/consts"
|
||||
"github.com/gogf/gf/v2/internal/instance"
|
||||
"github.com/gogf/gf/v2/internal/intlog"
|
||||
"github.com/gogf/gf/v2/net/ghttp"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
"github.com/gogf/gf/v2/util/gutil"
|
||||
)
|
||||
|
||||
const (
|
||||
frameCoreComponentNameServer = "gf.core.component.server" // Prefix for HTTP server instance.
|
||||
|
||||
)
|
||||
|
||||
// Server returns an instance of http server with specified name.
|
||||
// Note that it panics if any error occurs duration instance creating.
|
||||
func Server(name ...interface{}) *ghttp.Server {
|
||||
@ -34,7 +30,7 @@ func Server(name ...interface{}) *ghttp.Server {
|
||||
if len(name) > 0 && name[0] != "" {
|
||||
instanceName = gconv.String(name[0])
|
||||
}
|
||||
return localInstances.GetOrSetFuncLock(instanceKey, func() interface{} {
|
||||
return instance.GetOrSetFuncLock(instanceKey, func() interface{} {
|
||||
server := ghttp.GetServer(instanceName)
|
||||
if Config().Available(ctx) {
|
||||
// Server initialization from configuration.
|
||||
|
@ -11,15 +11,12 @@ import (
|
||||
"fmt"
|
||||
|
||||
"github.com/gogf/gf/v2/internal/consts"
|
||||
"github.com/gogf/gf/v2/internal/instance"
|
||||
"github.com/gogf/gf/v2/internal/intlog"
|
||||
"github.com/gogf/gf/v2/os/gview"
|
||||
"github.com/gogf/gf/v2/util/gutil"
|
||||
)
|
||||
|
||||
const (
|
||||
frameCoreComponentNameViewer = "gf.core.component.viewer"
|
||||
)
|
||||
|
||||
// View returns an instance of View with default settings.
|
||||
// The parameter `name` is the name for the instance.
|
||||
// Note that it panics if any error occurs duration instance creating.
|
||||
@ -29,7 +26,7 @@ func View(name ...string) *gview.View {
|
||||
instanceName = name[0]
|
||||
}
|
||||
instanceKey := fmt.Sprintf("%s.%s", frameCoreComponentNameViewer, instanceName)
|
||||
return localInstances.GetOrSetFuncLock(instanceKey, func() interface{} {
|
||||
return instance.GetOrSetFuncLock(instanceKey, func() interface{} {
|
||||
return getViewInstance(instanceName)
|
||||
}).(*gview.View)
|
||||
}
|
||||
|
@ -18,8 +18,8 @@ package gins
|
||||
//
|
||||
// time.Sleep(time.Second)
|
||||
//
|
||||
// localInstances.Clear()
|
||||
// defer localInstances.Clear()
|
||||
// instance.Clear()
|
||||
// defer instance.Clear()
|
||||
//
|
||||
// s := Server("tempByInstanceName")
|
||||
// s.BindHandler("/", func(r *ghttp.Request) {
|
||||
|
@ -1,44 +0,0 @@
|
||||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package gins_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/gogf/gf/v2/frame/gins"
|
||||
"github.com/gogf/gf/v2/test/gtest"
|
||||
)
|
||||
|
||||
func Test_SetGet(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
gins.Set("test-user", 1)
|
||||
t.Assert(gins.Get("test-user"), 1)
|
||||
t.Assert(gins.Get("none-exists"), nil)
|
||||
})
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
t.Assert(gins.GetOrSet("test-1", 1), 1)
|
||||
t.Assert(gins.Get("test-1"), 1)
|
||||
})
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
t.Assert(gins.GetOrSetFunc("test-2", func() interface{} {
|
||||
return 2
|
||||
}), 2)
|
||||
t.Assert(gins.Get("test-2"), 2)
|
||||
})
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
t.Assert(gins.GetOrSetFuncLock("test-3", func() interface{} {
|
||||
return 3
|
||||
}), 3)
|
||||
t.Assert(gins.Get("test-3"), 3)
|
||||
})
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
t.Assert(gins.SetIfNotExist("test-4", 4), true)
|
||||
t.Assert(gins.Get("test-4"), 4)
|
||||
t.Assert(gins.SetIfNotExist("test-4", 5), false)
|
||||
t.Assert(gins.Get("test-4"), 4)
|
||||
})
|
||||
}
|
@ -11,6 +11,7 @@ import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/gogf/gf/v2/internal/instance"
|
||||
"github.com/gogf/gf/v2/os/gcfg"
|
||||
"github.com/gogf/gf/v2/os/gfile"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
@ -56,7 +57,7 @@ func Test_View_Config(t *testing.T) {
|
||||
dirPath := gtest.DataPath("view1")
|
||||
Config().GetAdapter().(*gcfg.AdapterFile).SetContent(gfile.GetContents(gfile.Join(dirPath, "config.toml")))
|
||||
defer Config().GetAdapter().(*gcfg.AdapterFile).ClearContent()
|
||||
defer localInstances.Clear()
|
||||
defer instance.Clear()
|
||||
|
||||
view := View("test1")
|
||||
t.AssertNE(view, nil)
|
||||
@ -78,7 +79,7 @@ func Test_View_Config(t *testing.T) {
|
||||
dirPath := gtest.DataPath("view1")
|
||||
Config().GetAdapter().(*gcfg.AdapterFile).SetContent(gfile.GetContents(gfile.Join(dirPath, "config.toml")))
|
||||
defer Config().GetAdapter().(*gcfg.AdapterFile).ClearContent()
|
||||
defer localInstances.Clear()
|
||||
defer instance.Clear()
|
||||
|
||||
view := View("test2")
|
||||
t.AssertNE(view, nil)
|
||||
@ -100,7 +101,7 @@ func Test_View_Config(t *testing.T) {
|
||||
dirPath := gtest.DataPath("view2")
|
||||
Config().GetAdapter().(*gcfg.AdapterFile).SetContent(gfile.GetContents(gfile.Join(dirPath, "config.toml")))
|
||||
defer Config().GetAdapter().(*gcfg.AdapterFile).ClearContent()
|
||||
defer localInstances.Clear()
|
||||
defer instance.Clear()
|
||||
|
||||
view := View()
|
||||
t.AssertNE(view, nil)
|
||||
@ -122,7 +123,7 @@ func Test_View_Config(t *testing.T) {
|
||||
dirPath := gtest.DataPath("view2")
|
||||
Config().GetAdapter().(*gcfg.AdapterFile).SetContent(gfile.GetContents(gfile.Join(dirPath, "config.toml")))
|
||||
defer Config().GetAdapter().(*gcfg.AdapterFile).ClearContent()
|
||||
defer localInstances.Clear()
|
||||
defer instance.Clear()
|
||||
|
||||
view := View("test100")
|
||||
t.AssertNE(view, nil)
|
||||
|
77
internal/instance/instance.go
Normal file
77
internal/instance/instance.go
Normal file
@ -0,0 +1,77 @@
|
||||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
// Package instance provides instances management.
|
||||
package instance
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/v2/container/gmap"
|
||||
"github.com/gogf/gf/v2/encoding/ghash"
|
||||
)
|
||||
|
||||
const (
|
||||
groupNumber = 64
|
||||
)
|
||||
|
||||
var (
|
||||
groups = make([]*gmap.StrAnyMap, groupNumber)
|
||||
)
|
||||
|
||||
func init() {
|
||||
for i := 0; i < groupNumber; i++ {
|
||||
groups[i] = gmap.NewStrAnyMap(true)
|
||||
}
|
||||
}
|
||||
|
||||
func getGroup(key string) *gmap.StrAnyMap {
|
||||
return groups[int(ghash.DJB([]byte(key))%groupNumber)]
|
||||
}
|
||||
|
||||
// Get returns the instance by given name.
|
||||
func Get(name string) interface{} {
|
||||
return getGroup(name).Get(name)
|
||||
}
|
||||
|
||||
// Set sets an instance to the instance manager with given name.
|
||||
func Set(name string, instance interface{}) {
|
||||
getGroup(name).Set(name, instance)
|
||||
}
|
||||
|
||||
// GetOrSet returns the instance by name,
|
||||
// or set instance to the instance manager if it does not exist and returns this instance.
|
||||
func GetOrSet(name string, instance interface{}) interface{} {
|
||||
return getGroup(name).GetOrSet(name, instance)
|
||||
}
|
||||
|
||||
// GetOrSetFunc returns the instance by name,
|
||||
// or sets instance with returned value of callback function `f` if it does not exist
|
||||
// and then returns this instance.
|
||||
func GetOrSetFunc(name string, f func() interface{}) interface{} {
|
||||
return getGroup(name).GetOrSetFunc(name, f)
|
||||
}
|
||||
|
||||
// GetOrSetFuncLock returns the instance by name,
|
||||
// or sets instance with returned value of callback function `f` if it does not exist
|
||||
// and then returns this instance.
|
||||
//
|
||||
// GetOrSetFuncLock differs with GetOrSetFunc function is that it executes function `f`
|
||||
// with mutex.Lock of the hash map.
|
||||
func GetOrSetFuncLock(name string, f func() interface{}) interface{} {
|
||||
return getGroup(name).GetOrSetFuncLock(name, f)
|
||||
}
|
||||
|
||||
// SetIfNotExist sets `instance` to the map if the `name` does not exist, then returns true.
|
||||
// It returns false if `name` exists, and `instance` would be ignored.
|
||||
func SetIfNotExist(name string, instance interface{}) bool {
|
||||
return getGroup(name).SetIfNotExist(name, instance)
|
||||
}
|
||||
|
||||
// Clear deletes all instances stored.
|
||||
func Clear() {
|
||||
for i := 0; i < groupNumber; i++ {
|
||||
groups[i].Clear()
|
||||
}
|
||||
}
|
44
internal/instance/instance_test.go
Normal file
44
internal/instance/instance_test.go
Normal file
@ -0,0 +1,44 @@
|
||||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package instance_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/gogf/gf/v2/internal/instance"
|
||||
"github.com/gogf/gf/v2/test/gtest"
|
||||
)
|
||||
|
||||
func Test_SetGet(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
instance.Set("test-user", 1)
|
||||
t.Assert(instance.Get("test-user"), 1)
|
||||
t.Assert(instance.Get("none-exists"), nil)
|
||||
})
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
t.Assert(instance.GetOrSet("test-1", 1), 1)
|
||||
t.Assert(instance.Get("test-1"), 1)
|
||||
})
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
t.Assert(instance.GetOrSetFunc("test-2", func() interface{} {
|
||||
return 2
|
||||
}), 2)
|
||||
t.Assert(instance.Get("test-2"), 2)
|
||||
})
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
t.Assert(instance.GetOrSetFuncLock("test-3", func() interface{} {
|
||||
return 3
|
||||
}), 3)
|
||||
t.Assert(instance.Get("test-3"), 3)
|
||||
})
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
t.Assert(instance.SetIfNotExist("test-4", 4), true)
|
||||
t.Assert(instance.Get("test-4"), 4)
|
||||
t.Assert(instance.SetIfNotExist("test-4", 5), false)
|
||||
t.Assert(instance.Get("test-4"), 4)
|
||||
})
|
||||
}
|
@ -188,7 +188,7 @@ func (s *Server) Start() error {
|
||||
s.Logger().Fatalf(ctx, `%+v`, err)
|
||||
}
|
||||
}
|
||||
// Check the group routes again.
|
||||
// Check the group routes again for internally registered routes.
|
||||
s.handlePreBindItems(ctx)
|
||||
|
||||
// If there's no route registered and no static service enabled,
|
||||
@ -266,6 +266,10 @@ func (s *Server) getLocalListenedAddress() string {
|
||||
|
||||
// doRouterMapDump checks and dumps the router map to the log.
|
||||
func (s *Server) doRouterMapDump() {
|
||||
if !s.config.DumpRouterMap {
|
||||
return
|
||||
}
|
||||
|
||||
var (
|
||||
ctx = context.TODO()
|
||||
routes = s.GetRoutes()
|
||||
@ -283,7 +287,7 @@ func (s *Server) doRouterMapDump() {
|
||||
if isJustDefaultServerAndDomain {
|
||||
headers = []string{"ADDRESS", "METHOD", "ROUTE", "HANDLER", "MIDDLEWARE"}
|
||||
}
|
||||
if s.config.DumpRouterMap && len(routes) > 0 {
|
||||
if len(routes) > 0 {
|
||||
buffer := bytes.NewBuffer(nil)
|
||||
table := tablewriter.NewWriter(buffer)
|
||||
table.SetHeader(headers)
|
||||
|
@ -10,6 +10,8 @@ import (
|
||||
"fmt"
|
||||
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/internal/instance"
|
||||
"github.com/gogf/gf/v2/os/glog"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
)
|
||||
|
||||
@ -19,22 +21,28 @@ func (s *Server) handleAccessLog(r *Request) {
|
||||
return
|
||||
}
|
||||
var (
|
||||
scheme = "http"
|
||||
proto = r.Header.Get("X-Forwarded-Proto")
|
||||
scheme = "http"
|
||||
proto = r.Header.Get("X-Forwarded-Proto")
|
||||
loggerInstanceKey = fmt.Sprintf(`Acccess Logger Of Server:%s`, s.instance)
|
||||
)
|
||||
|
||||
if r.TLS != nil || gstr.Equal(proto, "https") {
|
||||
scheme = "https"
|
||||
}
|
||||
s.Logger().File(s.config.AccessLogPattern).
|
||||
Stdout(s.config.LogStdout).
|
||||
Printf(
|
||||
r.Context(),
|
||||
`%d "%s %s %s %s %s" %.3f, %s, "%s", "%s"`,
|
||||
r.Response.Status, r.Method, scheme, r.Host, r.URL.String(), r.Proto,
|
||||
float64(r.LeaveTime-r.EnterTime)/1000,
|
||||
r.GetClientIp(), r.Referer(), r.UserAgent(),
|
||||
)
|
||||
content := fmt.Sprintf(
|
||||
`%d "%s %s %s %s %s" %.3f, %s, "%s", "%s"`,
|
||||
r.Response.Status, r.Method, scheme, r.Host, r.URL.String(), r.Proto,
|
||||
float64(r.LeaveTime-r.EnterTime)/1000,
|
||||
r.GetClientIp(), r.Referer(), r.UserAgent(),
|
||||
)
|
||||
logger := instance.GetOrSetFuncLock(loggerInstanceKey, func() interface{} {
|
||||
l := s.Logger().Clone()
|
||||
l.SetFile(s.config.AccessLogPattern)
|
||||
l.SetStdoutPrint(s.config.LogStdout)
|
||||
l.SetLevelPrint(false)
|
||||
return l
|
||||
}).(*glog.Logger)
|
||||
logger.Printf(r.Context(), content)
|
||||
}
|
||||
|
||||
// handleErrorLog handles the error logging for server.
|
||||
@ -44,11 +52,12 @@ func (s *Server) handleErrorLog(err error, r *Request) {
|
||||
return
|
||||
}
|
||||
var (
|
||||
code = gerror.Code(err)
|
||||
scheme = "http"
|
||||
codeDetail = code.Detail()
|
||||
proto = r.Header.Get("X-Forwarded-Proto")
|
||||
codeDetailStr string
|
||||
code = gerror.Code(err)
|
||||
scheme = "http"
|
||||
codeDetail = code.Detail()
|
||||
proto = r.Header.Get("X-Forwarded-Proto")
|
||||
loggerInstanceKey = fmt.Sprintf(`Error Logger Of Server:%s`, s.instance)
|
||||
codeDetailStr string
|
||||
)
|
||||
if r.TLS != nil || gstr.Equal(proto, "https") {
|
||||
scheme = "https"
|
||||
@ -72,7 +81,13 @@ func (s *Server) handleErrorLog(err error, r *Request) {
|
||||
} else {
|
||||
content += ", " + err.Error()
|
||||
}
|
||||
s.Logger().File(s.config.ErrorLogPattern).
|
||||
Stdout(s.config.LogStdout).
|
||||
Print(r.Context(), content)
|
||||
logger := instance.GetOrSetFuncLock(loggerInstanceKey, func() interface{} {
|
||||
l := s.Logger().Clone()
|
||||
l.SetStack(false)
|
||||
l.SetFile(s.config.ErrorLogPattern)
|
||||
l.SetStdoutPrint(s.config.LogStdout)
|
||||
l.SetLevelPrint(false)
|
||||
return l
|
||||
}).(*glog.Logger)
|
||||
logger.Error(r.Context(), content)
|
||||
}
|
||||
|
@ -16,6 +16,7 @@ import (
|
||||
"github.com/gogf/gf/v2/container/gtype"
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/os/glog"
|
||||
"github.com/gogf/gf/v2/os/gtimer"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
)
|
||||
@ -132,23 +133,11 @@ func (entry *Entry) Close() {
|
||||
}
|
||||
|
||||
// checkAndRun is the core timing task check logic.
|
||||
// The running times limits feature is implemented by gcron.Entry and cannot be implemented by gtimer.Entry.
|
||||
// gcron.Entry relies on gtimer to implement a scheduled task check for gcron.Entry per second.
|
||||
func (entry *Entry) checkAndRun(ctx context.Context) {
|
||||
currentTime := time.Now()
|
||||
if !entry.schedule.checkMeetAndUpdateLastSeconds(ctx, currentTime) {
|
||||
// intlog.Printf(
|
||||
// ctx,
|
||||
// `timely check, current time does not meet cron job "%s"`,
|
||||
// entry.getJobNameWithPattern(),
|
||||
// )
|
||||
return
|
||||
}
|
||||
// intlog.Printf(
|
||||
// ctx,
|
||||
// `timely check, current time meets cron job "%s"`,
|
||||
// entry.getJobNameWithPattern(),
|
||||
// )
|
||||
switch entry.cron.status.Val() {
|
||||
case StatusStopped:
|
||||
return
|
||||
@ -160,6 +149,7 @@ func (entry *Entry) checkAndRun(ctx context.Context) {
|
||||
case StatusReady, StatusRunning:
|
||||
defer func() {
|
||||
if exception := recover(); exception != nil {
|
||||
// Exception caught, it logs the error content to logger in default behavior.
|
||||
entry.logErrorf(ctx,
|
||||
`cron job "%s(%s)" end with error: %+v`,
|
||||
entry.jobName, entry.schedule.pattern, exception,
|
||||
@ -167,7 +157,6 @@ func (entry *Entry) checkAndRun(ctx context.Context) {
|
||||
} else {
|
||||
entry.logDebugf(ctx, `cron job "%s" ends`, entry.getJobNameWithPattern())
|
||||
}
|
||||
|
||||
if entry.timerEntry.Status() == StatusClosed {
|
||||
entry.Close()
|
||||
}
|
||||
@ -183,7 +172,6 @@ func (entry *Entry) checkAndRun(ctx context.Context) {
|
||||
}
|
||||
}
|
||||
entry.logDebugf(ctx, `cron job "%s" starts`, entry.getJobNameWithPattern())
|
||||
|
||||
entry.Job(ctx)
|
||||
}
|
||||
}
|
||||
@ -199,7 +187,9 @@ func (entry *Entry) logDebugf(ctx context.Context, format string, v ...interface
|
||||
}
|
||||
|
||||
func (entry *Entry) logErrorf(ctx context.Context, format string, v ...interface{}) {
|
||||
if logger := entry.cron.GetLogger(); logger != nil {
|
||||
logger.Errorf(ctx, format, v...)
|
||||
logger := entry.cron.GetLogger()
|
||||
if logger == nil {
|
||||
logger = glog.DefaultLogger()
|
||||
}
|
||||
logger.Errorf(ctx, format, v...)
|
||||
}
|
||||
|
@ -271,10 +271,14 @@ func parsePatternItemValue(value string, itemType int) (int, error) {
|
||||
|
||||
// checkMeetAndUpdateLastSeconds checks if the given time `t` meets the runnable point for the job.
|
||||
func (s *cronSchedule) checkMeetAndUpdateLastSeconds(ctx context.Context, t time.Time) bool {
|
||||
var (
|
||||
lastTimestamp = s.getAndUpdateLastTimestamp(ctx, t)
|
||||
lastTime = gtime.NewFromTimeStamp(lastTimestamp)
|
||||
)
|
||||
|
||||
if s.everySeconds != 0 {
|
||||
// It checks using interval.
|
||||
secondsAfterCreated := t.Unix() - s.createTimestamp
|
||||
secondsAfterCreated += int64(s.getFixedTimestampDelta(ctx, t))
|
||||
secondsAfterCreated := lastTime.Timestamp() - s.createTimestamp
|
||||
if secondsAfterCreated > 0 {
|
||||
return secondsAfterCreated%s.everySeconds == 0
|
||||
}
|
||||
@ -282,22 +286,22 @@ func (s *cronSchedule) checkMeetAndUpdateLastSeconds(ctx context.Context, t time
|
||||
}
|
||||
|
||||
// It checks using normal cron pattern.
|
||||
if _, ok := s.secondMap[s.getFixedSecond(ctx, t)]; !ok {
|
||||
if _, ok := s.secondMap[lastTime.Second()]; !ok {
|
||||
return false
|
||||
}
|
||||
if _, ok := s.minuteMap[t.Minute()]; !ok {
|
||||
if _, ok := s.minuteMap[lastTime.Minute()]; !ok {
|
||||
return false
|
||||
}
|
||||
if _, ok := s.hourMap[t.Hour()]; !ok {
|
||||
if _, ok := s.hourMap[lastTime.Hour()]; !ok {
|
||||
return false
|
||||
}
|
||||
if _, ok := s.dayMap[t.Day()]; !ok {
|
||||
if _, ok := s.dayMap[lastTime.Day()]; !ok {
|
||||
return false
|
||||
}
|
||||
if _, ok := s.monthMap[int(t.Month())]; !ok {
|
||||
if _, ok := s.monthMap[lastTime.Month()]; !ok {
|
||||
return false
|
||||
}
|
||||
if _, ok := s.weekMap[int(t.Weekday())]; !ok {
|
||||
if _, ok := s.weekMap[int(lastTime.Weekday())]; !ok {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
|
@ -13,31 +13,25 @@ import (
|
||||
"github.com/gogf/gf/v2/internal/intlog"
|
||||
)
|
||||
|
||||
// getFixedSecond checks, fixes and returns the seconds that have delay fix in some seconds.
|
||||
// Reference: https://github.com/golang/go/issues/14410
|
||||
func (s *cronSchedule) getFixedSecond(ctx context.Context, t time.Time) int {
|
||||
return (t.Second() + s.getFixedTimestampDelta(ctx, t)) % 60
|
||||
}
|
||||
|
||||
// getFixedTimestampDelta checks, fixes and returns the timestamp delta that have delay fix in some seconds.
|
||||
// The tolerated timestamp delay is `3` seconds in default.
|
||||
func (s *cronSchedule) getFixedTimestampDelta(ctx context.Context, t time.Time) int {
|
||||
// getAndUpdateLastTimestamp checks fixes and returns the last timestamp that have delay fix in some seconds.
|
||||
func (s *cronSchedule) getAndUpdateLastTimestamp(ctx context.Context, t time.Time) int64 {
|
||||
var (
|
||||
currentTimestamp = t.Unix()
|
||||
lastTimestamp = s.lastTimestamp.Val()
|
||||
delta int
|
||||
)
|
||||
switch {
|
||||
case
|
||||
lastTimestamp == currentTimestamp:
|
||||
lastTimestamp += 1
|
||||
|
||||
case
|
||||
lastTimestamp == currentTimestamp-1:
|
||||
lastTimestamp = currentTimestamp
|
||||
|
||||
case
|
||||
lastTimestamp == currentTimestamp-2,
|
||||
lastTimestamp == currentTimestamp-3,
|
||||
lastTimestamp == currentTimestamp:
|
||||
lastTimestamp == currentTimestamp-3:
|
||||
lastTimestamp += 1
|
||||
delta = 1
|
||||
|
||||
default:
|
||||
// Too much delay, let's update the last timestamp to current one.
|
||||
@ -49,5 +43,5 @@ func (s *cronSchedule) getFixedTimestampDelta(ctx context.Context, t time.Time)
|
||||
lastTimestamp = currentTimestamp
|
||||
}
|
||||
s.lastTimestamp.Set(lastTimestamp)
|
||||
return delta
|
||||
return lastTimestamp
|
||||
}
|
||||
|
@ -19,7 +19,6 @@ import (
|
||||
"github.com/fatih/color"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
|
||||
"github.com/gogf/gf/v2/container/gtype"
|
||||
"github.com/gogf/gf/v2/debug/gdebug"
|
||||
"github.com/gogf/gf/v2/internal/consts"
|
||||
"github.com/gogf/gf/v2/internal/intlog"
|
||||
@ -35,9 +34,8 @@ import (
|
||||
|
||||
// Logger is the struct for logging management.
|
||||
type Logger struct {
|
||||
init *gtype.Bool // Initialized.
|
||||
parent *Logger // Parent logger, if it is not empty, it means the logger is used in chaining function.
|
||||
config Config // Logger configuration.
|
||||
parent *Logger // Parent logger, if it is not empty, it means the logger is used in chaining function.
|
||||
config Config // Logger configuration.
|
||||
}
|
||||
|
||||
const (
|
||||
@ -62,11 +60,9 @@ const (
|
||||
|
||||
// New creates and returns a custom logger.
|
||||
func New() *Logger {
|
||||
logger := &Logger{
|
||||
init: gtype.NewBool(),
|
||||
return &Logger{
|
||||
config: DefaultConfig(),
|
||||
}
|
||||
return logger
|
||||
}
|
||||
|
||||
// NewWithWriter creates and returns a custom logger with io.Writer.
|
||||
@ -76,13 +72,13 @@ func NewWithWriter(writer io.Writer) *Logger {
|
||||
return l
|
||||
}
|
||||
|
||||
// Clone returns a new logger, which is the clone the current logger.
|
||||
// It's commonly used for chaining operations.
|
||||
// Clone returns a new logger, which a `shallow copy` of the current logger.
|
||||
// Note that the attribute `config` of the cloned one is the shallow copy of current one.
|
||||
func (l *Logger) Clone() *Logger {
|
||||
newLogger := New()
|
||||
newLogger.config = l.config
|
||||
newLogger.parent = l
|
||||
return newLogger
|
||||
return &Logger{
|
||||
config: l.config,
|
||||
parent: l,
|
||||
}
|
||||
}
|
||||
|
||||
// getFilePath returns the logging file path.
|
||||
@ -101,15 +97,11 @@ func (l *Logger) print(ctx context.Context, level int, stack string, values ...i
|
||||
// Lazy initialize for rotation feature.
|
||||
// It uses atomic reading operation to enhance the performance checking.
|
||||
// It here uses CAP for performance and concurrent safety.
|
||||
p := l
|
||||
if p.parent != nil {
|
||||
p = p.parent
|
||||
}
|
||||
// It just initializes once for each logger.
|
||||
if p.config.RotateSize > 0 || p.config.RotateExpire > 0 {
|
||||
if !p.init.Val() && p.init.Cas(false, true) {
|
||||
gtimer.AddOnce(context.Background(), p.config.RotateCheckInterval, p.rotateChecksTimely)
|
||||
intlog.Printf(ctx, "logger rotation initialized: every %s", p.config.RotateCheckInterval.String())
|
||||
if l.config.RotateSize > 0 || l.config.RotateExpire > 0 {
|
||||
if !l.config.rotatedHandlerInitialized.Val() && l.config.rotatedHandlerInitialized.Cas(false, true) {
|
||||
gtimer.AddOnce(context.Background(), l.config.RotateCheckInterval, l.rotateChecksTimely)
|
||||
intlog.Printf(ctx, "logger rotation initialized: every %s", l.config.RotateCheckInterval.String())
|
||||
}
|
||||
}
|
||||
|
||||
@ -417,8 +409,3 @@ func (l *Logger) GetStack(skip ...int) string {
|
||||
}
|
||||
return gdebug.StackWithFilters(filters, stackSkip)
|
||||
}
|
||||
|
||||
// GetConfig returns the configuration of current Logger.
|
||||
func (l *Logger) GetConfig() Config {
|
||||
return l.config
|
||||
}
|
||||
|
@ -12,6 +12,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gogf/gf/v2/container/gtype"
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/internal/intlog"
|
||||
@ -35,6 +36,7 @@ type Config struct {
|
||||
CtxKeys []interface{} `json:"ctxKeys"` // Context keys for logging, which is used for value retrieving from context.
|
||||
HeaderPrint bool `json:"header"` // Print header or not(true in default).
|
||||
StdoutPrint bool `json:"stdout"` // Output to stdout or not(true in default).
|
||||
LevelPrint bool `json:"levelPrint"` // Print level format string or not(true in default).
|
||||
LevelPrefixes map[int]string `json:"levelPrefixes"` // Logging level to its prefix string mapping.
|
||||
RotateSize int64 `json:"rotateSize"` // Rotate the logging file if its size > 0 in bytes.
|
||||
RotateExpire time.Duration `json:"rotateExpire"` // Rotate the logging file if its mtime exceeds this duration.
|
||||
@ -44,6 +46,11 @@ type Config struct {
|
||||
RotateCheckInterval time.Duration `json:"rotateCheckInterval"` // Asynchronously checks the backups and expiration at intervals. It's 1 hour in default.
|
||||
StdoutColorDisabled bool `json:"stdoutColorDisabled"` // Logging level prefix with color to writer or not (false in default).
|
||||
WriterColorEnable bool `json:"writerColorEnable"` // Logging level prefix with color to writer or not (false in default).
|
||||
internalConfig
|
||||
}
|
||||
|
||||
type internalConfig struct {
|
||||
rotatedHandlerInitialized *gtype.Bool // Whether the rotation feature initialized.
|
||||
}
|
||||
|
||||
// DefaultConfig returns the default configuration for logger.
|
||||
@ -56,8 +63,12 @@ func DefaultConfig() Config {
|
||||
StStatus: 1,
|
||||
HeaderPrint: true,
|
||||
StdoutPrint: true,
|
||||
LevelPrint: true,
|
||||
LevelPrefixes: make(map[int]string, len(defaultLevelPrefixes)),
|
||||
RotateCheckInterval: time.Hour,
|
||||
internalConfig: internalConfig{
|
||||
rotatedHandlerInitialized: gtype.NewBool(),
|
||||
},
|
||||
}
|
||||
for k, v := range defaultLevelPrefixes {
|
||||
c.LevelPrefixes[k] = v
|
||||
@ -68,6 +79,11 @@ func DefaultConfig() Config {
|
||||
return c
|
||||
}
|
||||
|
||||
// GetConfig returns the configuration of current Logger.
|
||||
func (l *Logger) GetConfig() Config {
|
||||
return l.config
|
||||
}
|
||||
|
||||
// SetConfig set configurations for the logger.
|
||||
func (l *Logger) SetConfig(config Config) error {
|
||||
l.config = config
|
||||
@ -243,6 +259,11 @@ func (l *Logger) SetHeaderPrint(enabled bool) {
|
||||
l.config.HeaderPrint = enabled
|
||||
}
|
||||
|
||||
// SetLevelPrint sets whether output level string of the logging contents, which is true in default.
|
||||
func (l *Logger) SetLevelPrint(enabled bool) {
|
||||
l.config.LevelPrint = enabled
|
||||
}
|
||||
|
||||
// SetPrefix sets prefix string for every logging content.
|
||||
// Prefix is part of header, which means if header output is shut, no prefix will be output.
|
||||
func (l *Logger) SetPrefix(prefix string) {
|
||||
|
@ -18,7 +18,7 @@ type Handler func(ctx context.Context, in *HandlerInput)
|
||||
// HandlerInput is the input parameter struct for logging Handler.
|
||||
type HandlerInput struct {
|
||||
internalHandlerInfo
|
||||
Logger *Logger // Logger.
|
||||
Logger *Logger // Current Logger object.
|
||||
Buffer *bytes.Buffer // Buffer for logging content outputs.
|
||||
Time time.Time // Logging time, which is the time that logging triggers.
|
||||
TimeFormat string // Formatted time string, like "2016-01-09 12:00:00".
|
||||
@ -28,7 +28,7 @@ type HandlerInput struct {
|
||||
CallerFunc string // The source function name that calls logging, only available if F_CALLER_FN set.
|
||||
CallerPath string // The source file path and its line number that calls logging, only available if F_FILE_SHORT or F_FILE_LONG set.
|
||||
CtxStr string // The retrieved context value string from context, only available if Config.CtxKeys configured.
|
||||
TraceId string // Trace id, only available if tracing is enabled.
|
||||
TraceId string // Trace id, only available if OpenTelemetry is enabled.
|
||||
Prefix string // Custom prefix string for logging content.
|
||||
Content string // Content is the main logging content without error stack string produced by logger.
|
||||
Stack string // Stack string produced by logger, only available if Config.StStatus configured.
|
||||
@ -85,7 +85,7 @@ func (in *HandlerInput) getDefaultBuffer(withColor bool) *bytes.Buffer {
|
||||
if in.TimeFormat != "" {
|
||||
buffer.WriteString(in.TimeFormat)
|
||||
}
|
||||
if in.LevelFormat != "" {
|
||||
if in.Logger.config.LevelPrint && in.LevelFormat != "" {
|
||||
var levelStr = "[" + in.LevelFormat + "]"
|
||||
if withColor {
|
||||
in.addStringToBuffer(buffer, in.Logger.getColoredStr(
|
||||
|
@ -227,7 +227,7 @@ func Test_Async(t *testing.T) {
|
||||
Path(path).File(file).Async().Stdout(false).Debug(ctx, 1, 2, 3)
|
||||
content := gfile.GetContents(gfile.Join(path, file))
|
||||
t.Assert(content, "")
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
time.Sleep(1000 * time.Millisecond)
|
||||
|
||||
content = gfile.GetContents(gfile.Join(path, file))
|
||||
t.Assert(gstr.Count(content, defaultLevelPrefixes[LEVEL_DEBU]), 1)
|
||||
|
Loading…
Reference in New Issue
Block a user