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.
Guide to monitoring and debugging gocron applications.
Gocron provides multiple observability mechanisms:
Default logger outputs to stdout:
s, _ := gocron.NewScheduler()
// Uses default loggerImplement Logger interface:
type Logger interface {
Debug(msg string, args ...any)
Error(msg string, args ...any)
Info(msg string, args ...any)
Warn(msg string, args ...any)
}
type myLogger struct{}
func (l *myLogger) Debug(msg string, args ...any) {
log.Printf("[DEBUG] "+msg, args...)
}
// ... implement other methods
s, _ := gocron.NewScheduler(
gocron.WithLogger(&myLogger{}),
)Detailed guide: Logging
Track job execution metrics:
type myMonitor struct{}
func (m *myMonitor) RecordJobTiming(
start time.Time,
duration time.Duration,
jobID uuid.UUID,
jobName string,
tags []string,
) {
// Send to Prometheus, StatsD, etc.
prometheus.JobDuration.Observe(duration.Seconds())
}Detailed guide: Metrics
Comprehensive monitoring with SchedulerMonitor:
type myMonitor struct{}
func (m *myMonitor) JobScheduled(jobID uuid.UUID, job gocron.Job) {
log.Printf("Scheduled: %s at %v", job.Name(), job.NextRun())
}
func (m *myMonitor) JobStarted(jobID uuid.UUID, job gocron.Job) {
log.Printf("Started: %s", job.Name())
}
func (m *myMonitor) JobCompleted(jobID uuid.UUID, job gocron.Job, err error) {
if err != nil {
log.Printf("Failed: %s - %v", job.Name(), err)
} else {
log.Printf("Completed: %s", job.Name())
}
}
s, _ := gocron.NewScheduler(
gocron.WithSchedulerMonitor(&myMonitor{}),
)Detailed guide: Lifecycle Monitoring
Job-level event hooks:
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)
}),
gocron.AfterJobRunsWithError(func(jobID uuid.UUID, jobName string, err error) {
log.Printf("Failed: %s - %v", jobName, err)
// Alert, retry, etc.
}),
),
)Errors returned by task functions:
j, _ := s.NewJob(
gocron.DurationJob(time.Minute),
gocron.NewTask(func() error {
if err := doWork(); err != nil {
return fmt.Errorf("work failed: %w", err)
}
return nil
}),
gocron.WithEventListeners(
gocron.AfterJobRunsWithError(func(jobID uuid.UUID, jobName string, err error) {
log.Printf("Job error: %v", err)
// Handle error
}),
),
)Check errors during operations:
j, err := s.NewJob(
gocron.CronJob("invalid", false),
gocron.NewTask(myFunc),
)
if err != nil {
if errors.Is(err, gocron.ErrCronJobParse) {
log.Printf("Invalid cron: %v", err)
}
}Implement health endpoints:
http.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
jobs := s.Jobs()
healthy := true
for _, j := range jobs {
lastRun, _ := j.LastRun()
nextRun, _ := j.NextRun()
// Check if job is running as expected
if time.Since(lastRun) > 10*time.Minute {
healthy = false
break
}
}
if healthy {
w.WriteHeader(http.StatusOK)
w.Write([]byte("healthy"))
} else {
w.WriteHeader(http.StatusServiceUnavailable)
w.Write([]byte("unhealthy"))
}
})// List all jobs
jobs := s.Jobs()
for _, j := range jobs {
log.Printf("Job: %s", j.Name())
log.Printf(" ID: %s", j.ID())
log.Printf(" Next: %v", j.NextRun())
log.Printf(" Last: %v", j.LastRun())
log.Printf(" Tags: %v", j.Tags())
}// Check running status
jobs := s.Jobs()
log.Printf("Total jobs: %d", len(jobs))
log.Printf("Jobs waiting: %d", s.JobsWaitingInQueue())var (
jobDuration = prometheus.NewHistogramVec(
prometheus.HistogramOpts{
Name: "gocron_job_duration_seconds",
Help: "Job execution duration",
},
[]string{"job_name"},
)
)
func init() {
prometheus.MustRegister(jobDuration)
}
type prometheusMonitor struct{}
func (m *prometheusMonitor) RecordJobTiming(
start time.Time,
duration time.Duration,
jobID uuid.UUID,
jobName string,
tags []string,
) {
jobDuration.WithLabelValues(jobName).Observe(duration.Seconds())
}type statsdMonitor struct {
client *statsd.Client
}
func (m *statsdMonitor) RecordJobTiming(
start time.Time,
duration time.Duration,
jobID uuid.UUID,
jobName string,
tags []string,
) {
m.client.Timing("gocron.job.duration", duration, tags, 1.0)
}Common issues and solutions.
Detailed guide: Troubleshooting
Install with Tessl CLI
npx tessl i tessl/golang-github-com-go-co-op-gocron-v2@2.19.1docs
api
examples
guides