Telemetry - gonace/Cronitor GitHub Wiki
Report when your jobs run, complete or fail (Telemetry API).
Telemetry events report the state of a job to a monitor. A monitor that doesn't exist yet is created by its first event.
- Getting a client
- Events
- Using block
- Run, Complete and Fail
- Tick
- Environment
- BackgroundService
- Delivery and errors
- Custom commands
Telemetry is sent through an ITelemetriesClient: from the static CronitorClient, injected after .ConfigureCronitor(), or created with new TelemetriesClient("apiKey"). See Setup for each. The static class also has the telemetry methods directly:
await CronitorClient.TickAsync("monitorKey");
// or
await telemetriesClient.TickAsync("monitorKey");Each event has a sync and an async (…Async) method, on both ITelemetriesClient and the static CronitorClient:
| Method | Event |
|---|---|
Start / StartAsync
|
Reports run and returns an ITelemetryRun, see Using block
|
Run / RunAsync
|
The job has started |
Complete / CompleteAsync
|
The job has finished successfully |
Fail / FailAsync
|
The job has failed |
Tick / TickAsync
|
A heartbeat, for monitors without a start and end |
Ping / PingAsync
|
Sends a custom Command, see Custom commands
|
Run, Complete, Fail and Tick take the same parameters:
Task<bool> RunAsync(MonitorKey monitorKey, string message = null, string environment = null);Start takes a monitor key and an optional environment:
Task<ITelemetryRun> StartAsync(MonitorKey monitorKey, string environment = null);A monitor key is a MonitorKey, and a string converts to one, so RunAsync("monitorKey") works as before. It follows Cronitor's rules for keys: it can't be blank, contain /, be longer than 100 Unicode characters (an emoji counts as one), or start or end with whitespace, and it must be valid Unicode. An invalid key throws an ArgumentException before anything is sent, since Cronitor would otherwise drop the event without telling.
Start reports the run and returns an ITelemetryRun. Call Complete when the job succeeds; if the run is disposed without it (for example because the job threw an exception), it's reported as failed.
using (var run = await CronitorClient.StartAsync("monitorKey"))
{
// ... do the work
await run.CompleteAsync();
}To report a failure with a reason, call Fail instead:
using (var run = await telemetriesClient.StartAsync("monitorKey", environment: "production"))
{
var exported = Export();
if (exported == 0)
{
await run.FailAsync("Nothing to export");
return;
}
await run.CompleteAsync($"Exported {exported} rows");
}ITelemetryRun has these methods; each takes an optional message:
| Method | Event |
|---|---|
Complete / CompleteAsync
|
The run has finished successfully |
Fail / FailAsync
|
The run has failed |
Dispose |
Reports fail if the run was neither completed nor failed |
- Every run gets its own
series, so overlapping runs of the same monitor are matched correctly. - A run is finished once: after
CompleteorFail, further calls send nothing and returnfalse. -
Disposenever throws, so it can't hide an exception thrown by the job itself.
Without a using block, report each event yourself:
try
{
await CronitorClient.RunAsync("monitorKey");
// ... do the work
await CronitorClient.CompleteAsync("monitorKey", "Done");
}
catch (Exception ex)
{
await CronitorClient.FailAsync("monitorKey", ex.Message);
throw;
}Unlike the using block, these events have no series, so if runs of the same monitor overlap Cronitor can't tell which complete belongs to which run. Use a custom command with WithSeries if you need that.
Tick is a heartbeat: one event per execution, for monitors that only need to know a job is alive.
await telemetriesClient.TickAsync("monitorKey");Every event takes an optional environment, for monitors that run in more than one environment (e.g. staging and production). Alerting can be configured per environment.
await CronitorClient.RunAsync("monitorKey", environment: "staging");
await CronitorClient.CompleteAsync("monitorKey", "Done", "staging");
using (var run = await CronitorClient.StartAsync("monitorKey", "staging"))
{
await run.CompleteAsync();
}A hosted service that runs a job on an interval, reported with a using block. Register the clients with .ConfigureCronitor() and the service with AddHostedService:
using Cronitor.Extensions;
await Host.CreateDefaultBuilder()
.ConfigureCronitor((context) => context.Configuration.GetValue<string>("Cronitor:ApiKey"))
.ConfigureServices(services => services.AddHostedService<CleanupService>())
.Build()
.RunAsync();public class CleanupService : BackgroundService
{
private readonly ITelemetriesClient _telemetriesClient;
public CleanupService(ITelemetriesClient telemetriesClient)
{
_telemetriesClient = telemetriesClient;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
using var timer = new PeriodicTimer(TimeSpan.FromMinutes(15));
while (await timer.WaitForNextTickAsync(stoppingToken))
{
using (var run = await _telemetriesClient.StartAsync("cleanup"))
{
try
{
// ... do the work
await run.CompleteAsync();
}
catch (Exception exception) when (!stoppingToken.IsCancellationRequested)
{
await run.FailAsync(exception.Message);
}
}
}
}
}A failed pass is reported with its message and the loop continues; without the catch, an exception in ExecuteAsync stops the whole application by default. A pass interrupted by shutdown is reported as failed, since its run is disposed without being completed.
Telemetry never breaks the job it monitors: transient failures are retried, and if an event still can't be delivered the method returns false instead of throwing, and logs an error. An invalid monitor key, or using the static class before it's configured, still throws. See Errors and logging for the details and how to set up logging.
Use Ping to send a Command when you need more than a message and environment, such as metrics, the host or a series. The commands are RunCommand, CompleteCommand, FailCommand and TickCommand in Cronitor.Commands.
using Cronitor;
using Cronitor.Commands;
using Cronitor.Constants;
var command = new CompleteCommand()
.WithApiKey("apiKey")
.WithMonitorKey("monitorKey")
.WithEnvironment("production")
.WithHost("worker-01")
.WithMessage("Exported 42 rows")
.WithMetric(Metric.Count, 42)
.WithMetric(Metric.Duration, 12.5)
.WithSeries("3de5db91-9c02-4e95-b8a9-9a2442702336")
.WithStatus("0");
await CronitorClient.PingAsync(command);
// or
await telemetriesClient.PingAsync(command);Ping sends the command as it is, so it needs WithApiKey even when the client already has a key. See the Telemetry API parameters for what each one does in Cronitor.
| Method | Parameter | Description |
|---|---|---|
WithApiKey(string) |
Required. Your API key, found on your account settings page. | |
WithMonitorKey(MonitorKey) |
Required. The monitor's unique key, following the rules for keys. | |
WithEnvironment(string) |
env |
The environment the event is sent from. |
WithHost(string) |
host |
The hostname of the server sending the event. |
WithMessage(string) |
message |
A message of up to 2000 characters. |
WithMetric(Metric, value) |
metric |
A metric; value is a string, int, decimal or double. Call it once per metric. |
WithSeries(string) |
series |
A unique ID that matches a run to its complete or fail. Improves matching for jobs that ping every few seconds. |
WithStatus(string) |
status_code |
The exit code returned from the job. |
The metrics are in Cronitor.Constants.Metric:
| Metric | Sent as | Description |
|---|---|---|
Metric.Count |
count:* |
Counts of important events, such as processed records |
Metric.Duration |
duration:* |
The duration of the job |
Metric.Errors |
error_count:* |
The number of errors that occurred |
Every With… method except WithMetric throws ArgumentNullException for an empty or whitespace value. A monitor key that breaks the rules for keys in another way throws ArgumentException.