From dcf278fab6b237364d6e73a27c387c600b7c6090 Mon Sep 17 00:00:00 2001 From: Rizal Widyarta Gowandy Date: Mon, 11 Apr 2022 20:36:40 +0700 Subject: [PATCH 1/2] Lower Go Version Requirement --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 82a0cb5..090d2af 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/rizalgowandy/cronx -go 1.15 +go 1.14 require ( github.com/labstack/echo/v4 v4.5.0 From caf6eceb2330adef0b931fa6e3a98b4adffeb688 Mon Sep 17 00:00:00 2001 From: Rizal Widyarta Gowandy Date: Tue, 12 Apr 2022 13:13:10 +0700 Subject: [PATCH 2/2] Implement Option Pattern In order to inject more customize behaviour, implement option pattern. --- README.md | 110 +++++++++++++++++------------------------ cronx.go | 110 +++++++++++++++++++---------------------- cronx_test.go | 48 ++++++++---------- example/main.go | 94 +++++++++++------------------------ example/main_test.go | 31 ++---------- go.mod | 2 +- interceptor/recover.go | 14 +++--- job.go | 2 +- job_test.go | 4 +- option.go | 31 ++++++++++++ server_test.go | 12 ++--- 11 files changed, 198 insertions(+), 260 deletions(-) create mode 100644 option.go diff --git a/README.md b/README.md index 3ed9c26..1d32d10 100644 --- a/README.md +++ b/README.md @@ -82,7 +82,7 @@ func main() { interceptor.DefaultWorkerPool(), // Limit concurrent running job. ) - cronx.New(middleware) + cronx.New(cronx.WithInterceptor(middleware)) } ``` @@ -105,26 +105,6 @@ func Sleep() cronx.Interceptor { For more example check [here](interceptor). -## Custom Configuration - -```go -package main - -func main() { - // Create a cron with custom config. - cronx.NewManager(cronx.Config{ - Location: func() *time.Location { // Change timezone to Jakarta. - jakarta, err := time.LoadLocation("Asia/Jakarta") - if err != nil { - secondsEastOfUTC := int((7 * time.Hour).Seconds()) - jakarta = time.FixedZone("WIB", secondsEastOfUTC) - } - return jakarta - }(), - }) -} -``` - ## FAQ ### What are the available commands? @@ -157,26 +137,28 @@ Go to [here](interceptor) to see the available interceptors. ### Can I use my own router without starting the built-in router? -Yes, you can. This library is very modular. +Yes, you can. This library is very modular. Here's an example of using [gin](https://github.com/gin-gonic/gin). ```go package main -// Since we want to create custom HTTP server. -// Do not forget to shutdown the cron gracefully manually here. -manager := cronx.NewManager(cronx.Config{}) -defer manager.Stop() - -// An example using gin as the router. -r := gin.Default() -r.GET("/custom-path", func (c *gin.Context) { - c.JSON(http.StatusOK, manager.GetStatusJSON()) -}) +func main() { + // Since we want to create custom HTTP server. + // Do not forget to shutdown the cron gracefully manually here. + manager := cronx.NewManager() + defer manager.Stop() + + // An example using gin as the router. + r := gin.Default() + r.GET("/custom-path", func(c *gin.Context) { + c.JSON(http.StatusOK, manager.GetStatusJSON()) + }) -// Start your own server. -r.Run() + // Start your own server. + r.Run() +} ``` ### Can I still get the built-in template if I use my own router? @@ -186,21 +168,23 @@ Yes, you can. ```go package main -// Since we want to create custom HTTP server. -// Do not forget to shutdown the cron gracefully manually here. -manager := cronx.NewManager(cronx.Config{}) -defer manager.Stop() - -// GetStatusTemplate will return the built-in status page template. -index, _ := page.GetStatusTemplate() - -// An example using echo as the router. -e := echo.New() -index, _ := page.GetStatusTemplate() -e.GET("jobs", func (context echo.Context) error { - // Serve the template to the writer and pass the current status data. - return index.Execute(context.Response().Writer, manager.GetStatusData()) -}) +func main() { + // Since we want to create custom HTTP server. + // Do not forget to shutdown the cron gracefully manually here. + manager := cronx.NewManager() + defer manager.Stop() + + // GetStatusTemplate will return the built-in status page template. + index, _ := page.GetStatusTemplate() + + // An example using echo as the router. + e := echo.New() + index, _ := page.GetStatusTemplate() + e.GET("/jobs", func(context echo.Context) error { + // Serve the template to the writer and pass the current status data. + return index.Execute(context.Response().Writer, manager.GetStatusData()) + }) +} ``` ### Server is located in the US, but my user is in Jakarta, can I change the cron timezone? @@ -211,17 +195,17 @@ Yes, you can. By default, the cron timezone will follow the server location time package main func main() { + loc := func() *time.Location { // Change timezone to Jakarta. + jakarta, err := time.LoadLocation("Asia/Jakarta") + if err != nil { + secondsEastOfUTC := int((7 * time.Hour).Seconds()) + jakarta = time.FixedZone("WIB", secondsEastOfUTC) + } + return jakarta + }() + // Create a custom config. - cronx.NewManager(cronx.Config{ - Location: func() *time.Location { // Change timezone to Jakarta. - jakarta, err := time.LoadLocation("Asia/Jakarta") - if err != nil { - secondsEastOfUTC := int((7 * time.Hour).Seconds()) - jakarta = time.FixedZone("WIB", secondsEastOfUTC) - } - return jakarta - }(), - }) + cronx.NewManager(cronx.WithLocation(loc)) } ``` @@ -237,13 +221,9 @@ type subscription struct{} func (subscription) Run(ctx context.Context) error { md, ok := cronx.GetJobMetadata(ctx) if !ok { - return errors.New("cannot get job metadata") + return errors.New("cannot job metadata") } - - log.WithLevel(zerolog.InfoLevel). - Str("job", "subscription"). - Interface("metadata", md). - Msg("is running") + logx.INF(ctx, logx.KV{"job": fn.Name(), "metadata": md}, "subscription is running") return nil } ``` diff --git a/cronx.go b/cronx.go index be05acc..3925692 100644 --- a/cronx.go +++ b/cronx.go @@ -8,58 +8,56 @@ import ( "github.com/robfig/cron/v3" ) -var defaultConfig = Config{Location: time.Local} - -// Config defines the config for the manager. -type Config struct { - // Location describes the timezone current cron is running. - Location *time.Location -} - -// NewManager create a command controller with a specific config. -func NewManager(config Config, interceptors ...Interceptor) *Manager { - if config.Location == nil { - config.Location = defaultConfig.Location - } - +// Default configuration for the manager. +var ( // Support the v1 where the first parameter is second. - parser := cron.NewParser( + DefaultParser = cron.NewParser( cron.SecondOptional | cron.Minute | cron.Hour | cron.Dom | cron.Month | cron.Dow | cron.Descriptor, ) + DefaultInterceptors = Chain() + DefaultLocation = time.Local +) + +// NewManager create a command controller with a specific config. +func NewManager(opts ...Option) *Manager { + manager := &Manager{ + commander: nil, + interceptor: DefaultInterceptors, + parser: DefaultParser, + location: DefaultLocation, + createdTime: time.Now().In(DefaultLocation), + unregisteredJobs: nil, + } + for _, opt := range opts { + opt(manager) + } - // Create the commander. commander := cron.New( - cron.WithParser(parser), - cron.WithLocation(config.Location), + cron.WithParser(manager.parser), + cron.WithLocation(manager.location), ) commander.Start() - // Create command controller. - return &Manager{ - Commander: commander, - Interceptor: Chain(interceptors...), - Parser: parser, - UnregisteredJobs: nil, - Location: config.Location, - CreatedTime: time.Now().In(config.Location), - } + manager.commander = commander + manager.createdTime = time.Now().In(manager.location) + return manager } // Manager controls all the underlying job. type Manager struct { - // Commander holds all the underlying cron jobs. - Commander *cron.Cron - // Interceptor holds middleware that will be executed before current job operation. - Interceptor Interceptor - // Parser is a custom parser to support v1 that contains second as first parameter. - Parser cron.Parser - // UnregisteredJobs describes the list of jobs that have been failed to be registered. - UnregisteredJobs []*Job - // Location describes the timezone current cron is running. + // commander holds all the underlying cron jobs. + commander *cron.Cron + // interceptor holds middleware that will be executed before current job operation. + interceptor Interceptor + // parser is a custom parser to support v1 that contains second as first parameter. + parser cron.Parser + // location describes the timezone current cron is running. // By default the timezone will be the same timezone as the server. - Location *time.Location - // CreatedTime describes when the command controller created. - CreatedTime time.Time + location *time.Location + // createdTime describes when the command controller created. + createdTime time.Time + // unregisteredJobs describes the list of jobs that have been failed to be registered. + unregisteredJobs []*Job } // Schedule sets a job to run at specific time. @@ -96,38 +94,38 @@ func (m *Manager) Schedules(spec, separator string, job JobItf) error { func (m *Manager) schedule(spec string, job JobItf, waveNumber, totalWave int64) error { // Check if spec is correct. - schedule, err := m.Parser.Parse(spec) + schedule, err := m.parser.Parse(spec) if err != nil { downJob := NewJob(m, job, waveNumber, totalWave) downJob.Status = StatusCodeDown downJob.Error = err.Error() - m.UnregisteredJobs = append(m.UnregisteredJobs, downJob) + m.unregisteredJobs = append(m.unregisteredJobs, downJob) return err } j := NewJob(m, job, waveNumber, totalWave) - j.EntryID = m.Commander.Schedule(schedule, j) + j.EntryID = m.commander.Schedule(schedule, j) return nil } // Start starts jobs from running at the next scheduled time. func (m *Manager) Start() { - m.Commander.Start() + m.commander.Start() } // Stop stops active jobs from running at the next scheduled time. func (m *Manager) Stop() { - m.Commander.Stop() + m.commander.Stop() } // GetEntries returns all the current registered jobs. func (m *Manager) GetEntries() []cron.Entry { - return m.Commander.Entries() + return m.commander.Entries() } // GetEntry returns a snapshot of the given entry, or nil if it couldn't be found. func (m *Manager) GetEntry(id cron.EntryID) *cron.Entry { - entry := m.Commander.Entry(id) + entry := m.commander.Entry(id) return &entry } @@ -135,37 +133,33 @@ func (m *Manager) GetEntry(id cron.EntryID) *cron.Entry { // Get EntryID from the list job entries manager.GetEntries(). // If job is in the middle of running, once the process is finished it will be removed. func (m *Manager) Remove(id cron.EntryID) { - m.Commander.Remove(id) + m.commander.Remove(id) } // GetInfo returns command controller basic information. func (m *Manager) GetInfo() map[string]interface{} { - if m.Location == nil { - m.Location = defaultConfig.Location - } - - currentTime := time.Now().In(m.Location) + currentTime := time.Now().In(m.location) return map[string]interface{}{ "data": map[string]interface{}{ - "location": m.Location.String(), - "created_time": m.CreatedTime.String(), + "location": m.location.String(), + "created_time": m.createdTime.String(), "current_time": currentTime.String(), - "up_time": currentTime.Sub(m.CreatedTime).String(), + "up_time": currentTime.Sub(m.createdTime).String(), }, } } // GetStatusData returns all jobs status. func (m *Manager) GetStatusData() []StatusData { - if m.Commander == nil { + if m.commander == nil { return nil } - entries := m.Commander.Entries() + entries := m.commander.Entries() totalEntries := len(entries) - downs := m.UnregisteredJobs + downs := m.unregisteredJobs totalDowns := len(downs) totalJobs := totalEntries + totalDowns diff --git a/cronx_test.go b/cronx_test.go index 7299d35..a756d18 100644 --- a/cronx_test.go +++ b/cronx_test.go @@ -13,7 +13,6 @@ import ( func TestNewManager(t *testing.T) { type args struct { - config Config interceptors Interceptor } tests := []struct { @@ -27,7 +26,7 @@ func TestNewManager(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got := NewManager(tt.args.config, tt.args.interceptors) + got := NewManager(WithInterceptor(tt.args.interceptors)) assert.NotNil(t, got) }) } @@ -50,7 +49,7 @@ func TestManager_Schedule(t *testing.T) { spec: "this is clearly not a spec", job: Func(func(ctx context.Context) error { return nil }), mock: func() *Manager { - manager := NewManager(Config{}) + manager := NewManager() return manager }, }, @@ -62,7 +61,7 @@ func TestManager_Schedule(t *testing.T) { spec: "@every 5m", job: Func(func(ctx context.Context) error { return nil }), mock: func() *Manager { - manager := NewManager(Config{}) + manager := NewManager() return manager }, }, @@ -74,7 +73,7 @@ func TestManager_Schedule(t *testing.T) { spec: "0 */30 * * * *", job: Func(func(ctx context.Context) error { return nil }), mock: func() *Manager { - manager := NewManager(Config{}) + manager := NewManager() return manager }, }, @@ -86,7 +85,7 @@ func TestManager_Schedule(t *testing.T) { spec: "*/30 * * * *", job: Func(func(ctx context.Context) error { return nil }), mock: func() *Manager { - manager := NewManager(Config{}) + manager := NewManager() return manager }, }, @@ -171,7 +170,7 @@ func TestManager_Schedules(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - manager := NewManager(Config{}) + manager := NewManager() if err := manager.Schedules(tt.args.spec, tt.args.separator, tt.args.job); (err != nil) != tt.wantErr { t.Errorf("Schedules() error = %v, wantErr %v", err, tt.wantErr) } @@ -188,7 +187,7 @@ func TestManager_Start(t *testing.T) { { name: "Success", mock: func() *Manager { - manager := NewManager(Config{}) + manager := NewManager() _ = manager.Schedule("@every 5m", Func(func(ctx context.Context) error { return nil })) return manager }, @@ -212,7 +211,7 @@ func TestManager_Stop(t *testing.T) { { name: "Success", mock: func() *Manager { - manager := NewManager(Config{}) + manager := NewManager() _ = manager.Schedule("@every 5m", Func(func(ctx context.Context) error { return nil })) return manager }, @@ -236,7 +235,7 @@ func TestGetEntries(t *testing.T) { { name: "Success", mock: func() *Manager { - manager := NewManager(Config{}) + manager := NewManager() _ = manager.Schedule("@every 5m", Func(func(ctx context.Context) error { return nil })) return manager }, @@ -269,7 +268,7 @@ func TestManager_GetEntry(t *testing.T) { { name: "Success", mock: func() *Manager { - manager := NewManager(Config{}) + manager := NewManager() _ = manager.Schedule("@every 5m", Func(func(ctx context.Context) error { return nil })) return manager }, @@ -302,7 +301,7 @@ func TestManager_Remove(t *testing.T) { { name: "Success", mock: func() *Manager { - manager := NewManager(Config{}) + manager := NewManager() _ = manager.Schedule("@every 5m", Func(func(ctx context.Context) error { return nil })) return manager }, @@ -338,14 +337,7 @@ func TestManager_GetInfo(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - c := &Manager{ - Commander: tt.fields.Commander, - Interceptor: tt.fields.Interceptor, - Parser: tt.fields.Parser, - UnregisteredJobs: tt.fields.UnregisteredJobs, - Location: tt.fields.Location, - CreatedTime: tt.fields.CreatedTime, - } + c := NewManager() got := c.GetInfo() assert.NotNil(t, got) }) @@ -408,10 +400,10 @@ func TestManager_GetStatusData(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { c := &Manager{ - Commander: tt.fields.Commander, - Interceptor: tt.fields.Interceptor, - Parser: tt.fields.Parser, - UnregisteredJobs: tt.fields.UnregisteredJobs, + commander: tt.fields.Commander, + interceptor: tt.fields.Interceptor, + parser: tt.fields.Parser, + unregisteredJobs: tt.fields.UnregisteredJobs, } if got := c.GetStatusData(); !reflect.DeepEqual(got, tt.want) { t.Errorf("GetStatusData() = %v, want %v", got, tt.want) @@ -458,10 +450,10 @@ func TestManager_GetStatusJSON(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { c := &Manager{ - Commander: tt.fields.Commander, - Interceptor: tt.fields.Interceptor, - Parser: tt.fields.Parser, - UnregisteredJobs: tt.fields.UnregisteredJobs, + commander: tt.fields.Commander, + interceptor: tt.fields.Interceptor, + parser: tt.fields.Parser, + unregisteredJobs: tt.fields.UnregisteredJobs, } got := c.GetStatusJSON() assert.NotNil(t, got) diff --git a/example/main.go b/example/main.go index 3b9cf0e..0298498 100644 --- a/example/main.go +++ b/example/main.go @@ -7,44 +7,31 @@ import ( "github.com/rizalgowandy/cronx" "github.com/rizalgowandy/cronx/interceptor" "github.com/rizalgowandy/gdk/pkg/converter" - "github.com/rs/zerolog" - "github.com/rs/zerolog/log" + "github.com/rizalgowandy/gdk/pkg/errorx/v2" + "github.com/rizalgowandy/gdk/pkg/fn" + "github.com/rizalgowandy/gdk/pkg/logx" ) type sendEmail struct{} -func (s sendEmail) Run(context.Context) error { - log.WithLevel(zerolog.InfoLevel). - Str("job", "sendEmail"). - Msg("every 5 sec send reminder emails") +func (s sendEmail) Run(ctx context.Context) error { + logx.INF(ctx, logx.KV{"job": fn.Name()}, "every 5 sec send reminder emails") return nil } type payBill struct{} -func (p payBill) Run(context.Context) error { - log.WithLevel(zerolog.InfoLevel). - Str("job", "payBill"). - Msg("every 1 min pay bill") +func (p payBill) Run(ctx context.Context) error { + logx.INF(ctx, logx.KV{"job": fn.Name()}, "every 1 min pay bill") return nil } type alwaysError struct{} -func (a alwaysError) Run(context.Context) error { - log.WithLevel(zerolog.InfoLevel). - Str("job", "alwaysError"). - Msg("every 30 sec error") - return errors.New("some super long error message that come from executing the process") -} - -type everyJob struct{} - -func (everyJob) Run(context.Context) error { - log.WithLevel(zerolog.InfoLevel). - Str("job", "everyJob"). - Msg("is running") - return nil +func (a alwaysError) Run(ctx context.Context) error { + err := errorx.E("some super long error message that come from executing the process") + logx.ERR(ctx, err, "every 30 sec error") + return err } type subscription struct{} @@ -54,55 +41,48 @@ func (subscription) Run(ctx context.Context) error { if !ok { return errors.New("cannot job metadata") } - - log.WithLevel(zerolog.InfoLevel). - Str("job", "subscription"). - Interface("metadata", md). - Msg("is running") + logx.INF(ctx, logx.KV{"job": fn.Name(), "metadata": md}, "subscription is running") return nil } func main() { + ctx := logx.NewContext() + // Create middlewares. // The order is important. // The first one will be executed first. middlewares := cronx.Chain( + interceptor.RequestID, interceptor.Recover(), interceptor.Logger(), interceptor.DefaultWorkerPool(), ) // Create the manager with middleware. - manager := cronx.NewManager(cronx.Config{}, middlewares) + manager := cronx.NewManager(cronx.WithInterceptor(middlewares)) defer manager.Stop() // Register jobs. - RegisterJobs(manager) + RegisterJobs(ctx, manager) // =========================== // Start Main Server // =========================== server, err := cronx.NewServer(manager, ":9001") if err != nil { - log.WithLevel(zerolog.FatalLevel). - Err(err). - Msg("new server creation must success") + logx.FTL(ctx, errorx.E(err), "new server creation must success") return } if err := server.ListenAndServe(); err != nil { - log.WithLevel(zerolog.FatalLevel). - Err(err). - Msg("server listen and server must success") + logx.FTL(ctx, errorx.E(err), "server listen and server must success") } } -func RegisterJobs(manager *cronx.Manager) { +func RegisterJobs(ctx context.Context, manager *cronx.Manager) { // Struct name will become the name for the current job. if err := manager.Schedule("@every 5s", sendEmail{}); err != nil { // create log and send alert we fail to register job. - log.WithLevel(zerolog.ErrorLevel). - Err(err). - Msg("register sendEmail must success") + logx.ERR(ctx, errorx.E(err), "register sendEmail must success") } // Create some jobs with the same struct. @@ -110,9 +90,7 @@ func RegisterJobs(manager *cronx.Manager) { for i := 0; i < 3; i++ { spec := "@every " + converter.String(i+1) + "m" if err := manager.Schedule(spec, payBill{}); err != nil { - log.WithLevel(zerolog.ErrorLevel). - Err(err). - Msg("register payBill must success") + logx.ERR(ctx, errorx.E(err), "register payBill must success") } } @@ -120,43 +98,31 @@ func RegisterJobs(manager *cronx.Manager) { for i := 0; i < 3; i++ { spec := "broken spec " + converter.String(i+1) if err := manager.Schedule(spec, payBill{}); err != nil { - log.WithLevel(zerolog.ErrorLevel). - Err(err). - Msg("register payBill must success") + logx.ERR(ctx, errorx.E(err), "register payBill must success") } } // Create a job with run that will always be error. if err := manager.Schedule("@every 30s", alwaysError{}); err != nil { - log.WithLevel(zerolog.ErrorLevel). - Err(err). - Msg("register alwaysError must success") + logx.ERR(ctx, errorx.E(err), "register alwaysError must success") } // Create a custom job with missing name. if err := manager.Schedule("0 */1 * * *", cronx.Func(func(context.Context) error { - log.WithLevel(zerolog.InfoLevel). - Str("job", "nameless job"). - Msg("every 1h will be run") + logx.INF(ctx, logx.KV{"job": "nameless job"}, "every 1h will be run") return nil })); err != nil { - log.WithLevel(zerolog.ErrorLevel). - Err(err). - Msg("register job must success") + logx.ERR(ctx, errorx.E(err), "register job must success") } // Create a job with v1 specification that includes seconds. if err := manager.Schedule("0 0 1 * * *", subscription{}); err != nil { - log.WithLevel(zerolog.ErrorLevel). - Err(err). - Msg("register subscription must success") + logx.ERR(ctx, errorx.E(err), "register subscription must success") } // Create a job with multiple schedules if err := manager.Schedules("0 0 4 * * *#0 0 7 * * *#0 0 11 * * *", "#", subscription{}); err != nil { - log.WithLevel(zerolog.ErrorLevel). - Err(err). - Msg("register subscription must success") + logx.ERR(ctx, errorx.E(err), "register subscription must success") } // Remove a job. @@ -164,7 +130,5 @@ func RegisterJobs(manager *cronx.Manager) { manager.Remove(jobIDToBeRemoved) // Get all current registered job. - log.WithLevel(zerolog.InfoLevel). - Interface("entries", manager.GetEntries()). - Msg("current jobs") + logx.INF(ctx, logx.KV{"entries": manager.GetEntries()}, "current jobs") } diff --git a/example/main_test.go b/example/main_test.go index a023d5a..48b441c 100644 --- a/example/main_test.go +++ b/example/main_test.go @@ -5,6 +5,7 @@ import ( "testing" "github.com/rizalgowandy/cronx" + "github.com/rizalgowandy/gdk/pkg/logx" ) func Test_alwaysError_Run(t *testing.T) { @@ -32,31 +33,6 @@ func Test_alwaysError_Run(t *testing.T) { } } -func Test_everyJob_Run(t *testing.T) { - type args struct { - in0 context.Context - } - tests := []struct { - name string - args args - wantErr bool - }{ - { - name: "Success", - args: args{}, - wantErr: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - ev := everyJob{} - if err := ev.Run(tt.args.in0); (err != nil) != tt.wantErr { - t.Errorf("Run() error = %v, wantErr %v", err, tt.wantErr) - } - }) - } -} - func Test_subscription_Run(t *testing.T) { type args struct { in0 context.Context @@ -149,8 +125,9 @@ func TestRegisterJobs(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - manager := cronx.NewManager(cronx.Config{}) - RegisterJobs(manager) + ctx := logx.NewContext() + manager := cronx.NewManager() + RegisterJobs(ctx, manager) }) } } diff --git a/go.mod b/go.mod index 090d2af..09107ff 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,6 @@ require ( github.com/labstack/echo/v4 v4.5.0 github.com/rizalgowandy/gdk v0.3.0 github.com/robfig/cron/v3 v3.0.1 - github.com/rs/zerolog v1.26.1 + github.com/rs/zerolog v1.26.1 // indirect github.com/stretchr/testify v1.7.0 ) diff --git a/interceptor/recover.go b/interceptor/recover.go index cf8c414..cc0161a 100644 --- a/interceptor/recover.go +++ b/interceptor/recover.go @@ -5,9 +5,9 @@ import ( "runtime/debug" "github.com/rizalgowandy/cronx" + "github.com/rizalgowandy/gdk/pkg/errorx/v2" + "github.com/rizalgowandy/gdk/pkg/logx" "github.com/rizalgowandy/gdk/pkg/stack" - "github.com/rs/zerolog" - "github.com/rs/zerolog/log" ) // Recover is a middleware that recovers server from panic. @@ -16,11 +16,11 @@ func Recover() cronx.Interceptor { return func(ctx context.Context, job *cronx.Job, handler cronx.Handler) error { defer func() { if err := recover(); err != nil { - log.WithLevel(zerolog.PanicLevel). - Interface("err", err). - Interface("stack", stack.ToArr(stack.Trim(debug.Stack()))). - Interface("job", job). - Msg("recovered") + fields := errorx.Fields{ + "stack": stack.ToArr(stack.Trim(debug.Stack())), + "job": job, + } + logx.ERR(ctx, errorx.E(err, fields), "recovered") } }() diff --git a/job.go b/job.go index 214b7b0..0e1e27d 100644 --- a/job.go +++ b/job.go @@ -104,7 +104,7 @@ func (j *Job) Run() { j.UpdateStatus() // Run the job. - if err := j.manager.Interceptor(ctx, j, func(ctx context.Context, job *Job) error { + if err := j.manager.interceptor(ctx, j, func(ctx context.Context, job *Job) error { return job.inner.Run(ctx) }); err != nil { j.Error = err.Error() diff --git a/job_test.go b/job_test.go index e56b4d7..d28fa4e 100644 --- a/job_test.go +++ b/job_test.go @@ -39,7 +39,7 @@ func TestJob_Run(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - manager := NewManager(Config{}) + manager := NewManager() j := &Job{ manager: manager, Name: tt.fields.Name, @@ -139,7 +139,7 @@ func TestNewJob(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - manager := NewManager(Config{}) + manager := NewManager() got := NewJob(manager, tt.args.job, tt.args.waveNumber, tt.args.totalWave) t.Log(got) assert.NotNil(t, got) diff --git a/option.go b/option.go new file mode 100644 index 0000000..4e5b358 --- /dev/null +++ b/option.go @@ -0,0 +1,31 @@ +package cronx + +import ( + "time" + + "github.com/robfig/cron/v3" +) + +// Option represents a modification to the default behavior of the manager. +type Option func(*Manager) + +// WithLocation overrides the timezone of the cron instance. +func WithLocation(loc *time.Location) Option { + return func(m *Manager) { + m.location = loc + } +} + +// WithParser overrides the parser used for interpreting job schedules. +func WithParser(p cron.Parser) Option { + return func(m *Manager) { + m.parser = p + } +} + +// WithChain specifies Job wrappers to apply to all jobs added to this cron. +func WithInterceptor(interceptors ...Interceptor) Option { + return func(m *Manager) { + m.interceptor = Chain(interceptors...) + } +} diff --git a/server_test.go b/server_test.go index 4d3017e..3ff9432 100644 --- a/server_test.go +++ b/server_test.go @@ -26,8 +26,8 @@ func TestServerController_APIJobs(t *testing.T) { target: "/api/jobs", fields: fields{ Manager: &Manager{ - CreatedTime: time.Now(), - Location: time.Local, + createdTime: time.Now(), + location: time.Local, }, }, expect: http.StatusOK, @@ -67,8 +67,8 @@ func TestServerController_HealthCheck(t *testing.T) { target: "/", fields: fields{ Manager: &Manager{ - CreatedTime: time.Now(), - Location: time.Local, + createdTime: time.Now(), + location: time.Local, }, }, expect: http.StatusOK, @@ -108,8 +108,8 @@ func TestServerController_Jobs(t *testing.T) { target: "/jobs", fields: fields{ Manager: &Manager{ - CreatedTime: time.Now(), - Location: time.Local, + createdTime: time.Now(), + location: time.Local, }, }, expect: http.StatusOK,