Skip to content

Commit 848a6e5

Browse files
committed
bake retryable into the project
1 parent 53eb578 commit 848a6e5

File tree

5 files changed

+100
-0
lines changed

5 files changed

+100
-0
lines changed

docs/handler_plugins.md

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# Handler Plugins
2+
3+
This document provides in-depth information about handler plugins and how you can create your own to extend the functionality of the Hooks framework for your own deployment.
4+
5+
## Built-in Features
6+
7+
This section goes into details on the built-in features that exist in all handler plugins that extend the `Hooks::Plugins::Handlers::Base` class.
8+
9+
### `#log`
10+
11+
The `log.debug`, `log.info`, `log.warn`, and `log.error` methods are available in all handler plugins. They are used to log messages at different levels of severity.
12+
13+
### `#Retryable.with_context(:default)`
14+
15+
This method uses a default `Retryable` context to handle retries. It is used to wrap the execution of a block of code that may need to be retried in case of failure.
16+
17+
Here is how it can be used in a handler plugin:
18+
19+
```ruby
20+
class Example < Hooks::Plugins::Handlers::Base
21+
# Example webhook handler
22+
#
23+
# @param payload [Hash, String] Webhook payload
24+
# @param headers [Hash<String, String>] HTTP headers
25+
# @param config [Hash] Endpoint configuration
26+
# @return [Hash] Response data
27+
def call(payload:, headers:, config:)
28+
result = Retryable.with_context(:default) do
29+
some_operation_that_might_fail()
30+
end
31+
32+
log.debug("operation result: #{result}")
33+
34+
return {
35+
status: "success"
36+
}
37+
end
38+
end
39+
```
40+
41+
> If `Retryable.with_context(:default)` fails after all retries, it will re-raise the last exception encountered.
42+
43+
See the source code at `lib/hooks/utils/retry.rb` for more details on how `Retryable.with_context(:default)` works.

lib/hooks/core/builder.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
require_relative "logger_factory"
66
require_relative "plugin_loader"
77
require_relative "../app/api"
8+
require_relative "../utils/retry"
89

910
module Hooks
1011
module Core
@@ -34,6 +35,9 @@ def build
3435
)
3536
end
3637

38+
# Hydrate our Retryable instance
39+
Retry.setup!(log: @log)
40+
3741
# Load all plugins at boot time
3842
load_plugins(config)
3943

lib/hooks/utils/retry.rb

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# frozen_string_literal: true
2+
3+
require "retryable"
4+
5+
# Utility module for retry functionality
6+
module Retry
7+
# This method should be called as early as possible in the startup of your application
8+
# It sets up the Retryable gem with custom contexts and passes through a few options
9+
# Should the number of retries be reached without success, the last exception will be raised
10+
#
11+
# @param log [Logger] The logger to use for retryable logging
12+
# @raise [ArgumentError] If no logger is provided
13+
# @return [void]
14+
def self.setup!(log: nil, log_retries: ENV.fetch("RETRY_LOG_RETRIES", "true") == "true")
15+
raise ArgumentError, "a logger must be provided" if log.nil?
16+
17+
log_method = lambda do |retries, exception|
18+
# :nocov:
19+
if log_retries
20+
log.debug("[retry ##{retries}] #{exception.class}: #{exception.message} - #{exception.backtrace.join("\n")}")
21+
end
22+
# :nocov:
23+
end
24+
25+
######## Retryable Configuration ########
26+
# All defaults available here:
27+
# https://github.com/nfedyashev/retryable/blob/6a04027e61607de559e15e48f281f3ccaa9750e8/lib/retryable/configuration.rb#L22-L33
28+
Retryable.configure do |config|
29+
config.contexts[:default] = {
30+
on: [StandardError],
31+
sleep: ENV.fetch("DEFAULT_RETRY_SLEEP", "1").to_i,
32+
tries: ENV.fetch("DEFAULT_RETRY_TRIES", "10").to_i,
33+
log_method:
34+
}
35+
end
36+
end
37+
end

spec/acceptance/docker-compose.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ services:
1111
GITHUB_WEBHOOK_SECRET: "octoawesome-secret"
1212
ALT_WEBHOOK_SECRET: "octoawesome-too-secret"
1313
SHARED_SECRET: "octoawesome-shared-secret"
14+
DEFAULT_RETRY_SLEEP: 0
15+
RETRY_LOG_RETRIES: "false"
1416
command: ["script/server"]
1517
healthcheck:
1618
test: ["CMD", "curl", "-f", "http://0.0.0.0:8080/health"]

spec/acceptance/plugins/handlers/team1_handler.rb

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,20 @@ class Team1Handler < Hooks::Plugins::Handlers::Base
99
# @param config [Hash] Endpoint configuration
1010
# @return [Hash] Response data
1111
def call(payload:, headers:, config:)
12+
log.debug("got a call to #{self.class.name} with payload: #{payload.inspect}")
13+
14+
# demo the global retryable instance is a kinda silly way
15+
fail_on_first_time = true
16+
foo = Retryable.with_context(:default) do
17+
if fail_on_first_time
18+
fail_on_first_time = false
19+
raise StandardError, "This is a demo error to show retryable in action"
20+
end
21+
22+
"bar"
23+
end
24+
log.debug("we got back the value of foo: #{foo}")
25+
1226
# Process the payload based on type
1327
if payload.is_a?(Hash)
1428
event_type = payload[:event_type] || "unknown"

0 commit comments

Comments
 (0)