A Golang job scheduling library that lets you run Go functions at pre-determined intervals using cron expressions, fixed durations, daily, weekly, monthly, or one-time schedules with support for distributed deployments.
Callbacks for job lifecycle events.
func WithEventListeners(eventListeners ...EventListener) JobOption
type EventListener func(*internalJob) errorAttaches event callbacks. Multiple listeners can be set.
j, _ := s.NewJob(
gocron.DurationJob(time.Minute),
gocron.NewTask(myFunc),
gocron.WithEventListeners(
gocron.BeforeJobRuns(func(jobID uuid.UUID, jobName string) {
log.Printf("Starting %s", jobName)
}),
gocron.AfterJobRuns(func(jobID uuid.UUID, jobName string) {
log.Printf("Completed %s", jobName)
}),
),
)func BeforeJobRuns(eventListenerFunc func(jobID uuid.UUID, jobName string)) EventListenerCalled just before job execution.
Errors:
ErrEventListenerFuncNil: function is nilgocron.BeforeJobRuns(func(jobID uuid.UUID, jobName string) {
log.Printf("[%s] Starting job %s", jobID, jobName)
})Uses: Logging, metrics (increment "started" counter), state updates
func BeforeJobRunsSkipIfBeforeFuncErrors(eventListenerFunc func(jobID uuid.UUID, jobName string) error) EventListenerCalled before job runs. If returns error, current run is skipped and job is rescheduled.
Errors:
ErrEventListenerFuncNil: function is nilgocron.BeforeJobRunsSkipIfBeforeFuncErrors(func(jobID uuid.UUID, jobName string) error {
if !isDatabaseReady() {
return errors.New("database not ready")
}
return nil
})Uses: Precondition checks, circuit breaker, rate limiting
Note: Error is logged but doesn't propagate. Job reschedules normally.
func AfterJobRuns(eventListenerFunc func(jobID uuid.UUID, jobName string)) EventListenerCalled after job completes successfully (no error).
Errors:
ErrEventListenerFuncNil: function is nilgocron.AfterJobRuns(func(jobID uuid.UUID, jobName string) {
log.Printf("[%s] Completed job %s", jobID, jobName)
})Uses: Logging, metrics, triggering dependent jobs, cleanup
func AfterJobRunsWithError(eventListenerFunc func(jobID uuid.UUID, jobName string, err error)) EventListenerCalled after job returns error.
Errors:
ErrEventListenerFuncNil: function is nilgocron.AfterJobRunsWithError(func(jobID uuid.UUID, jobName string, err error) {
log.Printf("[%s] Job %s failed: %v", jobID, jobName, err)
})Uses: Error logging, alerting, metrics, custom retry logic
func AfterJobRunsWithPanic(eventListenerFunc func(jobID uuid.UUID, jobName string, recoverData any)) EventListenerCalled when job panics. gocron recovers automatically.
Errors:
ErrEventListenerFuncNil: function is nilgocron.AfterJobRunsWithPanic(func(jobID uuid.UUID, jobName string, recoverData any) {
log.Printf("[%s] Job %s panicked: %v", jobID, jobName, recoverData)
})Uses: Panic logging with stack trace, critical alerting, debugging
Note: Job is considered failed and rescheduled normally.
func AfterLockError(eventListenerFunc func(jobID uuid.UUID, jobName string, err error)) EventListenerCalled when distributed locker fails to acquire lock.
Errors:
ErrEventListenerFuncNil: function is nilgocron.AfterLockError(func(jobID uuid.UUID, jobName string, err error) {
log.Printf("[%s] Failed to acquire lock for %s: %v", jobID, jobName, err)
})Uses: Lock contention metrics, debugging, alerting, fallback logic
j, _ := s.NewJob(
gocron.DurationJob(time.Minute),
gocron.NewTask(myFunc),
gocron.WithEventListeners(
gocron.BeforeJobRuns(func(jobID uuid.UUID, jobName string) {
log.Printf("Starting %s", jobName)
}),
gocron.AfterJobRuns(func(jobID uuid.UUID, jobName string) {
log.Printf("Completed %s", jobName)
}),
),
)j, _ := s.NewJob(
gocron.DurationJob(time.Minute),
gocron.NewTask(func() error {
return doWork()
}),
gocron.WithEventListeners(
gocron.AfterJobRunsWithError(func(jobID uuid.UUID, jobName string, err error) {
log.Printf("Job %s failed: %v", jobName, err)
sendAlert(fmt.Sprintf("Job failure: %s", err))
}),
),
)j, _ := s.NewJob(
gocron.DurationJob(time.Minute),
gocron.NewTask(myFunc),
gocron.WithEventListeners(
gocron.AfterJobRunsWithPanic(func(jobID uuid.UUID, jobName string, recoverData any) {
log.Printf("PANIC in %s: %v", jobName, recoverData)
sendCriticalAlert(fmt.Sprintf("Job panicked: %v", recoverData))
}),
),
)j, _ := s.NewJob(
gocron.DurationJob(time.Minute),
gocron.NewTask(func() {
processQueue()
}),
gocron.WithEventListeners(
gocron.BeforeJobRunsSkipIfBeforeFuncErrors(func(jobID uuid.UUID, jobName string) error {
if !isQueueAvailable() {
return errors.New("queue not available")
}
return nil
}),
),
)j, _ := s.NewJob(
gocron.DurationJob(time.Minute),
gocron.NewTask(func() error {
return performTask()
}),
gocron.WithEventListeners(
gocron.BeforeJobRuns(func(jobID uuid.UUID, jobName string) {
metricsJobStarted.WithLabelValues(jobName).Inc()
log.Printf("[%s] Starting %s", jobID, jobName)
}),
gocron.AfterJobRuns(func(jobID uuid.UUID, jobName string) {
metricsJobSuccess.WithLabelValues(jobName).Inc()
log.Printf("[%s] Completed %s", jobID, jobName)
}),
gocron.AfterJobRunsWithError(func(jobID uuid.UUID, jobName string, err error) {
metricsJobError.WithLabelValues(jobName).Inc()
log.Printf("[%s] Failed %s: %v", jobID, jobName, err)
sendAlert(fmt.Sprintf("Job %s failed: %v", jobName, err))
}),
gocron.AfterJobRunsWithPanic(func(jobID uuid.UUID, jobName string, recoverData any) {
metricsJobPanic.WithLabelValues(jobName).Inc()
log.Printf("[%s] PANIC %s: %v", jobID, jobName, recoverData)
sendCriticalAlert(fmt.Sprintf("Job %s panicked: %v", jobName, recoverData))
}),
),
)type circuitBreaker struct {
failures int
maxFailures int
resetAfter time.Time
}
var cb = &circuitBreaker{maxFailures: 3}
j, _ := s.NewJob(
gocron.DurationJob(time.Minute),
gocron.NewTask(func() error {
return callExternalService()
}),
gocron.WithEventListeners(
gocron.BeforeJobRunsSkipIfBeforeFuncErrors(func(jobID uuid.UUID, jobName string) error {
if cb.failures >= cb.maxFailures && time.Now().Before(cb.resetAfter) {
return errors.New("circuit breaker open")
}
return nil
}),
gocron.AfterJobRuns(func(jobID uuid.UUID, jobName string) {
cb.failures = 0 // Reset on success
}),
gocron.AfterJobRunsWithError(func(jobID uuid.UUID, jobName string, err error) {
cb.failures++
if cb.failures >= cb.maxFailures {
cb.resetAfter = time.Now().Add(5 * time.Minute)
log.Println("Circuit breaker opened for 5 minutes")
}
}),
),
)j, _ := s.NewJob(
gocron.DurationJob(time.Minute),
gocron.NewTask(myFunc),
gocron.WithEventListeners(
gocron.AfterLockError(func(jobID uuid.UUID, jobName string, err error) {
metricsLockFailure.WithLabelValues(jobName).Inc()
log.Printf("Lock contention for %s: %v", jobName, err)
}),
),
)j1, _ := s.NewJob(
gocron.DurationJob(time.Hour),
gocron.NewTask(func() {
generateReport()
}),
gocron.WithName("generate-report"),
gocron.WithEventListeners(
gocron.AfterJobRuns(func(jobID uuid.UUID, jobName string) {
// Trigger dependent job
j2.RunNow()
}),
),
)
j2, _ := s.NewJob(
gocron.DurationJob(24*time.Hour),
gocron.NewTask(func() {
emailReport()
}),
gocron.WithName("email-report"),
)var startTime time.Time
j, _ := s.NewJob(
gocron.DurationJob(time.Minute),
gocron.NewTask(myFunc),
gocron.WithEventListeners(
gocron.BeforeJobRuns(func(jobID uuid.UUID, jobName string) {
startTime = time.Now()
}),
gocron.AfterJobRuns(func(jobID uuid.UUID, jobName string) {
duration := time.Since(startTime)
metricsJobDuration.WithLabelValues(jobName).Observe(duration.Seconds())
log.Printf("Job %s took %v", jobName, duration)
}),
),
)Install with Tessl CLI
npx tessl i tessl/golang-github-com-go-co-op-gocron-v2docs
api
examples
guides