sc4ry 0.1.7 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,11 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Sc4ry module
4
+ # @note namespace
1
5
  module Sc4ry
2
- module Exceptions
3
- class CircuitBreaked < StandardError
4
- def initialize(msg="Circuit just opened")
5
- super(msg)
6
- end
7
-
8
- end
6
+ # Sc4ry::Exceptions module
7
+ # @note namespace
8
+ module Exceptions
9
+ # Exception use in {Sc4ry::Circuits} when running circuit {Sc4ry::Circuits::run}
10
+ class CircuitBreaked < StandardError
11
+ def initialize(msg = 'Circuit just opened')
12
+ super(msg)
13
+ end
14
+ end
15
+
16
+ # Generic Exception use in {Sc4ry::Circuits}
17
+ class Sc4ryGenericError < StandardError
18
+ def initialize(msg = '')
19
+ super(msg)
20
+ end
21
+ end
22
+
23
+ # Exception use in {Sc4ry::Store} or/and {Sc4ry::Backend} on data string issues
24
+ class Sc4ryBackendError < StandardError
25
+ def initialize(msg = '')
26
+ super(msg)
27
+ end
28
+ end
29
+
30
+ # Exception use in {Sc4ry::Notifiers} on notification issues
31
+ class Sc4ryNotifierError < StandardError
32
+ def initialize(msg = '')
33
+ super(msg)
34
+ end
35
+ end
9
36
 
37
+ # Exception use in {Sc4ry::Circuits} on config management issues
38
+ class ConfigError < StandardError
39
+ def initialize(msg = '')
40
+ super(msg)
41
+ end
10
42
  end
11
- end
43
+ end
44
+ end
@@ -1 +1,3 @@
1
- Dir[File.dirname(__FILE__) + '/*.rb'].sort.each { |file| require file unless File.basename(file) == 'init.rb' }
1
+ # frozen_string_literal: true
2
+
3
+ Dir["#{File.dirname(__FILE__)}/*.rb"].sort.each { |file| require file unless File.basename(file) == 'init.rb' }
data/lib/sc4ry/helpers.rb CHANGED
@@ -1,10 +1,22 @@
1
+ # frozen_string_literal: true
1
2
 
3
+ # Sc4ry module
4
+ # @note namespace
2
5
  module Sc4ry
6
+ # Sc4ry::Helpers module
7
+ # @note namespace
3
8
  module Helpers
4
-
5
- def Helpers.log(options)
6
- Sc4ry::Logger.current = options[:target] if options[:target]
7
- Sc4ry::Logger.get.send options[:level], "Sc4ry : #{options[:message]}"
9
+ # class method (module) to help logging messages
10
+ # @param [Symbol] target a specific logger, restored old after
11
+ # @param [Symbol] level (default :info) a logging level (see Logger Stdlib)
12
+ # @param [String] message your message
13
+ # @return [Boolean]
14
+ def self.log(message:, target: nil, level: :info)
15
+ save = Sc4ry::Loggers.current
16
+ Sc4ry::Loggers.current = target if target
17
+ Sc4ry::Loggers.get.send level, "Sc4ry : #{message}"
18
+ Sc4ry::Loggers.current = save
19
+ true
8
20
  end
9
21
 
10
22
  # TCP/IP service checker
@@ -13,37 +25,34 @@ module Sc4ry
13
25
  # @option options [String] :host hostname
14
26
  # @option options [String] :port TCP port
15
27
  # @option options [String] :url full URL, priority on :host and :port
16
- def Helpers.verify_service(options ={})
17
- begin
18
- if options[:url] then
19
- uri = URI.parse(options[:url])
20
- host = uri.host
21
- port = uri.port
22
- else
23
- host = options[:host]
24
- port = options[:port]
25
- end
26
- Timeout::timeout(1) do
27
- begin
28
- s = TCPSocket.new(host, port)
29
- s.close
30
- return true
31
- rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH
32
- return false
33
- end
34
- end
35
- rescue Timeout::Error
28
+ def self.verify_service(options = {})
29
+ if options[:url]
30
+ uri = URI.parse(options[:url])
31
+ host = uri.host
32
+ port = uri.port
33
+ else
34
+ host = options[:host]
35
+ port = options[:port]
36
+ end
37
+ Timeout.timeout(1) do
38
+ s = TCPSocket.new(host, port)
39
+ s.close
40
+ return true
41
+ rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH
36
42
  return false
