sc4ry 0.1.7 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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