Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Check-In Upserts #3330

Merged
merged 37 commits into from
May 29, 2024
Merged
Show file tree
Hide file tree
Changes from 34 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
4337094
added missing properties
bitsandfoxes Apr 22, 2024
f3e54e4
enriching checkins
bitsandfoxes Apr 24, 2024
d3097ea
adding the trace id to the check-in
bitsandfoxes Apr 24, 2024
16f7bcd
simplify
bitsandfoxes Apr 24, 2024
c527a33
tests
bitsandfoxes Apr 25, 2024
bfc4098
real tests
bitsandfoxes Apr 25, 2024
4431428
Updated CHANGELOG.md
bitsandfoxes Apr 25, 2024
0111dcc
verify
bitsandfoxes Apr 25, 2024
b04d045
Merge branch 'main' into fix/crons-1
bitsandfoxes Apr 25, 2024
e1832d1
Merge branch 'fix/crons-1' of https://github.com/getsentry/sentry-dot…
bitsandfoxes Apr 25, 2024
8dfe884
Update test/Sentry.Tests/HubTests.cs
bitsandfoxes Apr 25, 2024
7803f45
added monitor config to checkins
bitsandfoxes Apr 25, 2024
45a283d
.
bitsandfoxes Apr 26, 2024
d8e7525
fixed test
bitsandfoxes Apr 26, 2024
fa23112
.
bitsandfoxes Apr 26, 2024
7cad476
verify
bitsandfoxes Apr 26, 2024
775b908
merge
bitsandfoxes Apr 26, 2024
d549bbb
iterating on it
bitsandfoxes May 6, 2024
77cdd2f
Merge branch 'main' into fix/crons-2
bitsandfoxes May 6, 2024
fbb0017
Merge branch 'main' into fix/crons-2
bitsandfoxes May 21, 2024
9d25bc5
pass the config callback to the client
bitsandfoxes May 21, 2024
a485094
tests
bitsandfoxes May 21, 2024
f9297ad
verify
bitsandfoxes May 21, 2024
899cf7f
timezones. how about no
bitsandfoxes May 21, 2024
f5f6c81
verify
bitsandfoxes May 21, 2024
ece0995
Updated CHANGELOG.md
bitsandfoxes May 21, 2024
c3149de
Updated CHANGELOG.md
bitsandfoxes May 21, 2024
2490e22
forward options
bitsandfoxes May 24, 2024
a81774c
validating crontab
bitsandfoxes May 24, 2024
07dcc56
.
bitsandfoxes May 24, 2024
a868b2e
Merge branch 'main' into fix/crons-2
bitsandfoxes May 24, 2024
5a30c16
verify
bitsandfoxes May 24, 2024
f23ab39
Merge branch 'fix/crons-2' of https://github.com/getsentry/sentry-dot…
bitsandfoxes May 24, 2024
4b28b64
Merge branch 'main' into fix/crons-2
bitsandfoxes May 27, 2024
5554bd4
regex optimization
bitsandfoxes May 28, 2024
cf2cec3
fixes
bitsandfoxes May 28, 2024
8875318
verify
bitsandfoxes May 28, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

### Features