37
43
  end
44
+ rescue Timeout::Error
45
+ false
38
46
  end
39
47
 
40
-
41
- def Helpers.notify(options = {})
48
+ # class method (module) to help send notifiesby Sc4ry::Notifiers
49
+ # @param [Hash] options a Notifying structure
50
+ # @return [Boolean]
51
+ def self.notify(options = {})
42
52
  Sc4ry::Notifiers.list.each do |record|
43
53
  notifier = Sc4ry::Notifiers.get name: record
44
- notifier[:class].notify(options) if options[:config][:notifiers].include? record
54
+ notifier[:class].notify(options) if options[:config][:notifiers].include? record
45
55
  end
46
56
  end
47
-
48
- end
49
- end
57
+ end
58
+ end
data/lib/sc4ry/logger.rb CHANGED
@@ -1,30 +1,67 @@
1
+ # frozen_string_literal: true
1
2
 
3
+ # Sc4ry module
4
+ # @note namespace
2
5
  module Sc4ry
3
- class Logger
4
-
5
- @@loggers = {:stdout => ::Logger.new(STDOUT)}
6
+ # Sc4ry loggers Factory/provider
7
+ # @note must be accessed by [Sc4ry::Circuits.loggers]
8
+ class Loggers
9
+ @@loggers = { stdout: ::Logger.new($stdout) }
6
10
  @@current = :stdout
7
-
8
- def Logger.list_avaible
9
- return @@loggers
11
+
12
+ # give the list of available loggers (initially internal Sc4ry logger )
13
+ # @return [Array] of [symbol] the list of defined loggers
14
+ # @note default :stdout => ::Logger($stdout) from Ruby Stdlib
15
+ # @example usage
16
+ # include Sc4ry
17
+ # Circuits.loggers.list_available.each {|logger| puts logger }
18
+ def self.list_available
19
+ @@loggers.keys
10
20
  end
11
-
12
- def Logger.current
13
- return @@current
21
+
22
+ # return the current logger name (initially :stdtout )
23
+ # @return [symbol] the name of the logger
24
+ # @example usage
25
+ # include Sc4ry
26
+ # puts Circuits.loggers.current
27
+ def self.current
28
+ @@current
14
29
  end
15
-
16
- def Logger.get
17
- return @@loggers[@@current]
30
+
31
+ # return the current logger Object (initially internal Sc4ry Stdlib Logger on STDOUT )
32
+ # @return [symbol] the name of the logger
33
+ # @example usage
34
+ # include Sc4ry
35
+ # Circuits.loggers.get :stdout
36
+ def self.get
37
+ @@loggers[@@current]
18
38
  end
19
-
20
- def Logger.current=(sym)
39
+
40
+ # Set the current logger
41
+ # @param [Symbol] sym the name of the logger
42
+ # @return [symbol] the name of the logger updated
43
+ # @example usage
44
+ # include Sc4ry
45
+ # Circuits.loggers.current = :newlogger
46
+ def self.current=(sym)
21
47
  raise "Logger not define : #{sym}" unless @@loggers.keys.include? sym
48
+
22
49
  @@current = sym
50
+ @@current
23
51
  end
24
-
25
- def Logger.register(options = {})
26
- @@loggers[options[:name]] = options[:instance]
52
+
53
+ # register un new logger
54
+ # @param [Symbol] name the name of the new logger
55
+ # @param [Object] instance the new logger object
56
+ # raise Sc4ry::Exceptions::Sc4ryGenericError if name is not a Symbol
57
+ # @example usage
58
+ # include Sc4ry
59
+ # Circuits.loggers.register name: :newlogger, instance: Logger::new('/path/to/my.log')
60
+ def self.register(name:, instance:)
61
+ raise Sc4ry::Exceptions::Sc4ryGenericError, 'name: keyword must be a Symbol' unless name.instance_of?(Symbol)
62
+
63
+ @@loggers[name] = instance
64
+ name
27
65
  end
28
-
29
66
  end
30
67
  end
@@ -1,30 +1,58 @@
1
- Dir[File.dirname(__FILE__) + '/*.rb'].sort.each { |file| require file unless File.basename(file) == 'init.rb' }
1
+ # frozen_string_literal: true
2
2
 
