Skip to content

Commit a3101e8

Browse files
committed
feat(cron): add cron extension with job scheduling, execution history, and metrics
- Introduced a new cron extension for Forge, enabling production-grade job scheduling with support for both simple and distributed modes. - Implemented job execution with concurrency control, retry logic, and detailed execution history tracking. - Added REST API for job management and metrics collection for observability. - Included configuration options for storage backends (memory, database, Redis) and job definitions via YAML/JSON. - Enhanced user experience with a web UI for monitoring and managing cron jobs. This commit lays the foundation for a robust cron job management system within the Forge framework.
1 parent 27a6cf8 commit a3101e8

42 files changed

Lines changed: 6939 additions & 51 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

extensions/cron/README.md

Lines changed: 305 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,305 @@
1+
# Cron Job Extension
2+
3+
A production-grade cron job scheduler for Forge with distributed support, execution history, metrics, and web UI.
4+
5+
## Features
6+
7+
-**Flexible Scheduling**: Use standard cron expressions with seconds precision
8+
-**Multiple Job Types**: Code-based handlers or shell commands
9+
-**Execution History**: Track all job executions with detailed status
10+
-**Retry Logic**: Automatic retries with exponential backoff
11+
-**Concurrency Control**: Configurable worker pool for job execution
12+
-**Storage Backends**: In-memory, database, or Redis
13+
-**Distributed Mode**: Leader election and distributed locking (requires consensus extension)
14+
-**REST API**: Full HTTP API for job management
15+
-**Metrics**: Prometheus metrics for observability
16+
-**Config-Based Jobs**: Define jobs in YAML/JSON files
17+
-**Web UI**: Dashboard for job management and monitoring
18+
19+
## Quick Start
20+
21+
### Simple Mode (Single Instance)
22+
23+
```go
24+
package main
25+
26+
import (
27+
"context"
28+
"github.com/xraph/forge"
29+
"github.com/xraph/forge/extensions/cron"
30+
)
31+
32+
func main() {
33+
app := forge.New()
34+
35+
// Register cron extension
36+
cronExt := cron.NewExtension(
37+
cron.WithMode("simple"),
38+
cron.WithStorage("memory"),
39+
cron.WithMaxConcurrentJobs(5),
40+
)
41+
app.RegisterExtension(cronExt)
42+
43+
// Register job handlers
44+
app.AfterRegister(func(ctx context.Context) error {
45+
registry := forge.MustResolve[*cron.JobRegistry](app.Container(), "cron.registry")
46+
47+
// Register a handler
48+
registry.Register("sendReport", func(ctx context.Context, job *cron.Job) error {
49+
// Your job logic here
50+
return nil
51+
})
52+
53+
// Create a job programmatically
54+
scheduler := forge.MustResolve[cron.Scheduler](app.Container(), "cron.scheduler")
55+
scheduler.AddJob(&cron.Job{
56+
ID: "daily-report",
57+
Name: "Daily Report",
58+
Schedule: "0 9 * * *", // Every day at 9 AM
59+
HandlerName: "sendReport",
60+
Enabled: true,
61+
})
62+
63+
return nil
64+
})
65+
66+
app.Run(context.Background())
67+
}
68+
```
69+
70+
### Configuration File
71+
72+
Create `jobs.yaml`:
73+
74+
```yaml
75+
jobs:
76+
- id: cleanup-temp
77+
name: Cleanup Temporary Files
78+
schedule: "0 2 * * *" # Daily at 2 AM
79+
command: /usr/local/bin/cleanup.sh
80+
timeout: 10m
81+
maxRetries: 3
82+
enabled: true
83+
84+
- id: backup-database
85+
name: Database Backup
86+
schedule: "0 0 * * *" # Daily at midnight
87+
command: /usr/local/bin/backup-db.sh
88+
args:
89+
- --compress
90+
- --output=/backups
91+
timeout: 30m
92+
enabled: true
93+
```
94+
95+
Load jobs from config:
96+
97+
```go
98+
app.AfterRegister(func(ctx context.Context) error {
99+
loader := cron.NewJobLoader(app.Logger(), registry)
100+
jobs, err := loader.LoadFromFile(ctx, "jobs.yaml")
101+
if err != nil {
102+
return err
103+
}
104+
105+
for _, job := range jobs {
106+
scheduler.AddJob(job)
107+
}
108+
109+
return nil
110+
})
111+
```
112+
113+
## Configuration
114+
115+
```yaml
116+
extensions:
117+
cron:
118+
mode: simple # "simple" or "distributed"
119+
storage: memory # "memory", "database", or "redis"
120+
max_concurrent_jobs: 10
121+
default_timeout: 5m
122+
default_timezone: UTC
123+
max_retries: 3
124+
retry_backoff: 1s
125+
retry_multiplier: 2.0
126+
max_retry_backoff: 30s
127+
history_retention_days: 30
128+
enable_api: true
129+
api_prefix: /api/cron
130+
enable_web_ui: true
131+
enable_metrics: true
132+
```
133+
134+
## Cron Schedule Format
135+
136+
The scheduler supports standard cron expressions with optional seconds:
137+
138+
```
139+
┌────────────── second (0-59) [optional]
140+
│ ┌──────────── minute (0-59)
141+
│ │ ┌────────── hour (0-23)
142+
│ │ │ ┌──────── day of month (1-31)
143+
│ │ │ │ ┌────── month (1-12 or JAN-DEC)
144+
│ │ │ │ │ ┌──── day of week (0-6 or SUN-SAT)
145+
│ │ │ │ │ │
146+
│ │ │ │ │ │
147+
* * * * * *
148+
```
149+
150+
Examples:
151+
- `0 9 * * *` - Every day at 9 AM
152+
- `*/15 * * * *` - Every 15 minutes
153+
- `0 0 * * 0` - Every Sunday at midnight
154+
- `0 9 * * 1-5` - Weekdays at 9 AM
155+
- `30 2 1 * *` - 2:30 AM on the first of every month
156+
157+
## REST API
158+
159+
### Job Management
160+
161+
- `GET /api/cron/jobs` - List all jobs
162+
- `POST /api/cron/jobs` - Create a job
163+
- `GET /api/cron/jobs/:id` - Get job details
164+
- `PUT /api/cron/jobs/:id` - Update a job
165+
- `DELETE /api/cron/jobs/:id` - Delete a job
166+
- `POST /api/cron/jobs/:id/trigger` - Manually trigger a job
167+
- `POST /api/cron/jobs/:id/enable` - Enable a job
168+
- `POST /api/cron/jobs/:id/disable` - Disable a job
169+
170+
### Execution History
171+
172+
- `GET /api/cron/executions` - List all executions
173+
- `GET /api/cron/jobs/:id/executions` - Get job execution history
174+
- `GET /api/cron/executions/:id` - Get execution details
175+
176+
### Statistics
177+
178+
- `GET /api/cron/stats` - Get scheduler statistics
179+
- `GET /api/cron/jobs/:id/stats` - Get job statistics
180+
181+
### Health
182+
183+
- `GET /api/cron/health` - Health check
184+
185+
## Distributed Mode
186+
187+
For multi-instance deployments with leader election:
188+
189+
```yaml
190+
extensions:
191+
cron:
192+
mode: distributed
193+
storage: redis
194+
redis_connection: default
195+
leader_election: true
196+
consensus_extension: consensus
197+
heartbeat_interval: 5s
198+
lock_ttl: 30s
199+
```
200+
201+
Distributed mode ensures only one instance schedules jobs, with automatic failover.
202+
203+
## Metrics
204+
205+
Prometheus metrics exposed:
206+
207+
- `cron_jobs_total` - Total registered jobs
208+
- `cron_executions_total` - Total executions by status
209+
- `cron_execution_duration_seconds` - Execution duration histogram
210+
- `cron_scheduler_lag_seconds` - Lag between scheduled and actual time
211+
- `cron_executor_queue_size` - Current executor queue size
212+
- `cron_leader_status` - Leader status (0=follower, 1=leader)
213+
214+
## Web UI
215+
216+
Access the web UI at `/cron/ui` (configurable) to:
217+
218+
- View all scheduled jobs
219+
- Monitor execution history
220+
- Manually trigger jobs
221+
- Enable/disable jobs
222+
- View real-time statistics
223+
224+
## Advanced Usage
225+
226+
### Retry Configuration
227+
228+
Configure retries per job:
229+
230+
```go
231+
job := &cron.Job{
232+
ID: "retry-example",
233+
Name: "Job with Custom Retry",
234+
Schedule: "*/5 * * * *",
235+
HandlerName: "unreliableTask",
236+
MaxRetries: 5,
237+
Timeout: 2 * time.Minute,
238+
Enabled: true,
239+
}
240+
```
241+
242+
### Job Middleware
243+
244+
Add middleware to job handlers:
245+
246+
```go
247+
// Logging middleware
248+
loggingMiddleware := cron.CreateLoggingMiddleware(func(ctx context.Context, job *cron.Job, err error) {
249+
logger.Info("Job executed",
250+
"job_id", job.ID,
251+
"duration", time.Since(start),
252+
"error", err,
253+
)
254+
})
255+
256+
// Panic recovery middleware
257+
panicMiddleware := cron.CreatePanicRecoveryMiddleware(func(ctx context.Context, job *cron.Job, recovered interface{}) {
258+
logger.Error("Job panicked", "panic", recovered)
259+
})
260+
261+
// Register with middleware
262+
registry.RegisterWithMiddleware("myJob", handler, loggingMiddleware, panicMiddleware)
263+
```
264+
265+
### Programmatic Job Management
266+
267+
```go
268+
// Get extension instance
269+
cronExt := app.Extension("cron").(*cron.Extension)
270+
271+
// Create job
272+
job := &cron.Job{
273+
ID: "dynamic-job",
274+
Name: "Dynamically Created Job",
275+
Schedule: "0 * * * *",
276+
Command: "/usr/local/bin/script.sh",
277+
Enabled: true,
278+
}
279+
cronExt.CreateJob(ctx, job)
280+
281+
// Update job
282+
update := &cron.JobUpdate{
283+
Enabled: forge.Ptr(false),
284+
}
285+
cronExt.UpdateJob(ctx, "dynamic-job", update)
286+
287+
// Delete job
288+
cronExt.DeleteJob(ctx, "dynamic-job")
289+
290+
// Trigger job
291+
executionID, err := cronExt.TriggerJob(ctx, "my-job")
292+
```
293+
294+
## Security Considerations
295+
296+
- Command injection: Validate all command inputs
297+
- API authentication: Integrate with auth extension
298+
- Rate limiting: Limit manual job triggers
299+
- Audit logging: Track all job modifications
300+
- Secrets: Use environment variables, never hardcode
301+
302+
## License
303+
304+
See the main Forge license.
305+

0 commit comments

Comments
 (0)