- The SDK now supports monitor upserting. You can programmatically set up your monitors via the options callback in `SentrySdk.CaptureCheckIn` ([#3330](https://github.com/getsentry/sentry-dotnet/pull/3330))
- Added an `SentrySdk.RunAsyncVoid` helper method that lets you capture exceptions from `async void` methods ([#3379](https://github.com/getsentry/sentry-dotnet/pull/3379))

### Fixes
Expand Down
9 changes: 9 additions & 0 deletions src/Sentry/Extensibility/DiagnosticLoggerExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,15 @@ public static void LogError(
string message)
=> logger.LogIfEnabled(SentryLevel.Error, null, message);

/// <summary>
/// Log an error message.
/// </summary>
public static void LogError<TArg>(
this IDiagnosticLogger logger,
string message,
TArg arg)
=> logger.LogIfEnabled(SentryLevel.Error, null, message, arg);

/// <summary>
/// Log an exception with an error message.
/// </summary>
Expand Down
9 changes: 7 additions & 2 deletions src/Sentry/Extensibility/DisabledHub.cs
Original file line number Diff line number Diff line change
Expand Up @@ -183,8 +183,13 @@ public void CaptureSession(SessionUpdate sessionUpdate)
/// <summary>
/// No-Op
/// </summary>
public SentryId CaptureCheckIn(string monitorSlug, CheckInStatus status, SentryId? sentryId = null,
TimeSpan? duration = null, Scope? scope = null)
public SentryId CaptureCheckIn(
string monitorSlug,
CheckInStatus status,
SentryId? sentryId = null,
TimeSpan? duration = null,
Scope? scope = null,
Action<SentryMonitorOptions>? configureMonitorOptions = null)
=> SentryId.Empty;

/// <summary>
Expand Down
11 changes: 8 additions & 3 deletions src/Sentry/Extensibility/HubAdapter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -270,9 +270,14 @@ public void CaptureSession(SessionUpdate sessionUpdate)
/// <summary>
/// Forwards the call to <see cref="SentrySdk"/>.
/// </summary>
public SentryId CaptureCheckIn(string monitorSlug, CheckInStatus status, SentryId? sentryId = null,
TimeSpan? duration = null, Scope? scope = null)
=> SentrySdk.CaptureCheckIn(monitorSlug, status, sentryId, duration);
public SentryId CaptureCheckIn(
string monitorSlug,
CheckInStatus status,
SentryId? sentryId = null,
TimeSpan? duration = null,
Scope? scope = null,
Action<SentryMonitorOptions>? monitorOptions = null)
=> SentrySdk.CaptureCheckIn(monitorSlug, status, sentryId, duration, scope, monitorOptions);

/// <summary>
/// Forwards the call to <see cref="SentrySdk"/>
Expand Down
9 changes: 7 additions & 2 deletions src/Sentry/ISentryClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,13 @@ public interface ISentryClient
/// <param name="sentryId"></param>
/// <param name="duration"></param>
/// <param name="scope"></param>
SentryId CaptureCheckIn(string monitorSlug, CheckInStatus status, SentryId? sentryId = null,
TimeSpan? duration = null, Scope? scope = null);
/// <param name="configureMonitorOptions">The optional monitor config used to create a Check-In programmatically.</param>
SentryId CaptureCheckIn(string monitorSlug,
CheckInStatus status,
SentryId? sentryId = null,
TimeSpan? duration = null,
Scope? scope = null,
Action<SentryMonitorOptions>? configureMonitorOptions = null);

/// <summary>
/// Flushes the queue of captured events until the timeout is reached.
Expand Down
10 changes: 7 additions & 3 deletions src/Sentry/Internal/Hub.cs
Original file line number Diff line number Diff line change
Expand Up @@ -561,9 +561,13 @@ public void CaptureSession(SessionUpdate sessionUpdate)
}
}

public SentryId CaptureCheckIn(string monitorSlug, CheckInStatus status, SentryId? sentryId = null,
public SentryId CaptureCheckIn(
string monitorSlug,
CheckInStatus status,
SentryId? sentryId = null,
TimeSpan? duration = null,
Scope? scope = null)
Scope? scope = null,
Action<SentryMonitorOptions>? configureMonitorOptions = null)
{
if (!IsEnabled)
{
Expand All @@ -580,7 +584,7 @@ public SentryId CaptureCheckIn(string monitorSlug, CheckInStatus status, SentryI
scope = currentScope;
}

return _ownedClient.CaptureCheckIn(monitorSlug, status, sentryId, duration, scope);
return _ownedClient.CaptureCheckIn(monitorSlug, status, sentryId, duration, scope, configureMonitorOptions);
}
catch (Exception e)
{
Expand Down
18 changes: 13 additions & 5 deletions src/Sentry/SentryCheckIn.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
namespace Sentry;

/// <summary>
/// The Checkin Status
/// The Check-In Status
/// </summary>
public enum CheckInStatus
{
Expand All @@ -25,13 +25,13 @@ public enum CheckInStatus
}

/// <summary>
/// Sentry Checkin
/// Sentry Check-In
/// </summary>
// https://develop.sentry.dev/sdk/check-ins/
public class SentryCheckIn : ISentryJsonSerializable
{
/// <summary>
/// CheckIn ID
/// Check-In ID
/// </summary>
public SentryId Id { get; }

Expand All @@ -40,13 +40,14 @@ public class SentryCheckIn : ISentryJsonSerializable
/// </summary>
public string MonitorSlug { get; }


/// <summary>
/// The status of the Checkin
/// The status of the Check-In
/// </summary>
public CheckInStatus Status { get; }

/// <summary>
/// The duration of the check-in in seconds. Will only take effect if the status is ok or error.
/// The duration of the Check-In in seconds. Will only take effect if the status is ok or error.
/// </summary>
public TimeSpan? Duration { get; set; }

Expand All @@ -65,6 +66,11 @@ public class SentryCheckIn : ISentryJsonSerializable
/// </summary>
internal SentryId? TraceId { get; set; }

/// <summary>
/// The Monitor Config
/// </summary>
internal SentryMonitorOptions? MonitorOptions { get; set; }

/// <summary>
/// Initializes a new instance of <see cref="SentryCheckIn"/>.
/// </summary>
Expand Down Expand Up @@ -103,6 +109,8 @@ public void WriteTo(Utf8JsonWriter writer, IDiagnosticLogger? logger)
writer.WriteEndObject();
}

MonitorOptions?.WriteTo(writer, logger);

writer.WriteEndObject();
}

Expand Down
17 changes: 15 additions & 2 deletions src/Sentry/SentryClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,13 @@ public void CaptureSession(SessionUpdate sessionUpdate)
=> CaptureEnvelope(Envelope.FromSession(sessionUpdate));

/// <inheritdoc />
public SentryId CaptureCheckIn(string monitorSlug, CheckInStatus status, SentryId? sentryId = null, TimeSpan? duration = null, Scope? scope = null)
public SentryId CaptureCheckIn(
string monitorSlug,
CheckInStatus status,
SentryId? sentryId = null,
TimeSpan? duration = null,
Scope? scope = null,
Action<SentryMonitorOptions>? configureMonitorOptions = null)
{
scope ??= new Scope(_options);

Expand All @@ -234,9 +240,16 @@ public SentryId CaptureCheckIn(string monitorSlug, CheckInStatus status, SentryI
var checkIn = new SentryCheckIn(monitorSlug, status, sentryId)
{
Duration = duration,
TraceId = traceId
TraceId = traceId,
};

if (configureMonitorOptions is not null)
{
var monitorOptions = new SentryMonitorOptions();
configureMonitorOptions.Invoke(monitorOptions);
checkIn.MonitorOptions = monitorOptions;
}

_enricher.Apply(checkIn);

return CaptureEnvelope(Envelope.FromCheckIn(checkIn))
Expand Down
188 changes: 188 additions & 0 deletions src/Sentry/SentryMonitorOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
using Sentry.Extensibility;
using Sentry.Internal.Extensions;

namespace Sentry;

internal enum SentryMonitorScheduleType
{
None,
Crontab,
Interval
}

/// <summary>
/// Sentry's intervals for monitors
/// </summary>
public enum SentryMonitorInterval
{
/// <summary>
/// Year
/// </summary>
Year,

/// <summary>
/// Month
/// </summary>
Month,

/// <summary>
/// Week
/// </summary>
Week,

/// <summary>
/// Day
/// </summary>
Day,

/// <summary>
/// Hour
/// </summary>
Hour,

/// <summary>
/// Minute
/// </summary>
Minute
}

/// <summary>
/// Sentry's options for monitors
/// </summary>
public class SentryMonitorOptions : ISentryJsonSerializable
{
private SentryMonitorScheduleType _type = SentryMonitorScheduleType.None;
private string? _crontab;
private int? _interval;
private SentryMonitorInterval? _unit;

// Breakdown of the validation
// ^(\*|([0-5]?\d)) Minute 0 - 59
// (\s+)(\*|([01]?\d|2[0-3])) Hour 0 - 23
// (\s+)(\*|([1-9]|[12]\d|3[01])) Day 1 - 31
// (\s+)(\*|([1-9]|1[0-2])) Month 1 - 12
// (\s+)(\*|([0-7])) Weekday 0 - 7
// $ End of string
private readonly Regex _crontabValidation = new(
@"^(\*|([0-5]?\d))(\s+)(\*|([01]?\d|2[0-3]))(\s+)(\*|([1-9]|[12]\d|3[01]))(\s+)(\*|([1-9]|1[0-2]))(\s+)(\*|([0-7]))$",
RegexOptions.Compiled | RegexOptions.CultureInvariant);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On .NET 8 we could use Source Generated regular expressions for better performance.

We do this for the BuiltInSystemDiagnosticsMeters, for example.


/// <summary>
/// Set Interval
/// </summary>
/// <param name="cronTab"></param>
public void Interval(string cronTab)
{
if (_type is not SentryMonitorScheduleType.None)
{
throw new ArgumentException("You tried to set the interval twice. The Check-Ins interval is supposed to be set only once.");
}

if (!_crontabValidation.IsMatch(cronTab))
{
throw new ArgumentException("The provided crontab does not match the expected format of '* * * * *' " +
"translating to 'minute', 'hour', 'day of the month', 'month', and 'day of the week'.");
}

_type = SentryMonitorScheduleType.Crontab;
_crontab = cronTab;
bitsandfoxes marked this conversation as resolved.
Show resolved Hide resolved
}

/// <summary>
/// Set Interval
/// </summary>
/// <param name="interval"></param>
/// <param name="unit"></param>
public void Interval(int interval, SentryMonitorInterval unit)
jamescrosswell marked this conversation as resolved.
Show resolved Hide resolved
{
if (_type is not SentryMonitorScheduleType.None)
{
throw new ArgumentException("You tried to set the interval twice. The Check-Ins interval is supposed to be set only once.");
}

_type = SentryMonitorScheduleType.Interval;
_interval = interval;
jamescrosswell marked this conversation as resolved.
Show resolved Hide resolved
_unit = unit;
}

/// <summary>
/// The allowed margin of minutes after the expected check-in time that the monitor will not be considered missed for.
/// </summary>
public TimeSpan? CheckInMargin { get; set; }

/// <summary>
/// The allowed duration in minutes that the monitor may be in_progress for before being considered failed due to timeout.
/// </summary>
public TimeSpan? MaxRuntime { get; set; }

/// <summary>
/// The number of consecutive failed check-ins it takes before an issue is created.
/// </summary>
public int? FailureIssueThreshold { get; set; }

/// <summary>
/// The number of consecutive OK check-ins it takes before an issue is resolved.
/// </summary>
public int RecoveryThreshold { get; set; }

/// <summary>
/// A tz database string representing the timezone which the monitor's execution schedule is in (i.e. "America/Los_Angeles").
/// </summary>
public string? Timezone { get; set; }
Copy link
Member

@bruno-garcia bruno-garcia May 29, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IIRC I raised this before but it's TimeZone: not Timezone: https://learn.microsoft.com/en-us/dotnet/api/system.timezone?view=net-8.0

2 words: time zone


/// <summary>
/// An actor identifier string. This looks like 'user:john@example.com team:a-sentry-team'. IDs can also be used but will result in a poor DX.
/// </summary>
public string? Owner { get; set; }

internal SentryMonitorOptions() { }

/// <inheritdoc />
public void WriteTo(Utf8JsonWriter writer, IDiagnosticLogger? logger)
{
Debug.Assert(_type != SentryMonitorScheduleType.None, "The Monitor Options do not contain a valid interval." +
"Please update your monitor options by setting the Interval.");
bitsandfoxes marked this conversation as resolved.
Show resolved Hide resolved

writer.WriteStartObject("monitor_config");
writer.WriteStartObject("schedule");

writer.WriteString("type", TypeToString(_type));
switch (_type)
{
case SentryMonitorScheduleType.Crontab:
Debug.Assert(string.IsNullOrEmpty(_crontab), "The provided 'crontab' cannot be an empty string.");
writer.WriteStringIfNotWhiteSpace("value", _crontab);
break;
case SentryMonitorScheduleType.Interval:
Debug.Assert(_interval != null, "The provided 'interval' cannot be null.");
writer.WriteNumberIfNotNull("value", _interval);
Debug.Assert(_unit != null, "The provided 'unit' cannot be null.");
writer.WriteStringIfNotWhiteSpace("unit", _unit.ToString()!.ToLower());
break;
default:
logger?.LogError("Invalid MonitorScheduleType: '{0}'", _type.ToString());
break;
}

writer.WriteEndObject();

writer.WriteNumberIfNotNull("checkin_margin", CheckInMargin?.TotalMinutes);
writer.WriteNumberIfNotNull("max_runtime", MaxRuntime?.TotalMinutes);
writer.WriteNumberIfNotNull("failure_issue_threshold", FailureIssueThreshold);
writer.WriteNumberIfNotNull("recovery_threshold", RecoveryThreshold);
writer.WriteStringIfNotWhiteSpace("timezone", Timezone);
writer.WriteStringIfNotWhiteSpace("owner", Owner);

writer.WriteEndObject();
}

private static string TypeToString(SentryMonitorScheduleType type)
{
return type switch
{
SentryMonitorScheduleType.Crontab => "crontab",
SentryMonitorScheduleType.Interval => "interval",
_ => throw new ArgumentException($"Unsupported Monitor Schedule Type: '{type}'.")
};
}
}
Loading
Loading