3
- module Sc4ry
3
+ Dir["#{File.dirname(__FILE__)}/*.rb"].sort.each { |file| require file unless File.basename(file) == 'init.rb' }
4
+
5
+ # Sc4ry module
6
+ # @note namespace
7
+ module Sc4ry
8
+ # Sc4ry::Notifiers module
9
+ # @note namespace
4
10
  module Notifiers
5
- @@notifiers_list = {:prometheus => {:class => Sc4ry::Notifiers::Prometheus, :config => {:url => 'http://localhost:9091'}},
6
- :mattermost => {:class => Sc4ry::Notifiers::Mattermost, :config => {:url => 'http://localhost:9999', :token => "<CHANGE_ME>"}}
7
- }
11
+ # default notifiers specifications
12
+ DEFAULT_NOTIFIERS = { prometheus: { class: Sc4ry::Notifiers::Prometheus, config: { url: 'http://localhost:9091' } },
13
+ mattermost: { class: Sc4ry::Notifiers::Mattermost, config: { url: 'http://localhost:9999', token: '<CHANGE_ME>' } } }
14
+ @@notifiers_list = DEFAULT_NOTIFIERS.dup
15
+
16
+ # class method how display a specific notifier config
17
+ # @param notifier [Symbol] a notifier name
18
+ # @return [Hash] the config
19
+ def self.display_config(notifier:)
20
+ unless @@notifiers_list.include? notifier
21
+ raise Sc4ry::Exceptions::Sc4ryNotifierError,
22
+ "Notifier #{notifier} not found"
23
+ end
8
24
 
9
- def Notifiers.list
10
- return @@notifiers_list.keys
25
+ @@notifiers_list[notifier][:config]
11
26
  end
12
27
 
13
- def Notifiers.get(options ={})
14
- return @@notifiers_list[options[:name]]
28
+ # class method how return the list of known notifiers
29
+ # @return [Array] a list of [Symbol] notifiers name
30
+ def self.list
31
+ @@notifiers_list.keys
15
32
  end
16
33
 
17
- def Notifiers.register(options)
18
- raise ":name is mandatory" unless options[:name]
19
- raise ":definition is mandatory" unless options[:definition]
20
- @@notifiers_list[options[:name]] = options[:definition]
34
+ # class method how return a specific notifier by name
35
+ # @param name [Symbol] a notifier name
36
+ # @return [Hash] the notifier structure
37
+ def self.get(name:)
38
+ @@notifiers_list[name]
21
39
  end
22
40
 
23
- def Notifiers.config(options)
24
- raise ":name is mandatory" unless options[:name]
25
- raise ":config is mandatory" unless options[:config]
26
- @@notifiers_list[options[:name]][:config] = options[:config]
41
+ # class method how register a specific notifier
42
+ # @param name [Symbol] a notifier name
43
+ # @param definition [Hash] a notifier definition
44
+ # @return [Hash] the notifier structure
45
+ def self.register(name:, definition:)
46
+ @@notifiers_list[name] = definition
27
47
  end
28
- end
29
- end
30
48
 
49
+ # class method how configure a specific notifier
50
+ # @param name [Symbol] a notifier name
51
+ # @param config [Hash] a notifier config
52
+ # @return [Hash] the notifier structure
53
+ def self.config(name:, config:)
54
+ @@notifiers_list[name][:config] = config
55
+ config
56
+ end
57
+ end
58
+ end
@@ -1,35 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Sc4ry module
4
+ # @note namespace
1
5
  module Sc4ry
