CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/golang-github-com-go-co-op-gocron-v2

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.

Overview
Eval results
Files

events.mddocs/api/options/

Event Listeners

Callbacks for job lifecycle events.

WithEventListeners

func WithEventListeners(eventListeners ...EventListener) JobOption

type EventListener func(*internalJob) error

Attaches 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)
        }),
    ),
)

BeforeJobRuns

func BeforeJobRuns(eventListenerFunc func(jobID uuid.UUID, jobName string)) EventListener

Called just before job execution.

Errors:

  • ErrEventListenerFuncNil: function is nil
gocron.BeforeJobRuns(func(jobID uuid.UUID, jobName string) {
    log.Printf("[%s] Starting job %s", jobID, jobName)
})

Uses: Logging, metrics (increment "started" counter), state updates

BeforeJobRunsSkipIfBeforeFuncErrors

func BeforeJobRunsSkipIfBeforeFuncErrors(eventListenerFunc func(jobID uuid.UUID, jobName string) error) EventListener

Called before job runs. If returns error, current run is skipped and job is rescheduled.

Errors:

  • ErrEventListenerFuncNil: function is nil
gocron.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.

AfterJobRuns

func AfterJobRuns(eventListenerFunc func(jobID uuid.UUID, jobName string)) EventListener

Called after job completes successfully (no error).

Errors:

  • ErrEventListenerFuncNil: function is nil
gocron.AfterJobRuns(func(jobID uuid.UUID, jobName string) {
    log.Printf("[%s] Completed job %s", jobID, jobName)
})

Uses: Logging, metrics, triggering dependent jobs, cleanup

AfterJobRunsWithError

func AfterJobRunsWithError(eventListenerFunc func(jobID uuid.UUID, jobName string, err error)) EventListener

Called after job returns error.

Errors:

  • ErrEventListenerFuncNil: function is nil
gocron.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

AfterJobRunsWithPanic

func AfterJobRunsWithPanic(eventListenerFunc func(jobID uuid.UUID, jobName string, recoverData any)) EventListener

Called when job panics. gocron recovers automatically.

Errors:

  • ErrEventListenerFuncNil: function is nil
gocron.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.

AfterLockError

func AfterLockError(eventListenerFunc func(jobID uuid.UUID, jobName string, err error)) EventListener

Called when distributed locker fails to acquire lock.

Errors:

  • ErrEventListenerFuncNil: function is nil
gocron.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

Usage Examples

Basic Logging

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)
        }),
    ),
)

Error Handling

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))
        }),
    ),
)

Panic Recovery

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))
        }),
    ),
)

Precondition Check

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
        }),
    ),
)

Comprehensive Monitoring

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))
        }),
    ),
)

Circuit Breaker Pattern

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")
            }
        }),
    ),
)

Distributed Lock Monitoring

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)
        }),
    ),
)

Triggering Dependent Jobs

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"),
)

Timing Metrics

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)
        }),
    ),
)

Event Execution Order

  1. BeforeJobRunsSkipIfBeforeFuncErrors - If returns error, skip to step 6
  2. BeforeJobRuns
  3. Distributed lock acquisition (if configured) - If fails, AfterLockError called
  4. Task execution
  5. One of:
    • AfterJobRuns (success)
    • AfterJobRunsWithError (error)
    • AfterJobRunsWithPanic (panic)
  6. Job rescheduled for next run

Best Practices

  1. Keep listeners fast: Don't block job execution with slow operations
  2. Handle listener errors: Listeners should not panic
  3. Use structured logging: Include jobID and jobName in logs
  4. Separate concerns: Different listeners for logging, metrics, alerting
  5. Test failure paths: Ensure error and panic listeners work correctly

See Also

  • Identity Options - Job naming for listeners
  • Timing Options - Context and lifecycle
  • Monitoring Types - Alternative monitoring interfaces
  • Observability Guide - Comprehensive monitoring patterns

Install with Tessl CLI

npx tessl i tessl/golang-github-com-go-co-op-gocron-v2@2.19.1

docs

api

errors.md

quick-reference.md

tasks.md

index.md

tile.json