├── .github ├── ISSUE_TEMPLATE │ ├── question.md │ ├── custom.md │ ├── feature_request.md │ └── bug_report.md ├── workflows │ ├── lint.yml │ └── build.yml ├── SECURITY.md ├── PULL_REQUEST_TEMPLATE.md ├── CODE_OF_CONDUCT.md └── CONTRIBUTING.md ├── .editorconfig ├── job ├── error.go ├── state.go ├── job_test.go └── job.go ├── .gitignore ├── jobmap.go ├── go.mod ├── state.go ├── .golangci.yml ├── examples ├── schedule-once │ ├── main.go │ └── README.md ├── schedule-fixed │ ├── main.go │ └── README.md ├── schedule-warn-expected │ ├── main.go │ └── README.md ├── schedule-cron │ ├── main.go │ └── README.md ├── schedule-panic │ ├── main.go │ └── README.md ├── schedule-console-metrics │ ├── main.go │ └── README.md ├── schedule-overlapping │ ├── main.go │ └── README.md ├── schedule-prom-metrics │ ├── main.go │ └── README.md ├── scheduler │ ├── main.go │ └── README.md ├── schedule-logs │ ├── main.go │ └── README.md ├── scheduler-extra-opts │ ├── main.go │ └── README.md └── schedule-four-mixed-timers │ ├── main.go │ └── README.md ├── LICENSE ├── log.go ├── logrus.go ├── scheduler.go ├── options.go ├── timer.go ├── metric.go ├── schedule.go ├── go.sum └── README.md /.github/ISSUE_TEMPLATE/question.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Question about: Ask a Question title: '' 3 | labels: 'question' assignees: '' 4 | 5 | --- 6 | 7 | Ask a question... 8 | 9 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/custom.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Custom issue template 3 | about: Describe this issue template's purpose here. 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | indent_size = 4 7 | indent_style = space 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.go] 12 | indent_style = tab 13 | 14 | [{Makefile, *.mk}] 15 | indent_style = tab 16 | 17 | [*.proto] 18 | indent_size = 2 19 | -------------------------------------------------------------------------------- /job/error.go: -------------------------------------------------------------------------------- 1 | package job 2 | 3 | //ErrorJobPanic Error returned when a Job panics 4 | type ErrorJobPanic struct { 5 | Message string 6 | } 7 | 8 | func (e ErrorJobPanic) Error() string { 9 | return e.Message 10 | } 11 | 12 | //ErrorJobStarted Error returned when a has already started. 13 | type ErrorJobStarted struct { 14 | Message string 15 | } 16 | 17 | func (e ErrorJobStarted) Error() string { 18 | return e.Message 19 | } 20 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | on: [ push, pull_request ] 2 | name: Linter 3 | jobs: 4 | lint: 5 | name: Lint project using GolangCI Lint 6 | runs-on: ubuntu-latest 7 | steps: 8 | - name: Check out code into the Go module directory 9 | uses: actions/checkout@v1 10 | 11 | - name: GolangCI-Lint Action 12 | uses: matoous/golangci-lint-action@v1.23.3 13 | with: 14 | config: .golangci.yml 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### General 2 | bin 3 | .env 4 | 5 | ### JetBrains template 6 | # User-specific stuff 7 | .idea 8 | 9 | 10 | ### Windows template 11 | # Windows thumbnail cache files 12 | Thumbs.db 13 | 14 | ### macOS template 15 | # General 16 | .DS_Store 17 | 18 | # Thumbnails 19 | ._* 20 | 21 | ### Go template 22 | # Binaries for programs and plugins 23 | *.exe 24 | *.exe~ 25 | *.dll 26 | *.so 27 | *.dylib 28 | 29 | # Test binary, built with `go test -c` 30 | *.test 31 | 32 | # Output of the go coverage tool, specifically when used with LiteIDE 33 | *.out 34 | 35 | ### VisualStudioCode template 36 | .vscode/* 37 | -------------------------------------------------------------------------------- /job/state.go: -------------------------------------------------------------------------------- 1 | package job 2 | 3 | //State Indicate the state of the Job 4 | type State int64 5 | 6 | const ( 7 | // NEW Job has just been created and hasn't started yet 8 | NEW State = iota 9 | // RUNNING Job started and is running. 10 | RUNNING 11 | // FINISHED Job started and finished processing. 12 | FINISHED 13 | // PANICKED Job started and finished but encountered a panic. 14 | PANICKED 15 | ) 16 | 17 | func (s State) String() string { 18 | switch s { 19 | case NEW: 20 | return "NEW" 21 | case RUNNING: 22 | return "RUNNING" 23 | case FINISHED: 24 | return "FINISHED" 25 | case PANICKED: 26 | return "PANICKED" 27 | default: 28 | return "UNKNOWN" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /jobmap.go: -------------------------------------------------------------------------------- 1 | package sched 2 | 3 | import ( 4 | "sync" 5 | 6 | "github.com/sherifabdlnaby/sched/job" 7 | ) 8 | 9 | type jobMap struct { 10 | jobs map[string]*job.Job 11 | mx sync.RWMutex 12 | } 13 | 14 | func newJobMap() *jobMap { 15 | return &jobMap{ 16 | jobs: make(map[string]*job.Job), 17 | } 18 | } 19 | 20 | func (jm *jobMap) add(j *job.Job) { 21 | jm.mx.Lock() 22 | defer jm.mx.Unlock() 23 | jm.jobs[j.ID()] = j 24 | } 25 | 26 | func (jm *jobMap) delete(j *job.Job) { 27 | jm.mx.Lock() 28 | defer jm.mx.Unlock() 29 | delete(jm.jobs, j.ID()) 30 | } 31 | 32 | func (jm *jobMap) len() int { 33 | jm.mx.RLock() 34 | defer jm.mx.RUnlock() 35 | return len(jm.jobs) 36 | } 37 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | Use this section to tell people about which versions of your project are currently being supported with security 6 | updates. 7 | 8 | | Version | Supported | 9 | | ------- | ------------------ | 10 | | 5.1.x | :white_check_mark: | 11 | | 5.0.x | :x: | 12 | | 4.0.x | :white_check_mark: | 13 | | < 4.0 | :x: | 14 | 15 | ## Reporting a Vulnerability 16 | 17 | Use this section to tell people how to report a vulnerability. 18 | 19 | Tell them where to go, how often they can expect to get an update on a reported vulnerability, what to expect if the 20 | vulnerability is accepted or declined, etc. 21 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | (Thanks for sending a pull request! Please make sure you click the link above to view the contribution guidelines, then 2 | fill out the blanks below.) 3 | 4 | What does this implement/fix? Explain your changes. 5 | --------------------------------------------------- 6 | … 7 | 8 | Does this close any currently open issues? 9 | ------------------------------------------ 10 | … 11 | 12 | 13 | Any relevant logs, error output, etc? 14 | ------------------------------------- 15 | (If it’s long, please paste to https://ghostbin.com/ and insert the link here.) 16 | 17 | Any other comments? 18 | ------------------- 19 | … 20 | 21 | Where has this been tested? 22 | --------------------------- 23 | … 24 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | on: [ push, pull_request ] 2 | name: Build 3 | jobs: 4 | test: 5 | strategy: 6 | matrix: 7 | go-version: [ 1.16.x, 1.15.x, 1.14.x, 1.13.x ] 8 | platform: [ ubuntu-latest, macos-latest, windows-latest ] 9 | runs-on: ${{ matrix.platform }} 10 | steps: 11 | - name: Install Go 12 | if: success() 13 | uses: actions/setup-go@v1 14 | with: 15 | go-version: ${{ matrix.go-version }} 16 | - name: Checkout code 17 | uses: actions/checkout@v1 18 | - name: Run tests 19 | run: go test -v -race -covermode=atomic 20 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/sherifabdlnaby/sched 2 | 3 | go 1.13 4 | 5 | require ( 6 | github.com/beorn7/perks v1.0.1 // indirect 7 | github.com/golang/protobuf v1.5.2 // indirect 8 | github.com/google/uuid v1.2.0 9 | github.com/gorhill/cronexpr v0.0.0-20180427100037-88b0669f7d75 10 | github.com/m3db/prometheus_client_golang v0.8.1 // indirect 11 | github.com/m3db/prometheus_client_model v0.1.0 // indirect 12 | github.com/m3db/prometheus_common v0.1.0 // indirect 13 | github.com/m3db/prometheus_procfs v0.8.1 // indirect 14 | github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect 15 | github.com/sirupsen/logrus v1.8.1 16 | github.com/uber-go/tally v3.3.17+incompatible 17 | go.uber.org/zap v1.16.0 18 | golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5 // indirect 19 | golang.org/x/tools v0.1.0 // indirect 20 | ) 21 | -------------------------------------------------------------------------------- /state.go: -------------------------------------------------------------------------------- 1 | package sched 2 | 3 | //State Indicate the state of the Schedule 4 | type State int64 5 | 6 | const ( 7 | //NEW Schedule has just been created and hasn't started before 8 | NEW State = iota 9 | 10 | // STARTED Start Schedule has started and is running. 11 | STARTED 12 | 13 | // STOPPING Schedule is Stopping and is waiting for all active jobs to finish. 14 | STOPPING 15 | 16 | // STOPPED Schedule has stopped and no longer scheduling new Jobs. 17 | STOPPED 18 | 19 | // FINISHED Schedule has finished, and will not be able to start again. 20 | FINISHED 21 | ) 22 | 23 | func (s State) String() string { 24 | switch s { 25 | case NEW: 26 | return "NEW" 27 | case STARTED: 28 | return "STARTED" 29 | case STOPPING: 30 | return "STOPPING" 31 | case STOPPED: 32 | return "STOPPED" 33 | case FINISHED: 34 | return "FINISHED" 35 | default: 36 | return "UNKNOWN" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | run: 2 | skip-dirs: 3 | - .gen 4 | 5 | skip-files: 6 | - ".*_gen\\.go$" 7 | 8 | linters-settings: 9 | gocyclo: 10 | min-complexity: 30 11 | goconst: 12 | min-len: 2 13 | min-occurrences: 2 14 | misspell: 15 | locale: US 16 | golint: 17 | min-confidence: 0.8 18 | 19 | linters: 20 | disable-all: true 21 | enable: 22 | - goconst 23 | - gocyclo 24 | - golint 25 | - govet 26 | - gofmt 27 | - errcheck 28 | - staticcheck 29 | - unused 30 | - gosimple 31 | - structcheck 32 | - varcheck 33 | - ineffassign 34 | - deadcode 35 | - unconvert 36 | - gosec 37 | - golint 38 | - bodyclose 39 | - misspell 40 | - unconvert 41 | - unparam 42 | - dogsled 43 | - goimports 44 | 45 | service: 46 | golangci-lint-version: 1.21.x 47 | -------------------------------------------------------------------------------- /examples/schedule-once/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/sherifabdlnaby/sched" 6 | "log" 7 | "os" 8 | "os/signal" 9 | "syscall" 10 | "time" 11 | ) 12 | 13 | func main() { 14 | 15 | fixedTimer5second, err := sched.NewFixed(30 * time.Second) 16 | if err != nil { 17 | panic(fmt.Sprintf("invalid interval: %s", err.Error())) 18 | } 19 | 20 | job := func() { 21 | log.Println("Doing some work...") 22 | time.Sleep(1 * time.Second) 23 | log.Println("Finished Work.") 24 | } 25 | 26 | // Create Schedule 27 | schedule := sched.NewSchedule("every30s", fixedTimer5second, job, sched.WithLogger(sched.DefaultLogger())) 28 | 29 | // Start Schedule 30 | schedule.Start() 31 | 32 | // Listen to CTRL + C And indefintly wait shutdown. 33 | signalChan := make(chan os.Signal, 1) 34 | signal.Notify(signalChan, syscall.SIGTERM, syscall.SIGINT, syscall.SIGQUIT) 35 | _ = <-signalChan 36 | 37 | // Stop before shutting down. 38 | schedule.Stop() 39 | 40 | return 41 | } 42 | -------------------------------------------------------------------------------- /examples/schedule-fixed/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/sherifabdlnaby/sched" 6 | "log" 7 | "os" 8 | "os/signal" 9 | "syscall" 10 | "time" 11 | ) 12 | 13 | func main() { 14 | 15 | fixedTimer30second, err := sched.NewFixed(30 * time.Second) 16 | if err != nil { 17 | panic(fmt.Sprintf("invalid interval: %s", err.Error())) 18 | } 19 | 20 | job := func() { 21 | log.Println("Doing some work...") 22 | time.Sleep(1 * time.Second) 23 | log.Println("Finished Work.") 24 | } 25 | 26 | // Create Schedule 27 | schedule := sched.NewSchedule("every30s", fixedTimer30second, job, sched.WithLogger(sched.DefaultLogger())) 28 | 29 | // Start Schedule 30 | schedule.Start() 31 | 32 | // Listen to CTRL + C And indefintly wait shutdown. 33 | signalChan := make(chan os.Signal, 1) 34 | signal.Notify(signalChan, syscall.SIGTERM, syscall.SIGINT, syscall.SIGQUIT) 35 | _ = <-signalChan 36 | 37 | // Stop before shutting down. 38 | schedule.Stop() 39 | 40 | return 41 | } 42 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /examples/schedule-warn-expected/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/sherifabdlnaby/sched" 6 | "log" 7 | "os" 8 | "os/signal" 9 | "syscall" 10 | "time" 11 | ) 12 | 13 | func main() { 14 | 15 | fixed2s, err := sched.NewFixed(2 * time.Second) 16 | if err != nil { 17 | panic(fmt.Sprintf("invalid interval: %s", err.Error())) 18 | } 19 | 20 | job := func() { 21 | log.Println("Doing some work...") 22 | time.Sleep(1 * time.Second) 23 | log.Println("Finished Work.") 24 | } 25 | 26 | // Create Schedule 27 | schedule := sched.NewSchedule("fixed2s", fixed2s, job, sched.WithLogger(sched.DefaultLogger()), sched.WithExpectedRunTime(500*time.Millisecond)) 28 | 29 | // Start Schedule 30 | schedule.Start() 31 | 32 | // Listen to CTRL + C And indefintly wait shutdown. 33 | signalChan := make(chan os.Signal, 1) 34 | signal.Notify(signalChan, syscall.SIGTERM, syscall.SIGINT, syscall.SIGQUIT) 35 | _ = <-signalChan 36 | 37 | // Stop before shutting down. 38 | schedule.Stop() 39 | 40 | return 41 | } 42 | -------------------------------------------------------------------------------- /examples/schedule-cron/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/sherifabdlnaby/sched" 6 | "log" 7 | "os" 8 | "os/signal" 9 | "syscall" 10 | "time" 11 | ) 12 | 13 | func main() { 14 | 15 | cronTimer, err := sched.NewCron("* * * * *") 16 | if err != nil { 17 | panic(fmt.Sprintf("invalid cron expression: %s", err.Error())) 18 | } 19 | 20 | job := func() { 21 | log.Println("Doing some work...") 22 | time.Sleep(1 * time.Second) 23 | log.Println("Finished Work.") 24 | } 25 | 26 | // Create Schedule 27 | schedule := sched.NewSchedule("cron", cronTimer, job, sched.WithLogger(sched.DefaultLogger())) 28 | 29 | // Start Schedule 30 | schedule.Start() 31 | 32 | // Stop schedule after 5 Minutes 33 | time.AfterFunc(5*time.Minute, func() { 34 | schedule.Stop() 35 | }) 36 | 37 | // Listen to CTRL + C 38 | signalChan := make(chan os.Signal, 1) 39 | signal.Notify(signalChan, syscall.SIGTERM, syscall.SIGINT, syscall.SIGQUIT) 40 | _ = <-signalChan 41 | 42 | // Stop before shutting down. 43 | schedule.Stop() 44 | 45 | return 46 | } 47 | -------------------------------------------------------------------------------- /examples/schedule-panic/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/sherifabdlnaby/sched" 6 | "log" 7 | "os" 8 | "os/signal" 9 | "syscall" 10 | "time" 11 | ) 12 | 13 | func main() { 14 | 15 | fixedTimer10second, err := sched.NewFixed(10 * time.Second) 16 | if err != nil { 17 | panic(fmt.Sprintf("invalid interval: %s", err.Error())) 18 | } 19 | 20 | job := func() { 21 | log.Println("Doing some work...") 22 | time.Sleep(1 * time.Second) 23 | 24 | panic("Oops, I panicked, we all do, sorry.") 25 | 26 | log.Println("Finished Work. (Shouldn't be printed)") 27 | } 28 | 29 | // Create Schedule 30 | schedule := sched.NewSchedule("every10s", fixedTimer10second, job, sched.WithLogger(sched.DefaultLogger())) 31 | 32 | // Start Schedule 33 | schedule.Start() 34 | 35 | // Listen to CTRL + C And indefintly wait shutdown. 36 | signalChan := make(chan os.Signal, 1) 37 | signal.Notify(signalChan, syscall.SIGTERM, syscall.SIGINT, syscall.SIGQUIT) 38 | _ = <-signalChan 39 | 40 | // Stop before shutting down. 41 | schedule.Stop() 42 | 43 | return 44 | } 45 | -------------------------------------------------------------------------------- /examples/schedule-console-metrics/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/sherifabdlnaby/sched" 6 | "log" 7 | "math/rand" 8 | "os" 9 | "os/signal" 10 | "syscall" 11 | "time" 12 | ) 13 | 14 | func main() { 15 | 16 | fixedEvery5s, err := sched.NewFixed(5 * time.Second) 17 | if err != nil { 18 | panic(fmt.Sprintf("invalid interval: %s", err.Error())) 19 | } 20 | 21 | job := func() { 22 | log.Println("Doing some work for random time...") 23 | time.Sleep(time.Duration(int(rand.Int63n(50)+1)*100) * time.Millisecond) 24 | log.Println("Finished Work.") 25 | } 26 | 27 | // Create Schedule 28 | schedule := sched.NewSchedule("every5s", fixedEvery5s, job, sched.WithLogger(sched.DefaultLogger()), 29 | sched.WithConsoleMetrics(20*time.Second)) 30 | 31 | // Start Schedule 32 | schedule.Start() 33 | 34 | // Listen to CTRL + C And indefintly wait shutdown. 35 | signalChan := make(chan os.Signal, 1) 36 | signal.Notify(signalChan, syscall.SIGTERM, syscall.SIGINT, syscall.SIGQUIT) 37 | _ = <-signalChan 38 | 39 | // Stop before shutting down. 40 | schedule.Stop() 41 | 42 | return 43 | } 44 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Sherif Abdel-Naby 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /examples/schedule-overlapping/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/sherifabdlnaby/sched" 6 | "log" 7 | "os" 8 | "os/signal" 9 | "syscall" 10 | "time" 11 | ) 12 | 13 | func main() { 14 | 15 | every1Sec, err := sched.NewFixed(1 * time.Second) 16 | if err != nil { 17 | panic(fmt.Sprintf("invalid interval: %s", err.Error())) 18 | } 19 | 20 | job := func() { 21 | log.Println("Doing some work for 5 seconds...") 22 | time.Sleep(5 * time.Second) 23 | log.Println("Finished Work.") 24 | } 25 | 26 | // Create Schedule 27 | schedule := sched.NewSchedule("every1Sec", every1Sec, job, sched.WithLogger(sched.DefaultLogger())) 28 | 29 | // Start Schedule 30 | schedule.Start() 31 | 32 | log.Println("Stopping schedule Automatically after 5 seconds") 33 | time.AfterFunc(5*time.Second, func() { 34 | schedule.Stop() 35 | }) 36 | 37 | // Listen to CTRL + C And indefintly wait shutdown. 38 | signalChan := make(chan os.Signal, 1) 39 | signal.Notify(signalChan, syscall.SIGTERM, syscall.SIGINT, syscall.SIGQUIT) 40 | _ = <-signalChan 41 | 42 | // Stop before shutting down. 43 | schedule.Stop() 44 | 45 | return 46 | } 47 | -------------------------------------------------------------------------------- /log.go: -------------------------------------------------------------------------------- 1 | package sched 2 | 3 | import ( 4 | "go.uber.org/zap" 5 | ) 6 | 7 | // Logger Sched logging interface similar to uber-go/zap, while keeping the option to change the logging implementation 8 | // It is a sub-interface of uber-go/zap SugaredLogger. 9 | type Logger interface { 10 | Debugw(msg string, keysAndValues ...interface{}) 11 | Errorw(msg string, keysAndValues ...interface{}) 12 | Fatalw(msg string, keysAndValues ...interface{}) 13 | Infow(msg string, keysAndValues ...interface{}) 14 | Panicw(msg string, keysAndValues ...interface{}) 15 | Warnw(msg string, keysAndValues ...interface{}) 16 | With(args ...interface{}) Logger 17 | Named(name string) Logger 18 | Sync() error 19 | } 20 | 21 | type logger struct { 22 | *zap.SugaredLogger 23 | } 24 | 25 | func (l logger) With(args ...interface{}) Logger { 26 | return logger{SugaredLogger: l.SugaredLogger.With(args...)} 27 | } 28 | 29 | func (l logger) Named(name string) Logger { 30 | return logger{SugaredLogger: l.SugaredLogger.Named(name)} 31 | } 32 | 33 | //DefaultLogger Return Default Sched Logger based on Zap's sugared logger 34 | func DefaultLogger() Logger { 35 | // TODO control verbosity 36 | loggerBase, _ := zap.NewDevelopment() 37 | sugarLogger := loggerBase.Sugar() 38 | return &logger{ 39 | sugarLogger, 40 | } 41 | } 42 | 43 | //NopLogger Return a No Op Logger that prints nothing. 44 | func NopLogger() Logger { 45 | loggerBase := zap.NewNop() 46 | sugarLogger := loggerBase.Sugar() 47 | return &logger{ 48 | sugarLogger, 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /examples/schedule-prom-metrics/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/sherifabdlnaby/sched" 6 | "github.com/uber-go/tally" 7 | "github.com/uber-go/tally/prometheus" 8 | "log" 9 | "math/rand" 10 | "net/http" 11 | "os" 12 | "os/signal" 13 | "syscall" 14 | "time" 15 | ) 16 | 17 | func main() { 18 | 19 | promReporter := prometheus.NewReporter(prometheus.Options{}) 20 | promMterics, closer := tally.NewRootScope(tally.ScopeOptions{ 21 | Tags: map[string]string{}, 22 | CachedReporter: promReporter, 23 | Separator: prometheus.DefaultSeparator, 24 | }, 1*time.Second) 25 | defer closer.Close() 26 | 27 | fixedEvery5s, err := sched.NewFixed(5 * time.Second) 28 | if err != nil { 29 | panic(fmt.Sprintf("invalid interval: %s", err.Error())) 30 | } 31 | 32 | job := func() { 33 | log.Println("Doing some work for random time...") 34 | time.Sleep(time.Duration(int(rand.Int63n(50)+1)*100) * time.Millisecond) 35 | log.Println("Finished Work.") 36 | } 37 | 38 | // Create Schedule 39 | schedule := sched.NewSchedule("every5s", fixedEvery5s, job, sched.WithLogger(sched.DefaultLogger()), 40 | sched.WithMetrics(promMterics)) 41 | 42 | // Start Schedule 43 | schedule.Start() 44 | 45 | // Star Prom Server 46 | http.Handle("/metrics", promReporter.HTTPHandler()) 47 | go http.ListenAndServe(":8080", nil) 48 | log.Println("Prometheus Metrics at :8080/metrics") 49 | 50 | // Listen to CTRL + C And indefintly wait shutdown. 51 | signalChan := make(chan os.Signal, 1) 52 | signal.Notify(signalChan, syscall.SIGTERM, syscall.SIGINT, syscall.SIGQUIT) 53 | _ = <-signalChan 54 | 55 | // Stop before shutting down. 56 | schedule.Stop() 57 | 58 | return 59 | } 60 | -------------------------------------------------------------------------------- /examples/scheduler/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/sherifabdlnaby/sched" 6 | "log" 7 | "os" 8 | "os/signal" 9 | "syscall" 10 | "time" 11 | ) 12 | 13 | func main() { 14 | 15 | job := func(id string) func() { 16 | return func() { 17 | log.Println(id + "\t Doing some work...") 18 | time.Sleep(1 * time.Second) 19 | log.Println(id + "\t Finished Work.") 20 | } 21 | } 22 | 23 | cronTimer, err := sched.NewCron("* * * * *") 24 | if err != nil { 25 | panic(fmt.Sprintf("invalid cron expression: %s", err.Error())) 26 | } 27 | 28 | cronTimer5, err := sched.NewCron("*/5 * * * *") 29 | if err != nil { 30 | panic(fmt.Sprintf("invalid cron expression: %s", err.Error())) 31 | } 32 | 33 | fixedTimer30second, err := sched.NewFixed(30 * time.Second) 34 | if err != nil { 35 | panic(fmt.Sprintf("invalid interval: %s", err.Error())) 36 | } 37 | 38 | onceAfter10s, err := sched.NewOnce(10 * time.Second) 39 | if err != nil { 40 | panic(fmt.Sprintf("invalid delay: %s", err.Error())) 41 | } 42 | 43 | // Create Schedule 44 | scheduler := sched.NewScheduler(sched.WithLogger(sched.DefaultLogger()), 45 | sched.WithConsoleMetrics(1*time.Minute)) 46 | 47 | _ = scheduler.Add("cronEveryMinute", cronTimer, job("every-minute-cron")) 48 | _ = scheduler.Add("cronEvery5Minute", cronTimer5, job("every-five-minute-cron")) 49 | _ = scheduler.Add("fixedTimer30second", fixedTimer30second, job("fixedEvery30Second")) 50 | _ = scheduler.Add("onceAfter10s", onceAfter10s, job("onceAfter10s")) 51 | 52 | scheduler.StartAll() 53 | 54 | // Listen to CTRL + C 55 | signalChan := make(chan os.Signal, 1) 56 | signal.Notify(signalChan, syscall.SIGTERM, syscall.SIGINT, syscall.SIGQUIT) 57 | _ = <-signalChan 58 | 59 | // Stop before shutting down. 60 | scheduler.StopAll() 61 | 62 | return 63 | } 64 | -------------------------------------------------------------------------------- /examples/schedule-logs/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/sherifabdlnaby/sched" 6 | "log" 7 | "os" 8 | "os/signal" 9 | "syscall" 10 | "time" 11 | ) 12 | 13 | func main() { 14 | 15 | job := func(id string) func() { 16 | return func() { 17 | log.Println(id + "\t Doing some work...") 18 | time.Sleep(1 * time.Second) 19 | log.Println(id + "\t Finished Work.") 20 | } 21 | } 22 | 23 | cronTimer, err := sched.NewCron("* * * * *") 24 | if err != nil { 25 | panic(fmt.Sprintf("invalid cron expression: %s", err.Error())) 26 | } 27 | 28 | cronTimer5, err := sched.NewCron("*/5 * * * *") 29 | if err != nil { 30 | panic(fmt.Sprintf("invalid cron expression: %s", err.Error())) 31 | } 32 | 33 | fixedTimer30second, err := sched.NewFixed(30 * time.Second) 34 | if err != nil { 35 | panic(fmt.Sprintf("invalid interval: %s", err.Error())) 36 | } 37 | 38 | onceAfter10s, err := sched.NewOnce(10 * time.Second) 39 | if err != nil { 40 | panic(fmt.Sprintf("invalid delay: %s", err.Error())) 41 | } 42 | 43 | // Create Schedule 44 | scheduler := sched.NewScheduler(sched.WithLogger(sched.LogrusDefaultLogger()), 45 | sched.WithConsoleMetrics(1*time.Minute)) 46 | 47 | _ = scheduler.Add("cronEveryMinute", cronTimer, job("every-minute-cron")) 48 | _ = scheduler.Add("cronEvery5Minute", cronTimer5, job("every-five-minute-cron")) 49 | _ = scheduler.Add("fixedTimer30second", fixedTimer30second, job("fixedEvery30Second")) 50 | _ = scheduler.Add("onceAfter10s", onceAfter10s, job("onceAfter10s")) 51 | 52 | scheduler.StartAll() 53 | 54 | // Listen to CTRL + C 55 | signalChan := make(chan os.Signal, 1) 56 | signal.Notify(signalChan, syscall.SIGTERM, syscall.SIGINT, syscall.SIGQUIT) 57 | _ = <-signalChan 58 | 59 | // Stop before shutting down. 60 | scheduler.StopAll() 61 | 62 | return 63 | } 64 | -------------------------------------------------------------------------------- /examples/scheduler-extra-opts/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/sherifabdlnaby/sched" 6 | "log" 7 | "os" 8 | "os/signal" 9 | "syscall" 10 | "time" 11 | ) 12 | 13 | func main() { 14 | 15 | job := func(id string) func() { 16 | return func() { 17 | log.Println(id + "\t Doing some work...") 18 | time.Sleep(1 * time.Second) 19 | log.Println(id + "\t Finished Work.") 20 | } 21 | } 22 | 23 | cronTimer, err := sched.NewCron("* * * * *") 24 | if err != nil { 25 | panic(fmt.Sprintf("invalid cron expression: %s", err.Error())) 26 | } 27 | 28 | cronTimer5, err := sched.NewCron("*/5 * * * *") 29 | if err != nil { 30 | panic(fmt.Sprintf("invalid cron expression: %s", err.Error())) 31 | } 32 | 33 | fixedTimer30second, err := sched.NewFixed(30 * time.Second) 34 | if err != nil { 35 | panic(fmt.Sprintf("invalid interval: %s", err.Error())) 36 | } 37 | 38 | onceAfter10s, err := sched.NewOnce(10 * time.Second) 39 | if err != nil { 40 | panic(fmt.Sprintf("invalid delay: %s", err.Error())) 41 | } 42 | 43 | // Create Schedule 44 | scheduler := sched.NewScheduler(sched.WithLogger(sched.DefaultLogger()), 45 | sched.WithConsoleMetrics(1*time.Minute)) 46 | 47 | _ = scheduler.Add("cronEveryMinute", cronTimer, job("every-minute-cron")) 48 | _ = scheduler.Add("cronEvery5Minute", cronTimer5, job("every-five-minute-cron")) 49 | _ = scheduler.Add("fixedTimer30second", fixedTimer30second, job("fixedEvery30Second"), sched.WithExpectedRunTime(500*time.Millisecond)) 50 | _ = scheduler.Add("onceAfter10s", onceAfter10s, job("onceAfter10s")) 51 | 52 | scheduler.StartAll() 53 | 54 | // Listen to CTRL + C 55 | signalChan := make(chan os.Signal, 1) 56 | signal.Notify(signalChan, syscall.SIGTERM, syscall.SIGINT, syscall.SIGQUIT) 57 | _ = <-signalChan 58 | 59 | // Stop before shutting down. 60 | scheduler.StopAll() 61 | 62 | return 63 | } 64 | -------------------------------------------------------------------------------- /examples/schedule-prom-metrics/README.md: -------------------------------------------------------------------------------- 1 | - Output with a job that run every 5s on a fixed schedule and run for a random amount of time no more than 5 S 2 | - Metrics at localhost:8080/metrics 3 | 4 | ## Output 5 | 6 | ``` 7 | # HELP sched_run_actual_elapsed_time sched_run_actual_elapsed_time summary 8 | # TYPE sched_run_actual_elapsed_time summary 9 | sched_run_actual_elapsed_time{id="every5s",quantile="0.5"} 0.203843151 10 | sched_run_actual_elapsed_time{id="every5s",quantile="0.75"} 1.104031623 11 | sched_run_actual_elapsed_time{id="every5s",quantile="0.95"} 1.104031623 12 | sched_run_actual_elapsed_time{id="every5s",quantile="0.99"} 1.104031623 13 | sched_run_actual_elapsed_time{id="every5s",quantile="0.999"} 1.104031623 14 | sched_run_actual_elapsed_time_sum{id="every5s"} 1.307874774 15 | sched_run_actual_elapsed_time_count{id="every5s"} 2 16 | # HELP sched_run_errors sched_run_errors counter 17 | # TYPE sched_run_errors counter 18 | sched_run_errors{id="every5s"} 0 19 | # HELP sched_run_exceed_expected_time sched_run_exceed_expected_time counter 20 | # TYPE sched_run_exceed_expected_time counter 21 | sched_run_exceed_expected_time{id="every5s"} 0 22 | # HELP sched_run_total_elapsed_time sched_run_total_elapsed_time summary 23 | # TYPE sched_run_total_elapsed_time summary 24 | sched_run_total_elapsed_time{id="every5s",quantile="0.5"} 0.203880714 25 | sched_run_total_elapsed_time{id="every5s",quantile="0.75"} 1.104065614 26 | sched_run_total_elapsed_time{id="every5s",quantile="0.95"} 1.104065614 27 | sched_run_total_elapsed_time{id="every5s",quantile="0.99"} 1.104065614 28 | sched_run_total_elapsed_time{id="every5s",quantile="0.999"} 1.104065614 29 | sched_run_total_elapsed_time_sum{id="every5s"} 1.307946328 30 | sched_run_total_elapsed_time_count{id="every5s"} 2 31 | # HELP sched_runs sched_runs counter 32 | # TYPE sched_runs counter 33 | sched_runs{id="every5s"} 2 34 | # HELP sched_runs_overlapping sched_runs_overlapping counter 35 | # TYPE sched_runs_overlapping counter 36 | sched_runs_overlapping{id="every5s"} 0 37 | # HELP sched_up sched_up gauge 38 | # TYPE sched_up gauge 39 | sched_up{id="every5s"} 1 40 | ``` 41 | -------------------------------------------------------------------------------- /examples/schedule-four-mixed-timers/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/sherifabdlnaby/sched" 6 | "log" 7 | "os" 8 | "os/signal" 9 | "syscall" 10 | "time" 11 | ) 12 | 13 | func main() { 14 | 15 | job := func(id string) func() { 16 | return func() { 17 | log.Println(id + "\t Doing some work...") 18 | time.Sleep(1 * time.Second) 19 | log.Println(id + "\t Finished Work.") 20 | } 21 | } 22 | 23 | cronTimer, err := sched.NewCron("* * * * *") 24 | if err != nil { 25 | panic(fmt.Sprintf("invalid cron expression: %s", err.Error())) 26 | } 27 | 28 | cronTimer5, err := sched.NewCron("*/5 * * * *") 29 | if err != nil { 30 | panic(fmt.Sprintf("invalid cron expression: %s", err.Error())) 31 | } 32 | 33 | fixedTimer30second, err := sched.NewFixed(30 * time.Second) 34 | if err != nil { 35 | panic(fmt.Sprintf("invalid interval: %s", err.Error())) 36 | } 37 | 38 | onceAfter10s, err := sched.NewOnce(10 * time.Second) 39 | if err != nil { 40 | panic(fmt.Sprintf("invalid delay: %s", err.Error())) 41 | } 42 | 43 | // Create Schedule 44 | schedule0 := sched.NewSchedule("cron-every-minute", cronTimer, job("job cron every minute"), sched.WithLogger(sched.DefaultLogger())) 45 | schedule1 := sched.NewSchedule("cron-every-5minute", cronTimer5, job("job cron every 5 minute"), sched.WithLogger(sched.DefaultLogger())) 46 | schedule2 := sched.NewSchedule("fixed-every-30seconds", fixedTimer30second, job("job every 30 seconds"), sched.WithLogger(sched.DefaultLogger())) 47 | schedule3 := sched.NewSchedule("once-after-10seconds", onceAfter10s, job("job once after 10 seconds"), sched.WithLogger(sched.DefaultLogger())) 48 | 49 | // Start Schedule 50 | schedule0.Start() 51 | schedule1.Start() 52 | schedule2.Start() 53 | schedule3.Start() 54 | 55 | // Listen to CTRL + C 56 | signalChan := make(chan os.Signal, 1) 57 | signal.Notify(signalChan, syscall.SIGTERM, syscall.SIGINT, syscall.SIGQUIT) 58 | _ = <-signalChan 59 | 60 | // Stop before shutting down. 61 | schedule0.Stop() 62 | schedule1.Stop() 63 | schedule2.Stop() 64 | schedule3.Stop() 65 | 66 | return 67 | } 68 | -------------------------------------------------------------------------------- /examples/schedule-panic/README.md: -------------------------------------------------------------------------------- 1 | # Output with a job that panics and is handled by schdeuler 2 | 3 | ## Output 4 | 5 | ```json 6 | 2021-04-10T13:06: 48.005+0200 INFO sched sched/schedule.go: 96 Job Schedule Started {"id": "every10s"} 7 | 2021-04-10T13:06: 48.005+0200 INFO sched sched/schedule.go: 168 Job Next Run Scheduled {"id": "every10s", "After": "10s", "At": "2021-04-10T13:06:58+02:00" 8 | } 9 | 2021-04-10T13: 06: 58.007+0200 INFO sched sched/schedule.go: 168 Job Next Run Scheduled {"id": "every10s", "After": "10s", "At": "2021-04-10T13:07:08+02:00"} 10 | 2021-04-10T13: 06:58.007+0200 INFO sched sched/schedule.go: 193 Job Run Starting {"id": "every10s", "Instance": "0f972951-37d0-44cf-bf9d-fa4a3e2ce0c4"} 11 | 2021/04/10 13: 06: 58 Doing some work... 12 | 2021-04-10T13: 06: 59.010+0200 ERROR sched sched/schedule.go:203 Job Error {"id": "every10s", "Instance": "0f972951-37d0-44cf-bf9d-fa4a3e2ce0c4", "Duration": "1.002s", "State": "PANICKED", "error": "job panicked: Oops, I panicked, we all do, sorry."} 13 | github.com/sherifabdlnaby/sched.(*Schedule).runJobInstance 14 | /Users/sherifabdlnaby/code/projects/sched/schedule.go: 203 15 | 2021-04-10T13:06: 59.010+0200 INFO sched sched/schedule.go: 208 Job Finished {"id": "every10s", "Instance": "0f972951-37d0-44cf-bf9d-fa4a3e2ce0c4", "Duration": "1.002s", "State": "PANICKED" 16 | } 17 | 2021-04-10T13: 07: 08.007+0200 INFO sched sched/schedule.go:168 Job Next Run Scheduled {"id": "every10s", "After": "10s", "At": "2021-04-10T13:07:18+02:00"} 18 | 2021-04-10T13:07: 08.007+0200 INFO sched sched/schedule.go: 193 Job Run Starting {"id": "every10s", "Instance": "122536fa-074a-42c8-912c-7eb1dd21e0da"} 19 | 2021/04/10 13: 07: 08 Doing some work... 20 | 2021-04-10T13: 07: 09.009+0200 ERROR sched sched/schedule.go: 203 Job Error {"id": "every10s", "Instance": "122536fa-074a-42c8-912c-7eb1dd21e0da", "Duration": "1.002s", "State": "PANICKED", "error": "job panicked: Oops, I panicked, we all do, sorry."} 21 | github.com/sherifabdlnaby/sched.(*Schedule).runJobInstance 22 | /Users/sherifabdlnaby/code/projects/sched/schedule.go: 203 23 | 2021-04-10T13: 07: 09.009+0200 INFO sched sched/schedule.go: 208 Job Finished {"id": "every10s", "Instance": "122536fa-074a-42c8-912c-7eb1dd21e0da", "Duration": "1.002s", "State": "PANICKED" 24 | } 25 | ... 26 | ... 27 | ... 28 | 29 | ``` 30 | -------------------------------------------------------------------------------- /job/job_test.go: -------------------------------------------------------------------------------- 1 | //nolint 2 | package job 3 | 4 | import ( 5 | "errors" 6 | "testing" 7 | "time" 8 | ) 9 | 10 | func TestErrorJobPanic_Error(t *testing.T) { 11 | want := "panic text" 12 | e := ErrorJobPanic{want} 13 | if got := e.Error(); got != want { 14 | t.Errorf("Error() = %v, want %v", got, want) 15 | } 16 | } 17 | 18 | func TestErrorJobStarted_Error(t *testing.T) { 19 | want := "panic text" 20 | e := ErrorJobPanic{want} 21 | if got := e.Error(); got != want { 22 | t.Errorf("Error() = %v, want %v", got, want) 23 | } 24 | } 25 | 26 | func TestJob_ActualElapsed(t *testing.T) { 27 | 28 | timeWait := 1 * time.Second 29 | j := NewJob(func() { 30 | time.Sleep(timeWait) 31 | }) 32 | 33 | j.Run() 34 | 35 | want := timeWait 36 | got := j.ActualElapsed().Round(1 * time.Second) 37 | if got != want { 38 | t.Errorf("Actual Elapsed Time not accurate, want %v, got %v", want, got) 39 | } 40 | } 41 | 42 | func TestJob_TotalElapsed(t *testing.T) { 43 | timeWait := 1 * time.Second 44 | 45 | j := NewJob(func() { 46 | time.Sleep(timeWait) 47 | }) 48 | time.Sleep(timeWait) 49 | 50 | j.Run() 51 | 52 | want := timeWait * 2 53 | got := j.TotalElapsed().Round(1 * time.Second) 54 | if got != want { 55 | t.Errorf("Total Elapsed Time not accurate, want %v, got %v", want, got) 56 | } 57 | } 58 | 59 | func TestJob_ID(t *testing.T) { 60 | want := "idxxx" 61 | j := &Job{ 62 | id: want, 63 | } 64 | if got := j.ID(); got != want { 65 | t.Errorf("ID() = %v, want %v", got, want) 66 | } 67 | } 68 | 69 | func TestJob_Run(t *testing.T) { 70 | 71 | receiveChan := make(chan string) 72 | receiveWant := "testx" 73 | j := NewJob(func() { 74 | receiveChan <- receiveWant 75 | }) 76 | 77 | go j.Run() 78 | 79 | select { 80 | case got := <-receiveChan: 81 | if got != receiveWant { 82 | t.Errorf("Job Run but got unexpcted result, want %v, got %v", receiveWant, got) 83 | } 84 | case <-time.After(5 * time.Second): 85 | t.Errorf("job didn't run [timeout]") 86 | } 87 | } 88 | 89 | func TestJob_RunPanicRecover(t *testing.T) { 90 | 91 | j := NewJob(func() { 92 | panic("panicked") 93 | }) 94 | 95 | err := j.Run() 96 | if err == nil { 97 | t.Error("Job panicked and returned no error.") 98 | return 99 | } 100 | 101 | ref := ErrorJobPanic{"example error"} 102 | 103 | if !errors.As(err, &ref) { 104 | t.Error("Job panicked and handled but returned different error type.") 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /logrus.go: -------------------------------------------------------------------------------- 1 | package sched 2 | 3 | import ( 4 | "github.com/sirupsen/logrus" 5 | ) 6 | 7 | type lruLogger struct { 8 | jl *logrus.Entry 9 | } 10 | 11 | func (l *lruLogger) Debugw(msg string, keysAndValues ...interface{}) { 12 | logger := l.jl 13 | for i := 0; i < len(keysAndValues); i++ { 14 | if i%2 == 0 { 15 | logger = logger.WithField(keysAndValues[i].(string), keysAndValues[i+1]) 16 | } 17 | } 18 | logger.Debug(msg) 19 | } 20 | func (l lruLogger) Errorw(msg string, keysAndValues ...interface{}) { 21 | logger := l.jl 22 | for i := 0; i < len(keysAndValues); i++ { 23 | if i%2 == 0 { 24 | logger = logger.WithField(keysAndValues[i].(string), keysAndValues[i+1]) 25 | } 26 | } 27 | logger.Error(msg) 28 | } 29 | func (l lruLogger) Fatalw(msg string, keysAndValues ...interface{}) { 30 | logger := l.jl 31 | for i := 0; i < len(keysAndValues); i++ { 32 | if i%2 == 0 { 33 | logger = logger.WithField(keysAndValues[i].(string), keysAndValues[i+1]) 34 | } 35 | } 36 | logger.Fatal(msg) 37 | } 38 | func (l lruLogger) Infow(msg string, keysAndValues ...interface{}) { 39 | logger := l.jl 40 | for i := 0; i < len(keysAndValues); i++ { 41 | if i%2 == 0 { 42 | logger = logger.WithField(keysAndValues[i].(string), keysAndValues[i+1]) 43 | } 44 | } 45 | logger.Info(msg) 46 | } 47 | func (l lruLogger) Panicw(msg string, keysAndValues ...interface{}) { 48 | logger := l.jl 49 | for i := 0; i < len(keysAndValues); i++ { 50 | if i%2 == 0 { 51 | logger = logger.WithField(keysAndValues[i].(string), keysAndValues[i+1]) 52 | } 53 | } 54 | logger.Panic(msg) 55 | } 56 | func (l lruLogger) Warnw(msg string, keysAndValues ...interface{}) { 57 | logger := l.jl 58 | for i := 0; i < len(keysAndValues); i++ { 59 | if i%2 == 0 { 60 | logger = logger.WithField(keysAndValues[i].(string), keysAndValues[i+1]) 61 | } 62 | } 63 | logger.Warn(msg) 64 | } 65 | func (l *lruLogger) With(args ...interface{}) Logger { 66 | for i := 0; i < len(args); i++ { 67 | if i%2 == 0 { 68 | l.jl = l.jl.WithField(args[i].(string), args[i+1]) 69 | } 70 | } 71 | return l 72 | } 73 | func (l lruLogger) Named(name string) Logger { 74 | logger := l.jl.WithField("From", name) 75 | return &lruLogger{jl: logger} 76 | } 77 | func (l *lruLogger) Sync() error { 78 | return nil 79 | } 80 | 81 | //LogrusDefaultLogger Return Logger based on logrus with new instance 82 | func LogrusDefaultLogger() Logger { 83 | // TODO control verbosity 84 | return &lruLogger{jl: logrus.NewEntry(logrus.New())} 85 | } 86 | 87 | //LogrusLogger Return Return Logger based on logrus with existing instance 88 | func LogrusLogger(log *logrus.Logger) Logger { 89 | // TODO control verbosity 90 | return &lruLogger{jl: logrus.NewEntry(log)} 91 | } 92 | -------------------------------------------------------------------------------- /scheduler.go: -------------------------------------------------------------------------------- 1 | package sched 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | ) 7 | 8 | // Scheduler manage one or more Schedule creating them using common options, enforcing unique IDs, and supply methods to 9 | // Start / Stop all schedule(s). 10 | type Scheduler struct { 11 | schedules map[string]*Schedule 12 | scheduleOpts []Option 13 | mx sync.RWMutex 14 | } 15 | 16 | //NewScheduler Creates new Scheduler, opt Options are applied to *every* schedule added and created by this scheduler. 17 | func NewScheduler(opts ...Option) *Scheduler { 18 | return &Scheduler{ 19 | schedules: make(map[string]*Schedule), 20 | scheduleOpts: opts, 21 | } 22 | } 23 | 24 | //Add Create a new schedule for` jobFunc func()` that will run according to `timer Timer` with the []Options of the Scheduler. 25 | func (s *Scheduler) Add(id string, timer Timer, job func(), extraOpts ...Option) error { 26 | s.mx.Lock() 27 | defer s.mx.Unlock() 28 | 29 | if _, ok := s.schedules[id]; ok { 30 | return fmt.Errorf("job with this id already exists") 31 | } 32 | 33 | // Create schedule 34 | schedule := NewSchedule(id, timer, job, append(s.scheduleOpts, extraOpts...)...) 35 | 36 | // Add to managed schedules 37 | s.schedules[id] = schedule 38 | 39 | return nil 40 | } 41 | 42 | //Start Start the Schedule with the given id. Return error if no Schedule with the given id exist. 43 | func (s *Scheduler) Start(id string) error { 44 | s.mx.Lock() 45 | defer s.mx.Unlock() 46 | 47 | // Find Schedule by id 48 | schedule, found := s.schedules[id] 49 | if !found { 50 | return fmt.Errorf("schdule with this id does not exit") 51 | } 52 | 53 | // Start it ¯\_(ツ)_/¯ 54 | schedule.Start() 55 | 56 | return nil 57 | } 58 | 59 | //StartAll Start All Schedules managed by the Scheduler 60 | func (s *Scheduler) StartAll() { 61 | s.mx.Lock() 62 | defer s.mx.Unlock() 63 | for _, schedule := range s.schedules { 64 | schedule.Start() 65 | } 66 | } 67 | 68 | //Stop Stop the Schedule with the given id. Return error if no Schedule with the given id exist. 69 | func (s *Scheduler) Stop(id string) error { 70 | s.mx.Lock() 71 | defer s.mx.Unlock() 72 | schedule, found := s.schedules[id] 73 | if !found { 74 | return fmt.Errorf("schdule with this id does not exit") 75 | } 76 | schedule.Stop() 77 | return nil 78 | } 79 | 80 | //StopAll Stops All Schedules managed by the Scheduler concurrently, but will block until ALL of them have stopped. 81 | func (s *Scheduler) StopAll() { 82 | s.mx.Lock() 83 | defer s.mx.Unlock() 84 | wg := sync.WaitGroup{} 85 | wg.Add(len(s.schedules)) 86 | for _, schedule := range s.schedules { 87 | go func(scheduleCpy *Schedule) { 88 | scheduleCpy.Stop() 89 | wg.Done() 90 | }(schedule) 91 | } 92 | wg.Wait() 93 | } 94 | -------------------------------------------------------------------------------- /options.go: -------------------------------------------------------------------------------- 1 | package sched 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/uber-go/tally" 7 | ) 8 | 9 | type options struct { 10 | logger Logger 11 | metricsScope tally.Scope 12 | expectedRunDuration time.Duration 13 | // ------------------ 14 | initDefaultScope bool 15 | defaultScopePrintEvery time.Duration 16 | } 17 | 18 | func defaultOptions() *options { 19 | logger := DefaultLogger() 20 | 21 | nopMetrics := tally.NoopScope 22 | 23 | return &options{ 24 | logger: logger, 25 | metricsScope: nopMetrics, 26 | } 27 | } 28 | 29 | // Option to customize schedule behavior, check the sched.With*() functions that implement Option interface for the 30 | // available options 31 | type Option interface { 32 | apply(*options) 33 | } 34 | 35 | type loggerOption struct { 36 | Logger Logger 37 | } 38 | 39 | func (l loggerOption) apply(opts *options) { 40 | opts.logger = l.Logger.Named("sched") 41 | } 42 | 43 | //WithLogger Use the supplied Logger as the logger. 44 | func WithLogger(logger Logger) Option { 45 | return loggerOption{Logger: logger} 46 | } 47 | 48 | type metricsOption struct { 49 | metricsScope tally.Scope 50 | 51 | // Indicate the usage of default console metrics scope. Metrics scope will be initialized later as it requires the 52 | // Logger() that is going to be used in this schedule. 53 | initConsoleMetrics bool 54 | defaultScopePrintEvery time.Duration 55 | } 56 | 57 | func (m metricsOption) apply(opts *options) { 58 | opts.metricsScope = m.metricsScope 59 | opts.initDefaultScope = m.initConsoleMetrics 60 | opts.defaultScopePrintEvery = m.defaultScopePrintEvery 61 | } 62 | 63 | // WithMetrics Supply a tally.Scope to expose schedule metrics with. Ex. uber-go/tally/prometheus scope to expose 64 | // schedule metrics via Prometheus endpoint. 65 | // Use WithConsoleMetrics() to supply a predefined metrics console reporter without the need to implement any 66 | // special metrics reporter scope. 67 | func WithMetrics(metricsScope tally.Scope) Option { 68 | return metricsOption{metricsScope: metricsScope, initConsoleMetrics: false, defaultScopePrintEvery: 0} 69 | } 70 | 71 | // WithConsoleMetrics a predefined console metrics reporter, uses the Logger interface of the schedule to print out 72 | // metrics logs. 73 | func WithConsoleMetrics(printEvery time.Duration) Option { 74 | return metricsOption{metricsScope: nil, initConsoleMetrics: true, defaultScopePrintEvery: printEvery} 75 | } 76 | 77 | type expectedRunTime struct { 78 | duration time.Duration 79 | } 80 | 81 | func (l expectedRunTime) apply(opts *options) { 82 | opts.expectedRunDuration = l.duration 83 | } 84 | 85 | //WithExpectedRunTime Use to indicate the expected Runtime ( Logs a warning and adds in metrics when it exceeds ) 86 | func WithExpectedRunTime(d time.Duration) Option { 87 | return expectedRunTime{duration: d} 88 | } 89 | -------------------------------------------------------------------------------- /job/job.go: -------------------------------------------------------------------------------- 1 | package job 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | "time" 7 | 8 | "github.com/google/uuid" 9 | ) 10 | 11 | //Job Wraps JobFun and provide: 12 | // 1. Creation, Start, and Finish Time 13 | // 2. Recover From Panics 14 | type Job struct { 15 | id string 16 | jobFunc func() 17 | createTime time.Time 18 | startTime time.Time 19 | finishTime time.Time 20 | state State 21 | mx sync.RWMutex 22 | } 23 | 24 | //State Return Job current state. 25 | func (j *Job) State() State { 26 | j.mx.RLock() 27 | defer j.mx.RUnlock() 28 | return j.state 29 | } 30 | 31 | //NewJobWithID Create new Job with the supplied Id. 32 | func NewJobWithID(id string, jobFunc func()) *Job { 33 | return &Job{ 34 | id: id, 35 | jobFunc: jobFunc, 36 | createTime: time.Now(), 37 | startTime: time.Time{}, 38 | finishTime: time.Time{}, 39 | state: NEW, 40 | } 41 | } 42 | 43 | //NewJob Create new Job, id is assigned a UUID instead. 44 | func NewJob(jobFunc func()) *Job { 45 | return NewJobWithID(uuid.New().String(), jobFunc) 46 | } 47 | 48 | //ID Return Job ID 49 | func (j *Job) ID() string { 50 | return j.id 51 | } 52 | 53 | //ActualElapsed Return the actual time of procession of Job. 54 | // Return -1 if job hasn't started yet. 55 | func (j *Job) ActualElapsed() time.Duration { 56 | j.mx.RLock() 57 | defer j.mx.RUnlock() 58 | 59 | if !j.startTime.IsZero() { 60 | if j.finishTime.IsZero() { 61 | return time.Since(j.startTime) 62 | } 63 | return j.finishTime.Sub(j.startTime) 64 | } 65 | return -1 66 | } 67 | 68 | //TotalElapsed Returns the total time between creation of object and finishing processing its job. 69 | // Return -1 if job hasn't started yet. 70 | func (j *Job) TotalElapsed() time.Duration { 71 | j.mx.RLock() 72 | defer j.mx.RUnlock() 73 | 74 | if !j.startTime.IsZero() { 75 | if j.finishTime.IsZero() { 76 | return time.Since(j.createTime) 77 | } 78 | return j.finishTime.Sub(j.createTime) 79 | } 80 | return -1 81 | } 82 | 83 | //Run Run the internal Job (synchronous) 84 | func (j *Job) Run() error { 85 | return j.run() 86 | } 87 | 88 | func (j *Job) run() (err error) { 89 | j.mx.Lock() 90 | if j.state != NEW { 91 | if j.state == RUNNING { 92 | err = ErrorJobStarted{Message: "job already started"} 93 | } else { 94 | err = ErrorJobStarted{Message: "job finished execution"} 95 | } 96 | j.mx.Unlock() 97 | return err 98 | } 99 | 100 | // Handle Panics and set correct state 101 | defer func() { 102 | j.mx.Lock() 103 | // TODO handle panics 104 | if r := recover(); r != nil { 105 | err = ErrorJobPanic{Message: fmt.Sprintf("job panicked: %v", r)} 106 | j.state = PANICKED 107 | } else { 108 | j.state = FINISHED 109 | } 110 | j.finishTime = time.Now() 111 | j.mx.Unlock() 112 | }() 113 | 114 | j.state = RUNNING 115 | j.startTime = time.Now() 116 | 117 | // Unlock State 118 | j.mx.Unlock() 119 | 120 | // Run Job 121 | j.jobFunc() 122 | 123 | return nil 124 | } 125 | -------------------------------------------------------------------------------- /timer.go: -------------------------------------------------------------------------------- 1 | package sched 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "github.com/gorhill/cronexpr" 8 | ) 9 | 10 | //Timer is an Interface for a Timer object that is used by a Schedule to determine when to run the next run of a job. 11 | // Timer need to implement the Next() method returning the time of the next Job run. Timer indicates that no jobs shall 12 | // be scheduled anymore by returning done == true. The `next time.Time` returned with `done bool` == true IS IGNORED. 13 | // Next() shall not return time in the past. Time in the past is reset to time.Now() at evaluation time in the scheduler. 14 | type Timer interface { 15 | Next() (next time.Time, done bool) 16 | } 17 | 18 | //Once A timer that run ONCE after an optional specific delay. 19 | type Once struct { 20 | delay time.Duration 21 | done bool 22 | } 23 | 24 | //NewOnce Return a timer that trigger ONCE after `d` delay as soon as Timer is inquired for the next Run. 25 | //Delay = 0 means the Timer return now(), aka as soon as time is inquired. 26 | func NewOnce(d time.Duration) (*Once, error) { 27 | if d < 0 { 28 | return nil, fmt.Errorf("invalid d, must be >= 0") 29 | } 30 | return &Once{ 31 | delay: d, 32 | }, nil 33 | } 34 | 35 | // NewOnceTime Return a timer that trigger ONCE at `t` time.Time. 36 | //If `t` is in the past at inquery time, timer will NOT run. 37 | func NewOnceTime(t time.Time) (*Once, error) { 38 | remaining := time.Until(t) 39 | if remaining < 0 { 40 | return &Once{ 41 | delay: remaining, 42 | done: true, 43 | }, nil 44 | } 45 | return &Once{ 46 | delay: remaining, 47 | }, nil 48 | } 49 | 50 | //Next Return Next Time OR a boolean indicating no more Next()(s) 51 | func (o *Once) Next() (time.Time, bool) { 52 | if !o.done { 53 | o.done = true 54 | return time.Now().Add(o.delay), false 55 | } 56 | return time.Time{}, o.done 57 | } 58 | 59 | //Fixed A Timer that fires at a fixed duration intervals 60 | type Fixed struct { 61 | duration time.Duration 62 | next time.Time 63 | } 64 | 65 | //NewFixed Returns Fixed Timer; A Timer that fires at a fixed duration intervals. 66 | func NewFixed(duration time.Duration) (*Fixed, error) { 67 | if duration < 0 { 68 | return nil, fmt.Errorf("invalid duration, must be >= 0") 69 | } 70 | return &Fixed{ 71 | duration: duration, 72 | next: time.Now().Add(duration), 73 | }, nil 74 | } 75 | 76 | //Next Return Next fire time. 77 | func (f *Fixed) Next() (time.Time, bool) { 78 | now := time.Now() 79 | if now.After(f.next) { 80 | f.next = f.next.Add(f.duration) 81 | } 82 | return f.next, false 83 | } 84 | 85 | //Cron A Timer that fires at according to a cron expression. 86 | //All expresion supported by `https://github.com/gorhill/cronexpr` are supported. 87 | type Cron struct { 88 | expression cronexpr.Expression 89 | } 90 | 91 | //NewCron returns a Timer that fires at according to a cron expression. 92 | //All expresion supported by `https://github.com/gorhill/cronexpr` are supported. 93 | func NewCron(cronExpression string) (*Cron, error) { 94 | expression, err := cronexpr.Parse(cronExpression) 95 | if err != nil { 96 | return nil, fmt.Errorf("cron expression invalid: %w", err) 97 | } 98 | return &Cron{expression: *expression}, nil 99 | } 100 | 101 | //Next Return Next fire time. 102 | func (c *Cron) Next() (time.Time, bool) { 103 | return c.expression.Next(time.Now()), false 104 | } 105 | -------------------------------------------------------------------------------- /metric.go: -------------------------------------------------------------------------------- 1 | package sched 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "github.com/uber-go/tally" 8 | ) 9 | 10 | type metrics struct { 11 | up tally.Gauge 12 | runs tally.Counter 13 | overlappingCount tally.Counter 14 | runActualElapsed tally.Timer 15 | runTotalElapsed tally.Timer 16 | runErrors tally.Counter 17 | runExceedExpected tally.Counter 18 | } 19 | 20 | func newMetrics(name string, metricsScope tally.Scope) *metrics { 21 | subScope := metricsScope.SubScope("sched") 22 | return &metrics{ 23 | up: subScope.Tagged(map[string]string{"id": name}).Gauge("up"), 24 | runs: subScope.Tagged(map[string]string{"id": name}).Counter("runs"), 25 | overlappingCount: subScope.Tagged(map[string]string{"id": name}).Counter("runs_overlapping"), 26 | runActualElapsed: subScope.Tagged(map[string]string{"id": name}).Timer("run_actual_elapsed_time"), 27 | runTotalElapsed: subScope.Tagged(map[string]string{"id": name}).Timer("run_total_elapsed_time"), 28 | runErrors: subScope.Tagged(map[string]string{"id": name}).Counter("run_errors"), 29 | runExceedExpected: subScope.Tagged(map[string]string{"id": name}).Counter("run_exceed_expected_time"), 30 | } 31 | } 32 | 33 | type consoleStatsReporter struct { 34 | logger Logger 35 | } 36 | 37 | func newConsoleStatsReporter(logger Logger) *consoleStatsReporter { 38 | return &consoleStatsReporter{logger: logger} 39 | } 40 | 41 | func (r *consoleStatsReporter) ReportCounter(name string, tags map[string]string, value int64) { 42 | r.logger.Infow(fmt.Sprintf("counter %s", name), "name", name, "value", value, "tags", tags) 43 | } 44 | 45 | func (r *consoleStatsReporter) ReportGauge(name string, tags map[string]string, value float64) { 46 | r.logger.Infow(fmt.Sprintf("gauge %s", name), "name", name, "value", value, "tags", tags) 47 | } 48 | 49 | func (r *consoleStatsReporter) ReportTimer(name string, tags map[string]string, interval time.Duration) { 50 | r.logger.Infow(fmt.Sprintf("timer %s", name), "name", name, "interval", interval, "tags", tags) 51 | } 52 | 53 | func (r *consoleStatsReporter) ReportHistogramValueSamples( 54 | name string, 55 | tags map[string]string, 56 | _ tally.Buckets, 57 | bucketLowerBound, 58 | bucketUpperBound float64, 59 | samples int64, 60 | ) { 61 | r.logger.Infow( 62 | fmt.Sprintf("histogram %s bucket", name), 63 | "name", 64 | name, 65 | "tags", 66 | tags, 67 | "lower", 68 | bucketLowerBound, 69 | "upper", 70 | bucketUpperBound, 71 | "samples", 72 | samples, 73 | ) 74 | } 75 | 76 | func (r *consoleStatsReporter) ReportHistogramDurationSamples( 77 | name string, 78 | tags map[string]string, 79 | _ tally.Buckets, 80 | bucketLowerBound, 81 | bucketUpperBound time.Duration, 82 | samples int64, 83 | ) { 84 | r.logger.Infow( 85 | fmt.Sprintf("histogram %s bucket", name), 86 | "name", 87 | name, 88 | "tags", 89 | tags, 90 | "lower", 91 | bucketLowerBound, 92 | "upper", 93 | bucketUpperBound, 94 | "samples", 95 | samples, 96 | ) 97 | } 98 | 99 | func (r *consoleStatsReporter) Capabilities() tally.Capabilities { 100 | return r 101 | } 102 | 103 | func (r *consoleStatsReporter) Reporting() bool { 104 | return true 105 | } 106 | 107 | func (r *consoleStatsReporter) Tagging() bool { 108 | return true 109 | } 110 | 111 | func (r *consoleStatsReporter) Flush() { 112 | _ = r.logger.Sync() 113 | } 114 | -------------------------------------------------------------------------------- /.github/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making 6 | participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, 7 | disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, 8 | socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. 9 | 10 | ## Our Standards 11 | 12 | Examples of behavior that contributes to creating a positive environment include: 13 | 14 | * Using welcoming and inclusive language 15 | * Being respectful of differing viewpoints and experiences 16 | * Gracefully accepting constructive criticism 17 | * Focusing on what is best for the community 18 | * Showing empathy towards other community members 19 | 20 | Examples of unacceptable behavior by participants include: 21 | 22 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 23 | * Trolling, insulting/derogatory comments, and personal or political attacks 24 | * Public or private harassment 25 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 26 | * Other conduct which could reasonably be considered inappropriate in a professional setting 27 | 28 | ## Our Responsibilities 29 | 30 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take 31 | appropriate and fair corrective action in response to any instances of unacceptable behavior. 32 | 33 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, 34 | issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any 35 | contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 36 | 37 | ## Scope 38 | 39 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the 40 | project or its community. Examples of representing a project or community include using an official project e-mail 41 | address, posting via an official social media account, or acting as an appointed representative at an online or offline 42 | event. Representation of a project may be further defined and clarified by project maintainers. 43 | 44 | ## Enforcement 45 | 46 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at 47 | sherifabdlnaby@gmail.com. All complaints will be reviewed and investigated and will result in a response that is deemed 48 | necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to 49 | the reporter of an incident. Further details of specific enforcement policies may be posted separately. 50 | 51 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent 52 | repercussions as determined by other members of the project's leadership. 53 | 54 | ## Attribution 55 | 56 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available 57 | at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 58 | 59 | [homepage]: https://www.contributor-covenant.org 60 | 61 | For answers to common questions about this code of conduct, see 62 | https://www.contributor-covenant.org/faq 63 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to make 6 | participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, 7 | disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, 8 | socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. 9 | 10 | ## Our Standards 11 | 12 | Examples of behavior that contributes to creating a positive environment include: 13 | 14 | * Using welcoming and inclusive language 15 | * Being respectful of differing viewpoints and experiences 16 | * Gracefully accepting constructive criticism 17 | * Focusing on what is best for the community 18 | * Showing empathy towards other community members 19 | 20 | Examples of unacceptable behavior by participants include: 21 | 22 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 23 | * Trolling, insulting/derogatory comments, and personal or political attacks 24 | * Public or private harassment 25 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 26 | * Other conduct which could reasonably be considered inappropriate in a professional setting 27 | 28 | ## Our Responsibilities 29 | 30 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take 31 | appropriate and fair corrective action in response to any instances of unacceptable behavior. 32 | 33 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, 34 | issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any 35 | contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 36 | 37 | ## Scope 38 | 39 | This Code of Conduct applies within all project spaces, and it also applies when an individual is representing the 40 | project or its community in public spaces. Examples of representing a project or community include using an official 41 | project e-mail address, posting via an official social media account, or acting as an appointed representative at an 42 | online or offline event. Representation of a project may be further defined and clarified by project maintainers. 43 | 44 | ## Enforcement 45 | 46 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team 47 | at [INSERT EMAIL ADDRESS]. All complaints will be reviewed and investigated and will result in a response that is deemed 48 | necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to 49 | the reporter of an incident. Further details of specific enforcement policies may be posted separately. 50 | 51 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent 52 | repercussions as determined by other members of the project's leadership. 53 | 54 | ## Attribution 55 | 56 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available 57 | at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 58 | 59 | [homepage]: https://www.contributor-covenant.org 60 | 61 | For answers to common questions about this code of conduct, see 62 | https://www.contributor-covenant.org/faq 63 | -------------------------------------------------------------------------------- /examples/schedule-cron/README.md: -------------------------------------------------------------------------------- 1 | # Output with a cron expression that run every minute 2 | 3 | ## Output for 3 minutes 4 | 5 | ```bash 6 | 2021-04-10T12:30: 13.132+0200 INFO sched sched/schedule.go: 96 Job Schedule Started {"id": "cron"} 7 | 2021-04-10T12:30: 13.132+0200 INFO sched sched/schedule.go: 168 Job Next Run Scheduled {"id": "cron", "After": "47s", "At": "2021-04-10T12:31:00+02:00"} 8 | 2021-04-10T12: 31: 00.000+0200 INFO sched sched/schedule.go: 168 Job Next Run Scheduled {"id": "cron", "After": "1m0s", "At": "2021-04-10T12:32:00+02:00"} 9 | 2021-04-10T12: 31:00.000+0200 INFO sched sched/schedule.go: 193 Job Run Starting {"id": "cron", "Instance": "8e1044ab-20b6-4acf-8a15-e06c0418522c"} 10 | 2021/04/10 12: 31: 00 Doing some work... 11 | 2021/04/10 12: 31: 01 Finished Work. 12 | 2021-04-10T12: 31: 01.001+0200 INFO sched sched/schedule.go: 208 Job Finished {"id": "cron", "Instance": "8e1044ab-20b6-4acf-8a15-e06c0418522c", "Duration": "1.001s", "State": "FINISHED"} 13 | 2021-04-10T12:32: 00.002+0200 INFO sched sched/schedule.go: 168 Job Next Run Scheduled {"id": "cron", "After": "1m0s", "At": "2021-04-10T12:33:00+02:00"} 14 | 2021-04-10T12: 32: 00.002+0200 INFO sched sched/schedule.go: 193 Job Run Starting {"id": "cron", "Instance": "baae94eb-f818-4b34-a1f4-45b521a360a1"} 15 | 2021/04/10 12: 32: 00 Doing some work... 16 | 2021/04/10 12: 32: 01 Finished Work. 17 | 2021-04-10T12:32: 01.005+0200 INFO sched sched/schedule.go: 208 Job Finished {"id": "cron", "Instance": "baae94eb-f818-4b34-a1f4-45b521a360a1", "Duration": "1.003s", "State": "FINISHED"} 18 | 2021-04-10T12: 33: 00.001+0200 INFO sched sched/schedule.go:168 Job Next Run Scheduled {"id": "cron", "After": "1m0s", "At": "2021-04-10T12:34:00+02:00"} 19 | 2021-04-10T12:33: 00.001+0200 INFO sched sched/schedule.go: 193 Job Run Starting {"id": "cron", "Instance": "71c8f0bf-3624-4a92-909c-b4149f3c62a3"} 20 | 2021/04/10 12: 33: 00 Doing some work... 21 | 2021/04/10 12: 33: 01 Finished Work. 22 | 2021-04-10T12: 33: 01.004+0200 INFO sched sched/schedule.go:208 Job Finished {"id": "cron", "Instance": "71c8f0bf-3624-4a92-909c-b4149f3c62a3", "Duration": "1.003s", "State": "FINISHED"} 23 | 24 | 25 | ``` 26 | 27 | ## Output With CTRL+C 28 | 29 | ```bash 30 | 2021-04-10T12:28: 45.591+0200 INFO sched sched/schedule.go: 96 Job Schedule Started {"id": "cron"} 31 | 2021-04-10T12:28: 45.592+0200 INFO sched sched/schedule.go: 168 Job Next Run Scheduled {"id": "cron", "After": "14s", "At": "2021-04-10T12:29:00+02:00"} 32 | 2021-04-10T12: 29: 00.000+0200 INFO sched sched/schedule.go: 168 Job Next Run Scheduled {"id": "cron", "After": "1m0s", "At": "2021-04-10T12:30:00+02:00"} 33 | 2021-04-10T12: 29:00.000+0200 INFO sched sched/schedule.go: 193 Job Run Starting {"id": "cron", "Instance": "786540f1-594b-44a0-9a66-7181619e38a6"} 34 | 2021/04/10 12: 29: 00 Doing some work... 35 | CTRL+C 36 | 2021-04-10T12: 29: 00.567+0200 INFO sched sched/schedule.go: 125 Stopping Schedule... {"id": "cron"} 37 | 2021-04-10T12: 29: 00.567+0200 INFO sched sched/schedule.go: 130 Waiting active jobs to finish... {"id": "cron"} 38 | 2021-04-10T12: 29: 00.567+0200 INFO sched sched/schedule.go: 171 Job Next Run Canceled {"id": "cron", "At": "2021-04-10T12:30:00+02:00"} 39 | 2021/04/10 12: 29: 01 Finished Work. 40 | 2021-04-10T12: 29:01.000+0200 INFO sched sched/schedule.go: 208 Job Finished {"id": "cron", "Instance": "786540f1-594b-44a0-9a66-7181619e38a6", "Duration": "1s", "State": "FINISHED"} 41 | 2021-04-10T12: 29: 01.000+0200 INFO sched sched/schedule.go: 133 Job Schedule Stopped {"id": "cron" } 42 | ``` 43 | -------------------------------------------------------------------------------- /examples/schedule-once/README.md: -------------------------------------------------------------------------------- 1 | # Output with a cron expression that run every minute 2 | 3 | ## Output for 3 minutes 4 | 5 | ```json 6 | 2021-04-10T12:30: 13.132+0200 INFO sched sched/schedule.go: 96 Job Schedule Started {"id": "cron"} 7 | 2021-04-10T12:30: 13.132+0200 INFO sched sched/schedule.go: 168 Job Next Run Scheduled {"id": "cron", "After": "47s", "At": "2021-04-10T12:31:00+02:00" 8 | } 9 | 2021-04-10T12: 31: 00.000+0200 INFO sched sched/schedule.go: 168 Job Next Run Scheduled {"id": "cron", "After": "1m0s", "At": "2021-04-10T12:32:00+02:00"} 10 | 2021-04-10T12: 31:00.000+0200 INFO sched sched/schedule.go: 193 Job Run Starting {"id": "cron", "Instance": "8e1044ab-20b6-4acf-8a15-e06c0418522c"} 11 | 2021/04/10 12: 31: 00 Doing some work... 12 | 2021/04/10 12: 31: 01 Finished Work. 13 | 2021-04-10T12: 31: 01.001+0200 INFO sched sched/schedule.go: 208 Job Finished {"id": "cron", "Instance": "8e1044ab-20b6-4acf-8a15-e06c0418522c", "Duration": "1.001s", "State": "FINISHED"} 14 | 2021-04-10T12:32: 00.002+0200 INFO sched sched/schedule.go: 168 Job Next Run Scheduled {"id": "cron", "After": "1m0s", "At": "2021-04-10T12:33:00+02:00" 15 | } 16 | 2021-04-10T12: 32: 00.002+0200 INFO sched sched/schedule.go: 193 Job Run Starting {"id": "cron", "Instance": "baae94eb-f818-4b34-a1f4-45b521a360a1"} 17 | 2021/04/10 12: 32: 00 Doing some work... 18 | 2021/04/10 12: 32: 01 Finished Work. 19 | 2021-04-10T12:32: 01.005+0200 INFO sched sched/schedule.go: 208 Job Finished {"id": "cron", "Instance": "baae94eb-f818-4b34-a1f4-45b521a360a1", "Duration": "1.003s", "State": "FINISHED" 20 | } 21 | 2021-04-10T12: 33: 00.001+0200 INFO sched sched/schedule.go:168 Job Next Run Scheduled {"id": "cron", "After": "1m0s", "At": "2021-04-10T12:34:00+02:00"} 22 | 2021-04-10T12:33: 00.001+0200 INFO sched sched/schedule.go: 193 Job Run Starting {"id": "cron", "Instance": "71c8f0bf-3624-4a92-909c-b4149f3c62a3"} 23 | 2021/04/10 12: 33: 00 Doing some work... 24 | 2021/04/10 12: 33: 01 Finished Work. 25 | 2021-04-10T12: 33: 01.004+0200 INFO sched sched/schedule.go:208 Job Finished {"id": "cron", "Instance": "71c8f0bf-3624-4a92-909c-b4149f3c62a3", "Duration": "1.003s", "State": "FINISHED"} 26 | 27 | 28 | ``` 29 | 30 | ## Output With CTRL+C 31 | 32 | ```json 33 | 2021-04-10T12:28: 45.591+0200 INFO sched sched/schedule.go: 96 Job Schedule Started {"id": "cron"} 34 | 2021-04-10T12:28: 45.592+0200 INFO sched sched/schedule.go: 168 Job Next Run Scheduled {"id": "cron", "After": "14s", "At": "2021-04-10T12:29:00+02:00" 35 | } 36 | 2021-04-10T12: 29: 00.000+0200 INFO sched sched/schedule.go: 168 Job Next Run Scheduled {"id": "cron", "After": "1m0s", "At": "2021-04-10T12:30:00+02:00"} 37 | 2021-04-10T12: 29:00.000+0200 INFO sched sched/schedule.go: 193 Job Run Starting {"id": "cron", "Instance": "786540f1-594b-44a0-9a66-7181619e38a6"} 38 | 2021/04/10 12: 29: 00 Doing some work... 39 | CTRL+C 40 | 2021-04-10T12: 29: 00.567+0200 INFO sched sched/schedule.go: 125 Stopping Schedule... {"id": "cron"} 41 | 2021-04-10T12: 29: 00.567+0200 INFO sched sched/schedule.go: 130 Waiting active jobs to finish... {"id": "cron" 42 | } 43 | 2021-04-10T12: 29: 00.567+0200 INFO sched sched/schedule.go: 171 Job Next Run Canceled {"id": "cron", "At": "2021-04-10T12:30:00+02:00" 44 | } 45 | 2021/04/10 12: 29: 01 Finished Work. 46 | 2021-04-10T12: 29:01.000+0200 INFO sched sched/schedule.go: 208 Job Finished {"id": "cron", "Instance": "786540f1-594b-44a0-9a66-7181619e38a6", "Duration": "1s", "State": "FINISHED" 47 | } 48 | 2021-04-10T12: 29: 01.000+0200 INFO sched sched/schedule.go: 133 Job Schedule Stopped { 49 | "id": "cron" 50 | } 51 | ``` 52 | -------------------------------------------------------------------------------- /examples/schedule-overlapping/README.md: -------------------------------------------------------------------------------- 1 | # Output with a job that run every 30s on a fixed schedule 2 | 3 | ## Output 4 | 5 | ```json 6 | 2021-04-10T13:35: 42.078+0200 INFO sched sched/schedule.go: 97 Job Schedule Started {"id": "every1Sec"} 7 | 2021/04/10 13: 35: 42 Stopping schedule Automatically after 5 seconds 8 | 2021-04-10T13: 35: 42.078+0200 INFO sched sched/schedule.go: 172 Job Next Run Scheduled {"id": "every1Sec", "After": "1s", "At": "2021-04-10T13:35:43+02:00" 9 | } 10 | 2021-04-10T13: 35: 43.081+0200 INFO sched sched/schedule.go:172 Job Next Run Scheduled {"id": "every1Sec", "After": "1s", "At": "2021-04-10T13:35:44+02:00"} 11 | 2021-04-10T13:35: 43.081+0200 INFO sched sched/schedule.go: 197 Job Run Starting {"id": "every1Sec", "Instance": "12e1c9f3-69a7-4a31-8082-7dd40416f00b"} 12 | 2021/04/10 13: 35: 43 Doing some work for 5 seconds... 13 | 2021-04-10T13: 35: 44.082+0200 INFO sched sched/schedule.go: 172 Job Next Run Scheduled {"id": "every1Sec", "After": "1s", "At": "2021-04-10T13:35:45+02:00" 14 | } 15 | 2021-04-10T13: 35: 44.082+0200 INFO sched sched/schedule.go: 197 Job Run Starting {"id": "every1Sec", "Instance": "46ccdc9c-1792-4fc6-8e7b-aa269ea8b5a0" 16 | } 17 | 2021/04/10 13:35: 44 Doing some work for 5 seconds... 18 | 2021-04-10T13: 35: 45.078+0200 INFO sched sched/schedule.go:172 Job Next Run Scheduled {"id": "every1Sec", "After": "1s", "At": "2021-04-10T13:35:46+02:00"} 19 | 2021-04-10T13:35: 45.078+0200 INFO sched sched/schedule.go: 197 Job Run Starting {"id": "every1Sec", "Instance": "4c663f7a-40a8-47a1-8eaa-4ed52cc80a94"} 20 | 2021/04/10 13: 35: 45 Doing some work for 5 seconds... 21 | 2021-04-10T13: 35: 46.079+0200 INFO sched sched/schedule.go: 172 Job Next Run Scheduled {"id": "every1Sec", "After": "1s", "At": "2021-04-10T13:35:47+02:00" 22 | } 23 | 2021-04-10T13: 35: 46.079+0200 INFO sched sched/schedule.go: 197 Job Run Starting {"id": "every1Sec", "Instance": "22a192be-2d42-4840-bbfe-05e28bee1716" 24 | } 25 | 2021/04/10 13:35: 46 Doing some work for 5 seconds... 26 | 2021-04-10T13: 35: 47.080+0200 INFO sched sched/schedule.go:172 Job Next Run Scheduled {"id": "every1Sec", "After": "1s", "At": "2021-04-10T13:35:48+02:00"} 27 | 2021-04-10T13:35: 47.080+0200 INFO sched sched/schedule.go: 126 Stopping Schedule... {"id": "every1Sec"} 28 | 2021-04-10T13: 35: 47.080+0200 INFO sched sched/schedule.go: 132 Waiting for '5' active jobs still running... {"id": "every1Sec"} 29 | 2021-04-10T13: 35: 47.080+0200 INFO sched sched/schedule.go: 197 Job Run Starting {"id": "every1Sec", "Instance": "08cb1143-c0cc-436c-a7e6-097b0ef4cbf0" 30 | } 31 | 2021-04-10T13: 35: 47.080+0200 INFO sched sched/schedule.go: 175 Job Next Run Canceled {"id": "every1Sec", "At": "2021-04-10T13:35:48+02:00"} 32 | 2021/04/10 13: 35: 47 Doing some work for 5 seconds... 33 | 2021/04/10 13: 35: 48 Finished Work. 34 | 2021-04-10T13: 35: 48.082+0200 INFO sched sched/schedule.go: 212 Job Finished {"id": "every1Sec", "Instance": "12e1c9f3-69a7-4a31-8082-7dd40416f00b", "Duration": "5.001s", "State": "FINISHED"} 35 | 2021/04/10 13: 35: 49 Finished Work. 36 | 2021-04-10T13: 35: 49.086+0200 INFO sched sched/schedule.go:212 Job Finished {"id": "every1Sec", "Instance": "46ccdc9c-1792-4fc6-8e7b-aa269ea8b5a0", "Duration": "5.004s", "State": "FINISHED"} 37 | 2021/04/10 13: 35: 50 Finished Work. 38 | 2021-04-10T13:35: 50.081+0200 INFO sched sched/schedule.go: 212 Job Finished {"id": "every1Sec", "Instance": "4c663f7a-40a8-47a1-8eaa-4ed52cc80a94", "Duration": "5.003s", "State": "FINISHED" 39 | } 40 | 2021/04/10 13: 35: 51 Finished Work. 41 | 2021-04-10T13: 35: 51.084+0200 INFO sched sched/schedule.go: 212 Job Finished {"id": "every1Sec", "Instance": "22a192be-2d42-4840-bbfe-05e28bee1716", "Duration": "5.005s", "State": "FINISHED"} 42 | 2021/04/10 13: 35: 52 Finished Work. 43 | 2021-04-10T13: 35:52.080+0200 INFO sched sched/schedule.go: 212 Job Finished {"id": "every1Sec", "Instance": "08cb1143-c0cc-436c-a7e6-097b0ef4cbf0", "Duration": "5s", "State": "FINISHED" 44 | } 45 | 2021-04-10T13: 35: 52.080+0200 INFO sched sched/schedule.go: 137 Job Schedule Stopped { 46 | "id": "every1Sec" 47 | } 48 | 49 | ``` 50 | -------------------------------------------------------------------------------- /examples/schedule-warn-expected/README.md: -------------------------------------------------------------------------------- 1 | # Output with a job that run every 2s, takes 1s to run, and expected is 500ms. 2 | 3 | Notice the WARN logs. 4 | 5 | ## Output 6 | 7 | ```json 8 | 2021-04-10T13:57:59.558+0200 INFO sched sched/schedule.go:101 Job Schedule Started {"id": "fixed2s"} 9 | 2021-04-10T13:57:59.558+0200 INFO sched sched/schedule.go:176 Job Next Run Scheduled {"id": "fixed2s", "After": "2s", "At": "2021-04-10T13:58:01+02:00"} 10 | 2021-04-10T13:58:01.558+0200 INFO sched sched/schedule.go:176 Job Next Run Scheduled {"id": "fixed2s", "After": "2s", "At": "2021-04-10T13:58:03+02:00"} 11 | 2021-04-10T13:58:01.558+0200 INFO sched sched/schedule.go:203 Job Run Starting {"id": "fixed2s", "Instance": "4cb6b448-5b93-4689-a129-5448297e727e"} 12 | 2021/04/10 13:58:01 Doing some work... 13 | 2021-04-10T13:58:02.060+0200 WARN sched sched/schedule.go:211 Job Run Exceeded Expected Time {"id": "fixed2s", "Instance": "4cb6b448-5b93-4689-a129-5448297e727e", "Expected": "1s"} 14 | github.com/sherifabdlnaby/sched.(*Schedule).runJobInstance.func1 15 | /Users/sherifabdlnaby/code/projects/sched/schedule.go:211 16 | 2021/04/10 13:58:02 Finished Work. 17 | 2021-04-10T13:58:02.560+0200 INFO sched sched/schedule.go:229 Job Finished {"id": "fixed2s", "Instance": "4cb6b448-5b93-4689-a129-5448297e727e", "Duration": "1.002s", "State": "FINISHED"} 18 | 2021-04-10T13:58:03.561+0200 INFO sched sched/schedule.go:176 Job Next Run Scheduled {"id": "fixed2s", "After": "2s", "At": "2021-04-10T13:58:05+02:00"} 19 | 2021-04-10T13:58:03.561+0200 INFO sched sched/schedule.go:203 Job Run Starting {"id": "fixed2s", "Instance": "3d47d01b-f2d3-4bf3-830c-1815308e3b1f"} 20 | 2021/04/10 13:58:03 Doing some work... 21 | 2021-04-10T13:58:04.062+0200 WARN sched sched/schedule.go:211 Job Run Exceeded Expected Time {"id": "fixed2s", "Instance": "3d47d01b-f2d3-4bf3-830c-1815308e3b1f", "Expected": "1s"} 22 | github.com/sherifabdlnaby/sched.(*Schedule).runJobInstance.func1 23 | /Users/sherifabdlnaby/code/projects/sched/schedule.go:211 24 | 2021/04/10 13:58:04 Finished Work. 25 | 2021-04-10T13:58:04.564+0200 INFO sched sched/schedule.go:229 Job Finished {"id": "fixed2s", "Instance": "3d47d01b-f2d3-4bf3-830c-1815308e3b1f", "Duration": "1.003s", "State": "FINISHED"} 26 | 2021-04-10T13:58:05.561+0200 INFO sched sched/schedule.go:176 Job Next Run Scheduled {"id": "fixed2s", "After": "2s", "At": "2021-04-10T13:58:07+02:00"} 27 | 2021-04-10T13:58:05.561+0200 INFO sched sched/schedule.go:203 Job Run Starting {"id": "fixed2s", "Instance": "78059970-4fc9-4e85-b310-15a3aef08602"} 28 | 2021/04/10 13:58:05 Doing some work... 29 | 2021-04-10T13:58:06.066+0200 WARN sched sched/schedule.go:211 Job Run Exceeded Expected Time {"id": "fixed2s", "Instance": "78059970-4fc9-4e85-b310-15a3aef08602", "Expected": "1s"} 30 | github.com/sherifabdlnaby/sched.(*Schedule).runJobInstance.func1 31 | /Users/sherifabdlnaby/code/projects/sched/schedule.go:211 32 | 2021/04/10 13:58:06 Finished Work. 33 | 2021-04-10T13:58:06.563+0200 INFO sched sched/schedule.go:229 Job Finished {"id": "fixed2s", "Instance": "78059970-4fc9-4e85-b310-15a3aef08602", "Duration": "1.002s", "State": "FINISHED"} 34 | 2021-04-10T13:58:07.561+0200 INFO sched sched/schedule.go:176 Job Next Run Scheduled {"id": "fixed2s", "After": "2s", "At": "2021-04-10T13:58:09+02:00"} 35 | 2021-04-10T13:58:07.561+0200 INFO sched sched/schedule.go:203 Job Run Starting {"id": "fixed2s", "Instance": "2e846c92-e0d6-4028-bca4-d930caded0ce"} 36 | 2021/04/10 13:58:07 Doing some work... 37 | 2021-04-10T13:58:08.066+0200 WARN sched sched/schedule.go:211 Job Run Exceeded Expected Time {"id": "fixed2s", "Instance": "2e846c92-e0d6-4028-bca4-d930caded0ce", "Expected": "1s"} 38 | github.com/sherifabdlnaby/sched.(*Schedule).runJobInstance.func1 39 | /Users/sherifabdlnaby/code/projects/sched/schedule.go:211 40 | 2021/04/10 13:58:08 Finished Work. 41 | 2021-04-10T13:58:08.561+0200 INFO sched sched/schedule.go:229 Job Finished {"id": "fixed2s", "Instance": "2e846c92-e0d6-4028-bca4-d930caded0ce", "Duration": "1s", "State": "FINISHED"} 42 | 2021-04-10T13:58:09.245+0200 INFO sched sched/schedule.go:130 Stopping Schedule... {"id": "fixed2s"} 43 | 2021-04-10T13:58:09.245+0200 INFO sched sched/schedule.go:141 Job Schedule Stopped {"id": "fixed2s"} 44 | 45 | ``` 46 | -------------------------------------------------------------------------------- /examples/scheduler-extra-opts/README.md: -------------------------------------------------------------------------------- 1 | # 4 Schedules in a Scheduler Manager 2 | 3 | 1. Cron Every Minute 4 | 2. Cron Every 5 Minutes 5 | 3. Fixed Interval Every 30 Secs 6 | 4. *Once* after 10 Secs from schedule start. 7 | 8 | Started and Stopped using `StartAll()` and `StopAll()` 9 | 10 | ## Extra Options 11 | 12 | While all schedules inherit the same opts passed to their scheduler. The `fixedTimer30second` schedule has an extra 13 | Option passed to it. Extra Options override inherited options. 14 | 15 | ## Output 16 | 17 | ```json 18 | 2021-04-10T14:02:12.912+0200 INFO sched sched/schedule.go:101 Job Schedule Started {"id": "cronEveryMinute"} 19 | 2021-04-10T14:02:12.912+0200 INFO sched sched/schedule.go:101 Job Schedule Started {"id": "cronEvery5Minute"} 20 | 2021-04-10T14:02:12.912+0200 INFO sched sched/schedule.go:101 Job Schedule Started {"id": "fixedTimer30second"} 21 | 2021-04-10T14:02:12.912+0200 INFO sched sched/schedule.go:101 Job Schedule Started {"id": "onceAfter10s"} 22 | 2021-04-10T14:02:12.912+0200 INFO sched sched/schedule.go:176 Job Next Run Scheduled {"id": "cronEveryMinute", "After": "47s", "At": "2021-04-10T14:03:00+02:00"} 23 | 2021-04-10T14:02:12.912+0200 INFO sched sched/schedule.go:176 Job Next Run Scheduled {"id": "fixedTimer30second", "After": "30s", "At": "2021-04-10T14:02:42+02:00"} 24 | 2021-04-10T14:02:12.912+0200 INFO sched sched/schedule.go:176 Job Next Run Scheduled {"id": "cronEvery5Minute", "After": "2m47s", "At": "2021-04-10T14:05:00+02:00"} 25 | 2021-04-10T14:02:12.912+0200 INFO sched sched/schedule.go:176 Job Next Run Scheduled {"id": "onceAfter10s", "After": "10s", "At": "2021-04-10T14:02:22+02:00"} 26 | 2021-04-10T14:02:22.917+0200 INFO sched sched/schedule.go:170 No more Jobs will be scheduled {"id": "onceAfter10s"} 27 | 2021-04-10T14:02:22.917+0200 INFO sched sched/schedule.go:130 Stopping Schedule... {"id": "onceAfter10s"} 28 | 2021-04-10T14:02:22.917+0200 INFO sched sched/schedule.go:203 Job Run Starting {"id": "onceAfter10s", "Instance": "9d70dff1-a120-446c-937c-64fae1c5922e"} 29 | 2021/04/10 14:02:22 onceAfter10s Doing some work... 30 | 2021/04/10 14:02:23 onceAfter10s Finished Work. 31 | 2021-04-10T14:02:23.921+0200 INFO sched sched/schedule.go:229 Job Finished {"id": "onceAfter10s", "Instance": "9d70dff1-a120-446c-937c-64fae1c5922e", "Duration": "1.003s", "State": "FINISHED"} 32 | 2021-04-10T14:02:23.921+0200 INFO sched.metrics sched/metric.go:48 timer sched.run_actual_elapsed_time {"id": "onceAfter10s", "name": "sched.run_actual_elapsed_time", "interval": "1.00318182s", "tags": {"ID":"onceAfter10s"}} 33 | 2021-04-10T14:02:23.921+0200 INFO sched.metrics sched/metric.go:48 timer sched.run_total_elapsed_time {"id": "onceAfter10s", "name": "sched.run_total_elapsed_time", "interval": "1.003226808s", "tags": {"ID":"onceAfter10s"}} 34 | 2021-04-10T14:02:23.921+0200 INFO sched sched/schedule.go:141 Job Schedule Stopped {"id": "onceAfter10s"} 35 | 2021-04-10T14:02:23.921+0200 INFO sched sched/schedule.go:161 Job Schedule Finished {"id": "onceAfter10s"} 36 | 2021-04-10T14:02:42.916+0200 INFO sched sched/schedule.go:176 Job Next Run Scheduled {"id": "fixedTimer30second", "After": "30s", "At": "2021-04-10T14:03:12+02:00"} 37 | 2021-04-10T14:02:42.916+0200 INFO sched sched/schedule.go:203 Job Run Starting {"id": "fixedTimer30second", "Instance": "2a02ec70-d141-48bd-885f-14bc89329ea6"} 38 | 2021/04/10 14:02:42 fixedEvery30Second Doing some work... 39 | 2021-04-10T14:02:43.419+0200 WARN sched sched/schedule.go:211 Job Run Exceeded Expected Time {"id": "fixedTimer30second", "Instance": "2a02ec70-d141-48bd-885f-14bc89329ea6", "Expected": "1s"} 40 | github.com/sherifabdlnaby/sched.(*Schedule).runJobInstance.func1 41 | /Users/sherifabdlnaby/code/projects/sched/schedule.go:211 42 | 2021/04/10 14:02:43 fixedEvery30Second Finished Work. 43 | 2021-04-10T14:02:43.921+0200 INFO sched sched/schedule.go:229 Job Finished {"id": "fixedTimer30second", "Instance": "2a02ec70-d141-48bd-885f-14bc89329ea6", "Duration": "1.005s", "State": "FINISHED"} 44 | 2021-04-10T14:02:43.921+0200 INFO sched.metrics sched/metric.go:48 timer sched.run_actual_elapsed_time {"id": "fixedTimer30second", "name": "sched.run_actual_elapsed_time", "interval": "1.004632668s", "tags": {"ID":"fixedTimer30second"}} 45 | 2021-04-10T14:02:43.921+0200 INFO sched.metrics sched/metric.go:48 timer sched.run_total_elapsed_time {"id": "fixedTimer30second", "name": "sched.run_total_elapsed_time", "interval": "1.004697199s", "tags": {"ID":"fixedTimer30second"}} 46 | 47 | ``` 48 | 49 | -------------------------------------------------------------------------------- /examples/scheduler/README.md: -------------------------------------------------------------------------------- 1 | # 4 Schedules in a Scheduler Manager 2 | 3 | 1. Cron Every Minute 4 | 2. Cron Every 5 Minutes 5 | 3. Fixed Interval Every 30 Secs 6 | 4. *Once* after 10 Secs from schedule start. 7 | 8 | Started and Stopped using `StartAll()` and `StopAll()` 9 | 10 | ## Output 11 | 12 | ```json 13 | 2021-04-10T13:26: 43.142+0200 INFO sched sched/schedule.go: 96 Job Schedule Started {"id": "cronEveryMinute"} 14 | 2021-04-10T13:26: 43.142+0200 INFO sched sched/schedule.go: 96 Job Schedule Started {"id": "cronEvery5Minute"} 15 | 2021-04-10T13:26: 43.142+0200 INFO sched sched/schedule.go: 96 Job Schedule Started {"id": "fixedTimer30second"} 16 | 2021-04-10T13:26: 43.142+0200 INFO sched sched/schedule.go: 96 Job Schedule Started {"id": "onceAfter10s"} 17 | 2021-04-10T13:26: 43.142+0200 INFO sched sched/schedule.go: 168 Job Next Run Scheduled {"id": "cronEveryMinute", "After": "17s", "At": "2021-04-10T13:27:00+02:00" 18 | } 19 | 2021-04-10T13: 26: 43.143+0200 INFO sched sched/schedule.go: 168 Job Next Run Scheduled {"id": "onceAfter10s", "After": "10s", "At": "2021-04-10T13:26:53+02:00"} 20 | 2021-04-10T13: 26:43.142+0200 INFO sched sched/schedule.go: 168 Job Next Run Scheduled {"id": "cronEvery5Minute", "After": "3m17s", "At": "2021-04-10T13:30:00+02:00" 21 | } 22 | 2021-04-10T13: 26: 43.143+0200 INFO sched sched/schedule.go: 168 Job Next Run Scheduled {"id": "fixedTimer30second", "After": "30s", "At": "2021-04-10T13:27:13+02:00"} 23 | 2021-04-10T13: 26: 53.143+0200 INFO sched sched/schedule.go: 162 No more Jobs will be scheduled {"id": "onceAfter10s"} 24 | 2021-04-10T13: 26: 53.143+0200 INFO sched sched/schedule.go: 125 Stopping Schedule... {"id": "onceAfter10s"} 25 | 2021-04-10T13: 26:53.143+0200 INFO sched sched/schedule.go: 130 Waiting active jobs to finish... {"id": "onceAfter10s"} 26 | 2021-04-10T13:26: 53.143+0200 INFO sched sched/schedule.go: 193 Job Run Starting {"id": "onceAfter10s", "Instance": "47fe7d35-3494-43e0-8771-4282ecb80f3a"} 27 | 2021/04/10 13: 26: 53 onceAfter10s Doing some work... 28 | 2021/04/10 13: 26: 54 onceAfter10s Finished Work. 29 | 2021-04-10T13: 26: 54.148+0200 INFO sched sched/schedule.go: 208 Job Finished {"id": "onceAfter10s", "Instance": "47fe7d35-3494-43e0-8771-4282ecb80f3a", "Duration": "1.005s", "State": "FINISHED"} 30 | 2021-04-10T13: 26: 54.148+0200 INFO sched.metrics sched/metric.go: 48 timer sched.run_actual_elapsed_time {"id": "onceAfter10s", "name": "sched.run_actual_elapsed_time", "interval": "1.004917899s", "tags": { 31 | "ID": "onceAfter10s" 32 | } 33 | } 34 | 2021-04-10T13: 26: 54.148+0200 INFO sched.metrics sched/metric.go: 48 timer sched.run_total_elapsed_time {"id": "onceAfter10s", "name": "sched.run_total_elapsed_time", "interval": "1.004966378s", "tags": {"ID": "onceAfter10s"} 35 | } 36 | 2021-04-10T13: 26: 54.148+0200 INFO sched sched/schedule.go: 133 Job Schedule Stopped {"id": "onceAfter10s"} 37 | 2021-04-10T13: 26: 54.148+0200 INFO sched sched/schedule.go: 153 Job Schedule Finished {"id": "onceAfter10s"} 38 | 2021-04-10T13: 26: 57.662+0200 INFO sched sched/schedule.go: 125 Stopping Schedule... {"id": "cronEveryMinute"} 39 | 2021-04-10T13: 26: 57.662+0200 INFO sched sched/schedule.go: 125 Stopping Schedule... {"id": "fixedTimer30second"} 40 | 2021-04-10T13: 26: 57.662+0200 INFO sched sched/schedule.go: 130 Waiting active jobs to finish... {"id": "cronEveryMinute"} 41 | 2021-04-10T13: 26: 57.662+0200 INFO sched sched/schedule.go: 125 Stopping Schedule... {"id": "cronEvery5Minute"} 42 | 2021-04-10T13: 26: 57.662+0200 INFO sched sched/schedule.go: 133 Job Schedule Stopped {"id": "cronEveryMinute"} 43 | 2021-04-10T13: 26: 57.662+0200 INFO sched sched/schedule.go: 130 Waiting active jobs to finish... {"id": "cronEvery5Minute" 44 | } 45 | 2021-04-10T13: 26: 57.662+0200 INFO sched sched/schedule.go: 171 Job Next Run Canceled {"id": "fixedTimer30second", "At": "2021-04-10T13:27:13+02:00" 46 | } 47 | 2021-04-10T13:26: 57.662+0200 INFO sched sched/schedule.go: 171 Job Next Run Canceled {"id": "cronEvery5Minute", "At": "2021-04-10T13:30:00+02:00" 48 | } 49 | 2021-04-10T13: 26: 57.662+0200 INFO sched sched/schedule.go: 133 Job Schedule Stopped {"id": "cronEvery5Minute" 50 | } 51 | 2021-04-10T13: 26: 57.662+0200 INFO sched sched/schedule.go: 130 Waiting active jobs to finish... {"id": "fixedTimer30second"} 52 | 2021-04-10T13: 26: 57.662+0200 INFO sched sched/schedule.go: 133 Job Schedule Stopped {"id": "fixedTimer30second"} 53 | 2021-04-10T13: 26: 57.662+0200 INFO sched sched/schedule.go: 171 Job Next Run Canceled {"id": "cronEveryMinute", "At": "2021-04-10T13:27:00+02:00"} 54 | 55 | ``` 56 | 57 | -------------------------------------------------------------------------------- /examples/schedule-logs/README.md: -------------------------------------------------------------------------------- 1 | # 4 Logging with Other logrus 2 | 3 | 1. Cron Every Minute 4 | 2. Cron Every 5 Minutes 5 | 3. Fixed Interval Every 30 Secs 6 | 4. *Once* after 10 Secs from schedule start. 7 | 8 | Started and Stopped using `StartAll()` and `StopAll()` 9 | 10 | ## Output 11 | 12 | ```json 13 | INFO[0000] Job Schedule Started From=sched id=cronEvery5Minute 14 | INFO[0000] Job Schedule Started From=sched id=fixedTimer30second 15 | INFO[0000] Job Schedule Started From=sched id=onceAfter10s 16 | INFO[0000] Job Schedule Started From=sched id=cronEveryMinute 17 | INFO[0000] Job Next Run Scheduled After=10s At="2021-04-19T13:09:08+08:00" From=sched id=onceAfter10s 18 | INFO[0000] Job Next Run Scheduled After=30s At="2021-04-19T13:09:28+08:00" From=sched id=fixedTimer30second 19 | INFO[0000] Job Next Run Scheduled After=2s At="2021-04-19T13:09:00+08:00" From=sched id=cronEveryMinute 20 | INFO[0000] Job Next Run Scheduled After=1m2s At="2021-04-19T13:10:00+08:00" From=sched id=cronEvery5Minute 21 | INFO[0001] Job Next Run Scheduled After=1m0s At="2021-04-19T13:10:00+08:00" From=sched id=cronEveryMinute 22 | INFO[0001] Job Run Starting From=sched Instance=2c114df6-e795-419a-8bf3-e4aada560a37 id=cronEveryMinute 23 | 2021/04/19 13:09:00 every-minute-cron Doing some work... 24 | 2021/04/19 13:09:01 every-minute-cron Finished Work. 25 | INFO[0002] Job Finished Duration=1s From=sched Instance=2c114df6-e795-419a-8bf3-e4aada560a37 State=FINISHED id=cronEveryMinute 26 | INFO[0002] timer sched.run_actual_elapsed_time From=metrics id=cronEveryMinute interval=1.0004944s name=sched.run_actual_elapsed_time tags="map[id:cronEveryMinute]" 27 | INFO[0002] timer sched.run_total_elapsed_time From=metrics id=cronEveryMinute interval=1.0006126s name=sched.run_total_elapsed_time tags="map[id:cronEveryMinute]" 28 | INFO[0010] No more Jobs will be scheduled From=sched id=onceAfter10s 29 | INFO[0010] Stopping Schedule... From=sched id=onceAfter10s 30 | INFO[0010] Waiting for '1' active jobs still running... From=sched id=onceAfter10s 31 | INFO[0010] Job Run Starting From=sched Instance=80691c48-8468-4029-9bc8-0264698a1cc2 id=onceAfter10s 32 | 2021/04/19 13:09:08 onceAfter10s Doing some work... 33 | 2021/04/19 13:09:09 onceAfter10s Finished Work. 34 | INFO[0011] Job Finished Duration=1s From=sched Instance=80691c48-8468-4029-9bc8-0264698a1cc2 State=FINISHED id=onceAfter10s 35 | INFO[0011] timer sched.run_actual_elapsed_time From=metrics id=onceAfter10s interval=1.0004959s name=sched.run_actual_elapsed_time tags="map[id:onceAfter10s]" 36 | INFO[0011] timer sched.run_total_elapsed_time From=metrics id=onceAfter10s interval=1.0006474s name=sched.run_total_elapsed_time tags="map[id:onceAfter10s]" 37 | INFO[0011] Job Schedule Stopped From=sched id=onceAfter10s 38 | INFO[0011] Job Schedule Finished From=sched id=onceAfter10s 39 | INFO[0030] Job Next Run Scheduled After=30s At="2021-04-19T13:09:58+08:00" From=sched id=fixedTimer30second 40 | INFO[0030] Job Run Starting From=sched Instance=eded3660-2b7c-46b0-8dcc-8a393102771b id=fixedTimer30second 41 | 2021/04/19 13:09:28 fixedEvery30Second Doing some work... 42 | 2021/04/19 13:09:29 fixedEvery30Second Finished Work. 43 | INFO[0031] Job Finished Duration=1.001s From=sched Instance=eded3660-2b7c-46b0-8dcc-8a393102771b State=FINISHED id=fixedTimer30second 44 | INFO[0031] timer sched.run_actual_elapsed_time From=metrics id=fixedTimer30second interval=1.0006731s name=sched.run_actual_elapsed_time tags="map[id:fixedTimer30second]" 45 | INFO[0031] timer sched.run_total_elapsed_time From=metrics id=fixedTimer30second interval=1.0008061s name=sched.run_total_elapsed_time tags="map[id:fixedTimer30second]" 46 | ^CINFO[0052] Stopping Schedule... From=sched id=cronEveryMinute 47 | INFO[0052] Job Schedule Stopped From=sched id=cronEveryMinute 48 | INFO[0052] Job Next Run Canceled At="2021-04-19T13:10:00+08:00" From=sched id=cronEveryMinute 49 | INFO[0052] Stopping Schedule... From=sched id=cronEvery5Minute 50 | INFO[0052] Job Schedule Stopped From=sched id=cronEvery5Minute 51 | INFO[0052] Job Next Run Canceled At="2021-04-19T13:10:00+08:00" From=sched id=cronEvery5Minute 52 | INFO[0052] Stopping Schedule... From=sched id=fixedTimer30second 53 | INFO[0052] Job Schedule Stopped From=sched id=fixedTimer30second 54 | ``` 55 | 56 | -------------------------------------------------------------------------------- /examples/schedule-fixed/README.md: -------------------------------------------------------------------------------- 1 | # Output with a job that run every 30s on a fixed schedule 2 | 3 | ## Output 4 | 5 | ```json 6 | 2021-04-10T12:58: 48.724+0200 INFO sched sched/schedule.go: 96 Job Schedule Started {"id": "every30s"} 7 | 2021-04-10T12:58: 48.725+0200 INFO sched sched/schedule.go: 168 Job Next Run Scheduled {"id": "every30s", "After": "30s", "At": "2021-04-10T12:59:18+02:00"} 8 | 2021-04-10T12: 59: 18.729+0200 INFO sched sched/schedule.go: 168 Job Next Run Scheduled {"id": "every30s", "After": "30s", "At": "2021-04-10T12:59:48+02:00"} 9 | 2021-04-10T12: 59:18.729+0200 INFO sched sched/schedule.go: 193 Job Run Starting {"id": "every30s", "Instance": "e05aa702-f11c-46ba-8d7c-6ae4049c382d"} 10 | 2021/04/10 12: 59: 18 Doing some work... 11 | 2021/04/10 12: 59: 19 Finished Work. 12 | 2021-04-10T12: 59: 19.733+0200 INFO sched sched/schedule.go: 208 Job Finished {"id": "every30s", "Instance": "e05aa702-f11c-46ba-8d7c-6ae4049c382d", "Duration": "1.004s", "State": "FINISHED"} 13 | 2021-04-10T12:59: 48.724+0200 INFO sched sched/schedule.go: 168 Job Next Run Scheduled {"id": "every30s", "After": "30s", "At": "2021-04-10T13:00:18+02:00"} 14 | 2021-04-10T12: 59: 48.724+0200 INFO sched sched/schedule.go: 193 Job Run Starting {"id": "every30s", "Instance": "80ed574e-e4fc-4a9f-9741-6eda96ebd470"} 15 | 2021/04/10 12: 59: 48 Doing some work... 16 | 2021/04/10 12: 59: 49 Finished Work. 17 | 2021-04-10T12:59: 49.727+0200 INFO sched sched/schedule.go: 208 Job Finished {"id": "every30s", "Instance": "80ed574e-e4fc-4a9f-9741-6eda96ebd470", "Duration": "1.003s", "State": "FINISHED" 18 | } 19 | 2021-04-10T13: 00: 18.726+0200 INFO sched sched/schedule.go:168 Job Next Run Scheduled {"id": "every30s", "After": "30s", "At": "2021-04-10T13:00:48+02:00"} 20 | 2021-04-10T13:00: 18.726+0200 INFO sched sched/schedule.go: 193 Job Run Starting {"id": "every30s", "Instance": "6bc1402c-a5e4-4634-9e49-acaaa6dcb5d0"} 21 | 2021/04/10 13: 00: 18 Doing some work... 22 | 2021/04/10 13: 00: 19 Finished Work. 23 | 2021-04-10T13: 00: 19.729+0200 INFO sched sched/schedule.go:208 Job Finished {"id": "every30s", "Instance": "6bc1402c-a5e4-4634-9e49-acaaa6dcb5d0", "Duration": "1.003s", "State": "FINISHED"} 24 | 2021-04-10T13: 00: 48.725+0200 INFO sched sched/schedule.go: 168 Job Next Run Scheduled {"id": "every30s", "After": "30s", "At": "2021-04-10T13:01:18+02:00"} 25 | 2021-04-10T13: 00: 48.725+0200 INFO sched sched/schedule.go:193 Job Run Starting {"id": "every30s", "Instance": "ed82fdd5-63a0-4fb4-b6a3-7fb74a973d1a"} 26 | 2021/04/10 13: 00: 48 Doing some work... 27 | 2021/04/10 13: 00: 49 Finished Work. 28 | 2021-04-10T13: 00: 49.730+0200 INFO sched sched/schedule.go: 208 Job Finished {"id": "every30s", "Instance": "ed82fdd5-63a0-4fb4-b6a3-7fb74a973d1a", "Duration": "1.005s", "State": "FINISHED"} 29 | 2021-04-10T13: 01: 18.724+0200 INFO sched sched/schedule.go: 168 Job Next Run Scheduled {"id": "every30s", "After": "30s", "At": "2021-04-10T13:01:48+02:00"} 30 | 2021-04-10T13: 01: 18.724+0200 INFO sched sched/schedule.go: 193 Job Run Starting {"id": "every30s", "Instance": "a48eb5fa-2f62-4e9d-ae4e-550fbedc0cd6"} 31 | 2021/04/10 13: 01: 18 Doing some work... 32 | 2021/04/10 13: 01: 19 Finished Work. 33 | 2021-04-10T13: 01: 19.728+0200 INFO sched sched/schedule.go: 208 Job Finished {"id": "every30s", "Instance": "a48eb5fa-2f62-4e9d-ae4e-550fbedc0cd6", "Duration": "1.003s", "State": "FINISHED"} 34 | 2021-04-10T13: 01: 48.725+0200 INFO sched sched/schedule.go: 168 Job Next Run Scheduled {"id": "every30s", "After": "30s", "At": "2021-04-10T13:02:18+02:00"} 35 | 2021-04-10T13: 01: 48.725+0200 INFO sched sched/schedule.go: 193 Job Run Starting {"id": "every30s", "Instance": "2347a38f-82d8-45aa-abb9-1f5192f53a09"} 36 | 2021/04/10 13: 01:48 Doing some work... 37 | 2021/04/10 13: 01: 49 Finished Work. 38 | 2021-04-10T13: 01: 49.725+0200 INFO sched sched/schedule.go: 208 Job Finished {"id": "every30s", "Instance": "2347a38f-82d8-45aa-abb9-1f5192f53a09", "Duration": "1s", "State": "FINISHED" 39 | } 40 | 2021-04-10T13: 02: 18.721+0200 INFO sched sched/schedule.go: 168 Job Next Run Scheduled {"id": "every30s", "After": "30s", "At": "2021-04-10T13:02:48+02:00"} 41 | 2021-04-10T13: 02: 18.721+0200 INFO sched sched/schedule.go: 193 Job Run Starting {"id": "every30s", "Instance": "b4f52aa8-999a-4d46-8cd0-f49c91b22ca3"} 42 | 2021/04/10 13: 02: 18 Doing some work... 43 | 2021/04/10 13: 02:19 Finished Work. 44 | 2021-04-10T13: 02: 19.722+0200 INFO sched sched/schedule.go: 208 Job Finished {"id": "every30s", "Instance": "b4f52aa8-999a-4d46-8cd0-f49c91b22ca3", "Duration": "1.001s", "State": "FINISHED"} 45 | 2021-04-10T13: 02: 48.721+0200 INFO sched sched/schedule.go: 193 Job Run Starting {"id": "every30s", "Instance": "e2c32b53-556c-48ca-98b0-9d39aa0fbd61"} 46 | 2021-04-10T13:02: 48.721+0200 INFO sched sched/schedule.go: 168 Job Next Run Scheduled {"id": "every30s", "After": "30s", "At": "2021-04-10T13:03:18+02:00"} 47 | 2021/04/10 13:02: 48 Doing some work... 48 | 2021/04/10 13: 02: 49 Finished Work. 49 | 2021-04-10T13: 02: 49.722+0200 INFO sched sched/schedule.go: 208 Job Finished {"id": "every30s", "Instance": "e2c32b53-556c-48ca-98b0-9d39aa0fbd61", "Duration": "1s", "State": "FINISHED"} 50 | 2021-04-10T13: 03: 18.719+0200 INFO sched sched/schedule.go: 168 Job Next Run Scheduled {"id": "every30s", "After": "30s", "At": "2021-04-10T13:03:48+02:00"} 51 | 2021-04-10T13: 03: 18.719+0200 INFO sched sched/schedule.go: 193 Job Run Starting {"id": "every30s", "Instance": "6a8aa794-3c9c-4ffe-a2a6-c4b78f31dd57"} 52 | 2021/04/10 13: 03: 18 Doing some work... 53 | 2021/04/10 13:03: 19 Finished Work. 54 | ... 55 | ... 56 | ... 57 | 58 | ``` 59 | -------------------------------------------------------------------------------- /schedule.go: -------------------------------------------------------------------------------- 1 | package sched 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | "time" 7 | 8 | "github.com/sherifabdlnaby/sched/job" 9 | "github.com/uber-go/tally" 10 | ) 11 | 12 | // Schedule A Schedule is an object that wraps a Job (func(){}) and runs it on a schedule according to the supplied 13 | // Timer; With the the ability to expose metrics, and write logs to indicate job health, state, and stats. 14 | type Schedule struct { 15 | id string 16 | 17 | // Source function used to create job.Job 18 | jobSrcFunc func() 19 | 20 | // Timer used to trigger Jobs 21 | timer Timer 22 | 23 | // SignalChan for termination 24 | stopScheduleSignal chan interface{} 25 | 26 | // Concurrent safe JobMap 27 | activeJobs jobMap 28 | 29 | // Wait-group 30 | wg sync.WaitGroup 31 | 32 | // Logging Interface 33 | logger Logger 34 | 35 | // Logging Interface 36 | mx sync.RWMutex 37 | 38 | // State 39 | state State 40 | 41 | // metrics 42 | metrics metrics 43 | 44 | // State 45 | expectedRuntime time.Duration 46 | } 47 | 48 | // NewSchedule Create a new schedule for` jobFunc func()` that will run according to `timer Timer` with the supplied []Options 49 | func NewSchedule(id string, timer Timer, jobFunc func(), opts ...Option) *Schedule { 50 | var options = defaultOptions() 51 | 52 | // Apply Options 53 | for _, option := range opts { 54 | option.apply(options) 55 | } 56 | 57 | // Set Logger 58 | logger := options.logger.With("id", id) 59 | 60 | // Set Metrics 61 | // // Init Default Scope if true, ignore io.closer on purpose. 62 | if options.initDefaultScope { 63 | options.metricsScope, _ = tally.NewRootScope(tally.ScopeOptions{ 64 | Reporter: newConsoleStatsReporter(logger.Named("metrics")), 65 | }, options.defaultScopePrintEvery) 66 | } 67 | metrics := *newMetrics(id, options.metricsScope) 68 | 69 | return &Schedule{ 70 | id: id, 71 | state: NEW, 72 | jobSrcFunc: jobFunc, 73 | timer: timer, 74 | activeJobs: *newJobMap(), 75 | logger: logger, 76 | metrics: metrics, 77 | expectedRuntime: options.expectedRunDuration, 78 | } 79 | } 80 | 81 | // Start Start the scheduler. Method is concurrent safe. Calling Start() have the following effects according to the 82 | // scheduler state: 83 | // 1. NEW: Start the Schedule; running the defined Job on the first Timer's Next() time. 84 | // 2. STARTED: No Effect (and prints warning) 85 | // 3. STOPPED: Restart the schedule 86 | // 4. FINISHED: No Effect (and prints warning) 87 | func (s *Schedule) Start() { 88 | s.mx.Lock() 89 | defer s.mx.Unlock() 90 | 91 | if s.state == FINISHED { 92 | s.logger.Warnw("Attempting to start a finished schedule") 93 | return 94 | } 95 | 96 | if s.state == STARTED { 97 | s.logger.Warnw("Attempting to start an already started schedule") 98 | return 99 | } 100 | 101 | s.logger.Infow("Job Schedule Started") 102 | s.state = STARTED 103 | s.metrics.up.Update(1) 104 | 105 | // Create stopSchedule signal channel, buffer = 1 to allow non-blocking signaling. 106 | s.stopScheduleSignal = make(chan interface{}, 1) 107 | 108 | go s.scheduleLoop() 109 | go func() {}() 110 | } 111 | 112 | // Stop stops the scheduler. Method is **Blocking** and concurrent safe. When called: 113 | // 1. Schedule will cancel all waiting scheduled jobs. 114 | // 2. Schedule will wait for all running jobs to finish. 115 | // Calling Stop() has the following effects depending on the state of the schedule: 116 | // 1. NEW: No Effect 117 | // 2. STARTED: Stop Schedule 118 | // 3. STOPPED: No Effect 119 | // 4. FINISHED: No Effect 120 | func (s *Schedule) Stop() { 121 | s.mx.Lock() 122 | defer s.mx.Unlock() 123 | 124 | if s.state == STOPPED || s.state == FINISHED || s.state == NEW { 125 | return 126 | } 127 | s.state = STOPPING 128 | 129 | // Stop control loop 130 | s.logger.Infow("Stopping Schedule...") 131 | s.stopScheduleSignal <- struct{}{} 132 | close(s.stopScheduleSignal) 133 | 134 | // Print No. of Active Jobs 135 | if noOfActiveJobs := s.activeJobs.len(); s.activeJobs.len() > 0 { 136 | s.logger.Infow(fmt.Sprintf("Waiting for '%d' active jobs still running...", noOfActiveJobs)) 137 | } 138 | 139 | s.wg.Wait() 140 | s.state = STOPPED 141 | s.logger.Infow("Job Schedule Stopped") 142 | s.metrics.up.Update(0) 143 | _ = s.logger.Sync() 144 | } 145 | 146 | // Finish stops the scheduler and put it FINISHED state so that schedule cannot re-start again. Finish() is called 147 | // automatically if Schedule Timer returned `done bool` == true. 148 | // Method is **Blocking** and concurrent safe. 149 | func (s *Schedule) Finish() { 150 | // Stop First 151 | s.Stop() 152 | 153 | s.mx.Lock() 154 | defer s.mx.Unlock() 155 | 156 | if s.state == FINISHED { 157 | return 158 | } 159 | 160 | s.state = FINISHED 161 | s.logger.Infow("Job Schedule Finished") 162 | } 163 | 164 | // scheduleLoop scheduler control loop 165 | func (s *Schedule) scheduleLoop() { 166 | // Main Loop 167 | for { 168 | nextRun, done := s.timer.Next() 169 | if done { 170 | s.logger.Infow("No more Jobs will be scheduled") 171 | break 172 | } 173 | nextRunDuration := time.Until(nextRun) 174 | nextRunDuration = negativeToZero(nextRunDuration) 175 | nextRunChan := time.After(nextRunDuration) 176 | s.logger.Infow("Job Next Run Scheduled", "After", nextRunDuration.Round(1*time.Second).String(), "At", nextRun.Format(time.RFC3339)) 177 | select { 178 | case <-s.stopScheduleSignal: 179 | s.logger.Infow("Job Next Run Canceled", "At", nextRun.Format(time.RFC3339)) 180 | return 181 | case <-nextRunChan: 182 | // Run job 183 | go s.runJobInstance() 184 | } 185 | } 186 | 187 | s.Finish() 188 | } 189 | 190 | func (s *Schedule) runJobInstance() { 191 | s.wg.Add(1) 192 | defer s.wg.Done() 193 | 194 | // Create a new instance of s.jobSrcFunc 195 | jobInstance := job.NewJob(s.jobSrcFunc) 196 | 197 | // Add to active jobs map 198 | s.activeJobs.add(jobInstance) 199 | defer s.activeJobs.delete(jobInstance) 200 | 201 | // Logs and Metrics -------------------------------------- 202 | // ------------------------------------------------------- 203 | s.logger.Infow("Job Run Starting", "Instance", jobInstance.ID()) 204 | s.metrics.runs.Inc(1) 205 | if s.activeJobs.len() > 1 { 206 | s.metrics.overlappingCount.Inc(1) 207 | } 208 | if s.expectedRuntime > 0 { 209 | time.AfterFunc(s.expectedRuntime, func() { 210 | if jobInstance.State() == job.RUNNING { 211 | s.logger.Warnw("Job Run Exceeded Expected Time", "Instance", jobInstance.ID(), 212 | "Expected", s.expectedRuntime.Round(1000*time.Millisecond)) 213 | s.metrics.runExceedExpected.Inc(1) 214 | } 215 | }) 216 | } 217 | // ------------------------------------------------------- 218 | 219 | // Synchronously Run Job Instance 220 | err := jobInstance.Run() 221 | 222 | // ------------------------------------------------------- 223 | // Logs and Metrics -------------------------------------- 224 | if err != nil { 225 | s.logger.Errorw("Job Error", "Instance", jobInstance.ID(), 226 | "Duration", jobInstance.ActualElapsed().Round(1*time.Millisecond), 227 | "State", jobInstance.State().String(), "error", err.Error()) 228 | s.metrics.runErrors.Inc(1) 229 | } 230 | s.logger.Infow("Job Finished", "Instance", jobInstance.ID(), 231 | "Duration", jobInstance.ActualElapsed().Round(1*time.Millisecond), 232 | "State", jobInstance.State().String()) 233 | s.metrics.runActualElapsed.Record(jobInstance.ActualElapsed()) 234 | s.metrics.runTotalElapsed.Record(jobInstance.TotalElapsed()) 235 | } 236 | 237 | func negativeToZero(nextRunDuration time.Duration) time.Duration { 238 | if nextRunDuration < 0 { 239 | nextRunDuration = 0 240 | } 241 | return nextRunDuration 242 | } 243 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= 2 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 3 | github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= 4 | github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= 5 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 6 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 7 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 8 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 9 | github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= 10 | github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 11 | github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= 12 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 13 | github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= 14 | github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs= 15 | github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 16 | github.com/gorhill/cronexpr v0.0.0-20180427100037-88b0669f7d75 h1:f0n1xnMSmBLzVfsMMvriDyA75NB/oBgILX2GcHXIQzY= 17 | github.com/gorhill/cronexpr v0.0.0-20180427100037-88b0669f7d75/go.mod h1:g2644b03hfBX9Ov0ZBDgXXens4rxSxmqFBbhvKv2yVA= 18 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 19 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 20 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 21 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 22 | github.com/m3db/prometheus_client_golang v0.8.1 h1:t7w/tcFws81JL1j5sqmpqcOyQOpH4RDOmIe3A3fdN3w= 23 | github.com/m3db/prometheus_client_golang v0.8.1/go.mod h1:8R/f1xYhXWq59KD/mbRqoBulXejss7vYtYzWmruNUwI= 24 | github.com/m3db/prometheus_client_model v0.1.0 h1:cg1+DiuyT6x8h9voibtarkH1KT6CmsewBSaBhe8wzLo= 25 | github.com/m3db/prometheus_client_model v0.1.0/go.mod h1:Qfsxn+LypxzF+lNhak7cF7k0zxK7uB/ynGYoj80zcD4= 26 | github.com/m3db/prometheus_common v0.1.0 h1:YJu6eCIV6MQlcwND24cRG/aRkZDX1jvYbsNNs1ZYr0w= 27 | github.com/m3db/prometheus_common v0.1.0/go.mod h1:EBmDQaMAy4B8i+qsg1wMXAelLNVbp49i/JOeVszQ/rs= 28 | github.com/m3db/prometheus_procfs v0.8.1 h1:LsxWzVELhDU9sLsZTaFLCeAwCn7bC7qecZcK4zobs/g= 29 | github.com/m3db/prometheus_procfs v0.8.1/go.mod h1:N8lv8fLh3U3koZx1Bnisj60GYUMDpWb09x1R+dmMOJo= 30 | github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= 31 | github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= 32 | github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= 33 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 34 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 35 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 36 | github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= 37 | github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= 38 | github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= 39 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 40 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 41 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 42 | github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= 43 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 44 | github.com/uber-go/tally v3.3.17+incompatible h1:nFHIuW3VQ22wItiE9kPXic8dEgExWOsVOHwpmoIvsMw= 45 | github.com/uber-go/tally v3.3.17+incompatible/go.mod h1:YDTIBxdXyOU/sCWilKB4bgyufu1cEi0jdVnRdxvjnmU= 46 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 47 | go.uber.org/atomic v1.6.0 h1:Ezj3JGmsOnG1MoRWQkPBsKLe9DwWD9QeXzTRzzldNVk= 48 | go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= 49 | go.uber.org/multierr v1.5.0 h1:KCa4XfM8CWFCpxXRGok+Q0SS/0XBhMDbHHGABQLvD2A= 50 | go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= 51 | go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4= 52 | go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= 53 | go.uber.org/zap v1.16.0 h1:uFRZXykJGK9lLY4HtgSw44DnIcAM+kRBP7x5m+NpAOM= 54 | go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ= 55 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 56 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 57 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 58 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 59 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs= 60 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 61 | golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5 h1:2M3HP5CCK1Si9FQhwnzYhXdG6DXeebvUHFpre8QvbyI= 62 | golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= 63 | golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= 64 | golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 65 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 66 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 67 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 68 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 69 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 70 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 71 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 72 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 73 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 74 | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 75 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 76 | golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4 h1:myAQVi0cGEoqQVR5POX+8RR2mrocKqNN1hmeMqhX27k= 77 | golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 78 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 79 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 80 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 81 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 82 | golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 83 | golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 84 | golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5 h1:hKsoRgsbwY1NafxrwTs+k64bikrLBkAgPir1TNCj3Zs= 85 | golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 86 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 87 | golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 88 | golang.org/x/tools v0.1.0 h1:po9/4sTYwZU9lPhi1tOrb4hCv3qrhiQ77LZfGa2OjwY= 89 | golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= 90 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 91 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 92 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= 93 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 94 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 95 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 96 | google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk= 97 | google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 98 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 99 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 100 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= 101 | gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= 102 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 103 | honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM= 104 | honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= 105 | -------------------------------------------------------------------------------- /examples/schedule-four-mixed-timers/README.md: -------------------------------------------------------------------------------- 1 | # 4 Schedules 2 | 3 | 1. Cron Every Minute 4 | 2. Cron Every 5 Minutes 5 | 3. Fixed Interval Every 30 Secs 6 | 4. *Once* after 10 Secs from schedule start. 7 | 8 | ## Output 9 | 10 | ```json 11 | 2021-04-10T12:46: 09.307+0200 INFO sched sched/schedule.go: 96 Job Schedule Started {"id": "cron-every-minute"} 12 | 2021-04-10T12:46: 09.307+0200 INFO sched sched/schedule.go: 96 Job Schedule Started {"id": "cron-every-5minute"} 13 | 2021-04-10T12:46: 09.307+0200 INFO sched sched/schedule.go: 96 Job Schedule Started {"id": "fixed-every-30seconds"} 14 | 2021-04-10T12:46: 09.307+0200 INFO sched sched/schedule.go: 96 Job Schedule Started {"id": "once-after-10seconds"} 15 | 2021-04-10T12:46: 09.307+0200 INFO sched sched/schedule.go: 168 Job Next Run Scheduled {"id": "once-after-10seconds", "After": "10s", "At": "2021-04-10T12:46:19+02:00" 16 | } 17 | 2021-04-10T12: 46: 09.307+0200 INFO sched sched/schedule.go: 168 Job Next Run Scheduled {"id": "cron-every-minute", "After": "51s", "At": "2021-04-10T12:47:00+02:00"} 18 | 2021-04-10T12: 46:09.307+0200 INFO sched sched/schedule.go: 168 Job Next Run Scheduled {"id": "fixed-every-30seconds", "After": "30s", "At": "2021-04-10T12:46:39+02:00" 19 | } 20 | 2021-04-10T12: 46: 09.307+0200 INFO sched sched/schedule.go: 168 Job Next Run Scheduled {"id": "cron-every-5minute", "After": "3m51s", "At": "2021-04-10T12:50:00+02:00"} 21 | 2021-04-10T12: 46: 19.311+0200 INFO sched sched/schedule.go: 162 No more Jobs will be scheduled {"id": "once-after-10seconds"} 22 | 2021-04-10T12: 46: 19.311+0200 INFO sched sched/schedule.go: 125 Stopping Schedule... {"id": "once-after-10seconds"} 23 | 2021-04-10T12: 46:19.311+0200 INFO sched sched/schedule.go: 130 Waiting active jobs to finish... {"id": "once-after-10seconds"} 24 | 2021-04-10T12:46: 19.312+0200 INFO sched sched/schedule.go: 193 Job Run Starting {"id": "once-after-10seconds", "Instance": "6f971652-8394-47d6-86ff-3173cde2b6a1"} 25 | 2021/04/10 12: 46: 19 job once after 10 seconds Doing some work... 26 | 2021/04/10 12: 46: 20 job once after 10 seconds Finished Work. 27 | 2021-04-10T12: 46: 20.314+0200 INFO sched sched/schedule.go:208 Job Finished {"id": "once-after-10seconds", "Instance": "6f971652-8394-47d6-86ff-3173cde2b6a1", "Duration": "1.002s", "State": "FINISHED"} 28 | 2021-04-10T12: 46: 20.314+0200 INFO sched sched/schedule.go: 133 Job Schedule Stopped {"id": "once-after-10seconds"} 29 | 2021-04-10T12: 46: 20.314+0200 INFO sched sched/schedule.go: 153 Job Schedule Finished {"id": "once-after-10seconds"} 30 | 2021-04-10T12: 46: 39.309+0200 INFO sched sched/schedule.go: 168 Job Next Run Scheduled {"id": "fixed-every-30seconds", "After": "30s", "At": "2021-04-10T12:47:09+02:00" 31 | } 32 | 2021-04-10T12: 46: 39.309+0200 INFO sched sched/schedule.go:193 Job Run Starting {"id": "fixed-every-30seconds", "Instance": "bf92e3f5-ea61-4044-b6a4-12d1ca654eec" 33 | } 34 | 2021/04/10 12: 46: 39 job every 30 seconds Doing some work... 35 | 2021/04/10 12: 46: 40 job every 30 seconds Finished Work. 36 | 2021-04-10T12:46: 40.311+0200 INFO sched sched/schedule.go: 208 Job Finished {"id": "fixed-every-30seconds", "Instance": "bf92e3f5-ea61-4044-b6a4-12d1ca654eec", "Duration": "1.001s", "State": "FINISHED" 37 | } 38 | 2021-04-10T12: 46: 59.999+0200 INFO sched sched/schedule.go:168 Job Next Run Scheduled {"id": "cron-every-minute", "After": "0s", "At": "2021-04-10T12:47:00+02:00"} 39 | 2021-04-10T12:46: 59.999+0200 INFO sched sched/schedule.go: 193 Job Run Starting {"id": "cron-every-minute", "Instance": "fc39385f-5c52-4402-aec0-3ee0ec08f3d2"} 40 | 2021/04/10 12: 46: 59 job cron every minute Doing some work... 41 | 2021-04-10T12: 47: 00.000+0200 INFO sched sched/schedule.go: 168 Job Next Run Scheduled {"id": "cron-every-minute", "After": "1m0s", "At": "2021-04-10T12:48:00+02:00"} 42 | 2021-04-10T12: 47: 00.000+0200 INFO sched sched/schedule.go: 193 Job Run Starting {"id": "cron-every-minute", "Instance": "b4911daa-5412-4816-9636-039af5f897ff" 43 | } 44 | 2021/04/10 12: 47: 00 job cron every minute Doing some work... 45 | 2021/04/10 12: 47: 01 job cron every minute Finished Work. 46 | 2021/04/10 12: 47: 01 job cron every minute Finished Work. 47 | 2021-04-10T12: 47: 01.002+0200 INFO sched sched/schedule.go: 208 Job Finished {"id": "cron-every-minute", "Instance": "b4911daa-5412-4816-9636-039af5f897ff", "Duration": "1.002s", "State": "FINISHED"} 48 | 2021-04-10T12: 47:01.002+0200 INFO sched sched/schedule.go: 208 Job Finished {"id": "cron-every-minute", "Instance": "fc39385f-5c52-4402-aec0-3ee0ec08f3d2", "Duration": "1.003s", "State": "FINISHED" 49 | } 50 | 2021-04-10T12: 47: 09.309+0200 INFO sched sched/schedule.go: 168 Job Next Run Scheduled {"id": "fixed-every-30seconds", "After": "30s", "At": "2021-04-10T12:47:39+02:00"} 51 | 2021-04-10T12: 47:09.309+0200 INFO sched sched/schedule.go: 193 Job Run Starting {"id": "fixed-every-30seconds", "Instance": "bd50d728-807e-4d0e-aed3-45044e4b925a"} 52 | 2021/04/10 12: 47: 09 job every 30 seconds Doing some work... 53 | 2021/04/10 12: 47: 10 job every 30 seconds Finished Work. 54 | 2021-04-10T12: 47: 10.309+0200 INFO sched sched/schedule.go: 208 Job Finished {"id": "fixed-every-30seconds", "Instance": "bd50d728-807e-4d0e-aed3-45044e4b925a", "Duration": "1s", "State": "FINISHED"} 55 | 2021-04-10T12: 47:39.306+0200 INFO sched sched/schedule.go: 168 Job Next Run Scheduled {"id": "fixed-every-30seconds", "After": "30s", "At": "2021-04-10T12:48:09+02:00" 56 | } 57 | 2021-04-10T12: 47: 39.306+0200 INFO sched sched/schedule.go: 193 Job Run Starting {"id": "fixed-every-30seconds", "Instance": "282dea9f-b64a-48ab-9ce8-3dcf533d95e4"} 58 | 2021/04/10 12: 47: 39 job every 30 seconds Doing some work... 59 | 2021/04/10 12: 47: 40 job every 30 seconds Finished Work. 60 | 2021-04-10T12: 47: 40.307+0200 INFO sched sched/schedule.go: 208 Job Finished {"id": "fixed-every-30seconds", "Instance": "282dea9f-b64a-48ab-9ce8-3dcf533d95e4", "Duration": "1.001s", "State": "FINISHED" 61 | } 62 | 2021-04-10T12: 48: 00.002+0200 INFO sched sched/schedule.go: 168 Job Next Run Scheduled {"id": "cron-every-minute", "After": "1m0s", "At": "2021-04-10T12:49:00+02:00"} 63 | 2021-04-10T12: 48: 00.002+0200 INFO sched sched/schedule.go: 193 Job Run Starting {"id": "cron-every-minute", "Instance": "9311f3ef-3a43-4fe1-8c8d-740616f42294" 64 | } 65 | 2021/04/10 12: 48: 00 job cron every minute Doing some work... 66 | 2021/04/10 12: 48: 01 job cron every minute Finished Work. 67 | 2021-04-10T12: 48: 01.007+0200 INFO sched sched/schedule.go: 208 Job Finished {"id": "cron-every-minute", "Instance": "9311f3ef-3a43-4fe1-8c8d-740616f42294", "Duration": "1.004s", "State": "FINISHED"} 68 | 2021-04-10T12: 48: 09.304+0200 INFO sched sched/schedule.go: 168 Job Next Run Scheduled {"id": "fixed-every-30seconds", "After": "30s", "At": "2021-04-10T12:48:39+02:00" 69 | } 70 | 2021-04-10T12: 48: 09.304+0200 INFO sched sched/schedule.go: 193 Job Run Starting {"id": "fixed-every-30seconds", "Instance": "083d7198-1981-4542-9677-f837509a346c" 71 | } 72 | 2021/04/10 12:48: 09 job every 30 seconds Doing some work... 73 | 2021/04/10 12: 48: 10 job every 30 seconds Finished Work. 74 | 2021-04-10T12: 48: 10.308+0200 INFO sched sched/schedule.go: 208 Job Finished {"id": "fixed-every-30seconds", "Instance": "083d7198-1981-4542-9677-f837509a346c", "Duration": "1.004s", "State": "FINISHED" 75 | } 76 | 2021-04-10T12: 48: 39.333+0200 INFO sched sched/schedule.go: 168 Job Next Run Scheduled {"id": "fixed-every-30seconds", "After": "30s", "At": "2021-04-10T12:49:09+02:00"} 77 | 2021-04-10T12: 48: 39.333+0200 INFO sched sched/schedule.go: 193 Job Run Starting {"id": "fixed-every-30seconds", "Instance": "8b8cffcb-b026-4383-813d-3f704d6d37a6"} 78 | 2021/04/10 12: 48: 39 job every 30 seconds Doing some work... 79 | 2021/04/10 12: 48: 40 job every 30 seconds Finished Work. 80 | 2021-04-10T12: 48: 40.338+0200 INFO sched sched/schedule.go: 208 Job Finished {"id": "fixed-every-30seconds", "Instance": "8b8cffcb-b026-4383-813d-3f704d6d37a6", "Duration": "1.004s", "State": "FINISHED"} 81 | 2021-04-10T12: 49: 00.035+0200 INFO sched sched/schedule.go: 168 Job Next Run Scheduled {"id": "cron-every-minute", "After": "1m0s", "At": "2021-04-10T12:50:00+02:00"} 82 | 2021-04-10T12: 49: 00.035+0200 INFO sched sched/schedule.go: 193 Job Run Starting {"id": "cron-every-minute", "Instance": "a4c6a479-2138-4037-b613-8adcae1e882d" 83 | } 84 | 2021/04/10 12: 49:00 job cron every minute Doing some work... 85 | 2021/04/10 12: 49: 01 job cron every minute Finished Work. 86 | 2021-04-10T12: 49: 01.039+0200 INFO sched sched/schedule.go: 208 Job Finished {"id": "cron-every-minute", "Instance": "a4c6a479-2138-4037-b613-8adcae1e882d", "Duration": "1.004s", "State": "FINISHED"} 87 | 2021-04-10T12: 49: 09.344+0200 INFO sched sched/schedule.go: 168 Job Next Run Scheduled {"id": "fixed-every-30seconds", "After": "30s", "At": "2021-04-10T12:49:39+02:00"} 88 | 2021-04-10T12: 49: 09.344+0200 INFO sched sched/schedule.go: 193 Job Run Starting {"id": "fixed-every-30seconds", "Instance": "6f17958b-2e83-4d81-808d-9432900cf24e"} 89 | 2021/04/10 12: 49: 09 job every 30 seconds Doing some work... 90 | 2021/04/10 12:49: 10 job every 30 seconds Finished Work. 91 | 2021-04-10T12: 49: 10.349+0200 INFO sched sched/schedule.go:208 Job Finished {"id": "fixed-every-30seconds", "Instance": "6f17958b-2e83-4d81-808d-9432900cf24e", "Duration": "1.004s", "State": "FINISHED"} 92 | 2021-04-10T12: 49: 39.346+0200 INFO sched sched/schedule.go: 168 Job Next Run Scheduled {"id": "fixed-every-30seconds", "After": "30s", "At": "2021-04-10T12:50:09+02:00" 93 | } 94 | 2021-04-10T12: 49: 39.346+0200 INFO sched sched/schedule.go:193 Job Run Starting {"id": "fixed-every-30seconds", "Instance": "11da6889-dee3-4e1c-8c52-af2e2618999a" 95 | } 96 | 2021/04/10 12: 49: 39 job every 30 seconds Doing some work... 97 | 2021/04/10 12: 49: 40 job every 30 seconds Finished Work. 98 | 2021-04-10T12:49: 40.348+0200 INFO sched sched/schedule.go: 208 Job Finished {"id": "fixed-every-30seconds", "Instance": "11da6889-dee3-4e1c-8c52-af2e2618999a", "Duration": "1.003s", "State": "FINISHED" 99 | } 100 | 2021-04-10T12: 50: 00.003+0200 INFO sched sched/schedule.go:168 Job Next Run Scheduled {"id": "cron-every-minute", "After": "1m0s", "At": "2021-04-10T12:51:00+02:00"} 101 | 2021-04-10T12:50: 00.003+0200 INFO sched sched/schedule.go: 193 Job Run Starting {"id": "cron-every-minute", "Instance": "35df9a8c-7900-4f79-943f-a0f0efe86032"} 102 | 2021/04/10 12: 50: 00 job cron every minute Doing some work... 103 | 2021-04-10T12: 50: 00.035+0200 INFO sched sched/schedule.go: 168 Job Next Run Scheduled {"id": "cron-every-5minute", "After": "5m0s", "At": "2021-04-10T12:55:00+02:00"} 104 | 2021-04-10T12: 50: 00.035+0200 INFO sched sched/schedule.go: 193 Job Run Starting {"id": "cron-every-5minute", "Instance": "0ba1a33f-e75f-4273-ace7-98a6c51113ff" 105 | } 106 | 2021/04/10 12: 50: 00 job cron every 5 minute Doing some work... 107 | 2021/04/10 12: 50: 01 job cron every minute Finished Work. 108 | 2021-04-10T12: 50: 01.003+0200 INFO sched sched/schedule.go: 208 Job Finished {"id": "cron-every-minute", "Instance": "35df9a8c-7900-4f79-943f-a0f0efe86032", "Duration": "1.001s", "State": "FINISHED" 109 | } 110 | 2021/04/10 12: 50:01 job cron every 5 minute Finished Work. 111 | 2021-04-10T12:50: 01.037+0200 INFO sched sched/schedule.go: 208 Job Finished {"id": "cron-every-5minute", "Instance": "0ba1a33f-e75f-4273-ace7-98a6c51113ff", "Duration": "1.002s", "State": "FINISHED" 112 | } 113 | CTRL+C 114 | 2021-04-10T12: 50: 08.092+0200 INFO sched sched/schedule.go: 125 Stopping Schedule... {"id": "cron-every-minute"} 115 | 2021-04-10T12: 50: 08.092+0200 INFO sched sched/schedule.go: 130 Waiting active jobs to finish... {"id": "cron-every-minute" 116 | } 117 | 2021-04-10T12: 50: 08.092+0200 INFO sched sched/schedule.go: 133 Job Schedule Stopped {"id": "cron-every-minute" 118 | } 119 | 2021-04-10T12: 50: 08.092+0200 INFO sched sched/schedule.go: 125 Stopping Schedule... {"id": "cron-every-5minute"} 120 | 2021-04-10T12: 50: 08.092+0200 INFO sched sched/schedule.go: 130 Waiting active jobs to finish... {"id": "cron-every-5minute" 121 | } 122 | 2021-04-10T12: 50: 08.092+0200 INFO sched sched/schedule.go: 171 Job Next Run Canceled {"id": "cron-every-minute", "At": "2021-04-10T12:51:00+02:00"} 123 | 2021-04-10T12: 50: 08.092+0200 INFO sched sched/schedule.go:133 Job Schedule Stopped { 124 | "id": "cron-every-5minute" 125 | } 126 | 2021-04-10T12: 50: 08.092+0200 INFO sched sched/schedule.go:171 Job Next Run Canceled {"id": "cron-every-5minute", "At": "2021-04-10T12:55:00+02:00"} 127 | 2021-04-10T12: 50: 08.092+0200 INFO sched sched/schedule.go: 125 Stopping Schedule... {"id": "fixed-every-30seconds" 128 | } 129 | 2021-04-10T12: 50: 08.093+0200 INFO sched sched/schedule.go: 130 Waiting active jobs to finish... {"id": "fixed-every-30seconds"} 130 | 2021-04-10T12: 50: 08.093+0200 INFO sched sched/schedule.go: 133 Job Schedule Stopped {"id": "fixed-every-30seconds"} 131 | 2021-04-10T12: 50: 08.093+0200 INFO sched sched/schedule.go: 171 Job Next Run Canceled {"id": "fixed-every-30seconds", "At": "2021-04-10T12:50:09+02:00"} 132 | 133 | ``` 134 | 135 | -------------------------------------------------------------------------------- /examples/schedule-console-metrics/README.md: -------------------------------------------------------------------------------- 1 | - Output with a job that run every 5s on a fixed schedule and run for a random amount of time no more than 5 S 2 | - Some metrics are printed to console every 5s 3 | 4 | ## Output 5 | 6 | ```json 7 | 2021-04-10T13:13: 35.554+0200 INFO sched sched/schedule.go: 96 Job Schedule Started {"id": "every5s"} 8 | 2021-04-10T13:13: 35.554+0200 INFO sched sched/schedule.go: 168 Job Next Run Scheduled {"id": "every5s", "After": "5s", "At": "2021-04-10T13:13:40+02:00" 9 | } 10 | 2021-04-10T13: 13: 40.556+0200 INFO sched sched/schedule.go: 168 Job Next Run Scheduled {"id": "every5s", "After": "5s", "At": "2021-04-10T13:13:45+02:00"} 11 | 2021-04-10T13: 13:40.556+0200 INFO sched sched/schedule.go: 193 Job Run Starting {"id": "every5s", "Instance": "bd21a8a3-a9e7-4d8f-83cd-6412913198d8"} 12 | 2021/04/10 13: 13: 40 Doing some work for random time... 13 | 2021/04/10 13: 13: 41 Finished Work. 14 | 2021-04-10T13: 13: 41.657+0200 INFO sched sched/schedule.go: 208 Job Finished {"id": "every5s", "Instance": "bd21a8a3-a9e7-4d8f-83cd-6412913198d8", "Duration": "1.101s", "State": "FINISHED"} 15 | 2021-04-10T13: 13: 41.657+0200 INFO sched.metrics sched/metric.go: 48 timer sched.run_actual_elapsed_time {"id": "every5s", "name": "sched.run_actual_elapsed_time", "interval": "1.101133682s", "tags": {"ID": "every5s"}} 16 | 2021-04-10T13: 13:41.657+0200 INFO sched.metrics sched/metric.go: 48 timer sched.run_total_elapsed_time {"id": "every5s", "name": "sched.run_total_elapsed_time", "interval": "1.101159753s", "tags": {"ID": "every5s"}} 17 | 2021-04-10T13: 13: 45.559+0200 INFO sched sched/schedule.go: 168 Job Next Run Scheduled {"id": "every5s", "After": "5s", "At": "2021-04-10T13:13:50+02:00"} 18 | 2021-04-10T13: 13: 45.559+0200 INFO sched sched/schedule.go: 193 Job Run Starting {"id": "every5s", "Instance": "484b9a12-610c-43c4-9709-23356d7a434d"} 19 | 2021/04/10 13: 13: 45 Doing some work for random time... 20 | 2021/04/10 13: 13: 45 Finished Work. 21 | 2021-04-10T13: 13: 45.763+0200 INFO sched sched/schedule.go: 208 Job Finished {"id": "every5s", "Instance": "484b9a12-610c-43c4-9709-23356d7a434d", "Duration": "204ms", "State": "FINISHED" 22 | } 23 | 2021-04-10T13: 13: 45.763+0200 INFO sched.metrics sched/metric.go: 48 timer sched.run_actual_elapsed_time {"id": "every5s", "name": "sched.run_actual_elapsed_time", "interval": "203.728891ms", "tags": {"ID": "every5s"}} 24 | 2021-04-10T13: 13: 45.763+0200 INFO sched.metrics sched/metric.go: 48 timer sched.run_total_elapsed_time {"id": "every5s", "name": "sched.run_total_elapsed_time", "interval": "203.793517ms", "tags": {"ID": "every5s" 25 | } 26 | } 27 | 2021-04-10T13: 13: 50.557+0200 INFO sched sched/schedule.go: 168 Job Next Run Scheduled {"id": "every5s", "After": "5s", "At": "2021-04-10T13:13:55+02:00"} 28 | 2021-04-10T13: 13: 50.557+0200 INFO sched sched/schedule.go: 193 Job Run Starting {"id": "every5s", "Instance": "905996a9-7fe8-46b3-8e32-938ca18f2d03" 29 | } 30 | 2021/04/10 13: 13: 50 Doing some work for random time... 31 | 2021/04/10 13: 13: 52 Finished Work. 32 | 2021-04-10T13:13: 52.757+0200 INFO sched sched/schedule.go: 208 Job Finished {"id": "every5s", "Instance": "905996a9-7fe8-46b3-8e32-938ca18f2d03", "Duration": "2.2s", "State": "FINISHED" 33 | } 34 | 2021-04-10T13: 13: 52.757+0200 INFO sched.metrics sched/metric.go:48 timer sched.run_actual_elapsed_time {"id": "every5s", "name": "sched.run_actual_elapsed_time", "interval": "2.200216259s", "tags": {"ID": "every5s" 35 | } 36 | } 37 | 2021-04-10T13: 13: 52.757+0200 INFO sched.metrics sched/metric.go: 48 timer sched.run_total_elapsed_time {"id": "every5s", "name": "sched.run_total_elapsed_time", "interval": "2.200301621s", "tags": { 38 | "ID": "every5s" 39 | } 40 | } 41 | 2021-04-10T13: 13: 55.558+0200 INFO sched sched/schedule.go:168 Job Next Run Scheduled {"id": "every5s", "After": "5s", "At": "2021-04-10T13:14:00+02:00"} 42 | 2021-04-10T13:13: 55.558+0200 INFO sched.metrics sched/metric.go: 40 counter sched.runs {"id": "every5s", "name": "sched.runs", "value": 3, "tags": {"ID":"every5s" 43 | } 44 | } 45 | 2021-04-10T13: 13: 55.558+0200 INFO sched sched/schedule.go: 193 Job Run Starting {"id": "every5s", "Instance": "39c3295f-63be-4f4a-b81e-79753bbcc91a" 46 | } 47 | 2021/04/10 13:13: 55 Doing some work for random time... 48 | 2021-04-10T13: 13: 55.558+0200 INFO sched.metrics sched/metric.go:44 gauge sched.up {"id": "every5s", "name": "sched.up", "value": 1, "tags": {"ID": "every5s" 49 | } 50 | } 51 | 2021/04/10 13: 13: 55 Finished Work. 52 | 2021-04-10T13: 13: 55.762+0200 INFO sched sched/schedule.go: 208 Job Finished {"id": "every5s", "Instance": "39c3295f-63be-4f4a-b81e-79753bbcc91a", "Duration": "204ms", "State": "FINISHED" 53 | } 54 | 2021-04-10T13: 13: 55.762+0200 INFO sched.metrics sched/metric.go: 48 timer sched.run_actual_elapsed_time {"id": "every5s", "name": "sched.run_actual_elapsed_time", "interval": "203.88384ms", "tags": {"ID": "every5s"}} 55 | 2021-04-10T13:13: 55.762+0200 INFO sched.metrics sched/metric.go: 48 timer sched.run_total_elapsed_time {"id": "every5s", "name": "sched.run_total_elapsed_time", "interval": "203.935885ms", "tags": {"ID":"every5s" 56 | } 57 | } 58 | 2021-04-10T13: 14: 00.555+0200 INFO sched sched/schedule.go: 168 Job Next Run Scheduled {"id": "every5s", "After": "5s", "At": "2021-04-10T13:14:05+02:00"} 59 | 2021-04-10T13: 14: 00.556+0200 INFO sched sched/schedule.go: 193 Job Run Starting {"id": "every5s", "Instance": "1cad0ba1-ac35-4c9d-8ffb-794cd2902c66"} 60 | 2021/04/10 13: 14: 00 Doing some work for random time... 61 | 2021/04/10 13: 14: 04 Finished Work. 62 | 2021-04-10T13: 14:04.359+0200 INFO sched sched/schedule.go: 208 Job Finished {"id": "every5s", "Instance": "1cad0ba1-ac35-4c9d-8ffb-794cd2902c66", "Duration": "3.804s", "State": "FINISHED" 63 | } 64 | 2021-04-10T13: 14: 04.359+0200 INFO sched.metrics sched/metric.go: 48 timer sched.run_actual_elapsed_time {"id": "every5s", "name": "sched.run_actual_elapsed_time", "interval": "3.803781236s", "tags": {"ID": "every5s"} 65 | } 66 | 2021-04-10T13: 14: 04.359+0200 INFO sched.metrics sched/metric.go: 48 timer sched.run_total_elapsed_time {"id": "every5s", "name": "sched.run_total_elapsed_time", "interval": "3.803831971s", "tags": { 67 | "ID": "every5s" 68 | } 69 | } 70 | 2021-04-10T13: 14: 05.558+0200 INFO sched sched/schedule.go: 168 Job Next Run Scheduled {"id": "every5s", "After": "5s", "At": "2021-04-10T13:14:10+02:00"} 71 | 2021-04-10T13: 14:05.558+0200 INFO sched sched/schedule.go: 193 Job Run Starting {"id": "every5s", "Instance": "8e70617b-8245-4b24-aff1-bf4a823ca6ef"} 72 | 2021/04/10 13: 14: 05 Doing some work for random time... 73 | 2021/04/10 13: 14: 07 Finished Work. 74 | 2021-04-10T13: 14: 07.660+0200 INFO sched sched/schedule.go: 208 Job Finished {"id": "every5s", "Instance": "8e70617b-8245-4b24-aff1-bf4a823ca6ef", "Duration": "2.102s", "State": "FINISHED"} 75 | 2021-04-10T13: 14: 07.660+0200 INFO sched.metrics sched/metric.go: 48 timer sched.run_actual_elapsed_time {"id": "every5s", "name": "sched.run_actual_elapsed_time", "interval": "2.10165422s", "tags": {"ID": "every5s"}} 76 | 2021-04-10T13: 14:07.660+0200 INFO sched.metrics sched/metric.go: 48 timer sched.run_total_elapsed_time {"id": "every5s", "name": "sched.run_total_elapsed_time", "interval": "2.101723938s", "tags": {"ID": "every5s"}} 77 | 2021-04-10T13: 14: 10.557+0200 INFO sched sched/schedule.go: 168 Job Next Run Scheduled {"id": "every5s", "After": "5s", "At": "2021-04-10T13:14:15+02:00"} 78 | 2021-04-10T13: 14: 10.557+0200 INFO sched sched/schedule.go: 193 Job Run Starting {"id": "every5s", "Instance": "b4d3b29e-6d21-43d5-8550-6e462625f7ee"} 79 | 2021/04/10 13: 14: 10 Doing some work for random time... 80 | 2021/04/10 13: 14: 11 Finished Work. 81 | 2021-04-10T13: 14: 11.462+0200 INFO sched sched/schedule.go: 208 Job Finished {"id": "every5s", "Instance": "b4d3b29e-6d21-43d5-8550-6e462625f7ee", "Duration": "905ms", "State": "FINISHED" 82 | } 83 | 2021-04-10T13: 14: 11.462+0200 INFO sched.metrics sched/metric.go: 48 timer sched.run_actual_elapsed_time {"id": "every5s", "name": "sched.run_actual_elapsed_time", "interval": "904.904666ms", "tags": {"ID": "every5s"}} 84 | 2021-04-10T13: 14: 11.462+0200 INFO sched.metrics sched/metric.go: 48 timer sched.run_total_elapsed_time {"id": "every5s", "name": "sched.run_total_elapsed_time", "interval": "905.002981ms", "tags": {"ID": "every5s" 85 | } 86 | } 87 | 2021-04-10T13: 14: 15.558+0200 INFO sched.metrics sched/metric.go: 40 counter sched.runs {"id": "every5s", "name": "sched.runs", "value": 4, "tags": {"ID": "every5s"}} 88 | 2021-04-10T13: 14: 15.558+0200 INFO sched sched/schedule.go: 168 Job Next Run Scheduled {"id": "every5s", "After": "5s", "At": "2021-04-10T13:14:20+02:00" 89 | } 90 | 2021-04-10T13: 14: 15.558+0200 INFO sched sched/schedule.go:193 Job Run Starting {"id": "every5s", "Instance": "ac3ff586-9d77-410a-b493-8274347d8b72" 91 | } 92 | 2021/04/10 13: 14: 15 Doing some work for random time... 93 | 2021/04/10 13: 14:20 Finished Work. 94 | 2021-04-10T13: 14: 20.459+0200 INFO sched sched/schedule.go: 208 Job Finished {"id": "every5s", "Instance": "ac3ff586-9d77-410a-b493-8274347d8b72", "Duration": "4.901s", "State": "FINISHED"} 95 | 2021-04-10T13: 14: 20.459+0200 INFO sched.metrics sched/metric.go: 48 timer sched.run_actual_elapsed_time {"id": "every5s", "name": "sched.run_actual_elapsed_time", "interval": "4.900706131s", "tags": { 96 | "ID": "every5s" 97 | } 98 | } 99 | 2021-04-10T13: 14: 20.459+0200 INFO sched.metrics sched/metric.go:48 timer sched.run_total_elapsed_time {"id": "every5s", "name": "sched.run_total_elapsed_time", "interval": "4.900801476s", "tags": {"ID": "every5s" 100 | } 101 | } 102 | 2021-04-10T13: 14: 20.555+0200 INFO sched sched/schedule.go: 168 Job Next Run Scheduled {"id": "every5s", "After": "5s", "At": "2021-04-10T13:14:25+02:00" 103 | } 104 | 2021-04-10T13: 14: 20.555+0200 INFO sched sched/schedule.go: 193 Job Run Starting {"id": "every5s", "Instance": "1d623dcb-e297-4987-baef-e840479abd06" 105 | } 106 | 2021/04/10 13:14: 20 Doing some work for random time... 107 | 2021/04/10 13: 14: 22 Finished Work. 108 | 2021-04-10T13: 14: 22.257+0200 INFO sched sched/schedule.go: 208 Job Finished {"id": "every5s", "Instance": "1d623dcb-e297-4987-baef-e840479abd06", "Duration": "1.702s", "State": "FINISHED"} 109 | 2021-04-10T13:14: 22.257+0200 INFO sched.metrics sched/metric.go: 48 timer sched.run_actual_elapsed_time {"id": "every5s", "name": "sched.run_actual_elapsed_time", "interval": "1.70196874s", "tags": {"ID":"every5s" 110 | } 111 | } 112 | 2021-04-10T13: 14: 22.257+0200 INFO sched.metrics sched/metric.go: 48 timer sched.run_total_elapsed_time {"id": "every5s", "name": "sched.run_total_elapsed_time", "interval": "1.702028643s", "tags": {"ID": "every5s"}} 113 | 2021-04-10T13:14: 25.557+0200 INFO sched sched/schedule.go: 168 Job Next Run Scheduled {"id": "every5s", "After": "5s", "At": "2021-04-10T13:14:30+02:00" 114 | } 115 | 2021-04-10T13: 14: 25.557+0200 INFO sched sched/schedule.go: 193 Job Run Starting {"id": "every5s", "Instance": "1ef7274b-c899-4156-a4f2-edb85a6db71a"} 116 | 2021/04/10 13: 14: 25 Doing some work for random time... 117 | 2021-04-10T13: 14: 30.556+0200 INFO sched sched/schedule.go: 168 Job Next Run Scheduled {"id": "every5s", "After": "5s", "At": "2021-04-10T13:14:35+02:00"} 118 | 2021-04-10T13: 14: 30.556+0200 INFO sched sched/schedule.go: 193 Job Run Starting {"id": "every5s", "Instance": "aface894-ff68-4e3c-8c72-0f0d8d1a4838"} 119 | 2021/04/10 13: 14: 30 Doing some work for random time... 120 | 2021/04/10 13: 14: 30 Finished Work. 121 | 2021-04-10T13: 14:30.557+0200 INFO sched sched/schedule.go: 208 Job Finished {"id": "every5s", "Instance": "1ef7274b-c899-4156-a4f2-edb85a6db71a", "Duration": "5s", "State": "FINISHED" 122 | } 123 | 2021-04-10T13: 14: 30.557+0200 INFO sched.metrics sched/metric.go: 48 timer sched.run_actual_elapsed_time {"id": "every5s", "name": "sched.run_actual_elapsed_time", "interval": "5.000234092s", "tags": {"ID": "every5s"} 124 | } 125 | 2021-04-10T13: 14: 30.557+0200 INFO sched.metrics sched/metric.go: 48 timer sched.run_total_elapsed_time {"id": "every5s", "name": "sched.run_total_elapsed_time", "interval": "5.00025765s", "tags": { 126 | "ID": "every5s" 127 | } 128 | } 129 | 2021-04-10T13: 14: 32.869+0200 INFO sched sched/schedule.go: 125 Stopping Schedule... {"id": "every5s"} 130 | 2021-04-10T13: 14:32.869+0200 INFO sched sched/schedule.go: 130 Waiting active jobs to finish... {"id": "every5s"} 131 | 2021-04-10T13:14: 32.869+0200 INFO sched sched/schedule.go: 171 Job Next Run Canceled {"id": "every5s", "At": "2021-04-10T13:14:35+02:00" 132 | } 133 | 2021/04/10 13: 14: 34 Finished Work. 134 | 2021-04-10T13: 14: 34.057+0200 INFO sched sched/schedule.go: 208 Job Finished {"id": "every5s", "Instance": "aface894-ff68-4e3c-8c72-0f0d8d1a4838", "Duration": "3.501s", "State": "FINISHED"} 135 | 2021-04-10T13: 14: 34.057+0200 INFO sched.metrics sched/metric.go: 48 timer sched.run_actual_elapsed_time {"id": "every5s", "name": "sched.run_actual_elapsed_time", "interval": "3.50128391s", "tags": { 136 | "ID": "every5s" 137 | } 138 | } 139 | 2021-04-10T13: 14: 34.057+0200 INFO sched.metrics sched/metric.go: 48 timer sched.run_total_elapsed_time {"id": "every5s", "name": "sched.run_total_elapsed_time", "interval": "3.501411708s", "tags": {"ID": "every5s"} 140 | } 141 | 2021-04-10T13: 14: 34.057+0200 INFO sched sched/schedule.go: 133 Job Schedule Stopped {"id": "every5s" 142 | } 143 | ``` 144 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 |
3 |
4 |

