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.
Detailed guide to monitoring job and scheduler lifecycle events.
The SchedulerMonitor and Monitor interfaces provide comprehensive lifecycle tracking for jobs and scheduler operations.
Monitor scheduler-level events:
type SchedulerMonitor interface {
JobScheduled(jobID uuid.UUID, job Job)
JobUnscheduled(jobID uuid.UUID, job Job)
JobStarted(jobID uuid.UUID, job Job)
JobCompleted(jobID uuid.UUID, job Job, err error)
ConcurrencyLimitReached(limitType string, job Job)
}type mySchedulerMonitor struct{}
func (m *mySchedulerMonitor) JobScheduled(jobID uuid.UUID, job gocron.Job) {
log.Printf("[SCHEDULED] %s (ID: %s) next run: %v",
job.Name(), jobID, job.NextRun())
}
func (m *mySchedulerMonitor) JobUnscheduled(jobID uuid.UUID, job gocron.Job) {
log.Printf("[UNSCHEDULED] %s (ID: %s)", job.Name(), jobID)
}
func (m *mySchedulerMonitor) JobStarted(jobID uuid.UUID, job gocron.Job) {
log.Printf("[STARTED] %s (ID: %s)", job.Name(), jobID)
}
func (m *mySchedulerMonitor) JobCompleted(jobID uuid.UUID, job gocron.Job, err error) {
if err != nil {
log.Printf("[FAILED] %s (ID: %s): %v", job.Name(), jobID, err)
} else {
log.Printf("[COMPLETED] %s (ID: %s)", job.Name(), jobID)
}
}
func (m *mySchedulerMonitor) ConcurrencyLimitReached(limitType string, job gocron.Job) {
log.Printf("[LIMIT] %s limit reached for %s", limitType, job.Name())
}
s, _ := gocron.NewScheduler(
gocron.WithSchedulerMonitor(&mySchedulerMonitor{}),
)Monitor job timing metrics:
type Monitor interface {
RecordJobTiming(
start time.Time,
duration time.Duration,
jobID uuid.UUID,
jobName string,
tags []string,
)
}type myMonitor struct{}
func (m *myMonitor) RecordJobTiming(
start time.Time,
duration time.Duration,
jobID uuid.UUID,
jobName string,
tags []string,
) {
log.Printf("[TIMING] %s took %v (tags: %v)",
jobName, duration, tags)
}
s, _ := gocron.NewScheduler(
gocron.WithMonitor(&myMonitor{}),
)Track job status changes:
type MonitorStatus interface {
UpdateStatus(jobID uuid.UUID, jobName string, status JobStatus)
}
type JobStatus int
const (
Scheduled JobStatus = iota
Running
Completed
Failed
)type myStatusMonitor struct {
statuses map[uuid.UUID]gocron.JobStatus
mu sync.Mutex
}
func (m *myStatusMonitor) UpdateStatus(
jobID uuid.UUID,
jobName string,
status gocron.JobStatus,
) {
m.mu.Lock()
defer m.mu.Unlock()
m.statuses[jobID] = status
log.Printf("[STATUS] %s -> %v", jobName, status)
}
s, _ := gocron.NewScheduler(
gocron.WithMonitorStatus(&myStatusMonitor{
statuses: make(map[uuid.UUID]gocron.JobStatus),
}),
)Typical event sequence for a job execution:
1. JobScheduled - Job added to scheduler
2. JobStarted - Execution begins
3. RecordJobTiming - Execution completes (Monitor)
4. JobCompleted - Execution finishes (SchedulerMonitor)
5. UpdateStatus - Status updated (MonitorStatus)Use multiple monitors together:
s, _ := gocron.NewScheduler(
gocron.WithSchedulerMonitor(&lifecycleMonitor{}),
gocron.WithMonitor(&metricsMonitor{}),
gocron.WithMonitorStatus(&statusMonitor{}),
)type alertingMonitor struct{}
func (m *alertingMonitor) JobCompleted(
jobID uuid.UUID,
job gocron.Job,
err error,
) {
if err != nil {
// Send alert
sendAlert(fmt.Sprintf("Job %s failed: %v", job.Name(), err))
}
}type timingMonitor struct {
durations map[string][]time.Duration
mu sync.Mutex
}
func (m *timingMonitor) RecordJobTiming(
start time.Time,
duration time.Duration,
jobID uuid.UUID,
jobName string,
tags []string,
) {
m.mu.Lock()
defer m.mu.Unlock()
m.durations[jobName] = append(m.durations[jobName], duration)
// Alert if duration exceeds threshold
if duration > 5*time.Minute {
log.Printf("SLOW: %s took %v", jobName, duration)
}
}type queueMonitor struct{}
func (m *queueMonitor) ConcurrencyLimitReached(
limitType string,
job gocron.Job,
) {
log.Printf("Queue building up for %s", job.Name())
// Check queue depth
if s.JobsWaitingInQueue() > 10 {
sendAlert("Queue depth exceeded threshold")
}
}import "github.com/prometheus/client_golang/prometheus"
var (
jobExecutions = prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "gocron_job_executions_total",
Help: "Total job executions",
},
[]string{"job_name", "status"},
)
jobDuration = prometheus.NewHistogramVec(
prometheus.HistogramOpts{
Name: "gocron_job_duration_seconds",
Help: "Job execution duration",
},
[]string{"job_name"},
)
)
type prometheusMonitor struct{}
func (m *prometheusMonitor) JobCompleted(
jobID uuid.UUID,
job gocron.Job,
err error,
) {
status := "success"
if err != nil {
status = "error"
}
jobExecutions.WithLabelValues(job.Name(), status).Inc()
}
func (m *prometheusMonitor) RecordJobTiming(
start time.Time,
duration time.Duration,
jobID uuid.UUID,
jobName string,
tags []string,
) {
jobDuration.WithLabelValues(jobName).Observe(duration.Seconds())
}import "github.com/DataDog/datadog-go/statsd"
type datadogMonitor struct {
client *statsd.Client
}
func (m *datadogMonitor) RecordJobTiming(
start time.Time,
duration time.Duration,
jobID uuid.UUID,
jobName string,
tags []string,
) {
m.client.Timing("gocron.job.duration", duration, tags, 1.0)
m.client.Incr("gocron.job.executions", tags, 1.0)
}
func (m *datadogMonitor) JobCompleted(
jobID uuid.UUID,
job gocron.Job,
err error,
) {
tags := []string{fmt.Sprintf("job:%s", job.Name())}
if err != nil {
tags = append(tags, "status:error")
m.client.Incr("gocron.job.errors", tags, 1.0)
} else {
tags = append(tags, "status:success")
}
}// Good: async processing
func (m *myMonitor) JobCompleted(jobID uuid.UUID, job gocron.Job, err error) {
go func() {
// Send to external system
sendMetric(job.Name(), err)
}()
}
// Bad: blocking operation
func (m *myMonitor) JobCompleted(jobID uuid.UUID, job gocron.Job, err error) {
// Blocks job completion
sendMetricSync(job.Name(), err)
}func (m *myMonitor) RecordJobTiming(...) {
if err := m.client.Send(...); err != nil {
// Log but don't fail
log.Printf("Failed to record metric: %v", err)
}
}// Good: low cardinality
prometheus.JobDuration.WithLabelValues(job.Name())
// Bad: high cardinality (unique IDs)
prometheus.JobDuration.WithLabelValues(job.ID().String())Install with Tessl CLI
npx tessl i tessl/golang-github-com-go-co-op-gocron-v2@2.19.1docs
api
examples
guides