diff --git a/README.md b/README.md index 5b1aac5..b46492a 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # mq -![Message Queue](https://cdn-images-1.medium.com/max/800/1*UbKJu2BcAYim8_oJg8Ns6A.png) +![Message Queue](https://cdn-images-1.medium.com/max/800/1*a3_1glo06Fgh-pkqIATyng.png) ## Message Queue A message queue is a communication method used in software systems to exchange information between different components or services asynchronously. It provides a way to send messages between producers (senders) and consumers (receivers) without requiring both parties to interact with the message queue at the same time. This decoupling allows for more scalable, reliable, and flexible system architectures. diff --git a/config/config.go b/config/config.go index 4192e23..b2eabc1 100644 --- a/config/config.go +++ b/config/config.go @@ -3,11 +3,12 @@ package config import ( "fmt" "github.com/spf13/viper" - "io/ioutil" "log" "os" "reflect" + "strconv" "strings" + "time" ) func Load(c interface{}, fileNames ...string) error { @@ -111,7 +112,7 @@ func LoadConfigWithEnv(parentPath string, directory string, env string, c interf // BindEnvs function will bind ymal file to struc model func BindEnvs(conf interface{}, parts ...string) error { ifv := reflect.Indirect(reflect.ValueOf(conf)) - ift := reflect.TypeOf(ifv) + ift := ifv.Type() num := min(ift.NumField(), ifv.NumField()) for i := 0; i < num; i++ { v := ifv.Field(i) @@ -122,9 +123,15 @@ func BindEnvs(conf interface{}, parts ...string) error { } switch v.Kind() { case reflect.Struct: - return BindEnvs(v.Interface(), append(parts, tv)...) + err := BindEnvs(v.Interface(), append(parts, tv)...) + if err != nil { + return err + } default: - return viper.BindEnv(strings.Join(append(parts, tv), ".")) + err := viper.BindEnv(strings.Join(append(parts, tv), ".")) + if err != nil { + return err + } } } return nil @@ -212,7 +219,7 @@ func LoadFileWithPath(parentPath string, directory string, env string, filename file = "./" + parentPath + "/" + directory + "/" + filename[0:indexDot] + "-" + env + filename[indexDot:] } if fileExists(file) { - return ioutil.ReadFile(file) + return os.ReadFile(file) } } } @@ -220,7 +227,7 @@ func LoadFileWithPath(parentPath string, directory string, env string, filename if !fileExists(file) { file = "./" + parentPath + "/" + directory + "/" + filename } - return ioutil.ReadFile(file) + return os.ReadFile(file) } else { if len(env) > 0 { indexDot := strings.LastIndex(filename, ".") @@ -230,7 +237,7 @@ func LoadFileWithPath(parentPath string, directory string, env string, filename file = "./" + parentPath + "/" + filename[0:indexDot] + "-" + env + filename[indexDot:] } if fileExists(file) { - return ioutil.ReadFile(file) + return os.ReadFile(file) } } } @@ -238,7 +245,7 @@ func LoadFileWithPath(parentPath string, directory string, env string, filename if !fileExists(file) { file = "./" + parentPath + "/" + filename } - return ioutil.ReadFile(file) + return os.ReadFile(file) } } func LoadFileWithEnv(env string, filename string) ([]byte, error) { @@ -282,3 +289,83 @@ func fileExists(filename string) bool { } return !info.IsDir() } + +func MakeDurations(vs []int64) []time.Duration { + durations := make([]time.Duration, 0) + for _, v := range vs { + d := time.Duration(v) * time.Second + durations = append(durations, d) + } + return durations +} +func MakeArray(v interface{}, prefix string, max int) []int64 { + var ar []int64 + v2 := reflect.Indirect(reflect.ValueOf(v)) + for i := 1; i <= max; i++ { + fn := prefix + strconv.Itoa(i) + v3 := v2.FieldByName(fn).Interface().(int64) + if v3 > 0 { + ar = append(ar, v3) + } else { + return ar + } + } + return ar +} +func DurationsFromValue(v interface{}, prefix string, max int) []time.Duration { + arr := MakeArray(v, prefix, max) + return MakeDurations(arr) +} + +type Retry struct { + Retry1 int64 `mapstructure:"1" json:"retry1,omitempty" gorm:"column:retry1" bson:"retry1,omitempty" dynamodbav:"retry1,omitempty" firestore:"retry1,omitempty"` + Retry2 int64 `mapstructure:"2" json:"retry2,omitempty" gorm:"column:retry2" bson:"retry2,omitempty" dynamodbav:"retry2,omitempty" firestore:"retry2,omitempty"` + Retry3 int64 `mapstructure:"3" json:"retry3,omitempty" gorm:"column:retry3" bson:"retry3,omitempty" dynamodbav:"retry3,omitempty" firestore:"retry3,omitempty"` + Retry4 int64 `mapstructure:"4" json:"retry4,omitempty" gorm:"column:retry4" bson:"retry4,omitempty" dynamodbav:"retry4,omitempty" firestore:"retry4,omitempty"` + Retry5 int64 `mapstructure:"5" json:"retry5,omitempty" gorm:"column:retry5" bson:"retry5,omitempty" dynamodbav:"retry5,omitempty" firestore:"retry5,omitempty"` + Retry6 int64 `mapstructure:"6" json:"retry6,omitempty" gorm:"column:retry6" bson:"retry6,omitempty" dynamodbav:"retry6,omitempty" firestore:"retry6,omitempty"` + Retry7 int64 `mapstructure:"7" json:"retry7,omitempty" gorm:"column:retry7" bson:"retry7,omitempty" dynamodbav:"retry7,omitempty" firestore:"retry7,omitempty"` + Retry8 int64 `mapstructure:"8" json:"retry8,omitempty" gorm:"column:retry8" bson:"retry8,omitempty" dynamodbav:"retry8,omitempty" firestore:"retry8,omitempty"` + Retry9 int64 `mapstructure:"9" json:"retry9,omitempty" gorm:"column:retry9" bson:"retry9,omitempty" dynamodbav:"retry9,omitempty" firestore:"retry9,omitempty"` + Retry10 int64 `mapstructure:"10" json:"retry10,omitempty" gorm:"column:retry10" bson:"retry10,omitempty" dynamodbav:"retry10,omitempty" firestore:"retry10,omitempty"` + Retry11 int64 `mapstructure:"11" json:"retry11,omitempty" gorm:"column:retry11" bson:"retry11,omitempty" dynamodbav:"retry11,omitempty" firestore:"retry11,omitempty"` + Retry12 int64 `mapstructure:"12" json:"retry12,omitempty" gorm:"column:retry12" bson:"retry12,omitempty" dynamodbav:"retry12,omitempty" firestore:"retry12,omitempty"` + Retry13 int64 `mapstructure:"13" json:"retry13,omitempty" gorm:"column:retry13" bson:"retry13,omitempty" dynamodbav:"retry13,omitempty" firestore:"retry13,omitempty"` + Retry14 int64 `mapstructure:"14" json:"retry14,omitempty" gorm:"column:retry14" bson:"retry14,omitempty" dynamodbav:"retry14,omitempty" firestore:"retry14,omitempty"` + Retry15 int64 `mapstructure:"15" json:"retry15,omitempty" gorm:"column:retry15" bson:"retry15,omitempty" dynamodbav:"retry15,omitempty" firestore:"retry15,omitempty"` + Retry16 int64 `mapstructure:"16" json:"retry16,omitempty" gorm:"column:retry16" bson:"retry16,omitempty" dynamodbav:"retry16,omitempty" firestore:"retry16,omitempty"` + Retry17 int64 `mapstructure:"17" json:"retry17,omitempty" gorm:"column:retry17" bson:"retry17,omitempty" dynamodbav:"retry17,omitempty" firestore:"retry17,omitempty"` + Retry18 int64 `mapstructure:"18" json:"retry18,omitempty" gorm:"column:retry18" bson:"retry18,omitempty" dynamodbav:"retry18,omitempty" firestore:"retry18,omitempty"` + Retry19 int64 `mapstructure:"19" json:"retry19,omitempty" gorm:"column:retry19" bson:"retry19,omitempty" dynamodbav:"retry19,omitempty" firestore:"retry19,omitempty"` + Retry20 int64 `mapstructure:"20" json:"retry20,omitempty" gorm:"column:retry20" bson:"retry20,omitempty" dynamodbav:"retry20,omitempty" firestore:"retry20,omitempty"` + Retry21 int64 `mapstructure:"21" json:"retry21,omitempty" gorm:"column:retry21" bson:"retry21,omitempty" dynamodbav:"retry21,omitempty" firestore:"retry21,omitempty"` + Retry22 int64 `mapstructure:"22" json:"retry22,omitempty" gorm:"column:retry22" bson:"retry22,omitempty" dynamodbav:"retry22,omitempty" firestore:"retry22,omitempty"` + Retry23 int64 `mapstructure:"23" json:"retry23,omitempty" gorm:"column:retry23" bson:"retry23,omitempty" dynamodbav:"retry23,omitempty" firestore:"retry23,omitempty"` + Retry24 int64 `mapstructure:"24" json:"retry24,omitempty" gorm:"column:retry24" bson:"retry24,omitempty" dynamodbav:"retry24,omitempty" firestore:"retry24,omitempty"` + Retry25 int64 `mapstructure:"25" json:"retry25,omitempty" gorm:"column:retry25" bson:"retry25,omitempty" dynamodbav:"retry25,omitempty" firestore:"retry25,omitempty"` + Retry26 int64 `mapstructure:"26" json:"retry26,omitempty" gorm:"column:retry26" bson:"retry26,omitempty" dynamodbav:"retry26,omitempty" firestore:"retry26,omitempty"` + Retry27 int64 `mapstructure:"27" json:"retry27,omitempty" gorm:"column:retry27" bson:"retry27,omitempty" dynamodbav:"retry27,omitempty" firestore:"retry27,omitempty"` + Retry28 int64 `mapstructure:"28" json:"retry28,omitempty" gorm:"column:retry28" bson:"retry28,omitempty" dynamodbav:"retry28,omitempty" firestore:"retry28,omitempty"` + Retry29 int64 `mapstructure:"29" json:"retry29,omitempty" gorm:"column:retry29" bson:"retry29,omitempty" dynamodbav:"retry29,omitempty" firestore:"retry29,omitempty"` + Retry30 int64 `mapstructure:"30" json:"retry30,omitempty" gorm:"column:retry30" bson:"retry30,omitempty" dynamodbav:"retry30,omitempty" firestore:"retry30,omitempty"` + Retry31 int64 `mapstructure:"31" json:"retry31,omitempty" gorm:"column:retry31" bson:"retry31,omitempty" dynamodbav:"retry31,omitempty" firestore:"retry31,omitempty"` + Retry32 int64 `mapstructure:"32" json:"retry32,omitempty" gorm:"column:retry32" bson:"retry32,omitempty" dynamodbav:"retry32,omitempty" firestore:"retry32,omitempty"` + Retry33 int64 `mapstructure:"33" json:"retry33,omitempty" gorm:"column:retry33" bson:"retry33,omitempty" dynamodbav:"retry33,omitempty" firestore:"retry33,omitempty"` + Retry34 int64 `mapstructure:"34" json:"retry34,omitempty" gorm:"column:retry34" bson:"retry34,omitempty" dynamodbav:"retry34,omitempty" firestore:"retry34,omitempty"` + Retry35 int64 `mapstructure:"35" json:"retry35,omitempty" gorm:"column:retry35" bson:"retry35,omitempty" dynamodbav:"retry35,omitempty" firestore:"retry35,omitempty"` + Retry36 int64 `mapstructure:"36" json:"retry36,omitempty" gorm:"column:retry36" bson:"retry36,omitempty" dynamodbav:"retry36,omitempty" firestore:"retry36,omitempty"` + Retry37 int64 `mapstructure:"37" json:"retry37,omitempty" gorm:"column:retry37" bson:"retry37,omitempty" dynamodbav:"retry37,omitempty" firestore:"retry37,omitempty"` + Retry38 int64 `mapstructure:"38" json:"retry38,omitempty" gorm:"column:retry38" bson:"retry38,omitempty" dynamodbav:"retry38,omitempty" firestore:"retry38,omitempty"` + Retry39 int64 `mapstructure:"39" json:"retry39,omitempty" gorm:"column:retry39" bson:"retry39,omitempty" dynamodbav:"retry39,omitempty" firestore:"retry39,omitempty"` + Retry40 int64 `mapstructure:"40" json:"retry40,omitempty" gorm:"column:retry40" bson:"retry40,omitempty" dynamodbav:"retry40,omitempty" firestore:"retry40,omitempty"` + Retry41 int64 `mapstructure:"41" json:"retry41,omitempty" gorm:"column:retry41" bson:"retry41,omitempty" dynamodbav:"retry41,omitempty" firestore:"retry41,omitempty"` + Retry42 int64 `mapstructure:"42" json:"retry42,omitempty" gorm:"column:retry42" bson:"retry42,omitempty" dynamodbav:"retry42,omitempty" firestore:"retry42,omitempty"` + Retry43 int64 `mapstructure:"43" json:"retry43,omitempty" gorm:"column:retry43" bson:"retry43,omitempty" dynamodbav:"retry43,omitempty" firestore:"retry43,omitempty"` + Retry44 int64 `mapstructure:"44" json:"retry44,omitempty" gorm:"column:retry44" bson:"retry44,omitempty" dynamodbav:"retry44,omitempty" firestore:"retry44,omitempty"` + Retry45 int64 `mapstructure:"45" json:"retry45,omitempty" gorm:"column:retry45" bson:"retry45,omitempty" dynamodbav:"retry45,omitempty" firestore:"retry45,omitempty"` + Retry46 int64 `mapstructure:"46" json:"retry46,omitempty" gorm:"column:retry46" bson:"retry46,omitempty" dynamodbav:"retry46,omitempty" firestore:"retry46,omitempty"` + Retry47 int64 `mapstructure:"47" json:"retry47,omitempty" gorm:"column:retry47" bson:"retry47,omitempty" dynamodbav:"retry47,omitempty" firestore:"retry47,omitempty"` + Retry48 int64 `mapstructure:"48" json:"retry48,omitempty" gorm:"column:retry48" bson:"retry48,omitempty" dynamodbav:"retry48,omitempty" firestore:"retry48,omitempty"` + Retry49 int64 `mapstructure:"49" json:"retry49,omitempty" gorm:"column:retry49" bson:"retry49,omitempty" dynamodbav:"retry49,omitempty" firestore:"retry49,omitempty"` + Retry50 int64 `mapstructure:"50" json:"retry50,omitempty" gorm:"column:retry50" bson:"retry50,omitempty" dynamodbav:"retry50,omitempty" firestore:"retry50,omitempty"` +} diff --git a/log/config.go b/log/config.go index 8c01365..9d5b7a7 100644 --- a/log/config.go +++ b/log/config.go @@ -3,16 +3,17 @@ package log import "github.com/sirupsen/logrus" type Config struct { - Level string `mapstructure:"level" json:"level,omitempty" gorm:"column:level" bson:"level,omitempty" dynamodbav:"level,omitempty" firestore:"level,omitempty"` - Duration string `mapstructure:"duration" json:"duration,omitempty" gorm:"column:duration" bson:"duration,omitempty" dynamodbav:"duration,omitempty" firestore:"duration,omitempty"` - Fields string `mapstructure:"fields" json:"fields,omitempty" gorm:"column:fields" bson:"fields,omitempty" dynamodbav:"fields,omitempty" firestore:"fields,omitempty"` - FieldMap string `mapstructure:"field_map" json:"fieldMap,omitempty" gorm:"column:fieldmap" bson:"fieldMap,omitempty" dynamodbav:"fieldMap,omitempty" firestore:"fieldMap,omitempty"` - Map *logrus.FieldMap `mapstructure:"map" json:"map,omitempty" gorm:"column:map" bson:"map,omitempty" dynamodbav:"map,omitempty" firestore:"map,omitempty"` - TimestampFormat string `mapstructure:"timestamp_format" json:"timestampFormat,omitempty" gorm:"column:timestampformat" bson:"timestampFormat,omitempty" dynamodbav:"timestampFormat,omitempty" firestore:"timestampFormat,omitempty"` + Level string `yaml:"level" mapstructure:"level" json:"level,omitempty" gorm:"column:level" bson:"level,omitempty" dynamodbav:"level,omitempty" firestore:"level,omitempty"` + Output string `yaml:"output" mapstructure:"output" json:"output,omitempty" gorm:"column:output" bson:"output,omitempty" dynamodbav:"output,omitempty" firestore:"output,omitempty"` + Duration string `yaml:"duration" mapstructure:"duration" json:"duration,omitempty" gorm:"column:duration" bson:"duration,omitempty" dynamodbav:"duration,omitempty" firestore:"duration,omitempty"` + Fields string `yaml:"fields" mapstructure:"fields" json:"fields,omitempty" gorm:"column:fields" bson:"fields,omitempty" dynamodbav:"fields,omitempty" firestore:"fields,omitempty"` + FieldMap string `yaml:"field_map" mapstructure:"field_map" json:"fieldMap,omitempty" gorm:"column:fieldmap" bson:"fieldMap,omitempty" dynamodbav:"fieldMap,omitempty" firestore:"fieldMap,omitempty"` + Map *logrus.FieldMap `yaml:"map" mapstructure:"map" json:"map,omitempty" gorm:"column:map" bson:"map,omitempty" dynamodbav:"map,omitempty" firestore:"map,omitempty"` + TimestampFormat string `yaml:"timestamp_format" mapstructure:"timestamp_format" json:"timestampFormat,omitempty" gorm:"column:timestampformat" bson:"timestampFormat,omitempty" dynamodbav:"timestampFormat,omitempty" firestore:"timestampFormat,omitempty"` } type FieldConfig struct { - FieldMap string `mapstructure:"field_map" json:"fieldMap,omitempty" gorm:"column:fieldmap" bson:"fieldMap,omitempty" dynamodbav:"fieldMap,omitempty" firestore:"fieldMap,omitempty"` - Duration string `mapstructure:"duration" json:"duration,omitempty" gorm:"column:duration" bson:"duration,omitempty" dynamodbav:"duration,omitempty" firestore:"duration,omitempty"` - Fields *[]string `mapstructure:"fields" json:"fields,omitempty" gorm:"column:fields" bson:"fields,omitempty" dynamodbav:"fields,omitempty" firestore:"fields,omitempty"` + FieldMap string `yaml:"field_map" mapstructure:"field_map" json:"fieldMap,omitempty" gorm:"column:fieldmap" bson:"fieldMap,omitempty" dynamodbav:"fieldMap,omitempty" firestore:"fieldMap,omitempty"` + Duration string `yaml:"duration" mapstructure:"duration" json:"duration,omitempty" gorm:"column:duration" bson:"duration,omitempty" dynamodbav:"duration,omitempty" firestore:"duration,omitempty"` + Fields *[]string `yaml:"fields" mapstructure:"fields" json:"fields,omitempty" gorm:"column:fields" bson:"fields,omitempty" dynamodbav:"fields,omitempty" firestore:"fields,omitempty"` } diff --git a/log/logrus.go b/log/logrus.go index 0b30ae0..53243fe 100644 --- a/log/logrus.go +++ b/log/logrus.go @@ -5,6 +5,8 @@ import ( "encoding/json" "fmt" "github.com/sirupsen/logrus" + "io" + "os" "strings" "time" ) @@ -12,7 +14,11 @@ import ( var fieldConfig FieldConfig var logger *logrus.Logger -func Initialize(c Config) *logrus.Logger { +func Initialize(c Config, opts...func(logLocation string, rotationTime time.Duration) (io.Writer, func() error)) *logrus.Logger { + var getWriter func(logLocation string, rotationTime time.Duration) (io.Writer, func() error) + if len(opts) > 0 && opts[0] != nil { + getWriter = opts[0] + } fieldConfig.FieldMap = c.FieldMap if len(c.Duration) > 0 { fieldConfig.Duration = c.Duration @@ -36,6 +42,16 @@ func Initialize(c Config) *logrus.Logger { x := &formatter l.SetFormatter(x) logrus.SetFormatter(x) + if len(c.Output) > 0 && getWriter != nil { + CreatePath(c.Output) + w, close := getWriter(c.Output, 24*time.Hour) + l.SetOutput(io.MultiWriter(os.Stderr, w)) + logrus.SetOutput(io.MultiWriter(os.Stderr, w)) + handler := func() { + close() + } + logrus.RegisterExitHandler(handler) + } if len(c.Level) > 0 { if level, err := logrus.ParseLevel(c.Level); err == nil { l.SetLevel(level) @@ -222,6 +238,9 @@ func Panicf(ctx context.Context, format string, args ...interface{}) { func BuildLogFields(m map[string]interface{}) logrus.Fields { logFields := logrus.Fields{} + if m == nil || len(m) == 0 { + return logFields + } for k, v := range m { logFields[k] = v } @@ -257,6 +276,15 @@ func LogfWithFields(ctx context.Context, level logrus.Level, fields map[string]i } } +func TraceWithFields(ctx context.Context, msg interface{}, fields map[string]interface{}) { + LogWithFields(ctx, logrus.TraceLevel, msg, fields) +} +func TracefWithFields(ctx context.Context, fields map[string]interface{}, format string, args ...interface{}) { + if logrus.IsLevelEnabled(logrus.TraceLevel) { + msg := fmt.Sprintf(format, args...) + LogWithFields(ctx, logrus.TraceLevel, msg, fields) + } +} func DebugWithFields(ctx context.Context, msg interface{}, fields map[string]interface{}) { LogWithFields(ctx, logrus.DebugLevel, msg, fields) } @@ -327,3 +355,53 @@ func FatalFields(ctx context.Context, msg string, fields map[string]interface{}) func PanicFields(ctx context.Context, msg string, fields map[string]interface{}) { LogWithFields(ctx, logrus.PanicLevel, msg, fields) } + +func LogTrace(ctx context.Context, msg string, opts ...map[string]interface{}) { + if len(opts) > 0 { + TraceWithFields(ctx, msg, opts[0]) + } else { + TraceWithFields(ctx, msg, nil) + } +} +func LogDebug(ctx context.Context, msg string, opts ...map[string]interface{}) { + if len(opts) > 0 { + DebugWithFields(ctx, msg, opts[0]) + } else { + DebugWithFields(ctx, msg, nil) + } +} +func LogInfo(ctx context.Context, msg string, opts ...map[string]interface{}) { + if len(opts) > 0 { + InfoWithFields(ctx, msg, opts[0]) + } else { + InfoWithFields(ctx, msg, nil) + } +} +func LogWarn(ctx context.Context, msg string, opts ...map[string]interface{}) { + if len(opts) > 0 { + WarnWithFields(ctx, msg, opts[0]) + } else { + WarnWithFields(ctx, msg, nil) + } +} +func LogError(ctx context.Context, msg string, opts ...map[string]interface{}) { + if len(opts) > 0 { + ErrorWithFields(ctx, msg, opts[0]) + } else { + ErrorWithFields(ctx, msg, nil) + } +} +func LogFatal(ctx context.Context, msg string, opts ...map[string]interface{}) { + if len(opts) > 0 { + FatalWithFields(ctx, msg, opts[0]) + } else { + FatalWithFields(ctx, msg, nil) + } +} +func LogPanic(ctx context.Context, msg string, opts ...map[string]interface{}) { + if len(opts) > 0 { + PanicWithFields(ctx, msg, opts[0]) + } else { + PanicWithFields(ctx, msg, nil) + } +} diff --git a/log/util.go b/log/util.go new file mode 100644 index 0000000..fda7e3d --- /dev/null +++ b/log/util.go @@ -0,0 +1,39 @@ +package log + +import ( + "errors" + "os" + "path/filepath" + "regexp" + "strings" + "time" +) + +func GenerateFileNameLog(pathFileNameFormat string) string { + path := pathFileNameFormat + if strings.Contains(pathFileNameFormat, "$") { + s := getLayoutsFormat(pathFileNameFormat) + if len(s) > 1 { + timeFormat := time.Now().Format(s[1]) + path = strings.ReplaceAll(pathFileNameFormat, s[0], timeFormat) + } + } + CreatePath(path) + return path +} + +func CreatePath(path string) error { + dir := filepath.Dir(path) + if _, err := os.Stat(dir); errors.Is(err, os.ErrNotExist) { + err2 := os.Mkdir(dir, os.ModePerm) + if err2 != nil { + return err2 + } + } + return nil +} + +func getLayoutsFormat(s string) []string { + re := regexp.MustCompile("\\$\\{(.*?)\\}") + return re.FindStringSubmatch(s) +} diff --git a/zap/caller.go b/zap/caller.go new file mode 100644 index 0000000..b03df65 --- /dev/null +++ b/zap/caller.go @@ -0,0 +1,76 @@ +package log + +import ( + "go.uber.org/zap/zapcore" + "runtime" +) + +const callerSkipOffset = 2 + +type levelFilterCore struct { + core zapcore.Core + level zapcore.LevelEnabler + skip int + levelCaller map[zapcore.Level]interface{} +} + +// NewLogTraceLevelCore creates a core that can be used to increase the level of +// an existing Core. It cannot be used to decrease the logging level, as it acts +// as a filter before calling the underlying core. If level decreases the log level, +// an error is returned. +func NewLogTraceLevelCore(core zapcore.Core, level zapcore.LevelEnabler, skip int, levelCaller ...zapcore.Level) (zapcore.Core, error) { + lc := make(map[zapcore.Level]interface{}, 0) + if len(levelCaller) > 0 { + for _, z := range levelCaller { + lc[z] = z + } + } + return &levelFilterCore{core, level, skip, lc}, nil +} + +func (c *levelFilterCore) Enabled(lvl zapcore.Level) bool { + return c.level.Enabled(lvl) +} + +func (c *levelFilterCore) With(fields []zapcore.Field) zapcore.Core { + return &levelFilterCore{c.core.With(fields), c.level, c.skip, c.levelCaller} +} + +func (c *levelFilterCore) Check(ent zapcore.Entry, ce *zapcore.CheckedEntry) *zapcore.CheckedEntry { + if _, ok := c.levelCaller[ent.Level]; ok { + frame, defined := getCallerFrame(c.skip + callerSkipOffset) + ent.Caller = zapcore.EntryCaller{ + Defined: defined, + PC: frame.PC, + File: frame.File, + Line: frame.Line, + Function: frame.Function, + } + return ce.AddCore(ent, c.core) + } + if !c.core.Enabled(ent.Level) { + return ce + } + return c.core.Check(ent, ce) +} + +func (c *levelFilterCore) Write(ent zapcore.Entry, fields []zapcore.Field) error { + return c.core.Write(ent, fields) +} + +func (c *levelFilterCore) Sync() error { + return c.core.Sync() +} + +func getCallerFrame(skip int) (frame runtime.Frame, ok bool) { + const skipOffset = 2 // skip getCallerFrame and Callers + + pc := make([]uintptr, 1) + numFrames := runtime.Callers(skip+skipOffset, pc) + if numFrames < 1 { + return + } + + frame, _ = runtime.CallersFrames(pc).Next() + return frame, frame.PC != 0 +} diff --git a/zap/config.go b/zap/config.go index 2735960..727971c 100644 --- a/zap/config.go +++ b/zap/config.go @@ -1,24 +1,29 @@ package log type Config struct { - Level string `mapstructure:"level" json:"level,omitempty" gorm:"column:level" bson:"level,omitempty" dynamodbav:"level,omitempty" firestore:"level,omitempty"` - Duration string `mapstructure:"duration" json:"duration,omitempty" gorm:"column:duration" bson:"duration,omitempty" dynamodbav:"duration,omitempty" firestore:"duration,omitempty"` - Fields string `mapstructure:"fields" json:"fields,omitempty" gorm:"column:fields" bson:"fields,omitempty" dynamodbav:"fields,omitempty" firestore:"fields,omitempty"` - FieldMap string `mapstructure:"field_map" json:"fieldMap,omitempty" gorm:"column:fieldmap" bson:"fieldMap,omitempty" dynamodbav:"fieldMap,omitempty" firestore:"fieldMap,omitempty"` - Map *FieldMap `mapstructure:"map" json:"map,omitempty" gorm:"column:map" bson:"map,omitempty" dynamodbav:"map,omitempty" firestore:"map,omitempty"` + Level string `yaml:"level" mapstructure:"level" json:"level,omitempty" gorm:"column:level" bson:"level,omitempty" dynamodbav:"level,omitempty" firestore:"level,omitempty"` + Duration string `yaml:"duration" mapstructure:"duration" json:"duration,omitempty" gorm:"column:duration" bson:"duration,omitempty" dynamodbav:"duration,omitempty" firestore:"duration,omitempty"` + Fields string `yaml:"fields" mapstructure:"fields" json:"fields,omitempty" gorm:"column:fields" bson:"fields,omitempty" dynamodbav:"fields,omitempty" firestore:"fields,omitempty"` + FieldMap string `yaml:"field_map" mapstructure:"field_map" json:"fieldMap,omitempty" gorm:"column:fieldmap" bson:"fieldMap,omitempty" dynamodbav:"fieldMap,omitempty" firestore:"fieldMap,omitempty"` + Map *FieldMap `yaml:"map" mapstructure:"map" json:"map,omitempty" gorm:"column:map" bson:"map,omitempty" dynamodbav:"map,omitempty" firestore:"map,omitempty"` + CallerLevel string `yaml:"caller_level" mapstructure:"caller_level" json:"callerLevel,omitempty" gorm:"column:callerlevel" bson:"callerLevel,omitempty" dynamodbav:"callerLevel,omitempty" firestore:"callerLevel,omitempty"` + CallerSkip int `yaml:"caller_skip" mapstructure:"caller_skip" json:"callerSkip,omitempty" gorm:"column:callerskip" bson:"callerSkip,omitempty" dynamodbav:"callerSkip,omitempty" firestore:"callerSkip,omitempty"` + Output string `yaml:"output" mapstructure:"output" json:"output,omitempty" gorm:"column:output" bson:"output,omitempty" dynamodbav:"output,omitempty" firestore:"output,omitempty"` + MaxSize SizeOfFile `yaml:"max_file_size" mapstructure:"max_file_size" json:"max_file_size,omitempty" gorm:"column:max_file_size" bson:"max_file_size,omitempty" dynamodbav:"max_file_size,omitempty" firestore:"max_file_size,omitempty"` } type FieldMap struct { - Time string `mapstructure:"time" json:"time,omitempty" gorm:"column:time" bson:"time,omitempty" dynamodbav:"time,omitempty" firestore:"time,omitempty"` - Level string `mapstructure:"level" json:"level,omitempty" gorm:"column:level" bson:"level,omitempty" dynamodbav:"level,omitempty" firestore:"level,omitempty"` - Name string `mapstructure:"name" json:"name,omitempty" gorm:"column:name" bson:"name,omitempty" dynamodbav:"name,omitempty" firestore:"name,omitempty"` - Caller string `mapstructure:"caller" json:"caller,omitempty" gorm:"column:caller" bson:"caller,omitempty" dynamodbav:"caller,omitempty" firestore:"caller,omitempty"` - Msg string `mapstructure:"msg" json:"msg,omitempty" gorm:"column:msg" bson:"msg,omitempty" dynamodbav:"msg,omitempty" firestore:"msg,omitempty"` - Stacktrace string `mapstructure:"stacktrace" json:"stacktrace,omitempty" gorm:"column:stacktrace" bson:"stacktrace,omitempty" dynamodbav:"stacktrace,omitempty" firestore:"stacktrace,omitempty"` + Time string `yaml:"time" mapstructure:"time" json:"time,omitempty" gorm:"column:time" bson:"time,omitempty" dynamodbav:"time,omitempty" firestore:"time,omitempty"` + Level string `yaml:"level" mapstructure:"level" json:"level,omitempty" gorm:"column:level" bson:"level,omitempty" dynamodbav:"level,omitempty" firestore:"level,omitempty"` + Name string `yaml:"name" mapstructure:"name" json:"name,omitempty" gorm:"column:name" bson:"name,omitempty" dynamodbav:"name,omitempty" firestore:"name,omitempty"` + Caller string `yaml:"caller" mapstructure:"caller" json:"caller,omitempty" gorm:"column:caller" bson:"caller,omitempty" dynamodbav:"caller,omitempty" firestore:"caller,omitempty"` + Function string `yaml:"function" mapstructure:"function" json:"function,omitempty" gorm:"column:function" bson:"function,omitempty" dynamodbav:"function,omitempty" firestore:"function,omitempty"` + Msg string `yaml:"msg" mapstructure:"msg" json:"msg,omitempty" gorm:"column:msg" bson:"msg,omitempty" dynamodbav:"msg,omitempty" firestore:"msg,omitempty"` + Stacktrace string `yaml:"stacktrace" mapstructure:"stacktrace" json:"stacktrace,omitempty" gorm:"column:stacktrace" bson:"stacktrace,omitempty" dynamodbav:"stacktrace,omitempty" firestore:"stacktrace,omitempty"` } type FieldConfig struct { - FieldMap string `mapstructure:"field_map" json:"fieldMap,omitempty" gorm:"column:fieldmap" bson:"fieldMap,omitempty" dynamodbav:"fieldMap,omitempty" firestore:"fieldMap,omitempty"` - Duration string `mapstructure:"duration" json:"duration,omitempty" gorm:"column:duration" bson:"duration,omitempty" dynamodbav:"duration,omitempty" firestore:"duration,omitempty"` - Fields *[]string `mapstructure:"fields" json:"fields,omitempty" gorm:"column:fields" bson:"fields,omitempty" dynamodbav:"fields,omitempty" firestore:"fields,omitempty"` + FieldMap string `yaml:"field_map" mapstructure:"field_map" json:"fieldMap,omitempty" gorm:"column:fieldmap" bson:"fieldMap,omitempty" dynamodbav:"fieldMap,omitempty" firestore:"fieldMap,omitempty"` + Duration string `yaml:"duration" mapstructure:"duration" json:"duration,omitempty" gorm:"column:duration" bson:"duration,omitempty" dynamodbav:"duration,omitempty" firestore:"duration,omitempty"` + Fields *[]string `yaml:"fields" mapstructure:"fields" json:"fields,omitempty" gorm:"column:fields" bson:"fields,omitempty" dynamodbav:"fields,omitempty" firestore:"fields,omitempty"` } diff --git a/zap/file.go b/zap/file.go new file mode 100644 index 0000000..a674ee7 --- /dev/null +++ b/zap/file.go @@ -0,0 +1,39 @@ +package log + +import ( + "errors" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" + "os" + "path/filepath" +) + +func NewWriter(ws zapcore.WriteSyncer, conf zap.Config) zap.Option { + var enc zapcore.Encoder + switch conf.Encoding { + case "json": + enc = zapcore.NewJSONEncoder(conf.EncoderConfig) + case "console": + enc = zapcore.NewConsoleEncoder(conf.EncoderConfig) + default: + panic("unknown encoding") + } + return zap.WrapCore(func(core zapcore.Core) zapcore.Core { + return zapcore.NewCore(enc, ws, conf.Level) + }) +} + +func GetWriteSyncer(path string) zapcore.WriteSyncer { + dir := filepath.Dir(path) + if _, err := os.Stat(dir); errors.Is(err, os.ErrNotExist) { + err2 := os.Mkdir(dir, os.ModePerm) + if err2 != nil { + panic(err2) + } + } + file, err := os.OpenFile(path, os.O_RDWR|os.O_APPEND|os.O_CREATE, 644) + if err != nil { + panic(err) + } + return zapcore.AddSync(file) +} diff --git a/zap/sizefile.go b/zap/sizefile.go new file mode 100644 index 0000000..250d48b --- /dev/null +++ b/zap/sizefile.go @@ -0,0 +1,61 @@ +package log + +import ( + "errors" + "regexp" + "strconv" + "strings" +) + +type SizeOfFile string + +type Bytesize float64 + +// bytes sizes generally used for computer storage and memory +const ( + B Bytesize = 1 + KiB Bytesize = 1024 * B + MiB Bytesize = 1024 * KiB + GiB Bytesize = 1024 * MiB + TiB Bytesize = 1024 * GiB + PiB Bytesize = 1024 * TiB + EiB Bytesize = 1024 * PiB +) + +func (s *SizeOfFile) Parse(n int64, unit string) int64 { + unit = strings.ToUpper(unit) + switch unit { + case "B": + return n + case "MB": + return n * int64(MiB) + case "KB": + return n * int64(KiB) + case "GB": + return n * int64(GiB) + } + return 0 +} + +func (s SizeOfFile) GetByteSize() (int64, error) { + size, unit, err := s.parse(string(s)) + if err != nil { + return 0, err + } + return s.Parse(size, unit), nil +} + +func (s SizeOfFile) parse(input string) (int64, string, error) { + var rxPattern = regexp.MustCompile(`(\d*)(B|KB|MB|GB)`) + submatch := rxPattern.FindStringSubmatch(input) + if len(submatch) == 3 { + value := submatch[1] + size, err := strconv.ParseInt(value, 10, 64) + if err != nil { + return size, "", err + } + unit := submatch[2] + return size, unit, nil + } + return 0, "", errors.New("Invalid format of size") +} diff --git a/zap/util.go b/zap/util.go new file mode 100644 index 0000000..fda7e3d --- /dev/null +++ b/zap/util.go @@ -0,0 +1,39 @@ +package log + +import ( + "errors" + "os" + "path/filepath" + "regexp" + "strings" + "time" +) + +func GenerateFileNameLog(pathFileNameFormat string) string { + path := pathFileNameFormat + if strings.Contains(pathFileNameFormat, "$") { + s := getLayoutsFormat(pathFileNameFormat) + if len(s) > 1 { + timeFormat := time.Now().Format(s[1]) + path = strings.ReplaceAll(pathFileNameFormat, s[0], timeFormat) + } + } + CreatePath(path) + return path +} + +func CreatePath(path string) error { + dir := filepath.Dir(path) + if _, err := os.Stat(dir); errors.Is(err, os.ErrNotExist) { + err2 := os.Mkdir(dir, os.ModePerm) + if err2 != nil { + return err2 + } + } + return nil +} + +func getLayoutsFormat(s string) []string { + re := regexp.MustCompile("\\$\\{(.*?)\\}") + return re.FindStringSubmatch(s) +} diff --git a/zap/zap.go b/zap/zap.go index ea39a3a..d677a1c 100644 --- a/zap/zap.go +++ b/zap/zap.go @@ -6,6 +6,8 @@ import ( "fmt" "go.uber.org/zap" "go.uber.org/zap/zapcore" + "io" + "os" "strings" "time" ) @@ -16,8 +18,10 @@ var logger *zap.Logger func SetLogger(logger0 *zap.Logger) { logger = logger0 } - -func Initialize(c Config) (*zap.Logger, error) { +func Initialize(c Config, opts ...zapcore.Core) (*zap.Logger, error) { + return InitializeWithWriter(c, nil, opts...) +} +func InitializeWithWriter(c Config, getWriter func(logLocation string, rotationTime time.Duration, maxSize int64) (io.Writer, func() error), opts ...zapcore.Core) (*zap.Logger, error) { fieldConfig.FieldMap = c.FieldMap if len(c.Duration) > 0 { fieldConfig.Duration = c.Duration @@ -28,7 +32,47 @@ func Initialize(c Config) (*zap.Logger, error) { fields := strings.Split(c.Fields, ",") fieldConfig.Fields = &fields } - l, err := NewConfig(c).Build() + + var showCallerLv []zapcore.Level + if len(c.CallerLevel) > 0 { + ar := strings.Split(c.CallerLevel, ",") + for _, value := range ar { + lv := zap.InfoLevel + if err := lv.Set(value); err == nil { + showCallerLv = append(showCallerLv, lv) + } + } + } else { + showCallerLv = []zapcore.Level{zapcore.ErrorLevel, zapcore.PanicLevel} + } + level := zap.InfoLevel + if err := level.Set(c.Level); err != nil { + return nil, err + } + cfg := NewConfig(c) + options := []zap.Option{zap.WrapCore(func(core zapcore.Core) zapcore.Core { + if len(opts) > 0 && opts[0] != nil { + return opts[0] + } else { + c, _ := NewLogTraceLevelCore(core, zap.DebugLevel, c.CallerSkip, showCallerLv...) + return c + } + })} + if len(c.Output) > 0 && getWriter != nil { + err := CreatePath(c.Output) + if err != nil { + return nil, err + } + dailyRotate := 24 * time.Hour + byteSizeLog, err := c.MaxSize.GetByteSize() + if err != nil { + byteSizeLog = 0 + } + w, _ := getWriter(c.Output, dailyRotate, byteSizeLog) + syncer := zap.CombineWriteSyncers(os.Stdout, zapcore.AddSync(w)) + options = append(options, NewWriter(syncer, cfg)) + } + l, err := cfg.Build(options...) if err == nil { logger = l } @@ -82,6 +126,7 @@ func NewConfig(c Config) zap.Config { Thereafter: 100, }, Encoding: "json", + DisableCaller: true, EncoderConfig: NewEncoderConfig(c), OutputPaths: []string{"stderr"}, ErrorOutputPaths: []string{"stderr"}, @@ -95,6 +140,7 @@ func NewEncoderConfig(c Config) zapcore.EncoderConfig { c2.Level = c1.Level c2.Name = c1.Name c2.Caller = c1.Caller + c2.Function = c1.Function c2.Msg = c1.Msg c2.Stacktrace = c1.Stacktrace } @@ -110,22 +156,23 @@ func NewEncoderConfig(c Config) zapcore.EncoderConfig { if len(c2.Msg) == 0 { c2.Msg = "msg" } - if len(c2.Stacktrace) == 0 { - c2.Stacktrace = "stacktrace" - } - return zapcore.EncoderConfig{ + ec := zapcore.EncoderConfig{ TimeKey: c2.Time, LevelKey: c2.Level, NameKey: c2.Name, CallerKey: c2.Caller, + FunctionKey: c2.Function, MessageKey: c2.Msg, - StacktraceKey: c2.Stacktrace, LineEnding: zapcore.DefaultLineEnding, EncodeLevel: zapcore.LowercaseLevelEncoder, EncodeTime: zapcore.ISO8601TimeEncoder, EncodeDuration: zapcore.SecondsDurationEncoder, EncodeCaller: zapcore.ShortCallerEncoder, } + if len(c2.Stacktrace) > 0 { + ec.StacktraceKey = "stacktrace" + } + return ec } func Logger() *zap.Logger { @@ -396,6 +443,9 @@ func Panicf(ctx context.Context, format string, args ...interface{}) { func BuildLogFields(m map[string]interface{}) []zap.Field { fields := make([]zap.Field, 0) + if m == nil || len(m) == 0 { + return fields + } for k, v := range m { s, ok := v.(string) if ok { @@ -412,14 +462,20 @@ func BuildLogFields(m map[string]interface{}) []zap.Field { f3 := zap.Int64(k, i3) fields = append(fields, f3) } else { - i4, ok4 := v.(float64) + i4, ok4 := v.(map[string]interface{}) if ok4 { - f4 := zap.Float64(k, i4) + f4 := zap.Reflect(k, i4) fields = append(fields, f4) } else { - msg := fmt.Sprintf("%v", v) - f5 := zap.String(k, msg) - fields = append(fields, f5) + i5, ok5 := v.(float64) + if ok5 { + f5 := zap.Float64(k, i5) + fields = append(fields, f5) + } else { + msg := fmt.Sprintf("%v", v) + f6 := zap.String(k, msg) + fields = append(fields, f6) + } } } } @@ -531,3 +587,53 @@ func PanicFields(ctx context.Context, msg string, fields map[string]interface{}) func DPanicFields(ctx context.Context, msg string, fields map[string]interface{}) { DPanicWithFields(ctx, msg, fields) } + +func LogDebug(ctx context.Context, msg string, opts ...map[string]interface{}) { + if len(opts) > 0 { + DebugWithFields(ctx, msg, opts[0]) + } else { + DebugWithFields(ctx, msg, nil) + } +} +func LogInfo(ctx context.Context, msg string, opts ...map[string]interface{}) { + if len(opts) > 0 { + InfoWithFields(ctx, msg, opts[0]) + } else { + InfoWithFields(ctx, msg, nil) + } +} +func LogWarn(ctx context.Context, msg string, opts ...map[string]interface{}) { + if len(opts) > 0 { + WarnWithFields(ctx, msg, opts[0]) + } else { + WarnWithFields(ctx, msg, nil) + } +} +func LogError(ctx context.Context, msg string, opts ...map[string]interface{}) { + if len(opts) > 0 { + ErrorWithFields(ctx, msg, opts[0]) + } else { + ErrorWithFields(ctx, msg, nil) + } +} +func LogFatal(ctx context.Context, msg string, opts ...map[string]interface{}) { + if len(opts) > 0 { + FatalWithFields(ctx, msg, opts[0]) + } else { + FatalWithFields(ctx, msg, nil) + } +} +func LogPanic(ctx context.Context, msg string, opts ...map[string]interface{}) { + if len(opts) > 0 { + PanicWithFields(ctx, msg, opts[0]) + } else { + PanicWithFields(ctx, msg, nil) + } +} +func LogDPanic(ctx context.Context, msg string, opts ...map[string]interface{}) { + if len(opts) > 0 { + DPanicWithFields(ctx, msg, opts[0]) + } else { + DPanicWithFields(ctx, msg, nil) + } +}