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:
John Guo 2023-01-06 14:15:30 +08:00 committed by GitHub
parent 5a8b33fa09
commit ae4f14c2e2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 254 additions and 216 deletions

View File

@ -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)

View File

@ -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)
}

View File

@ -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{}

View File

@ -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)
}

View File

@ -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 (

View File

@ -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)

View File

@ -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.

View File

@ -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)
}

View File

@ -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) {

View File

@ -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)
})
}

View File

@ -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)

View 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()
}
}

View 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)
})
}

View File

@ -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)

View File

@ -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)
}

View File

@ -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...)
}

View File

@ -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

View File

@ -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
}

View File

@ -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
}

View File

@ -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) {

View File

@ -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(

View File

@ -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)