Documentation
¶
Overview ¶
Package cronwatch is cron and scheduled-job monitoring that lives inside a Go app: wrap a job once, and every run is recorded in a database the app already has, and alerts go out when a run is missed, fails, gets stuck, runs slow, goes over budget, or quietly does nothing. It is the Go port of @cronwatch/sdk, with the same rules, alert text, and stored rows, so a Go process can share a database with the SDK and its other ports.
A client is made once, jobs are declared on it, and each run of a job is its function called through Run:
cw, err := cronwatch.New(cronwatch.WithStore(store))
nightly, err := cw.Job("nightly-report", cronwatch.Schedule("0 2 * * *"), cronwatch.Grace("15m"))
err = nightly.Run(ctx, func(ctx context.Context, job *cronwatch.JobContext) error {
job.Log("Report written")
return nil
})
Missed and stuck runs are found by Check, called on an interval by Start in a long-running service, or from a crontab line. The sqlstore package keeps everything in SQLite, Postgres, or MySQL over the app's own *sql.DB; MemoryStore, the default, forgets on restart. The alerts package holds the SDK's alert channels (Slack, Discord, a webhook, email, SMS, and error trackers), the triage package Claude triage, and the pgcron package a source that watches pg_cron's jobs.
The dashboard and its JSON API are an http.Handler, Client.Routes, and a job run by a platform cron that calls a URL is one too, Job.Handler:
routes, err := cw.Routes(cronwatch.WithToken(os.Getenv("CRONWATCH_TOKEN")))
mux.Handle("/cronwatch/", routes)
mux.Handle("POST /api/cron/nightly", nightly.Handler(buildReport))
Every value a store holds is the SDK's JSON: each type's MarshalJSON writes it byte for byte, with keys in JavaScript's order.
Example ¶
A job declared once and run: the failure is recorded, judged, and alerted.
package main
import (
"context"
"errors"
"fmt"
"time"
cronwatch "cronwatch.dev/go"
)
func main() {
ctx := context.Background()
now := time.Date(2026, 1, 5, 2, 0, 0, 0, time.UTC).UnixMilli()
alerts := cronwatch.ChannelFunc("print", func(_ context.Context, a cronwatch.Alert) error {
fmt.Println(a.Title)
fmt.Println(a.Message)
return nil
})
cw := cronwatch.MustNew(cronwatch.WithAlerts(alerts), cronwatch.WithClock(func() int64 { return now }))
nightly := cw.MustJob("nightly-report", cronwatch.Schedule("0 2 * * *"), cronwatch.Timezone("UTC"), cronwatch.Grace("15m"))
err := nightly.Run(ctx, func(ctx context.Context, job *cronwatch.JobContext) error {
job.Log("connecting to postgres://app:" + "hunter" + "2@db.internal/app")
return errors.New("connection refused")
})
fmt.Println("returned:", err)
runs, _ := cw.Runs(ctx, "nightly-report", 1)
fmt.Println(runs[0].Status, *runs[0].Output)
}
Output: nightly-report failed Started 2026-01-05 02:00:00 UTC (now), ran 0ms. Error: connection refused Output (tail): connecting to postgres://app:[redacted]@db.internal/app returned: connection refused failed connecting to postgres://app:[redacted]@db.internal/app
Index ¶
- Constants
- Variables
- func HandlerValue[T any](job *Job, ...) http.Handler
- func Lambda(h http.Handler) func(ctx context.Context, event LambdaEvent) (LambdaResult, error)
- func RedactSecrets(text string) string
- func RunValue[T any](ctx context.Context, job *Job, ...) (T, error)
- type Alert
- type AlertDetails
- type AlertType
- type BudgetBreach
- type Channel
- type ChannelContext
- type CheckResult
- type Client
- func (c *Client) Check(ctx context.Context) (*CheckResult, error)
- func (c *Client) Close() error
- func (c *Client) CronSecret() string
- func (c *Client) Declares(name string) bool
- func (c *Client) DefinedJobs() []Definition
- func (c *Client) Forget(ctx context.Context, name string) error
- func (c *Client) GetRun(ctx context.Context, id string) (*Run, error)
- func (c *Client) GoString() string
- func (c *Client) Job(name string, options ...JobOption) (*Job, error)
- func (c *Client) JobSummary(ctx context.Context, name string) (*JobSummary, error)
- func (c *Client) Jobs(ctx context.Context) ([]JobSummary, error)
- func (c *Client) JobsWithRuns(ctx context.Context, limit int) ([]JobWithRuns, error)
- func (c *Client) LogValue() slog.Value
- func (c *Client) MustJob(name string, options ...JobOption) *Job
- func (c *Client) MustRoutes(options ...RoutesOption) *Routes
- func (c *Client) Now() int64
- func (c *Client) RecordRun(ctx context.Context, input Run, options ...RecordOption) ([]Alert, error)
- func (c *Client) ReportError(err error, where string)
- func (c *Client) ResumeRun(ctx context.Context, name, runID string) (*RunHandle, error)
- func (c *Client) Routes(options ...RoutesOption) (*Routes, error)
- func (c *Client) Run(ctx context.Context, name string, fn JobFunc, options ...JobOption) error
- func (c *Client) Runs(ctx context.Context, name string, limit int) ([]Run, error)
- func (c *Client) Silence(ctx context.Context, name string, d time.Duration) (JobState, error)
- func (c *Client) Start(every time.Duration)deprecated
- func (c *Client) StartChecking(every time.Duration)
- func (c *Client) Stop()
- func (c *Client) Store() Store
- func (c *Client) String() string
- func (c *Client) SyncJob(ctx context.Context, name string) (bool, error)
- func (c *Client) Unsilence(ctx context.Context, name string) (JobState, error)
- type Condition
- type Definition
- func (d Definition) Description() string
- func (d Definition) Expect() string
- func (d Definition) Get(key string) (any, bool)
- func (d Definition) JSValue() anydeprecated
- func (d Definition) Keys() []string
- func (d Definition) MarshalJSON() ([]byte, error)
- func (d Definition) Name() string
- func (d Definition) Schedule() string
- func (d Definition) Tags() []string
- func (d Definition) Timezone() string
- func (d *Definition) UnmarshalJSON(b []byte) error
- type DeliverMode
- type Duration
- type FailureDetails
- type HandlerFunc
- type HandlerOption
- type Job
- func (j *Job) Definition() Definition
- func (j *Job) Handler(fn HandlerFunc, options ...HandlerOption) http.Handler
- func (j *Job) Name() string
- func (j *Job) Resume(ctx context.Context, runID string) (*RunHandle, error)
- func (j *Job) Run(ctx context.Context, fn JobFunc, options ...RunOption) error
- func (j *Job) Start(ctx context.Context, options ...RunOption) (*RunHandle, error)
- type JobContext
- type JobFunc
- type JobHealth
- type JobOption
- func Budget(metric string, ceiling float64) JobOption
- func Description(text string) JobOption
- func Expect(text string) JobOption
- func ExpectFunc(fn func(output string) bool) JobOption
- func ExpectMatch(re *regexp.Regexp) JobOption
- func FailuresBeforeAlert(n int) JobOption
- func Floor(metric string, floor float64) JobOption
- func Grace[D Duration](d D) JobOption
- func MaxDuration[D Duration](d D) JobOption
- func Schedule(expr string) JobOption
- func Tags(tags ...string) JobOption
- func Timeout[D Duration](d D) JobOption
- func Timezone(name string) JobOption
- type JobState
- type JobSummary
- type JobWithRuns
- type LambdaEvent
- type LambdaRequestContext
- type LambdaResult
- type MemoryStore
- func (m *MemoryStore) Close() error
- func (m *MemoryStore) CompareAndSetState(_ context.Context, state JobState, expected int64) (bool, error)
- func (m *MemoryStore) DeleteJob(_ context.Context, name string) error
- func (m *MemoryStore) DeleteRunIf(_ context.Context, id, job string, status RunStatus) (bool, error)
- func (m *MemoryStore) GetJob(_ context.Context, name string) (*StoredJob, error)
- func (m *MemoryStore) GetRun(_ context.Context, id string) (*Run, error)
- func (m *MemoryStore) GetState(_ context.Context, job string) (*JobState, error)
- func (m *MemoryStore) Init(context.Context) error
- func (m *MemoryStore) InsertRun(_ context.Context, run Run) error
- func (m *MemoryStore) LastRun(ctx context.Context, job string) (*Run, error)
- func (m *MemoryStore) ListJobs(context.Context) ([]StoredJob, error)
- func (m *MemoryStore) ListRuns(_ context.Context, job string, limit int) ([]Run, error)
- func (m *MemoryStore) Prune(_ context.Context, before int64) (int, error)
- func (m *MemoryStore) RunningRuns(context.Context) ([]Run, error)
- func (m *MemoryStore) SetState(_ context.Context, state JobState) error
- func (m *MemoryStore) UpdateRun(_ context.Context, run Run) error
- func (m *MemoryStore) UpdateRunIf(_ context.Context, run Run, from []RunStatus) (bool, error)
- func (m *MemoryStore) UpsertJob(_ context.Context, def Definition, now int64) error
- type Metric
- type Metrics
- type MissedDetails
- type OpenCondition
- type Option
- func WithAlerts(channels ...Channel) Option
- func WithClock(now func() int64) Option
- func WithCronSecret(secret string) Option
- func WithDefaults(options ...JobOption) Option
- func WithDeliver(mode DeliverMode) Option
- func WithErrorHandler(fn func(err error, where string)) Option
- func WithRedact(fn func(text string) string) Option
- func WithRetention[D Duration](d D) Option
- func WithSources(sources ...Source) Option
- func WithStore(store Store) Option
- func WithTriage(fn TriageFunc) Option
- func WithoutCronSecret() Option
- func WithoutRedaction() Option
- type OverBudgetDetails
- type RecordOption
- type RecoveredDetails
- type Routes
- type RoutesOption
- type Run
- type RunDeleter
- type RunHandle
- func (h *RunHandle) Active() bool
- func (h *RunHandle) Fail(ctx context.Context, err error) *Run
- func (h *RunHandle) Finish(ctx context.Context) *Run
- func (h *RunHandle) FinishWith(ctx context.Context, result any) *Run
- func (h *RunHandle) Flush(ctx context.Context)
- func (h *RunHandle) ID() string
- func (h *RunHandle) Job() string
- func (h *RunHandle) Log(parts ...any)
- func (h *RunHandle) Metric(name string, value float64) error
- func (h *RunHandle) StartedAt() (int64, bool)
- type RunOption
- type RunStatus
- type RunUpdater
- type SendingAlert
- type SlowDetails
- type Source
- type SourceHost
- type StateComparer
- type Stats
- type Store
- type StoredJob
- type TriageContext
- type TriageFunc
- type UnderFloorDetails
Examples ¶
Constants ¶
const ( // DefaultBasePath is where the dashboard is taken to be mounted when // nothing else says: not WithBasePath, not http.StripPrefix, not a // ServeMux pattern. DefaultBasePath = "/cronwatch" )
const MaxBody = maxBody
MaxBody is the most of a request body the dashboard reads (1 MiB); a body past it is answered 413.
Deprecated: MaxBody is internal, and goes in 1.0. The limit is documented on the dashboard's page and does not change.
const ReservedRunIDPrefix = reservedRunIDPrefix
ReservedRunIDPrefix starts the run ids of the pg_cron source, so Start and Resume refuse it.
Deprecated: ReservedRunIDPrefix is internal, and goes in 1.0. The prefix ("pgcron:") is documented with Start and does not change.
const Version = "0.14.3"
Version is the release this module is, the same as every CronWatch package's. scripts/release.mjs bumps it; the module's version is its tag, packages/go/vX.Y.Z.
Variables ¶
var Stderr io.Writer = os.Stderr
Stderr is where the default error handler, the in-memory store's warning, and the console channel's failures go.
Deprecated: Stderr is a hook for this module's own tests. Give the client WithErrorHandler, and a channel of your own in place of Console, to send what they write elsewhere. It goes in 1.0.
var Stdout io.Writer = os.Stdout
Stdout is where the console channel writes recoveries.
Deprecated: Stdout is a hook for this module's own tests. Give the client a channel of your own in place of Console to send recoveries elsewhere. It goes in 1.0.
Functions ¶
func HandlerValue ¶
func HandlerValue[T any](job *Job, fn func(ctx context.Context, job *JobContext, w http.ResponseWriter, r *http.Request) (T, error), options ...HandlerOption) http.Handler
HandlerValue is Job.Handler for a function that returns a value, as RunValue is Run's: a string is the run's output when nothing was logged, and an *http.Response (a call to another service, say) is the answer, copied to the caller, its status of 400 or more failing the run with "HTTP <status> <reason>". The response's body is closed. A response returned with an error is not the answer: the run failed with the error, and the caller gets the JSON answer.
func Lambda ¶
func Lambda(h http.Handler) func(ctx context.Context, event LambdaEvent) (LambdaResult, error)
Lambda is h as an AWS Lambda function behind API Gateway or a function URL, for aws-lambda-go's lambda.Start, which the app imports:
lambda.Start(cronwatch.Lambda(nightly.Handler(buildReport)))
The event becomes an *http.Request over https (the host from its Host header or its domain name, the body decoded when it is base64), and h's answer the proxy result, its body as text when it is UTF-8 and base64 otherwise, without a Content-Length (API Gateway sets its own). A directly invoked function (EventBridge Scheduler) has no headers to carry a bearer; IAM decides who may invoke it, so such a handler takes WithoutSecret.
func RedactSecrets ¶
RedactSecrets is the default redaction: it blanks values that look like secrets (secret-named pairs, credentials in URLs, authorization headers, private keys, JWTs, webhook URLs, and common API key formats), exactly as the SDK's default does. A WithRedact function can call it and add its own patterns on top.
Example ¶
A WithRedact function that keeps the default and adds a pattern of its own.
card := regexp.MustCompile(`\d{16}`)
redact := func(text string) string { return card.ReplaceAllString(RedactSecrets(text), "[card]") }
fmt.Println(redact("charged 4242424242424242"))
Output: charged [card]
func RunValue ¶
func RunValue[T any](ctx context.Context, job *Job, fn func(ctx context.Context, job *JobContext) (T, error), options ...RunOption) (T, error)
RunValue runs fn as a recorded run of job, as Job.Run does, and returns what it returns. A string is the run's output when nothing was logged (and what an expect rule checks), and an *http.Response whose status is 400 or more fails the run with "HTTP <status> <reason>".
Types ¶
type Alert ¶
type Alert struct {
Type AlertType
// The run behind the alert, when there is one.
Run *Run
Details AlertDetails
Job string
// The job's definition when the alert was made.
Definition Definition
// One line, suitable as a notification title.
Title string
// A few lines of plain text with the specifics.
Message string
// A short diagnosis from the triage function, when one is configured.
// Nil with TriageTried set means triage was tried and gave nothing; it
// is not tried again for this alert.
Triage *string
TriageTried bool
At int64
// contains filtered or unexported fields
}
Alert is a condition opening or closing, with the text every channel shows.
func (Alert) MarshalJSON ¶
MarshalJSON writes the SDK's JSON.
func (*Alert) UnmarshalJSON ¶
UnmarshalJSON reads the SDK's JSON.
type AlertDetails ¶
type AlertDetails interface {
// contains filtered or unexported methods
}
AlertDetails is what an alert carries beyond its title and message: a MissedDetails, FailureDetails (failed and stuck), SlowDetails, OverBudgetDetails, UnderFloorDetails, or RecoveredDetails.
type BudgetBreach ¶
type BudgetBreach struct {
Metric string
Value float64
Limit float64
// "budget", "floor", or how the baseline was worked out.
Basis string
}
BudgetBreach is one metric over its ceiling or its baseline, or under its floor.
type Channel ¶
type Channel interface {
// Name names the channel in errors ("alert channel <name>").
Name() string
// Send returns once the alert went out (to at least one recipient), or
// an error when it went nowhere, and then the next check sends it to
// this channel again, and to no channel that took it. ctx ends after 15
// seconds.
Send(ctx context.Context, alert Alert, cc ChannelContext) error
}
Channel is where alerts go.
func ChannelFunc ¶
ChannelFunc wraps a function as a channel (the SDK's custom()).
type ChannelContext ¶
type ChannelContext struct {
// contains filtered or unexported fields
}
ChannelContext is what the client hands a channel with each alert.
func NewChannelContext ¶
func NewChannelContext(report func(error)) ChannelContext
NewChannelContext is a channel context that reports to report, for sending to a channel outside a client (a test, say).
func (ChannelContext) ReportError ¶
func (cc ChannelContext) ReportError(err error)
ReportError reports a problem that did not stop the alert going out, such as one of several recipients refusing it. It goes to the client's error handler; the zero ChannelContext writes it to standard error, as the SDK does for a channel called without one.
type CheckResult ¶
type CheckResult struct {
CheckedAt int64
Jobs []JobSummary
Alerts []Alert
Pruned int
}
CheckResult is what a check found and sent.
func (CheckResult) JSValue
deprecated
func (c CheckResult) JSValue() any
JSValue is the result as the SDK writes it.
Deprecated: JSValue is how this module writes the SDK's JSON, and goes in 1.0. Use MarshalJSON, or encoding/json, for the same bytes.
func (CheckResult) MarshalJSON ¶
func (c CheckResult) MarshalJSON() ([]byte, error)
MarshalJSON writes the SDK's JSON.
type Client ¶
type Client struct {
// contains filtered or unexported fields
}
Client watches an app's jobs: it records their runs in a store, judges each one, sends alerts, and runs the checks that find missed and stuck runs. One per app, made once with New. It is safe for use by many goroutines at once.
func New ¶
New makes a client. With no options it keeps everything in memory and writes alerts to the console.
func (*Client) Check ¶
func (c *Client) Check(ctx context.Context) (*CheckResult, error)
Check looks for missed and stuck runs across every job, sends alerts, retries alerts to the channels that refused them, and prunes old runs. Call it from an interval (Start), a cron hitting the dashboard's check route, or by hand. Concurrent calls share one check. A job that cannot be evaluated is reported to the error handler and shown as failing; the error returned is for the store failing as the check starts.
The shared check runs to the end without the first caller's cancellation (its values still reach the store), so a caller that gives up (a request that ended) neither fails the check for the others nor leaves a job half checked; each caller stops waiting when its own ctx ends, with its cause. The shared check has a deadline of its own, ten minutes, so a store that never answers fails it rather than holding it for good.
func (*Client) Close ¶
Close stops the interval, waits for a check already under way (the interval's, or one a caller began: bounded by its own channel, triage, and retry timeouts; what it returns went to whoever started it), then closes the store, so that check neither writes after the store is closed nor loses the alerts it would queue. Call it from outside a check (not from a channel or a source), since it waits for the check to end.
The wait is bounded: a check still under way after its own deadline (a store that ignores its context) is left to fail as the store closes.
func (*Client) CronSecret ¶
CronSecret is the secret job handlers' requests must carry, or "" when none is set.
func (*Client) Declares ¶ added in v0.10.0
Declares reports whether this client declares a job of that name now: it was declared with Job and not forgotten since (the dashboard's forget takes it out). A scheduler integration asks, so a job the scheduler still runs is declared again after a forget.
func (*Client) DefinedJobs ¶
func (c *Client) DefinedJobs() []Definition
DefinedJobs are the definitions declared in this process, in the order they were first declared.
func (*Client) Forget ¶
Forget removes a job and its runs from the store. A job still declared in code comes back, with no history: here on its next run, declared as it was written (Run and ResumeRun by name included), and in any other process that declares it on its next run there, or at that process's next check or dashboard read.
func (*Client) Job ¶
Job declares a job and returns its handle. Call it once, at startup, and keep the handle. Declaring a name again replaces its definition.
Example ¶
Options keep the order they are given in, as the SDK's object literal does, so the stored definition is the same JSON a Node process writes.
package main
import (
"fmt"
"time"
cronwatch "cronwatch.dev/go"
)
func main() {
cw := cronwatch.MustNew()
job := cw.MustJob("sync", cronwatch.Schedule("every 5m"), cronwatch.Timeout(90*time.Second),
cronwatch.Budget("tokens", 5000), cronwatch.Expect("synced"))
out, _ := job.Definition().MarshalJSON()
fmt.Println(string(out))
}
Output: {"schedule":"every 5m","timeout":90000,"budget":{"tokens":5000},"name":"sync","expect":"contains \"synced\""}
func (*Client) JobSummary ¶
JobSummary is one job's summary, or nil when the store does not know it. One declared here and forgotten elsewhere is written again, as a check or a dashboard read writes it.
func (*Client) Jobs ¶
func (c *Client) Jobs(ctx context.Context) ([]JobSummary, error)
Jobs is every job the store knows about, with its health. It sends no alerts.
func (*Client) JobsWithRuns ¶
JobsWithRuns is every job's summary with its newest limit runs (0 to 500), read together. What the dashboard shows.
func (*Client) LogValue ¶ added in v0.10.0
LogValue is what log/slog writes for the client: String's fields.
func (*Client) MustJob ¶
MustJob is Job for package-level declarations: it panics when an option is invalid.
func (*Client) MustRoutes ¶
func (c *Client) MustRoutes(options ...RoutesOption) *Routes
MustRoutes is Routes for a package-level declaration: it panics where Routes returns an error.
func (*Client) RecordRun ¶
func (c *Client) RecordRun(ctx context.Context, input Run, options ...RecordOption) ([]Alert, error)
RecordRun records a run that happened outside this process, for a Source. Its job must be declared with Job first. Runs are keyed by id: a new one is inserted, a stored one still running (or marked timeout by a check) is finished when this one is not running, and anything else is left alone, so recording the same run twice changes nothing. Finishing is conditional: when two processes record the same finish, only the one whose write lands evaluates it, and the other reports it as already finished. A stored run of another job is left alone and reported. A finished run is judged as if it had been wrapped here (expect, failures, duration, budgets) and its output and error are redacted the same way. A status other than the four RunStatus values, and a metric that is not a finite number (as JobContext.Metric refuses it), are refused before anything is written. Returns the alerts it sent.
func (*Client) ReportError ¶
ReportError hands an error to the client's error handler, as the client reports its own. A source uses it.
func (*Client) Routes ¶
func (c *Client) Routes(options ...RoutesOption) (*Routes, error)
Routes is the dashboard and its JSON API as an http.Handler, the SDK's cw.routes(). Mount it under a prefix with http.StripPrefix or a ServeMux pattern:
routes, err := cw.Routes(cronwatch.WithToken(os.Getenv("CRONWATCH_TOKEN")))
mux.Handle("/cronwatch/", routes) // the base is /cronwatch
mux.Handle("/ops/cron/", http.StripPrefix("/ops/cron", routes)) // the base is /ops/cron
The error is for a WithOrigin value that is not an http or https URL.
Example ¶
The dashboard and its JSON API, mounted under a prefix: the base path its links use is found from the mount.
package main
import (
"fmt"
"net/http"
"net/http/httptest"
cronwatch "cronwatch.dev/go"
)
func main() {
cw := cronwatch.MustNew(cronwatch.WithoutCronSecret())
routes, err := cw.Routes(cronwatch.WithToken("a-long-random-token"))
if err != nil {
panic(err)
}
mux := http.NewServeMux()
mux.Handle("/ops/cron/", http.StripPrefix("/ops/cron", routes))
req := httptest.NewRequest("GET", "/ops/cron/api/jobs", nil)
req.Header.Set("Authorization", "Bearer a-long-random-token")
rec := httptest.NewRecorder()
mux.ServeHTTP(rec, req)
fmt.Println(rec.Code, rec.Body.String())
}
Output: 200 {"ok":true,"jobs":[]}
func (*Client) Run ¶
Run runs a job by name without keeping a handle, declaring it on first use (or again, when options are given).
func (*Client) Silence ¶
Silence stops alerts for a job for a while. State keeps updating underneath: nothing opens while it is silenced, so the first problem after the silence alerts as usual.
func (*Client) StartChecking ¶ added in v0.11.0
StartChecking checks on an interval in a goroutine, for long-running servers: the first check a second from now, then every `every` (a minute when 0, five seconds at least). Not for serverless functions, where nothing runs between requests: call Check from a cron there instead. A second call does nothing. (A job's Start opens a run; this starts the checks.)
func (*Client) Stop ¶
func (c *Client) Stop()
Stop stops the interval StartChecking began. A check in flight finishes; Close waits for it.
func (*Client) String ¶ added in v0.10.0
String names the client, how many jobs it declares, and its store's type, and says whether a cron secret is set, never the secret: fmt and loggers print a value's fields otherwise.
func (*Client) SyncJob ¶
SyncJob writes the definition declared in this process under name to the store now, unless the store already holds that definition (whatever order its keys come back in), and says whether it wrote. A run or a check writes a declaration anyway, once; a scheduler integration calls SyncJob so a process that only schedules (and never runs or checks) still puts its jobs where the processes that run and check them read them, and so a definition another process changed since (took the schedule out of, say) is written back. A name not declared here is an error.
type Condition ¶
type Condition string
Condition is something wrong with a job that opens once, alerts, and closes with a recovery.
type Definition ¶
type Definition struct {
// contains filtered or unexported fields
}
Definition is a job's definition as a store holds it: the SDK's JSON object, its fields in the order they were given (defaults, then the job's options, then name), `expect` described in words. Fields a newer writer added are kept.
A store that reads a definition that is not a JSON object (a foreign, hand-edited, or damaged row's) gives the zero Definition: the client reads that job as one it cannot evaluate, reports it, and shows it as failing, and the other jobs carry on.
func DescribeJob ¶
func DescribeJob(name string, options ...JobOption) Definition
DescribeJob is the definition these options give a job, before any client's defaults and without checking them: what a source compares to tell whether a job it declares has changed (the SDK compares the options object's JSON).
func (Definition) Description ¶
func (d Definition) Description() string
Description is the job's description, or "".
func (Definition) Expect ¶
func (d Definition) Expect() string
Expect describes the expect rule ("contains \"done\""), or "".
func (Definition) Get ¶
func (d Definition) Get(key string) (any, bool)
Get is a field as JSON reads it (a string, a float64, a bool, nil, a []any, or a map), and whether it is there.
func (Definition) JSValue
deprecated
func (d Definition) JSValue() any
JSValue is the definition as a JSON object.
Deprecated: JSValue is how this module writes the SDK's JSON, and goes in 1.0. Use MarshalJSON, or encoding/json, for the same bytes.
func (Definition) MarshalJSON ¶
func (d Definition) MarshalJSON() ([]byte, error)
MarshalJSON writes the SDK's JSON.
func (Definition) Schedule ¶
func (d Definition) Schedule() string
Schedule is the cron expression or "every <duration>", or "".
func (Definition) Timezone ¶
func (d Definition) Timezone() string
Timezone is the IANA zone the schedule is read in, or "".
func (*Definition) UnmarshalJSON ¶
func (d *Definition) UnmarshalJSON(b []byte) error
UnmarshalJSON reads a JSON object.
type DeliverMode ¶
type DeliverMode string
DeliverMode says where alerts are sent from.
const ( // DeliverNow sends each alert from the process that produced it. The default. DeliverNow DeliverMode = "now" // DeliverAtCheck sends nothing from this process: each alert is queued // in the store, and the next check in a process that delivers now sends // it (with triage). For a process that records runs but cannot reach // the network, such as a sandboxed backup job. DeliverAtCheck DeliverMode = "check" )
type Duration ¶
Duration is what a duration option takes: text like "15m", "1h30m", "90s", or "2d" (the SDK's form, kept as written in the stored definition), a time.Duration, or a whole or fractional number of milliseconds. A time.Duration is stored as its milliseconds, as the SDK stores a number.
type FailureDetails ¶
type FailureDetails struct {
ConsecutiveFailures int
Threshold int
// contains filtered or unexported fields
}
FailureDetails counts the failures behind a failed or stuck alert.
type HandlerFunc ¶
type HandlerFunc func(ctx context.Context, job *JobContext, w http.ResponseWriter, r *http.Request) error
HandlerFunc is a job's work for one request. ctx carries the request's values with the job's timeout added, but not the request's cancellation: a caller that hangs up (a curl with --max-time, a scheduler's attempt deadline) leaves the job running to its end, as the SDK's handler does. The function may answer the request itself through w, and a status of 400 or more written there fails the run; when it writes nothing, the handler answers with how the run went.
type HandlerOption ¶
type HandlerOption func(*handlerConfig)
HandlerOption configures a job's handler.
func WithSecret ¶
func WithSecret(secret string) HandlerOption
WithSecret is the secret the handler's requests must carry, as `Authorization: Bearer <secret>`, in place of the client's cron secret (WithCronSecret, $CRON_SECRET by default). It is trimmed as JavaScript trims, and one that is empty or only whitespace counts as unset, and the client's is used.
func WithoutSecret ¶
func WithoutSecret() HandlerOption
WithoutSecret lets anyone run the job through the handler (the SDK's `secret: null`), for one behind your own auth, or a function only its platform can invoke.
type Job ¶
type Job struct {
// contains filtered or unexported fields
}
Job is a declared job's handle.
func (*Job) Definition ¶
func (j *Job) Definition() Definition
Definition is the job's definition as it is stored.
func (*Job) Handler ¶
func (j *Job) Handler(fn HandlerFunc, options ...HandlerOption) http.Handler
Handler is the job as an http.Handler (the SDK's job.handler()). A request must send `Authorization: Bearer <secret>` (the scheme in any case, the secret compared in constant time): the handler's WithSecret, else the client's cron secret. With no secret at all it answers 503 and reports it once to the error handler as "handler", unless the environment is development (see WithToken) or WithoutSecret (or the client's WithoutCronSecret) lets anyone in; a wrong or missing bearer is 401. Each request it lets in runs fn as a run with the trigger "handler", answered with {"ok","job","run","status","durationMs"}, 200 when the run was ok and 500 when it failed, with the error's first line as "error" for a caller who sent the secret. A function that wrote its own answer is answered with that, and a status of 400 or more it wrote fails the run. A panic in fn is a failed run like an error (`panic: <value>`), answered 500 as the SDK answers a throw; a panic with http.ErrAbortHandler, or one after fn began its own answer, is recorded the same way and then aborts the response, as net/http does.
mux.Handle("POST /api/cron/nightly", nightly.Handler(func(ctx context.Context, job *cronwatch.JobContext, w http.ResponseWriter, r *http.Request) error {
path, err := buildReport(ctx)
job.Log("Report written:", path)
return err
}))
Example ¶
A job run by a platform cron that calls a URL with the cron secret.
package main
import (
"context"
"encoding/json"
"fmt"
"net/http"
"net/http/httptest"
cronwatch "cronwatch.dev/go"
)
func main() {
cw := cronwatch.MustNew(cronwatch.WithCronSecret("the-cron-secret"))
nightly := cw.MustJob("nightly-report")
handler := nightly.Handler(func(ctx context.Context, job *cronwatch.JobContext, w http.ResponseWriter, r *http.Request) error {
job.Log("Report written")
return nil
})
req := httptest.NewRequest("POST", "/api/cron/nightly", nil)
req.Header.Set("Authorization", "Bearer the-cron-secret")
rec := httptest.NewRecorder()
handler.ServeHTTP(rec, req)
var answer struct {
OK bool `json:"ok"`
Status string `json:"status"`
}
_ = json.Unmarshal(rec.Body.Bytes(), &answer)
fmt.Println(rec.Code, answer.OK, answer.Status)
}
Output: 200 true ok
func (*Job) Resume ¶
Resume is a handle on a run this job started elsewhere, by its id, so this process can log to it and finish it.
func (*Job) Run ¶
Run runs fn now as a recorded run and returns its error. The run is recorded however the store is doing: store errors go to the error handler, never to the caller. A panic in fn is recorded as a failed run and then carries on up the stack.
func (*Job) Start ¶
Start records a running run now, to finish later with the handle, perhaps from another process (see Resume). Store failures go to the error handler; it returns an error only for an invalid run id or one that belongs to another job. A run that is never finished is marked stuck by the first check after the job's timeout.
Example ¶
A run that starts in one call and ends in another, as the README shows: work handed to a queue, a webhook that reports back later.
package main
import (
"context"
"fmt"
cronwatch "cronwatch.dev/go"
)
func main() {
ctx := context.Background()
cw := cronwatch.MustNew(cronwatch.WithoutCronSecret())
nightly := cw.MustJob("nightly-report")
h, err := nightly.Start(ctx, cronwatch.WithRunID("delivery-42"))
if err != nil {
panic(err)
}
// ... later, perhaps in another process:
h, err = nightly.Resume(ctx, "delivery-42")
if err != nil {
panic(err)
}
h.Log("done")
run := h.Finish(ctx)
fmt.Println(run.ID, run.Status, *run.Output)
}
Output: delivery-42 ok done
type JobContext ¶
type JobContext struct {
// contains filtered or unexported fields
}
JobContext is what a job's function gets: its run, and where it logs output and reports metrics.
func Current ¶
func Current(ctx context.Context) *JobContext
Current is the job context of the run ctx belongs to, or nil outside one. A nil *JobContext is safe to use: it logs and reports nothing, and has no name, run, or start, so code shared with work run outside a job (a gocron task, say) may call Current(ctx).Log without a check.
func (*JobContext) Log ¶
func (j *JobContext) Log(parts ...any)
Log appends a line of output: the parts joined by spaces, strings as they are, errors as "Name: message", anything else as JSON. Kept with the run (the last 16 KB), shown in alerts and on the dashboard.
func (*JobContext) Metric ¶
func (j *JobContext) Metric(name string, value float64) error
Metric reports a number for this run: tokens, cost, rows, anything. Watched against budgets and baselines. A later value for the same name replaces an earlier one.
func (*JobContext) Metrics ¶
func (j *JobContext) Metrics(values Metrics) error
Metrics reports several numbers at once.
func (*JobContext) StartedAt ¶
func (j *JobContext) StartedAt() int64
StartedAt is when the run started, in epoch milliseconds.
type JobFunc ¶
type JobFunc func(ctx context.Context, job *JobContext) error
JobFunc is a job's work. ctx is cancelled when the job's timeout passes (and when the caller's context is); job logs output and reports metrics. A returned error fails the run and is returned from Run.
type JobOption ¶
type JobOption func(*jobConfig)
JobOption configures a job: when it runs and what counts as trouble.
func Budget ¶
Budget sets a ceiling for a metric reported with Metric: Budget("cost", 2) alerts when a run reports cost above 2. Give it once per metric. Metrics without a ceiling alert when a run reports more than three times the recent median, once there are five runs to compare against.
func Description ¶
Description describes the job on the dashboard.
func Expect ¶
Expect makes a successful run fail unless its output contains text. Catches the job that exits cleanly and did nothing.
func ExpectFunc ¶
ExpectFunc makes a successful run fail unless fn returns true for its output. A panic in fn fails the run with what it panicked with.
func ExpectMatch ¶
ExpectMatch makes a successful run fail unless the regular expression matches its output.
func FailuresBeforeAlert ¶
FailuresBeforeAlert alerts on the nth consecutive failure rather than the first. Default 1.
func Floor ¶ added in v0.12.0
Floor sets a floor for a metric reported with Metric: Floor("rows", 1) alerts when a run reports rows below 1. Give it once per metric. Metrics without a floor alert when a run reports 0 or less and the five to twenty successful runs before it all reported more than 0. Catches the job that ran cleanly and wrote nothing. A floor of 0 lets a metric reach 0 without alerting.
func MaxDuration ¶
MaxDuration alerts when a successful run takes longer. Without it, a run is slow when it takes more than twice the p95 of recent runs (and over 10s), once there are five runs to compare against.
func Schedule ¶
Schedule is when the job is supposed to run: a five or six field cron expression ("0 2 * * *"), a nickname ("@hourly"), or an interval ("every 5m"). Leave it out for a job with no fixed cadence: failures, duration, and budgets are still watched, but nothing is ever missed.
type JobState ¶
type JobState struct {
Job string
// Conditions currently open, in the order they opened.
Open []OpenCondition
ConsecutiveFailures int
SilencedUntil *int64
// When an alert last reached at least one channel.
LastAlertAt *int64
// Conditions that alerted and have since closed, waiting for the
// recovered alert the next successful run sends. Nil is a state written
// before the field existed (absent from the JSON).
PendingRecovery []Condition
// Alerts some channel still owes, each retried once per check to those
// channels only, their runs trimmed. Nil is absent.
Undelivered []Alert
// The outbox: alerts written with the state that opened their
// condition, while the process that wrote them sends them. Each leaves
// once that process records how the send went; one still here after its
// Until (the process stopped part way) goes to Undelivered at the next
// check. Nil is absent, as it is when empty and in state written before
// the field existed.
Sending []SendingAlert
// The metrics under their floor at the job's last successful run (see
// floorBreaches). Nil is absent, as it is when none and in state
// written before the field existed.
UnderFloor []string
// The ids of runs started and not yet judged, oldest first (see
// holdRun). A run leaves when its finish is evaluated; one a check finds
// finished and still here (the process that finished it stopped before
// writing its evaluation) is evaluated by that check. Nil is absent, as
// it is when empty and in state written before the field existed.
PendingRuns []string
// Goes up by one on every write (see Store.CompareAndSetState). Nil is
// a state written before versions, which counts as 0, as does a
// version outside 0 to 2^53 - 1. One that is not a whole number (a
// foreign row's 1.5 or "x") reads as nil.
Version *int64
// contains filtered or unexported fields
}
JobState is what the checks remember about a job between runs.
func (JobState) MarshalJSON ¶
MarshalJSON writes the SDK's JSON.
func (*JobState) UnmarshalJSON ¶
UnmarshalJSON reads the SDK's JSON.
type JobSummary ¶
type JobSummary struct {
Name string
Definition Definition
Health JobHealth
Open []Condition
LastRun *Run
// When the schedule says the next run is due. Nil without a schedule.
NextExpectedAt *int64
ConsecutiveFailures int
SilencedUntil *int64
Stats Stats
}
JobSummary is a job and its health, as the dashboard shows it.
func (JobSummary) JSValue
deprecated
func (s JobSummary) JSValue() any
JSValue is the summary as the SDK writes it.
Deprecated: JSValue is how this module writes the SDK's JSON, and goes in 1.0. Use MarshalJSON, or encoding/json, for the same bytes.
func (JobSummary) MarshalJSON ¶
func (s JobSummary) MarshalJSON() ([]byte, error)
MarshalJSON writes the SDK's JSON.
type JobWithRuns ¶
type JobWithRuns struct {
Job JobSummary
Runs []Run
}
JobWithRuns is a job's summary and its newest runs.
type LambdaEvent ¶
type LambdaEvent struct {
Version string `json:"version,omitempty"`
// 1.0
HTTPMethod string `json:"httpMethod,omitempty"`
Path string `json:"path,omitempty"`
MultiValueHeaders map[string][]string `json:"multiValueHeaders,omitempty"`
QueryStringParameters map[string]string `json:"queryStringParameters,omitempty"`
MultiValueQueryStringParameters map[string][]string `json:"multiValueQueryStringParameters,omitempty"`
// 2.0
RawPath string `json:"rawPath,omitempty"`
RawQueryString string `json:"rawQueryString,omitempty"`
Cookies []string `json:"cookies,omitempty"`
// Both
Headers map[string]string `json:"headers,omitempty"`
RequestContext LambdaRequestContext `json:"requestContext"`
Body string `json:"body,omitempty"`
IsBase64Encoded bool `json:"isBase64Encoded,omitempty"`
}
LambdaEvent is the request API Gateway (a REST API's payload format 1.0, an HTTP API's 1.0 or 2.0) or a function URL (2.0) hands a function: the fields an http.Handler needs of either format.
type LambdaRequestContext ¶
type LambdaRequestContext struct {
DomainName string `json:"domainName,omitempty"`
// 1.0's method.
HTTPMethod string `json:"httpMethod,omitempty"`
// 2.0's method and path.
HTTP struct {
Method string `json:"method,omitempty"`
Path string `json:"path,omitempty"`
} `json:"http"`
}
LambdaRequestContext is what a LambdaEvent says of where it came from.
type LambdaResult ¶
type LambdaResult struct {
StatusCode int `json:"statusCode"`
Headers map[string]string `json:"headers,omitempty"`
MultiValueHeaders map[string][]string `json:"multiValueHeaders,omitempty"`
Cookies []string `json:"cookies,omitempty"`
Body string `json:"body"`
IsBase64Encoded bool `json:"isBase64Encoded"`
}
LambdaResult is the proxy result a function answers with. Only the keys the format knows are written, since a REST API refuses a result with others: a header given more than once is in MultiValueHeaders for 1.0, and Set-Cookie in Cookies for 2.0.
type MemoryStore ¶
type MemoryStore struct {
// contains filtered or unexported fields
}
MemoryStore keeps everything in process memory (stores/memory.ts). The default when no store is given, good for tests and for trying the library out. State is gone on restart, so a missed run cannot be noticed across one. Safe for use by many goroutines at once.
func NewMemoryStore ¶
func NewMemoryStore() *MemoryStore
NewMemoryStore is an empty in-memory store.
func (*MemoryStore) CompareAndSetState ¶
func (*MemoryStore) DeleteJob ¶
func (m *MemoryStore) DeleteJob(_ context.Context, name string) error
func (*MemoryStore) DeleteRunIf ¶
func (*MemoryStore) InsertRun ¶
func (m *MemoryStore) InsertRun(_ context.Context, run Run) error
InsertRun refuses an id already recorded, like SQL's primary key.
func (*MemoryStore) ListJobs ¶
func (m *MemoryStore) ListJobs(context.Context) ([]StoredJob, error)
ListJobs is every job by name in byte order, as the SQL stores sort.
func (*MemoryStore) Prune ¶
Prune keeps each job's newest run whatever its age: without it, a job that runs less often than the retention looks like it never ran.
func (*MemoryStore) RunningRuns ¶
func (m *MemoryStore) RunningRuns(context.Context) ([]Run, error)
func (*MemoryStore) SetState ¶
func (m *MemoryStore) SetState(_ context.Context, state JobState) error
func (*MemoryStore) UpdateRun ¶
func (m *MemoryStore) UpdateRun(_ context.Context, run Run) error
UpdateRun changes only the finish's fields; a run that is gone (its job was forgotten) stays gone, as SQL's UPDATE has it.
func (*MemoryStore) UpdateRunIf ¶
func (*MemoryStore) UpsertJob ¶
func (m *MemoryStore) UpsertJob(_ context.Context, def Definition, now int64) error
type Metrics ¶
type Metrics []Metric
Metrics are a run's numbers in JavaScript's key order: names that are array indices ("10", "200") first in ascending order, then the rest in the order they were first reported. Budgets use the same type.
func (Metrics) MarshalJSON ¶
MarshalJSON writes the SDK's JSON.
func (*Metrics) Set ¶
Set reports a value for name: a later value replaces an earlier one in its place, and a new name takes its place in JavaScript's order.
func (*Metrics) UnmarshalJSON ¶
UnmarshalJSON reads a JSON object of numbers.
type MissedDetails ¶
MissedDetails says which run was missed.
type OpenCondition ¶
OpenCondition is a condition that is open, and when it opened.
type Option ¶
Option configures a client.
func WithAlerts ¶
WithAlerts is where alerts go, replacing the default console channel. WithAlerts() with none sends nowhere.
func WithCronSecret ¶
WithCronSecret is the shared secret job handlers' requests must carry. The default is $CRON_SECRET, trimmed, which counts as unset when it is empty or only whitespace. A secret given here is trimmed too (as JavaScript trims), and one that is empty or only whitespace means none, with no fallback to $CRON_SECRET.
func WithDefaults ¶
WithDefaults applies Grace, Timeout, Timezone, and FailuresBeforeAlert to every job that does not set its own.
func WithDeliver ¶
func WithDeliver(mode DeliverMode) Option
WithDeliver sets where alerts are sent from: DeliverNow (the default) or DeliverAtCheck.
func WithErrorHandler ¶
WithErrorHandler is called with anything that goes wrong outside a job: the store failing, an alert channel failing, a triage timeout. where says what was being done ("recording nightly", "alert channel slack"). The default writes to standard error.
func WithRedact ¶
WithRedact replaces the default redaction (see RedactSecrets) of every run's output and error before it is stored, shown, or sent anywhere. A redact function that panics is reported to the error handler and the default is used.
func WithRetention ¶
WithRetention is how long finished runs are kept. Default "30d".
func WithSources ¶
WithSources adds sources of runs this process does not wrap (the pg_cron source). Each is synced at the start of every check; one that fails is reported to the error handler and the check carries on.
func WithStore ¶
WithStore is where jobs, runs, and state live. The default is a MemoryStore, which forgets on restart.
func WithTriage ¶
func WithTriage(fn TriageFunc) Option
WithTriage adds a short diagnosis to every alert except recoveries.
func WithoutCronSecret ¶
func WithoutCronSecret() Option
WithoutCronSecret lets job handlers run without a secret.
func WithoutRedaction ¶
func WithoutRedaction() Option
WithoutRedaction keeps output and errors exactly as logged.
type OverBudgetDetails ¶
type OverBudgetDetails struct {
Breaches []BudgetBreach
}
OverBudgetDetails lists the metrics over their limits.
type RecordOption ¶
type RecordOption func(*recordConfig)
RecordOption configures RecordRun.
func WithoutEvaluation ¶
func WithoutEvaluation() RecordOption
WithoutEvaluation stores the run without judging it, for history imported on first sight.
type RecoveredDetails ¶
RecoveredDetails names the conditions that closed. Reason "unscheduled" closes missed alone because the job no longer has a schedule; Since is when missed opened.
type Routes ¶
type Routes struct {
// contains filtered or unexported fields
}
Routes is the dashboard and JSON API, an http.Handler made by Client.Routes.
func (*Routes) LogValue ¶ added in v0.10.0
LogValue is what log/slog writes for the routes: String's fields.
func (*Routes) ServeHTTP ¶
func (rt *Routes) ServeHTTP(w http.ResponseWriter, r *http.Request)
ServeHTTP answers one request as the SDK's routes answer it. A store failure (or a panic) is reported to the client's error handler as "routes" and answered 500.
type RoutesOption ¶
type RoutesOption func(*routesConfig)
RoutesOption configures the dashboard (Client.Routes).
func WithBasePath ¶
func WithBasePath(path string) RoutesOption
WithBasePath is where the dashboard is mounted ("" for the root), so its links resolve. Without it the base is found from the request: what http.StripPrefix took off, else the literal part of the ServeMux pattern that matched (Go 1.22's "/cronwatch/" or "/cronwatch/{path...}"), else DefaultBasePath.
func WithOrigin ¶
func WithOrigin(origin string) RoutesOption
WithOrigin is the public origin the dashboard is served from, such as "https://app.example.com", for an app behind a proxy whose requests carry an internal host or scheme. It stands in for the request's own origin in the cross-site check on writes, the sign-in cookie's Secure flag, the Referer the redirect back after a form follows, and the development sign-in line. Anything that is not an http or https URL is an error from Routes. It takes precedence over WithTrustProxy.
func WithToken ¶
func WithToken(token string) RoutesOption
WithToken is the token the dashboard asks for. Send it as `Authorization: Bearer <token>`, or open the dashboard once with ?token=<token> and a cookie is set, or sign in through the form on the sign-in page. The default is CRONWATCH_TOKEN; a token, given here or in the variable, is trimmed as JavaScript trims, and one that is empty or only whitespace counts as unset. With no token in development (CRONWATCH_ENV, APP_ENV, or GO_ENV naming it), the routes make a random one and print a sign-in link to Stdout on their first request; with no token otherwise they answer 503. /api/check also takes the client's cron secret as a bearer, so a platform cron can run checks without the token.
func WithTrustProxy ¶
func WithTrustProxy() RoutesOption
WithTrustProxy takes the public origin from X-Forwarded-Proto and X-Forwarded-Host (the first value of each, the request's own scheme or host for whichever is missing) when a request carries either. Only for an app whose proxy sets or overwrites both headers: a client can send them too.
func WithoutToken ¶
func WithoutToken() RoutesOption
WithoutToken serves the dashboard to anyone, everywhere, for one behind your own auth (the SDK's `token: null`).
type Run ¶
type Run struct {
ID string
Job string
Status RunStatus
StartedAt int64
FinishedAt *int64
DurationMs *int64
Error *string
// Lines logged, or the string the job returned. Capped at 16 KB.
Output *string
Metrics Metrics
// What started the run: "run", "handler", "start", or a value of yours.
Trigger string
}
Run is one execution of a job, as a store keeps it. Times are epoch milliseconds.
func (Run) MarshalJSON ¶
MarshalJSON writes the SDK's JSON.
func (*Run) UnmarshalJSON ¶
UnmarshalJSON reads the SDK's JSON.
type RunDeleter ¶
type RunDeleter interface {
// DeleteRunIf deletes the run id only when its stored job is job and its
// status is status (SQL: DELETE ... WHERE id = ? AND job = ? AND status
// = ?), and says whether it deleted.
DeleteRunIf(ctx context.Context, id, job string, status RunStatus) (bool, error)
}
RunDeleter is a store that can take back a run it recorded, only while the run is still of one job and in one status. The SDK has no counterpart: it is how an attempt a queue gave back without failing (a River job that snoozed or cancelled itself, an Asynq task revoked) leaves no run behind, neither a failure nor a success (see DiscardWhen), as the PHP port's stores take back a released Laravel job's attempt.
type RunHandle ¶
type RunHandle struct {
// contains filtered or unexported fields
}
RunHandle is a run recorded by Start or found by Resume, to finish later. Lines and metrics wait in the handle until Flush or Finish merges them onto a fresh read of the stored run. It is safe for use by many goroutines at once; Flush and Finish take their turns.
func (*RunHandle) Active ¶
Active is false once finished, and from the start for a resumed run that already finished or does not exist.
func (*RunHandle) Fail ¶
Fail finishes the run as failed with err, written like an error a Run function returned.
func (*RunHandle) Finish ¶
Finish finishes the run successfully (unless an expect rule says otherwise), judges it like any other, and sends what that produces. It returns the run as recorded, or nil when nothing was recorded: the run was already finished (here or elsewhere), was not found, or belongs to another job, which is reported to the error handler. When several processes finish one run, only the one whose write lands judges it. A store that fails is reported, nothing is recorded, and the handle stays active so Finish can be called again.
func (*RunHandle) FinishWith ¶
FinishWith finishes the run with a result, treated like the value a RunValue function returns: a string is the output when nothing was logged (and what an expect rule checks), and an *http.Response of 400 or more fails the run.
func (*RunHandle) Flush ¶
Flush appends the lines and metrics added so far to the stored run, which must still be running and belong to this job. A read, change, and write of the run's row, written only while it is still running: two processes appending to one run at the same moment can lose one's lines, but a flush never undoes a finish. Problems go to the error handler.
func (*RunHandle) Log ¶
Log adds a line of output, kept in the handle until Flush or Finish. It holds the handle's lock as it writes, so a line logged as a Flush takes the recorder goes to the one taken or to its successor, never to one already read.
type RunOption ¶
type RunOption func(*runConfig)
RunOption configures one run.
func DiscardWhen ¶
DiscardWhen takes a run back rather than judging it when the function returns an error discard answers true for: an attempt a queue gives back without failing, such as a River job that snoozes itself. The run's row is deleted while it is still running (through the store's RunDeleter), no alert is sent, the job's failures in a row are left as they were, and the error is still returned. A row a check already marked stuck is left as it is, and a store that is not a RunDeleter records the run as it ended; both are reported to the error handler as "discarding <job>". The SDK has no counterpart; the PHP port takes back a released Laravel job's attempt the same way. Run and RunValue only.
func WithRunID ¶
WithRunID gives Start your own stable id for the run, such as a queue's job id: 1 to 200 characters, not starting with "pgcron:" (the pg_cron source's). A start with an id already recorded for this job records nothing and returns a handle on that run instead; an id recorded for another job is an error. Start only.
func WithTrigger ¶
WithTrigger names what started the run. The default is "run" for Run and "start" for Start.
type RunUpdater ¶
type RunUpdater interface {
// UpdateRunIf writes the run as UpdateRun does, only when its stored
// status is one of from, in one step (SQL: UPDATE ... WHERE id = ? AND
// status IN (...)), and says whether it wrote. This is what lets exactly
// one of several processes finishing the same run evaluate it.
UpdateRunIf(ctx context.Context, run Run, from []RunStatus) (bool, error)
}
RunUpdater is a store that can finish a run conditionally. Without it the client reads then writes, which is safe only when one process at a time finishes a given run.
type SendingAlert ¶ added in v0.10.0
type SendingAlert struct {
// Until is when the sender's lease runs out, in epoch milliseconds.
Until int64
// Alert is the alert as composed, without triage (never stored here).
Alert Alert
// contains filtered or unexported fields
}
SendingAlert is an alert in JobState.Sending.
type SlowDetails ¶
type SlowDetails struct {
DurationMs int64
ThresholdMs float64
// "maxDuration", or how the baseline was worked out.
Basis string
}
SlowDetails says how slow a run was.
type Source ¶
type Source interface {
Name() string
// Sync declares the jobs and records their new runs, returning the
// alerts recording them sent.
Sync(ctx context.Context, host SourceHost) ([]Alert, error)
}
Source is where runs this process does not wrap come from, such as pg_cron's jobs. Check syncs each one first, so what it records is evaluated in the same check.
type SourceHost ¶
type SourceHost interface {
Job(name string, options ...JobOption) (*Job, error)
RecordRun(ctx context.Context, run Run, options ...RecordOption) ([]Alert, error)
Store() Store
Now() int64
ReportError(err error, where string)
}
SourceHost is what a Source may use of the client. A *Client is one.
type StateComparer ¶
type StateComparer interface {
// CompareAndSetState writes state only when the stored state's version
// (absent, or no row at all, counts as 0) equals expected, and says
// whether it wrote. The client reads, works out the next state, and on
// a refused write reads again.
CompareAndSetState(ctx context.Context, state JobState, expected int64) (bool, error)
}
StateComparer is a store that can write a job's state conditionally. Without it the client writes unconditionally, which is safe only when one process at a time writes a job's state.
type Stats ¶
Stats summarize a job's last twenty runs of any status; the percentiles are over the successful ones among them.
type Store ¶
type Store interface {
// Init is called once before first use: create tables here.
Init(ctx context.Context) error
UpsertJob(ctx context.Context, definition Definition, now int64) error
// GetJob is nil when the store does not know the job.
GetJob(ctx context.Context, name string) (*StoredJob, error)
// ListJobs is every job, by name in byte order.
ListJobs(ctx context.Context) ([]StoredJob, error)
// DeleteJob removes a job, its runs, and its state.
DeleteJob(ctx context.Context, name string) error
// InsertRun refuses an id already stored.
InsertRun(ctx context.Context, run Run) error
// UpdateRun writes a run's status, finish, duration, error, output, and
// metrics. A run that is gone stays gone.
UpdateRun(ctx context.Context, run Run) error
// GetRun is nil when there is no such run.
GetRun(ctx context.Context, id string) (*Run, error)
// ListRuns is a job's newest runs first, at most limit of them.
ListRuns(ctx context.Context, job string, limit int) ([]Run, error)
LastRun(ctx context.Context, job string) (*Run, error)
// RunningRuns is every run still running, oldest first.
RunningRuns(ctx context.Context) ([]Run, error)
// GetState is nil when the job has no state yet.
GetState(ctx context.Context, job string) (*JobState, error)
// SetState writes a job's state unconditionally. Used only when the
// store is not a StateComparer.
SetState(ctx context.Context, state JobState) error
// Prune deletes finished runs that started before this time, keeping
// each job's newest run, and returns how many.
Prune(ctx context.Context, before int64) (int, error)
Close() error
}
Store is where jobs, runs, and state live. MemoryStore is one; the sqlstore package keeps them in the app's own database. A store of your own should pass the storetest package's contract test.
type StoredJob ¶
type StoredJob struct {
Name string
Definition Definition
CreatedAt int64
UpdatedAt int64
}
StoredJob is a job as a store knows it.
type TriageContext ¶
TriageContext is what a triage function is given.
type TriageFunc ¶
type TriageFunc func(ctx context.Context, tc TriageContext) (string, error)
TriageFunc diagnoses an alert in a few sentences, or answers "" for no diagnosis. ctx ends when the client stops waiting (25 seconds); pass it to any request made.
type UnderFloorDetails ¶ added in v0.12.0
type UnderFloorDetails struct {
Breaches []BudgetBreach
}
UnderFloorDetails lists the metrics under their floors. A breach's Limit is the floor, or for a metric without one the lowest of the earlier runs it was judged against.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package alerts holds the SDK's alert channels, request for request, on net/http alone: Slack, Discord, and a signed Webhook; the email providers Resend, Postmark, Sendgrid, Mailgun, and SES (signed with SigV4, no AWS SDK); Twilio for SMS; and the trackers Sentry, Honeybadger, Datadog, Rollbar, Bugsnag, and NewRelic.
|
Package alerts holds the SDK's alert channels, request for request, on net/http alone: Slack, Discord, and a signed Webhook; the email providers Resend, Postmark, Sendgrid, Mailgun, and SES (signed with SigV4, no AWS SDK); Twilio for SMS; and the trackers Sentry, Honeybadger, Datadog, Rollbar, Bugsnag, and NewRelic. |
|
asynq
module
|
|
|
Package bridge is what the scheduler integrations share: the robfigcron, gocron, river, and asynq modules beside this one, each a module of its own.
|
Package bridge is what the scheduler integrations share: the robfigcron, gocron, river, and asynq modules beside this one, each a module of its own. |
|
gocron
module
|
|
|
internal
|
|
|
clientreplay
Package clientreplay replays conformance/client.json's unknownFields through the client's public API over a store, so the package's tests replay it over the memory store and sqltest's over the SQL store on each dialect.
|
Package clientreplay replays conformance/client.json's unknownFields through the client's public API over a store, so the package's tests replay it over the memory store and sqltest's over the SQL store on each dialect. |
|
js
Package js holds what the port needs of JavaScript's own behaviour, so that every value the SDK writes, compares, or counts is written, compared, and counted the same way here: numbers as Number.prototype.toString prints them, JSON.stringify and JSON.parse (objects keep JavaScript's key order), string lengths and cuts in UTF-16 code units, the characters \s matches, and Date's calendar arithmetic.
|
Package js holds what the port needs of JavaScript's own behaviour, so that every value the SDK writes, compares, or counts is written, compared, and counted the same way here: numbers as Number.prototype.toString prints them, JSON.stringify and JSON.parse (objects keep JavaScript's key order), string lengths and cuts in UTF-16 code units, the characters \s matches, and Date's calendar arithmetic. |
|
jsre
Package jsre is a small backtracking regular expression engine with JavaScript's semantics, for the SDK's secret redaction patterns.
|
Package jsre is a small backtracking regular expression engine with JavaScript's semantics, for the SDK's secret redaction patterns. |
|
output
Package output is the SDK's output.ts and the recorder of job.ts: the output cap, error text, secret redaction, and the lines and metrics a run collects.
|
Package output is the SDK's output.ts and the recorder of job.ts: the output cap, error text, secret redaction, and the lines and metrics a run collects. |
|
post
Package post is the one POST the alert channels and Claude triage make, as the SDK makes it with fetch (alerts/shared.ts): the URL cleaned and read as fetch reads it, only http and https, headers checked as fetch checks them, one ten second deadline for the whole request, a redirect refused rather than followed, at most 1 MiB of an answer read (64 KiB of an error answer), and an error that names only the URL's origin, with every secret the caller holds cut out of a quoted answer before it is cut to 200 characters.
|
Package post is the one POST the alert channels and Claude triage make, as the SDK makes it with fetch (alerts/shared.ts): the URL cleaned and read as fetch reads it, only http and https, headers checked as fetch checks them, one ten second deadline for the whole request, a redirect refused rather than followed, at most 1 MiB of an answer read (64 KiB of an error answer), and an error that names only the URL's origin, with every secret the caller holds cut out of a quoted answer before it is cut to 200 characters. |
|
schedule
Package schedule is the SDK's duration.ts and schedule.ts: durations ("15m", "1h30m", a number of milliseconds) parsed and written as the SDK does, and schedules ("0 2 * * *", "@hourly", "every 5m") with their fire times, due times, deadlines, and what a run covers.
|
Package schedule is the SDK's duration.ts and schedule.ts: durations ("15m", "1h30m", a number of milliseconds) parsed and written as the SDK does, and schedules ("0 2 * * *", "@hourly", "every 5m") with their fire times, due times, deadlines, and what a run covers. |
|
schedule/cron
Package cron is a port of croner 10, the cron library the SDK uses: its reading of an expression (CronPattern, with its checks and its messages word for word) and its walk to the next matching time (CronDate), habits included: a day the month does not have rolls over, a wall-clock time in a spring-forward gap moves forward by the gap, and a time that happens twice is the earlier one.
|
Package cron is a port of croner 10, the cron library the SDK uses: its reading of an expression (CronPattern, with its checks and its messages word for word) and its walk to the next matching time (CronDate), habits included: a day the month does not have rolls over, a wall-clock time in a spring-forward gap moves forward by the gap, and a time that happens twice is the earlier one. |
|
webserver
command
Command webserver serves the dashboard over HTTP for packages/mcp/test/go-web.test.ts, which drives @cronwatch/mcp against it.
|
Command webserver serves the dashboard over HTTP for packages/mcp/test/go-web.test.ts, which drives @cronwatch/mcp against it. |
|
Package pgcron watches pg_cron jobs, which run inside Postgres where nothing can wrap them (sources/pgcron.ts).
|
Package pgcron watches pg_cron jobs, which run inside Postgres where nothing can wrap them (sources/pgcron.ts). |
|
river
module
|
|
|
robfigcron
module
|
|
|
Package sqlstore keeps CronWatch's jobs, runs, and state in the app's own database through database/sql: SQLite, Postgres, or MySQL (and MariaDB).
|
Package sqlstore keeps CronWatch's jobs, runs, and state in the app's own database through database/sql: SQLite, Postgres, or MySQL (and MariaDB). |
|
Package storetest is the test every CronWatch store passes: the memory store, and the sqlstore package on SQLite, Postgres, and MySQL.
|
Package storetest is the test every CronWatch store passes: the memory store, and the sqlstore package on SQLite, Postgres, and MySQL. |
|
Package triage holds Claude triage (triage/anthropic.ts), over plain HTTP: the Messages API is one POST, so no Anthropic SDK is needed.
|
Package triage holds Claude triage (triage/anthropic.ts), over plain HTTP: the Messages API is one POST, so no Anthropic SDK is needed. |