-
-
Notifications
You must be signed in to change notification settings - Fork 195
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Introduce manual check-ins for crons (#697)
- Loading branch information
1 parent
47e2d65
commit c5c959b
Showing
7 changed files
with
486 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,193 @@ | ||
defmodule Sentry.CheckIn do | ||
@moduledoc """ | ||
This module represents the struct for a "check-in". | ||
Check-ins are used to report the status of a monitor to Sentry. This is used | ||
to track the health and progress of **cron jobs**. This module is somewhat | ||
low level, and mostly useful when you want to report the status of a cron | ||
but you are not using any common library to manage your cron jobs. | ||
> #### Using `capture_check_in/1` {: .tip} | ||
> | ||
> Instead of using this module directly, you'll probably want to use | ||
> `Sentry.capture_check_in/1` to manually report the status of your cron jobs. | ||
See <https://develop.sentry.dev/sdk/check-ins/>. This struct is available | ||
since v10.2.0. | ||
""" | ||
@moduledoc since: "10.2.0" | ||
|
||
alias Sentry.{Config, Interfaces, UUID} | ||
|
||
@typedoc """ | ||
The possible status of the check-in. | ||
""" | ||
@type status() :: :in_progress | :ok | :error | ||
|
||
@typedoc """ | ||
The possible values for the `:schedule` option under `:monitor_config`. | ||
If the `:type` is `:crontab`, then the `:value` must be a string representing | ||
a crontab expression. If the `:type` is `:interval`, then the `:value` must be | ||
a number representing the interval and the `:unit` must be present and be one of `:year`, | ||
`:month`, `:week`, `:day`, `:hour`, or `:minute`. | ||
""" | ||
@type monitor_config_schedule() :: | ||
%{type: :crontab, value: String.t()} | ||
| %{ | ||
type: :interval, | ||
value: number(), | ||
unit: :year | :month | :week | :day | :hour | :minute | ||
} | ||
|
||
@typedoc """ | ||
The type for the check-in struct. | ||
""" | ||
@type t() :: %__MODULE__{ | ||
check_in_id: String.t(), | ||
monitor_slug: String.t(), | ||
status: status(), | ||
duration: float() | nil, | ||
release: String.t() | nil, | ||
environment: String.t() | nil, | ||
monitor_config: | ||
nil | ||
| %{ | ||
required(:schedule) => monitor_config_schedule(), | ||
optional(:checkin_margin) => number(), | ||
optional(:max_runtime) => number(), | ||
optional(:failure_issue_threshold) => number(), | ||
optional(:recovery_threshold) => number(), | ||
optional(:timezone) => String.t() | ||
}, | ||
contexts: Interfaces.context() | ||
} | ||
|
||
@enforce_keys [ | ||
:check_in_id, | ||
:monitor_slug, | ||
:status | ||
] | ||
defstruct @enforce_keys ++ | ||
[ | ||
:duration, | ||
:release, | ||
:environment, | ||
:monitor_config, | ||
:contexts | ||
] | ||
|
||
number_schema_opts = [type: {:or, [:integer, :float]}, type_doc: "`t:number/0`"] | ||
|
||
crontab_schedule_opts_schema = [ | ||
type: [type: {:in, [:crontab]}, required: true], | ||
value: [type: :string, required: true] | ||
] | ||
|
||
interval_schedule_opts_schema = [ | ||
type: [type: {:in, [:interval]}, required: true], | ||
value: number_schema_opts, | ||
unit: [type: {:in, [:year, :month, :week, :day, :hour, :minute]}, required: true] | ||
] | ||
|
||
create_check_in_opts_schema = [ | ||
check_in_id: [ | ||
type: :string | ||
], | ||
status: [ | ||
type: {:in, [:in_progress, :ok, :error]}, | ||
required: true, | ||
type_doc: "`t:status/0`" | ||
], | ||
monitor_slug: [ | ||
type: :string, | ||
required: true | ||
], | ||
duration: number_schema_opts, | ||
contexts: [ | ||
type: :map, | ||
default: %{}, | ||
doc: """ | ||
The contexts to attach to the check-in. This is a map of arbitrary data, | ||
but right now Sentry supports the `trace_id` key under the | ||
[trace context](https://develop.sentry.dev/sdk/event-payloads/contexts/#trace-context) | ||
to connect the check-in with related errors. | ||
""" | ||
], | ||
monitor_config: [ | ||
doc: "If you pass this optional option, you **must** pass the nested `:schedule` option.", | ||
type: :keyword_list, | ||
keys: [ | ||
checkin_margin: number_schema_opts, | ||
max_runtime: number_schema_opts, | ||
failure_issue_threshold: number_schema_opts, | ||
recovery_threshold: number_schema_opts, | ||
timezone: [type: :string], | ||
schedule: [ | ||
type: | ||
{:or, | ||
[ | ||
{:keyword_list, crontab_schedule_opts_schema}, | ||
{:keyword_list, interval_schedule_opts_schema} | ||
]}, | ||
type_doc: "`t:monitor_config_schedule/0`" | ||
] | ||
] | ||
] | ||
] | ||
|
||
@create_check_in_opts_schema NimbleOptions.new!(create_check_in_opts_schema) | ||
|
||
@doc """ | ||
Creates a new check-in struct with the given options. | ||
## Options | ||
The options you can pass match a subset of the fields of the `t:t/0` struct. | ||
You can pass: | ||
#{NimbleOptions.docs(@create_check_in_opts_schema)} | ||
## Examples | ||
iex> check_in = CheckIn.new(status: :ok, monitor_slug: "my-slug") | ||
iex> check_in.status | ||
:ok | ||
iex> check_in.monitor_slug | ||
"my-slug" | ||
""" | ||
@spec new(keyword()) :: t() | ||
def new(opts) when is_list(opts) do | ||
opts = NimbleOptions.validate!(opts, @create_check_in_opts_schema) | ||
|
||
monitor_config = | ||
case Keyword.fetch(opts, :monitor_config) do | ||
{:ok, monitor_config} -> | ||
monitor_config | ||
|> Map.new() | ||
|> Map.update!(:schedule, &Map.new/1) | ||
|
||
:error -> | ||
nil | ||
end | ||
|
||
%__MODULE__{ | ||
check_in_id: Keyword.get_lazy(opts, :check_in_id, &UUID.uuid4_hex/0), | ||
status: Keyword.fetch!(opts, :status), | ||
monitor_slug: Keyword.fetch!(opts, :monitor_slug), | ||
duration: Keyword.get(opts, :duration), | ||
release: Config.release(), | ||
environment: Config.environment_name(), | ||
monitor_config: monitor_config, | ||
contexts: Keyword.fetch!(opts, :contexts) | ||
} | ||
end | ||
|
||
# Used to then encode the returned map to JSON. | ||
@doc false | ||
@spec to_map(t()) :: map() | ||
def to_map(%__MODULE__{} = check_in) do | ||
Map.from_struct(check_in) | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.