2
- module Notifiers
3
-
4
- class Mattermost
5
-
6
- # send metrics to Prometheus PushGateway
7
- # @return [Bool]
8
- def Mattermost.notify(options = {})
9
- config = Sc4ry::Notifiers.get({name: :mattermost})[:config]
10
- status = options[:config][:status][:general]
11
- circuit = options[:circuit]
12
- status_map = {:open => 0, :half_open => 1, :closed => 2}
13
-
14
- uri = URI.parse("#{config[:url]}/hooks/#{config[:token]}")
15
- message = "notifying for circuit #{circuit.to_s}, state : #{status.to_s}."
16
- if Sc4ry::Helpers::verify_service url: config[:url] then
17
- request = ::Net::HTTP::Post.new(uri)
18
- request.content_type = "application/json"
19
- req_options = {
20
- use_ssl: uri.scheme == "https",
21
- }
22
- payload = { "text" => "message : #{message } from #{Socket.gethostname}", "username" => "Sc4ry" }
23
- Sc4ry::Helpers.log level: :debug, message: "Mattermost Notifier : #{message}"
24
- request.body = ::JSON.dump(payload)
25
- response = ::Net::HTTP.start(uri.hostname, uri.port, req_options) do |http|
26
- http.request(request)
27
- end
28
-
29
- else
30
- Sc4ry::Helpers.log level: :warn, message: "Mattermost Notifier : can't notify Mattermost not reachable."
31
- end
6
+ # Sc4ry::Notifiers module
7
+ # @note namespace
8
+ module Notifiers
9
+ # Mattermost Notifier class
10
+ class Mattermost
11
+ # send metrics to Prometheus PushGateway
12
+ # @return [Bool]
13
+ def self.notify(options = {})
14
+ config = Sc4ry::Notifiers.get(name: :mattermost)[:config]
15
+ status = options[:config][:status][:general]
16
+ circuit = options[:circuit]
17
+ begin
18
+ uri = URI.parse("#{config[:url]}/hooks/#{config[:token]}")
19
+ message = "notifying for circuit #{circuit}, state : #{status}."
20
+ if Sc4ry::Helpers.verify_service url: config[:url]
21
+ request = ::Net::HTTP::Post.new(uri)
22
+ request.content_type = 'application/json'
23
+ req_options = {
24
+ use_ssl: uri.scheme == 'https'
25
+ }
26
+ payload = { 'text' => "message : #{message} from #{Socket.gethostname}", 'username' => 'Sc4ry' }
27
+ Sc4ry::Helpers.log level: :debug, message: "Mattermost Notifying : #{message}"
28
+ request.body = ::JSON.dump(payload)
29
+ ::Net::HTTP.start(uri.hostname, uri.port, req_options) do |http|
30
+ http.request(request)
32
31
  end
32
+
33
+ else
34
+ Sc4ry::Helpers.log level: :warn, message: "Mattermost Notifier : can't notify Mattermost not reachable."
35
+ end
36
+ rescue URI::InvalidURIError
37
+ Sc4ry::Helpers.log level: :warn, message: 'Mattermost Notifier : URL malformed'
33
38
  end
39
+ end
34
40
  end
35
- end
41
+ end
42
+ end
@@ -1,37 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Sc4ry module
4
+ # @note namespace
1
5
  module Sc4ry
6
+ # Sc4ry::Notifiers module
7
+ # @note namespace
2
8
  module Notifiers
3
-
4
-
9
+ # Prometheus notifier class
5
10
  class Prometheus
6
-
7
- @@registry = ::Prometheus::Client::Registry::new
8
- @@metric_circuit_state = ::Prometheus::Client::Gauge.new(:cuircuit_state, docstring: 'Sc4ry metric : state of a circuit', labels: [:circuit])
11
+ @@registry = ::Prometheus::Client::Registry.new
12
+ @@metric_circuit_state = ::Prometheus::Client::Gauge.new(:cuircuit_state,
13
+ docstring: 'Sc4ry metric : state of a circuit',
14
+ labels: [:circuit])
9
15
 
10
16
  @@registry.register(@@metric_circuit_state)
11
17
 
12
-
13
-
14
18
  # send metrics to Prometheus PushGateway
15
19
  # @return [Bool]
16
- def Prometheus.notify(options = {})
17
- @config = Sc4ry::Notifiers.get({name: :prometheus})[:config]
20
+ def self.notify(options = {})
21
+ @config = Sc4ry::Notifiers.get(name: :prometheus)[:config]
18
22
  status = options[:config][:status][:general]
19
23
  circuit = options[:circuit]
20
- status_map = {:open => 0, :half_open => 1, :closed => 2}
21
- if Sc4ry::Helpers::verify_service url: @config[:url] then
22
-
23
- @@metric_circuit_state.set(status_map[status], labels: {circuit: circuit.to_s })
24
- Sc4ry::Helpers.log level: :debug, message: "Prometheus Notifier : notifying for circuit #{circuit.to_s}, state : #{status.to_s}."
24
+ status_map = { open: 0, half_open: 1, closed: 2 }
25
+ if Sc4ry::Helpers.verify_service url: @config[:url]
25
26
 
