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

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");

Events

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.

Using block

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 Complete or Fail, further calls send nothing and return false.
  • Dispose never throws, so it can't hide an exception thrown by the job itself.

Run, Complete and Fail

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

Tick is a heartbeat: one event per execution, for monitors that only need to know a job is alive.

await telemetriesClient.TickAsync("monitorKey");

Environment

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();
}

BackgroundService

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.

Delivery and errors

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.

Custom commands

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.

⚠️ **GitHub.com Fallback** ⚠️