diff --git a/README.md b/README.md index a44d741..cb3aa76 100644 --- a/README.md +++ b/README.md @@ -96,6 +96,15 @@ Implemented Jobs - CurlJob - FunctionJob +Logger interface. Any type that implements it can be used to log messages. +```go +type LoggerAdapter interface { + Log(msg string) +} +``` +Implemented LoggerAdapter +- StdoutLogger + ## Cron expression format | Field Name | Mandatory | Allowed Values | Allowed Special Characters | diff --git a/examples/custom_log_adapter.go b/examples/custom_log_adapter.go new file mode 100644 index 0000000..011512c --- /dev/null +++ b/examples/custom_log_adapter.go @@ -0,0 +1,35 @@ +package main + +import ( + "context" + "fmt" + "github.com/reugn/go-quartz/quartz" + "sync" + "time" +) + +type CustomLogger struct{} + +func (c *CustomLogger) Log(msg string) { + fmt.Printf("[CUSTOM LOGGER] %s", msg) +} + +func main() { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + wg := new(sync.WaitGroup) + wg.Add(1) + + go func(ctx context.Context, wg *sync.WaitGroup) { + defer wg.Done() + sched := quartz.NewStdSchedulerWithOptions(quartz.StdSchedulerOptions{}, &CustomLogger{}) + sched.Start(ctx) + sched.ScheduleJob(ctx, quartz.NewFunctionJob[int](func(ctx context.Context) (int, error) { + return 12, nil + }), quartz.NewRunOnceTrigger(time.Second)) + + time.Sleep(time.Second * 2) + }(ctx, wg) + + wg.Wait() +} diff --git a/quartz/log.go b/quartz/log.go new file mode 100644 index 0000000..10c954e --- /dev/null +++ b/quartz/log.go @@ -0,0 +1,16 @@ +package quartz + +import ( + "log" +) + +type LoggerAdapter interface { + Log(msg string) +} + +type StdoutLogger struct { +} + +func (s *StdoutLogger) Log(msg string) { + log.Println(msg) +} diff --git a/quartz/log_test.go b/quartz/log_test.go new file mode 100644 index 0000000..3b6adc0 --- /dev/null +++ b/quartz/log_test.go @@ -0,0 +1,23 @@ +package quartz_test + +import ( + "bytes" +) + +type BufferedLogger struct { + buffer *bytes.Buffer +} + +func (b *BufferedLogger) Log(msg string) { + b.buffer.WriteString(msg + "\n") +} + +func (b *BufferedLogger) GetContents() string { + return b.buffer.String() +} + +func NewBufferedLogger() *BufferedLogger { + return &BufferedLogger{ + buffer: new(bytes.Buffer), + } +} diff --git a/quartz/scheduler.go b/quartz/scheduler.go index 8a8d6e7..0414575 100644 --- a/quartz/scheduler.go +++ b/quartz/scheduler.go @@ -4,7 +4,7 @@ import ( "container/heap" "context" "errors" - "log" + "fmt" "sync" "time" ) @@ -64,6 +64,7 @@ type StdScheduler struct { dispatch chan *item started bool opts StdSchedulerOptions + logger LoggerAdapter } type StdSchedulerOptions struct { @@ -102,11 +103,15 @@ var _ Scheduler = (*StdScheduler)(nil) func NewStdScheduler() Scheduler { return NewStdSchedulerWithOptions(StdSchedulerOptions{ OutdatedThreshold: 100 * time.Millisecond, - }) + }, nil) } // NewStdSchedulerWithOptions returns a new StdScheduler configured as specified. -func NewStdSchedulerWithOptions(opts StdSchedulerOptions) *StdScheduler { +func NewStdSchedulerWithOptions(opts StdSchedulerOptions, logger LoggerAdapter) *StdScheduler { + if nil == logger { + logger = &StdoutLogger{} + } + return &StdScheduler{ queue: &priorityQueue{}, wg: &sync.WaitGroup{}, @@ -114,6 +119,7 @@ func NewStdSchedulerWithOptions(opts StdSchedulerOptions) *StdScheduler { feeder: make(chan *item), dispatch: make(chan *item), opts: opts, + logger: logger, } } @@ -244,7 +250,7 @@ func (sched *StdScheduler) Stop() { return } - log.Printf("Closing the StdScheduler.") + sched.logger.Log("Closing the StdScheduler.") sched.cancel() sched.started = false } @@ -256,7 +262,7 @@ func (sched *StdScheduler) startExecutionLoop(ctx context.Context) { select { case <-sched.interrupt: case <-ctx.Done(): - log.Printf("Exit the empty execution loop.") + sched.logger.Log("Exit the empty execution loop.") return } } else { @@ -269,7 +275,7 @@ func (sched *StdScheduler) startExecutionLoop(ctx context.Context) { t.Stop() case <-ctx.Done(): - log.Printf("Exit the execution loop.") + sched.logger.Log("Exit the execution loop.") t.Stop() return } @@ -354,7 +360,7 @@ func (sched *StdScheduler) executeAndReschedule(ctx context.Context) { // reschedule the Job nextRunTime, err := it.Trigger.NextFireTime(it.priority) if err != nil { - log.Printf("The Job '%s' got out the execution loop: %q", it.Job.Description(), err.Error()) + sched.logger.Log(fmt.Sprintf("The Job '%s' got out the execution loop: %q", it.Job.Description(), err.Error())) return } it.priority = nextRunTime @@ -377,7 +383,7 @@ func (sched *StdScheduler) startFeedReader(ctx context.Context) { sched.reset(ctx) }() case <-ctx.Done(): - log.Printf("Exit the feed reader.") + sched.logger.Log("Exit the feed reader.") return } } diff --git a/quartz/scheduler_test.go b/quartz/scheduler_test.go index e145307..f0fe24e 100644 --- a/quartz/scheduler_test.go +++ b/quartz/scheduler_test.go @@ -4,6 +4,7 @@ import ( "context" "net/http" "runtime" + "strings" "sync/atomic" "testing" "time" @@ -89,7 +90,7 @@ func TestSchedulerBlockingSemantics(t *testing.T) { opts.OutdatedThreshold = 10 * time.Millisecond - sched := quartz.NewStdSchedulerWithOptions(opts) + sched := quartz.NewStdSchedulerWithOptions(opts, nil) ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) defer cancel() sched.Start(ctx) @@ -259,3 +260,21 @@ func TestSchedulerCancel(t *testing.T) { }) } } + +func TestLogger(t *testing.T) { + ctx := context.Background() + logger := NewBufferedLogger() + sched := quartz.NewStdSchedulerWithOptions(quartz.StdSchedulerOptions{}, logger) + sched.Start(ctx) + sched.Stop() + + content := logger.GetContents() + + if len(content) == 0 { + t.Fatal("The log buffer is empty") + } + + if !strings.Contains(content, "Closing the StdScheduler") { + t.Fatal(`The buffer not contains a string "Closing the StdScheduler""`) + } +}