26
- return ::Prometheus::Client::Push.new(job: "Sc4ry", instance: Socket.gethostname, gateway: @config[:url]).add(@@registry)
27
+ @@metric_circuit_state.set(status_map[status], labels: { circuit: circuit.to_s })
28
+ Sc4ry::Helpers.log level: :debug,
29
+ message: "Prometheus Notifier : notifying for circuit #{circuit}, state : #{status}."
30
+
31
+ ::Prometheus::Client::Push.new(job: 'Sc4ry', instance: Socket.gethostname,
32
+ gateway: @config[:url]).add(@@registry)
27
33
  else
28
34
  Sc4ry::Helpers.log level: :warn, message: "Prometheus Notifier : can't notify Push Gateway not reachable."
29
35
  end
30
36
  end
31
37
  end
32
-
33
-
34
-
35
38
  end
36
-
37
39
  end
@@ -1,9 +1,15 @@
1
+ # frozen_string_literal: true
1
2
 
3
+ # Sc4ry module
4
+ # @note namespace
2
5
  module Sc4ry
6
+ # class Facility to run and update values/status for a circuit Proc
3
7
  class RunController
4
-
8
+ # return the execution time of the proc
5
9
  attr_reader :execution_time
6
10
 
11
+ # constructor
12
+ # @param [Hash] circuit the data of the circuit
7
13
  def initialize(circuit = {})
8
14
  @circuit = circuit
9
15
  @execution_time = 0
@@ -12,53 +18,56 @@ module Sc4ry
12
18
  @overtime = false
13
19
  end
14
20
 
21
+ # return if the Proc failed on a covered exception by this circuit
22
+ # @return [Boolean]
15
23
  def failed?
16
- return @failure
17
- end
18
-
19
- def overtimed?
20
- return @overtime
24
+ @failure
25
+ end
26
+
27
+ # return if the Proc overtime the specified time of the circuit
28
+ # @return [Boolean]
29
+ def overtimed?
30
+ @overtime
21
31
  end
22
32
 
23
- def timeout?
24
- return @timeout
33
+ # return if the Proc timeout the timeout defined value of the circuit, if timeout is active
34
+ # @return [Boolean]
35
+ def timeout?
36
+ @timeout
25
37
  end
26
38
 
27
-
28
- def run(options = {})
39
+ # run and update values for the bloc given by keyword
40
+ # @param [Proc] block a block to run and calculate
41
+ # @return [Hash] a result Hash
42
+ def run(block:)
29
43
  start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
30
- begin
44
+ begin
31
45
  if @circuit[:timeout] == true
32
- Timeout::timeout(@circuit[:timeout_value]) do
33
- options[:block].call
46
+ Timeout.timeout(@circuit[:timeout_value]) do
47
+ block.call
34
48
  end
35
49
  @timeout = false
36
50
  else
37
- options[:block].call
51
+ block.call
38
52
  end
39
- rescue Exception => e
40
- @last_exception = e.class
41
- if e.class == Timeout::Error then
53
+ rescue StandardError => e
54
+ @last_exception = e.class.to_s
55
+ if e.instance_of?(Timeout::Error)
42
56
  @timeout = true
43
57
  elsif @circuit[:exceptions].include? e.class
44
58
  @failure = true
45
- else
46
- if @circuit[:forward_unknown_exceptions] then
47
- raise e.class, "Sc4ry forward: #{e.message}"
48
- else
49
- Sc4ry::Helpers.log level: :debug, message: "skipped : #{@last_exception}"
50
- end
51
-
52
- end
59
+ elsif @circuit[:forward_unknown_exceptions]
60
+ raise e.class, "Sc4ry forward: #{e.message}"
61
+ else
62
+ Sc4ry::Helpers.log level: :debug, message: "skipped : #{@last_exception}"
63
+
64
+ end
53
65
  end
54
66
  @end_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
55
67
  @execution_time = @end_time - start_time
56
- @overtime = @execution_time > @circuit[:max_time]
57
- return {failure: @failure, overtime: @overtime, timeout: @timeout, execution_time: @execution_time, end_time: @end_time, last_exception: @last_exception}
58
-
68
+ @overtime = @execution_time > @circuit[:max_time]
69
+ { failure: @failure, overtime: @overtime, timeout: @timeout, execution_time: @execution_time,
70
+ end_time: @end_time, last_exception: @last_exception }
59
71
  end
60
-
61
-
62
-
63
72
  end
64
- end
73
+ end