🕰 Sched

5 |

6 |

Go In-Process Scheduler with Cron Expression Support

7 |
Run Jobs on a schedule, supports fixed interval, timely, and cron-expression timers; Instrument your processes and expose metrics for each job.
8 |

9 | 10 | Go Doc 11 | 12 | 13 | 14 | 15 | 16 | Go Version 17 | 18 | 19 | 20 | 21 | 22 | Go Report 23 | 24 | 25 | GitHub license 26 | 27 |

28 | 29 | # Introduction 30 | 31 | A simple process manager that allows you to specify a Schedule that execute a Job based on a Timer. Schedule manage the 32 | state of this job allowing you to start/stop/restart in concurrent safe way. Schedule also instrument this Job and 33 | gather metrics and optionally expose them via [uber-go/tally](https://github.com/uber-go/tally#report-your-metrics) 34 | scope. 35 | 36 | # Install 37 | 38 | ``` bash 39 | go get github.com/sherifabdlnaby/sched 40 | ``` 41 | 42 | ``` go 43 | import "github.com/sherifabdlnaby/sched" 44 | ``` 45 | 46 | # Requirements 47 | 48 | Go 1.13 >= 49 | 50 | ----- 51 | 52 | # Concepts 53 | 54 | ## Job 55 | 56 | Simply a `func(){}` implementation that is the schedule goal to run, and instrument. 57 | 58 | ## Timer 59 | 60 | An Object that Implements 61 | the [type Timer interface{}](https://pkg.go.dev/github.com/sherifabdlnaby/sched?utm_source=godoc#Timer). A Timer is 62 | responsible for providing a schedule with the **next time the job should run** and if there will be subsequent runs. 63 | 64 | Packaged Implementations: 65 | 66 | 1. [Fixed](https://pkg.go.dev/github.com/sherifabdlnaby/sched?utm_source=godoc#Fixed) :- Infinitely Fires a job at a 67 | Fixed Interval (`time.Duration`) 68 | 2. [Cron](https://pkg.go.dev/github.com/sherifabdlnaby/sched?utm_source=godoc#Cron) :- Infinitely Fires a job based on 69 | a [Cron Expression](https://en.wikipedia.org/wiki/Cron#CRON_expression), all Expressions supported 70 | by [gorhill/cronexpr](https://github.com/gorhill/cronexpr) are supported. 71 | 2. [Once](https://pkg.go.dev/github.com/sherifabdlnaby/sched?utm_source=godoc#Once) :- A Timer that run **ONCE** after 72 | an optional specific delay or at a specified time, schedule will stop after it fires. 73 | 74 | You can Implement your own Timer for your specific scheduling needs by implementing 75 | 76 | ```go 77 | type Timer interface { 78 | // done indicated that there will be no more runs. 79 | Next() (next time.Time, done bool) 80 | } 81 | ``` 82 | 83 | ## Schedule 84 | 85 | A Schedule wraps a Job and fires it according to Timer. 86 | 87 | ```go 88 | fixedTimer30second, _ := sched.NewFixed(30 * time.Second) 89 | 90 | job := func() { 91 | log.Println("Doing some work...") 92 | time.Sleep(1 * time.Second) 93 | log.Println("Finished Work.") 94 | } 95 | 96 | // Create Schedule 97 | schedule := sched.NewSchedule("every30s", fixedTimer30second, job) 98 | 99 | // Start 100 | schedule.Start() 101 | ``` 102 | 103 | ### Options 104 | 105 | Additional Options can be passed to Schedule to change its behavior. 106 | 107 | ```go 108 | // Create Schedule 109 | schedule := sched.NewSchedule("every30s", fixedTimer30second, job, 110 | sched.WithLogger(sched.DefaultLogger()), 111 | opt2, 112 | opt3, 113 | ...., 114 | ) 115 | ``` 116 | 117 | #### Logger Option 118 | 119 | `WithLogger( logger Logger)` -> Supply the Schedule the Logger it is going to use for logging. 120 | 1. [func DefaultLogger() Logger](https://pkg.go.dev/github.com/sherifabdlnaby/sched?utm_source=godoc#DefaultLogger) : 121 | Provide a Default Logging Interface to be used Instead of Implementing your own. 122 | 1. [func NopLogger() Logger](https://pkg.go.dev/github.com/sherifabdlnaby/sched?utm_source=godoc#NopLogger) : A nop 123 | Logger that will not output anything to stdout. 124 | 125 | #### Metrics Option 126 | 127 | `WithMetrics( scope tally.Scope)` -> Supply the Schedule with a metrics scope it can use to export metrics. 128 | 129 | 1. Use any of `uber-go/tally` implementations (Prometheus, statsd, etc) 130 | 1. 131 | Use [`func WithConsoleMetrics(printEvery time.Duration) Option`](https://pkg.go.dev/github.com/sherifabdlnaby/sched?utm_source=godoc#WithConsoleMetrics) 132 | Implementation to Output Metrics to stdout (good for debugging) 133 | 134 | #### Expected Runtime 135 | 136 | `WithExpectedRunTime(d time.Duration)` -> Supply the Schedule with the expected duration for the job to run, schedule 137 | will output corresponding logs and metrics if job run exceeded expected. 138 | 139 | ## Schedule(r) 140 | 141 | Scheduler manage one or more Schedule creating them using common options, enforcing unique IDs, and supply methods to 142 | Start / Stop all schedule(s). 143 | 144 | 145 | ---- 146 | 147 | # Exported Metrics 148 | 149 | | Metric | Type | Desc | 150 | |--------------------------|----------|--------------------------------------------------------------------------------------------------------------------------------------------------------| 151 | | up | Gauge | If the schedule is Running / Stopped | 152 | | runs | Counter | Number of Runs Since Starting | 153 | | runs_overlapping | Counter | Number of times more than one job was running together. (Overlapped) | 154 | | run_actual_elapsed_time | Time | Elapsed Time between Starting and Ending of Job Execution | 155 | | run_total_elapsed_time | Time | Total Elapsed Time between Creating the Job and Ending of Job Execution, This differ from Actual Elapsed time when Overlapping blocking is Implemented | 156 | | run_errors | Counter | Count Number of Times a Job error'd(Panicked) during execution. | 157 | | run_exceed_expected_time | Counter | Count Number of Times a Job Execution Time exceeded the Expected Time | 158 | 159 | ### In Prometheus Format 160 | 161 | ``` 162 | # HELP sched_run_actual_elapsed_time sched_run_actual_elapsed_time summary 163 | # TYPE sched_run_actual_elapsed_time summary 164 | sched_run_actual_elapsed_time{id="every5s",quantile="0.5"} 0.203843151 165 | sched_run_actual_elapsed_time{id="every5s",quantile="0.75"} 1.104031623 166 | sched_run_actual_elapsed_time{id="every5s",quantile="0.95"} 1.104031623 167 | sched_run_actual_elapsed_time{id="every5s",quantile="0.99"} 1.104031623 168 | sched_run_actual_elapsed_time{id="every5s",quantile="0.999"} 1.104031623 169 | sched_run_actual_elapsed_time_sum{id="every5s"} 1.307874774 170 | sched_run_actual_elapsed_time_count{id="every5s"} 2 171 | # HELP sched_run_errors sched_run_errors counter 172 | # TYPE sched_run_errors counter 173 | sched_run_errors{id="every5s"} 0 174 | # HELP sched_run_exceed_expected_time sched_run_exceed_expected_time counter 175 | # TYPE sched_run_exceed_expected_time counter 176 | sched_run_exceed_expected_time{id="every5s"} 0 177 | # HELP sched_run_total_elapsed_time sched_run_total_elapsed_time summary 178 | # TYPE sched_run_total_elapsed_time summary 179 | sched_run_total_elapsed_time{id="every5s",quantile="0.5"} 0.203880714 180 | sched_run_total_elapsed_time{id="every5s",quantile="0.75"} 1.104065614 181 | sched_run_total_elapsed_time{id="every5s",quantile="0.95"} 1.104065614 182 | sched_run_total_elapsed_time{id="every5s",quantile="0.99"} 1.104065614 183 | sched_run_total_elapsed_time{id="every5s",quantile="0.999"} 1.104065614 184 | sched_run_total_elapsed_time_sum{id="every5s"} 1.307946328 185 | sched_run_total_elapsed_time_count{id="every5s"} 2 186 | # HELP sched_runs sched_runs counter 187 | # TYPE sched_runs counter 188 | sched_runs{id="every5s"} 2 189 | # HELP sched_runs_overlapping sched_runs_overlapping counter 190 | # TYPE sched_runs_overlapping counter 191 | sched_runs_overlapping{id="every5s"} 0 192 | # HELP sched_up sched_up gauge 193 | # TYPE sched_up gauge 194 | sched_up{id="every5s"} 1 195 | ``` 196 | 197 | # Examples 198 | 199 | 1. [schedule-console-metrics](https://github.com/sherifabdlnaby/sched/tree/main/examples/schedule-console-metrics) 200 | 1. [schedule-cron](https://github.com/sherifabdlnaby/sched/tree/main/examples/schedule-cron) 201 | 1. [schedule-fixed](https://github.com/sherifabdlnaby/sched/tree/main/examples/schedule-fixed) 202 | 1. [schedule-four-mixed-timers](https://github.com/sherifabdlnaby/sched/tree/main/examples/schedule-four-mixed-timers) 203 | 1. [schedule-once](https://github.com/sherifabdlnaby/sched/tree/main/examples/schedule-console-metrics) 204 | 1. [schedule-overlapping](https://github.com/sherifabdlnaby/sched/tree/main/examples/schedule-overlapping) 205 | 1. [schedule-panic](https://github.com/sherifabdlnaby/sched/tree/main/examples/schedule-panic) 206 | 1. [schedule-prom-metrics](https://github.com/sherifabdlnaby/sched/tree/main/examples/schedule-prom-metrics) 207 | 1. [schedule-warn-expected](https://github.com/sherifabdlnaby/sched/tree/main/examples/schedule-warn-expected) 208 | 1. [scheduler](https://github.com/sherifabdlnaby/sched/tree/main/examples/scheduler) 209 | 1. [scheduler-extra-opts](https://github.com/sherifabdlnaby/sched/tree/main/examples/scheduler-extra-opts) 210 | 211 | ## Inline Example 212 | 213 | ```go 214 | package main 215 | 216 | import ( 217 | "fmt" 218 | "github.com/sherifabdlnaby/sched" 219 | "log" 220 | "os" 221 | "os/signal" 222 | "syscall" 223 | "time" 224 | ) 225 | 226 | func main() { 227 | 228 | cronTimer, err := sched.NewCron("* * * * *") 229 | if err != nil { 230 | panic(fmt.Sprintf("invalid cron expression: %s", err.Error())) 231 | } 232 | 233 | job := func() { 234 | log.Println("Doing some work...") 235 | time.Sleep(1 * time.Second) 236 | log.Println("Finished Work.") 237 | } 238 | 239 | // Create Schedule 240 | schedule := sched.NewSchedule("cron", cronTimer, job, sched.WithLogger(sched.DefaultLogger())) 241 | 242 | // Start Schedule 243 | schedule.Start() 244 | 245 | // Stop schedule after 5 Minutes 246 | time.AfterFunc(5*time.Minute, func() { 247 | schedule.Stop() 248 | }) 249 | 250 | // Listen to CTRL + C 251 | signalChan := make(chan os.Signal, 1) 252 | signal.Notify(signalChan, syscall.SIGTERM, syscall.SIGINT, syscall.SIGQUIT) 253 | _ = <-signalChan 254 | 255 | // Stop before shutting down. 256 | schedule.Stop() 257 | 258 | return 259 | } 260 | 261 | ``` 262 | 263 | ### Output for 3 minutes 264 | 265 | ```bash 266 | 2021-04-10T12:30: 13.132+0200 INFO sched sched/schedule.go: 96 Job Schedule Started {"id": "cron"} 267 | 2021-04-10T12:30: 13.132+0200 INFO sched sched/schedule.go: 168 Job Next Run Scheduled {"id": "cron", "After": "47s", "At": "2021-04-10T12:31:00+02:00"} 268 | 2021-04-10T12: 31: 00.000+0200 INFO sched sched/schedule.go: 168 Job Next Run Scheduled {"id": "cron", "After": "1m0s", "At": "2021-04-10T12:32:00+02:00"} 269 | 2021-04-10T12: 31:00.000+0200 INFO sched sched/schedule.go: 193 Job Run Starting {"id": "cron", "Instance": "8e1044ab-20b6-4acf-8a15-e06c0418522c"} 270 | 2021/04/10 12: 31: 00 Doing some work... 271 | 2021/04/10 12: 31: 01 Finished Work. 272 | 2021-04-10T12: 31: 01.001+0200 INFO sched sched/schedule.go: 208 Job Finished {"id": "cron", "Instance": "8e1044ab-20b6-4acf-8a15-e06c0418522c", "Duration": "1.001s", "State": "FINISHED"} 273 | 2021-04-10T12:32: 00.002+0200 INFO sched sched/schedule.go: 168 Job Next Run Scheduled {"id": "cron", "After": "1m0s", "At": "2021-04-10T12:33:00+02:00"} 274 | 2021-04-10T12: 32: 00.002+0200 INFO sched sched/schedule.go: 193 Job Run Starting {"id": "cron", "Instance": "baae94eb-f818-4b34-a1f4-45b521a360a1"} 275 | 2021/04/10 12: 32: 00 Doing some work... 276 | 2021/04/10 12: 32: 01 Finished Work. 277 | 2021-04-10T12:32: 01.005+0200 INFO sched sched/schedule.go: 208 Job Finished {"id": "cron", "Instance": "baae94eb-f818-4b34-a1f4-45b521a360a1", "Duration": "1.003s", "State": "FINISHED"} 278 | 2021-04-10T12: 33: 00.001+0200 INFO sched sched/schedule.go:168 Job Next Run Scheduled {"id": "cron", "After": "1m0s", "At": "2021-04-10T12:34:00+02:00"} 279 | 2021-04-10T12:33: 00.001+0200 INFO sched sched/schedule.go: 193 Job Run Starting {"id": "cron", "Instance": "71c8f0bf-3624-4a92-909c-b4149f3c62a3"} 280 | 2021/04/10 12: 33: 00 Doing some work... 281 | 2021/04/10 12: 33: 01 Finished Work. 282 | 2021-04-10T12: 33: 01.004+0200 INFO sched sched/schedule.go:208 Job Finished {"id": "cron", "Instance": "71c8f0bf-3624-4a92-909c-b4149f3c62a3", "Duration": "1.003s", "State": "FINISHED"} 283 | 284 | 285 | ``` 286 | 287 | ### Output With CTRL+C 288 | 289 | ```bash 290 | 2021-04-10T12:28: 45.591+0200 INFO sched sched/schedule.go: 96 Job Schedule Started {"id": "cron"} 291 | 2021-04-10T12:28: 45.592+0200 INFO sched sched/schedule.go: 168 Job Next Run Scheduled {"id": "cron", "After": "14s", "At": "2021-04-10T12:29:00+02:00"} 292 | 2021-04-10T12: 29: 00.000+0200 INFO sched sched/schedule.go: 168 Job Next Run Scheduled {"id": "cron", "After": "1m0s", "At": "2021-04-10T12:30:00+02:00"} 293 | 2021-04-10T12: 29:00.000+0200 INFO sched sched/schedule.go: 193 Job Run Starting {"id": "cron", "Instance": "786540f1-594b-44a0-9a66-7181619e38a6"} 294 | 2021/04/10 12: 29: 00 Doing some work... 295 | CTRL+C 296 | 2021-04-10T12: 29: 00.567+0200 INFO sched sched/schedule.go: 125 Stopping Schedule... {"id": "cron"} 297 | 2021-04-10T12: 29: 00.567+0200 INFO sched sched/schedule.go: 130 Waiting active jobs to finish... {"id": "cron"} 298 | 2021-04-10T12: 29: 00.567+0200 INFO sched sched/schedule.go: 171 Job Next Run Canceled {"id": "cron", "At": "2021-04-10T12:30:00+02:00"} 299 | 2021/04/10 12: 29: 01 Finished Work. 300 | 2021-04-10T12: 29:01.000+0200 INFO sched sched/schedule.go: 208 Job Finished {"id": "cron", "Instance": "786540f1-594b-44a0-9a66-7181619e38a6", "Duration": "1s", "State": "FINISHED"} 301 | 2021-04-10T12: 29: 01.000+0200 INFO sched sched/schedule.go: 133 Job Schedule Stopped {"id": "cron" } 302 | ``` 303 | 304 | # Todo(s) and Enhancements 305 | 306 | - [ ] Control Logging Verbosity 307 | - [ ] Make Panic Recovery Optional 308 | - [ ] Make Job a func() error and allow retry(s), backoff, and collect errors and their metrics 309 | - [ ] Make Jobs context aware and support canceling Jobs Context. 310 | - [ ] Make allow Overlapping Optional and Configure How Overlapping is handled/denied. 311 | - [ ] Global Package-Level Metrics 312 | 313 | # License 314 | 315 | [MIT License](https://raw.githubusercontent.com/sherifabdlnaby/sched/master/LICENSE) 316 | Copyright (c) 2021 Sherif Abdel-Naby 317 | 318 | # Contribution 319 | 320 | PR(s) are Open and Welcomed. ❤️ 321 | 322 | --------------------------------------------------------------------------------