diff --git a/cmd/gf/internal/cmd/cmd_build.go b/cmd/gf/internal/cmd/cmd_build.go index 29cc8660c..5f3a28be6 100644 --- a/cmd/gf/internal/cmd/cmd_build.go +++ b/cmd/gf/internal/cmd/cmd_build.go @@ -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) diff --git a/frame/gins/gins.go b/frame/gins/gins.go index c6fab084e..207cbc4ce 100644 --- a/frame/gins/gins.go +++ b/frame/gins/gins.go @@ -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) -} diff --git a/frame/gins/gins_database.go b/frame/gins/gins_database.go index 5fdeb9057..ece4ed70e 100644 --- a/frame/gins/gins_database.go +++ b/frame/gins/gins_database.go @@ -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{} diff --git a/frame/gins/gins_httpclient.go b/frame/gins/gins_httpclient.go index 324307d39..c3fb70d2f 100644 --- a/frame/gins/gins_httpclient.go +++ b/frame/gins/gins_httpclient.go @@ -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) } diff --git a/frame/gins/gins_log.go b/frame/gins/gins_log.go index 39f50e17c..bc1889f4a 100644 --- a/frame/gins/gins_log.go +++ b/frame/gins/gins_log.go @@ -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 ( diff --git a/frame/gins/gins_redis.go b/frame/gins/gins_redis.go index 5cafb7f0d..18ca81330 100644 --- a/frame/gins/gins_redis.go +++ b/frame/gins/gins_redis.go @@ -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) diff --git a/frame/gins/gins_server.go b/frame/gins/gins_server.go index e940649a2..dba6f67db 100644 --- a/frame/gins/gins_server.go +++ b/frame/gins/gins_server.go @@ -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. diff --git a/frame/gins/gins_view.go b/frame/gins/gins_view.go index ba342e338..e9d166d82 100644 --- a/frame/gins/gins_view.go +++ b/frame/gins/gins_view.go @@ -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) } diff --git a/frame/gins/gins_z_unit_server_test.go b/frame/gins/gins_z_unit_server_test.go index e212ecce6..132f6c562 100644 --- a/frame/gins/gins_z_unit_server_test.go +++ b/frame/gins/gins_z_unit_server_test.go @@ -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) { diff --git a/frame/gins/gins_z_unit_test.go b/frame/gins/gins_z_unit_test.go deleted file mode 100644 index f6a674e70..000000000 --- a/frame/gins/gins_z_unit_test.go +++ /dev/null @@ -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) - }) -} diff --git a/frame/gins/gins_z_unit_view_test.go b/frame/gins/gins_z_unit_view_test.go index 907c8f13f..5a713e5b7 100644 --- a/frame/gins/gins_z_unit_view_test.go +++ b/frame/gins/gins_z_unit_view_test.go @@ -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) diff --git a/internal/instance/instance.go b/internal/instance/instance.go new file mode 100644 index 000000000..37152493a --- /dev/null +++ b/internal/instance/instance.go @@ -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() + } +} diff --git a/internal/instance/instance_test.go b/internal/instance/instance_test.go new file mode 100644 index 000000000..afdda594a --- /dev/null +++ b/internal/instance/instance_test.go @@ -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) + }) +} diff --git a/net/ghttp/ghttp_server.go b/net/ghttp/ghttp_server.go index 2d6ea51fe..1076e9209 100644 --- a/net/ghttp/ghttp_server.go +++ b/net/ghttp/ghttp_server.go @@ -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) diff --git a/net/ghttp/ghttp_server_log.go b/net/ghttp/ghttp_server_log.go index bcf350521..7e8ca755a 100644 --- a/net/ghttp/ghttp_server_log.go +++ b/net/ghttp/ghttp_server_log.go @@ -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) } diff --git a/os/gcron/gcron_entry.go b/os/gcron/gcron_entry.go index c3f19c604..878f2fa84 100644 --- a/os/gcron/gcron_entry.go +++ b/os/gcron/gcron_entry.go @@ -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...) } diff --git a/os/gcron/gcron_schedule.go b/os/gcron/gcron_schedule.go index e926e748e..abeb345ec 100644 --- a/os/gcron/gcron_schedule.go +++ b/os/gcron/gcron_schedule.go @@ -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 diff --git a/os/gcron/gcron_schedule_fix.go b/os/gcron/gcron_schedule_fix.go index d3dcb00c7..fac3da1c4 100644 --- a/os/gcron/gcron_schedule_fix.go +++ b/os/gcron/gcron_schedule_fix.go @@ -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 } diff --git a/os/glog/glog_logger.go b/os/glog/glog_logger.go index 4bc50ad64..1486c7e0c 100644 --- a/os/glog/glog_logger.go +++ b/os/glog/glog_logger.go @@ -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 -} diff --git a/os/glog/glog_logger_config.go b/os/glog/glog_logger_config.go index 9dcd1ee51..356945f18 100644 --- a/os/glog/glog_logger_config.go +++ b/os/glog/glog_logger_config.go @@ -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) { diff --git a/os/glog/glog_logger_handler.go b/os/glog/glog_logger_handler.go index f500dbc92..4929fb515 100644 --- a/os/glog/glog_logger_handler.go +++ b/os/glog/glog_logger_handler.go @@ -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( diff --git a/os/glog/glog_z_unit_logger_chaining_test.go b/os/glog/glog_z_unit_logger_chaining_test.go index b794b8184..efc579d8b 100644 --- a/os/glog/glog_z_unit_logger_chaining_test.go +++ b/os/glog/glog_z_unit_logger_chaining_test.go @@ -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)