github.com/ngicks/gokugen@v0.0.5/middleware/log/log.go (about) 1 package log 2 3 //go:generate mockgen -source log.go -destination __mock/log.go 4 5 import ( 6 "context" 7 "encoding/json" 8 "errors" 9 "fmt" 10 "time" 11 12 "github.com/ngicks/gokugen" 13 ) 14 15 type Logger interface { 16 Info(v any, logValues ...string) 17 Error(e error, logValues ...string) 18 } 19 20 type LogMiddleware struct { 21 logger Logger 22 timeFormat string 23 shouldLogParam bool 24 additionalValues []any 25 } 26 27 type Option = func(mw *LogMiddleware) *LogMiddleware 28 29 func SetTimeFormat(timeFormat string) (Option, error) { 30 _, err := time.Parse(timeFormat, time.Now().Format(timeFormat)) 31 if err != nil { 32 return nil, err 33 } 34 35 return func(mw *LogMiddleware) *LogMiddleware { 36 mw.timeFormat = timeFormat 37 return mw 38 }, nil 39 } 40 41 func LogParam() Option { 42 return func(mw *LogMiddleware) *LogMiddleware { 43 mw.shouldLogParam = true 44 return mw 45 } 46 } 47 48 func LogAdditionalValues(keys ...any) Option { 49 return func(mw *LogMiddleware) *LogMiddleware { 50 mw.additionalValues = keys 51 return mw 52 } 53 } 54 55 func New(logger Logger, options ...Option) *LogMiddleware { 56 mw := &LogMiddleware{ 57 logger: logger, 58 timeFormat: time.RFC3339Nano, 59 } 60 for _, opt := range options { 61 mw = opt(mw) 62 } 63 if mw.additionalValues == nil { 64 mw.additionalValues = make([]any, 0) 65 } 66 return mw 67 } 68 69 func (mw *LogMiddleware) Middleware(handler gokugen.ScheduleHandlerFn) gokugen.ScheduleHandlerFn { 70 return func(ctx gokugen.SchedulerContext) (gokugen.Task, error) { 71 return handler( 72 gokugen.WrapContext( 73 ctx, 74 gokugen.WithWorkFnWrapper(wrapper(mw.logger, mw.buildLogValueSet)), 75 ), 76 ) 77 } 78 } 79 80 type logValueSetBuilder = func(ctx gokugen.SchedulerContext) (logValues []string) 81 82 func wrapper(logger Logger, logVal logValueSetBuilder) func(self gokugen.SchedulerContext, workFn gokugen.WorkFn) gokugen.WorkFn { 83 return func(self gokugen.SchedulerContext, workFn gokugen.WorkFn) gokugen.WorkFn { 84 return func(taskCtx context.Context, scheduled time.Time) (any, error) { 85 values := logVal(self) 86 logger.Info(nil, append(values, "timing", "before_work")...) 87 ret, err := workFn(taskCtx, scheduled) 88 values = append(values, "timing", "after_work") 89 if err != nil { 90 logger.Error(err, values...) 91 } else { 92 logger.Info(ret, values...) 93 } 94 return ret, err 95 } 96 } 97 } 98 99 func (mw *LogMiddleware) buildLogValueSet(ctx gokugen.SchedulerContext) (logValues []string) { 100 logValues = append(logValues, "scheduled_at", ctx.ScheduledTime().Format(time.RFC3339Nano)) 101 102 taskId, _ := gokugen.GetTaskId(ctx) 103 if taskId != "" { 104 logValues = append(logValues, "task_id", taskId) 105 } 106 workId, _ := gokugen.GetWorkId(ctx) 107 if workId != "" { 108 logValues = append(logValues, "work_id", workId) 109 } 110 111 if mw.shouldLogParam { 112 param, err := gokugen.GetParam(ctx) 113 if err == nil { 114 marshaled, err := json.Marshal(param) 115 if err == nil { 116 logValues = append(logValues, "param", string(marshaled)) 117 } else { 118 logValues = append(logValues, "param", "marshalling error") 119 } 120 } else if !errors.Is(err, gokugen.ErrValueNotFound) { 121 logValues = append(logValues, "param", "loading error") 122 } 123 } 124 125 for _, key := range mw.additionalValues { 126 val, err := ctx.Value(key) 127 keyLabel := fmt.Sprintf("%v", key) 128 if err != nil && !errors.Is(err, gokugen.ErrValueNotFound) { 129 logValues = append(logValues, keyLabel, "loading error") 130 } else { 131 marshaled, err := json.Marshal(val) 132 if err == nil { 133 logValues = append(logValues, keyLabel, string(marshaled)) 134 } else { 135 logValues = append(logValues, keyLabel, "marshalling error") 136 } 137 } 138 } 139 140 return 141 }