startback-websocket 0.14.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/Gemfile +3 -0
- data/README.md +13 -0
- data/Rakefile +18 -0
- data/lib/startback/audit/prometheus.rb +87 -0
- data/lib/startback/audit/shared.rb +17 -0
- data/lib/startback/audit/trailer.rb +129 -0
- data/lib/startback/audit.rb +3 -0
- data/lib/startback/caching/entity_cache.rb +157 -0
- data/lib/startback/caching/no_store.rb +28 -0
- data/lib/startback/caching/store.rb +34 -0
- data/lib/startback/context/h_factory.rb +43 -0
- data/lib/startback/context/middleware.rb +53 -0
- data/lib/startback/context.rb +122 -0
- data/lib/startback/errors.rb +197 -0
- data/lib/startback/event/agent.rb +84 -0
- data/lib/startback/event/bus/bunny/async.rb +162 -0
- data/lib/startback/event/bus/bunny.rb +1 -0
- data/lib/startback/event/bus/memory/async.rb +45 -0
- data/lib/startback/event/bus/memory/sync.rb +35 -0
- data/lib/startback/event/bus/memory.rb +2 -0
- data/lib/startback/event/bus.rb +100 -0
- data/lib/startback/event/engine.rb +94 -0
- data/lib/startback/event/ext/context.rb +5 -0
- data/lib/startback/event/ext/operation.rb +13 -0
- data/lib/startback/event.rb +47 -0
- data/lib/startback/ext/date_time.rb +9 -0
- data/lib/startback/ext/time.rb +9 -0
- data/lib/startback/ext.rb +2 -0
- data/lib/startback/model.rb +6 -0
- data/lib/startback/operation/error_operation.rb +19 -0
- data/lib/startback/operation/multi_operation.rb +28 -0
- data/lib/startback/operation.rb +78 -0
- data/lib/startback/services.rb +11 -0
- data/lib/startback/support/data_object.rb +71 -0
- data/lib/startback/support/env.rb +41 -0
- data/lib/startback/support/fake_logger.rb +18 -0
- data/lib/startback/support/hooks.rb +48 -0
- data/lib/startback/support/log_formatter.rb +34 -0
- data/lib/startback/support/logger.rb +34 -0
- data/lib/startback/support/operation_runner.rb +150 -0
- data/lib/startback/support/robustness.rb +157 -0
- data/lib/startback/support/transaction_manager.rb +25 -0
- data/lib/startback/support/transaction_policy.rb +33 -0
- data/lib/startback/support/world.rb +54 -0
- data/lib/startback/support.rb +26 -0
- data/lib/startback/version.rb +8 -0
- data/lib/startback/web/api.rb +99 -0
- data/lib/startback/web/auto_caching.rb +85 -0
- data/lib/startback/web/catch_all.rb +52 -0
- data/lib/startback/web/cors_headers.rb +80 -0
- data/lib/startback/web/health_check.rb +49 -0
- data/lib/startback/web/magic_assets/ng_html_transformer.rb +80 -0
- data/lib/startback/web/magic_assets/rake_tasks.rb +64 -0
- data/lib/startback/web/magic_assets.rb +98 -0
- data/lib/startback/web/middleware.rb +13 -0
- data/lib/startback/web/prometheus.rb +16 -0
- data/lib/startback/web/shield.rb +58 -0
- data/lib/startback.rb +43 -0
- data/spec/spec_helper.rb +49 -0
- data/spec/unit/audit/test_prometheus.rb +72 -0
- data/spec/unit/audit/test_trailer.rb +105 -0
- data/spec/unit/caching/test_entity_cache.rb +136 -0
- data/spec/unit/context/test_abstraction_factory.rb +64 -0
- data/spec/unit/context/test_dup.rb +42 -0
- data/spec/unit/context/test_fork.rb +37 -0
- data/spec/unit/context/test_h_factory.rb +31 -0
- data/spec/unit/context/test_middleware.rb +45 -0
- data/spec/unit/context/test_with_world.rb +20 -0
- data/spec/unit/context/test_world.rb +17 -0
- data/spec/unit/event/bus/memory/test_async.rb +43 -0
- data/spec/unit/event/bus/memory/test_sync.rb +43 -0
- data/spec/unit/support/hooks/test_after_hook.rb +54 -0
- data/spec/unit/support/hooks/test_before_hook.rb +54 -0
- data/spec/unit/support/operation_runner/test_around_run.rb +156 -0
- data/spec/unit/support/operation_runner/test_before_after_call.rb +48 -0
- data/spec/unit/support/test_data_object.rb +156 -0
- data/spec/unit/support/test_env.rb +75 -0
- data/spec/unit/support/test_robusteness.rb +229 -0
- data/spec/unit/support/test_transaction_manager.rb +64 -0
- data/spec/unit/support/test_world.rb +72 -0
- data/spec/unit/test_event.rb +62 -0
- data/spec/unit/test_operation.rb +55 -0
- data/spec/unit/test_support.rb +40 -0
- data/spec/unit/web/fixtures/assets/app/hello.es6 +4 -0
- data/spec/unit/web/fixtures/assets/app/hello.html +1 -0
- data/spec/unit/web/fixtures/assets/index.es6 +1 -0
- data/spec/unit/web/test_api.rb +82 -0
- data/spec/unit/web/test_auto_caching.rb +81 -0
- data/spec/unit/web/test_catch_all.rb +77 -0
- data/spec/unit/web/test_cors_headers.rb +88 -0
- data/spec/unit/web/test_healthcheck.rb +59 -0
- data/spec/unit/web/test_magic_assets.rb +82 -0
- data/tasks/test.rake +14 -0
- metadata +237 -0
@@ -0,0 +1,100 @@
|
|
1
|
+
module Startback
|
2
|
+
class Event
|
3
|
+
#
|
4
|
+
# Sync and async bus abstraction allowing to register listeners and
|
5
|
+
# emitting events towards them.
|
6
|
+
#
|
7
|
+
# This bus actually decorates two busses, one in synchronous and the
|
8
|
+
# other one is asynchronous (optional).
|
9
|
+
#
|
10
|
+
# * A synchronous bus MUST call the listeners as part of emitting
|
11
|
+
# process, and MUST re-raise any error occuring during that process.
|
12
|
+
# See, e.g. Startback::Bus::Memory::Sync
|
13
|
+
#
|
14
|
+
# * An asynchronous bus MAY call the listeners later, but MUST hide
|
15
|
+
# errors to the emitter.
|
16
|
+
# See, e.g. Startback::Bus::Memory::Async
|
17
|
+
#
|
18
|
+
# This bus facade emits events to both sync and async busses (if any),
|
19
|
+
# and listen on the sync one by default.
|
20
|
+
#
|
21
|
+
# For emitters:
|
22
|
+
#
|
23
|
+
# # This will synchronously call every listeners who `listen`
|
24
|
+
# # on the synchronous bus (& reraise exceptions) then call
|
25
|
+
# # (possibly later) all listeners who `listen` on the
|
26
|
+
# # asynchronous bus if any (& hide exceptions).
|
27
|
+
# bus.emit(event)
|
28
|
+
#
|
29
|
+
# # This only reaches sync listeners
|
30
|
+
# bus.sync.emit(event)
|
31
|
+
#
|
32
|
+
# # This only reaches async listeners (an async bus must be set)
|
33
|
+
# bus.async.emit(event)
|
34
|
+
#
|
35
|
+
# Please note that there is currently no way to reach sync listeners
|
36
|
+
# without having to implement error handling on the emitter side.
|
37
|
+
#
|
38
|
+
# For listeners:
|
39
|
+
#
|
40
|
+
# # This will listen synchronously and make the emitter fail if
|
41
|
+
# # anything goes wrong with the callback:
|
42
|
+
# bus.listen(event_type) do |event|
|
43
|
+
# ...
|
44
|
+
# end
|
45
|
+
#
|
46
|
+
# # It is a shortcut for:
|
47
|
+
# bus.sync.listen(event_type) do |event| ... end
|
48
|
+
#
|
49
|
+
# This will listen asynchronously and could not make the emitter
|
50
|
+
# fail if something goes wrong with the callback.
|
51
|
+
# bus.async.listen(event_type) do |event|
|
52
|
+
# ...
|
53
|
+
# end
|
54
|
+
#
|
55
|
+
# Feel free to access the sync and async busses directly for specific
|
56
|
+
# cases though.
|
57
|
+
#
|
58
|
+
class Bus
|
59
|
+
include Support::Robustness
|
60
|
+
|
61
|
+
def initialize(sync = Memory::Sync.new, async = nil)
|
62
|
+
@sync = sync
|
63
|
+
@async = async
|
64
|
+
end
|
65
|
+
attr_reader :sync, :async
|
66
|
+
|
67
|
+
def connect
|
68
|
+
sync.connect if sync
|
69
|
+
async.connect if async
|
70
|
+
end
|
71
|
+
|
72
|
+
# Emits a particular event to the listeners.
|
73
|
+
#
|
74
|
+
# @arg event an event, should be an Event instance (through duck
|
75
|
+
# typing is allowed)
|
76
|
+
def emit(event)
|
77
|
+
monitor({
|
78
|
+
op: "Startback::Bus#emit",
|
79
|
+
op_data: {
|
80
|
+
event: { type: event.type }
|
81
|
+
}
|
82
|
+
}, event.context) do
|
83
|
+
sync.emit(event)
|
84
|
+
async.emit(event) if async
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
# Registers `listener` as being interested in receiving events of
|
89
|
+
# a specific type.
|
90
|
+
#
|
91
|
+
# @arg type: Symbol, the type of event the listener is interested in.
|
92
|
+
# @arg listener: Proc, the listener itself.
|
93
|
+
def listen(type, processor = nil, listener = nil, &bl)
|
94
|
+
sync.listen(type, processor, listener, &bl)
|
95
|
+
end
|
96
|
+
|
97
|
+
end # class Bus
|
98
|
+
end # class Event
|
99
|
+
end # module Startback
|
100
|
+
require_relative 'bus/memory'
|
@@ -0,0 +1,94 @@
|
|
1
|
+
require 'rack'
|
2
|
+
require 'startback'
|
3
|
+
module Startback
|
4
|
+
class Event
|
5
|
+
#
|
6
|
+
# This class is the starting point of event handling in
|
7
|
+
# Startback. It holds a Bus instance to which emitters
|
8
|
+
# and listeners can connect.
|
9
|
+
#
|
10
|
+
# The Engine exposes a rack app (.rack_app) with a /healthcheck webservice.
|
11
|
+
# It is supposed to be mounted to a webserver such as puma.
|
12
|
+
#
|
13
|
+
# This class goes hand in hand with the `startback:engine`
|
14
|
+
# docker image. It can be extended by subclasses to override
|
15
|
+
# the following methods:
|
16
|
+
#
|
17
|
+
# - bus to use something else than a simple memory bus
|
18
|
+
# - on_health_check to check specific health conditions
|
19
|
+
# - create_agents to instantiate all listening agents
|
20
|
+
# (unless auto_create_agents is used)
|
21
|
+
# - rack_app if you want to customize the API running
|
22
|
+
#
|
23
|
+
class Engine
|
24
|
+
include Support::Robustness
|
25
|
+
|
26
|
+
DEFAULT_OPTIONS = {
|
27
|
+
|
28
|
+
}
|
29
|
+
|
30
|
+
def initialize(options = {}, context = Context.new)
|
31
|
+
@options = DEFAULT_OPTIONS.merge(options)
|
32
|
+
@context = context
|
33
|
+
@context.engine = self
|
34
|
+
end
|
35
|
+
attr_reader :options, :context
|
36
|
+
|
37
|
+
class << self
|
38
|
+
def auto_create_agents?
|
39
|
+
!!@auto_create_agents
|
40
|
+
end
|
41
|
+
|
42
|
+
# Register a base class which will be used to discover
|
43
|
+
# the agents to start when the engine is ran.
|
44
|
+
def auto_create_agents(base_class = nil)
|
45
|
+
@auto_create_agents ||= base_class
|
46
|
+
@auto_create_agents
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# This method is executed on health check and can be
|
51
|
+
# overriden by subclasses to perform specific checks.
|
52
|
+
def on_health_check
|
53
|
+
"Ok"
|
54
|
+
end
|
55
|
+
|
56
|
+
def bus
|
57
|
+
@bus ||= ::Startback::Event::Bus.new
|
58
|
+
end
|
59
|
+
|
60
|
+
def connect
|
61
|
+
log(:info, self, "Connecting to the bus now!")
|
62
|
+
bus.connect
|
63
|
+
end
|
64
|
+
|
65
|
+
def create_agents
|
66
|
+
return unless parent = self.class.auto_create_agents
|
67
|
+
|
68
|
+
ObjectSpace
|
69
|
+
.each_object(Class)
|
70
|
+
.select { |klass| klass <= parent }
|
71
|
+
.each { |klass| klass.new(self) }
|
72
|
+
end
|
73
|
+
|
74
|
+
def factor_event(event_data)
|
75
|
+
Event.json(event_data, context)
|
76
|
+
end
|
77
|
+
|
78
|
+
def rack_app
|
79
|
+
engine = self
|
80
|
+
Rack::Builder.new do
|
81
|
+
use Startback::Web::CatchAll
|
82
|
+
|
83
|
+
map '/health-check' do
|
84
|
+
health = Startback::Web::HealthCheck.new {
|
85
|
+
engine.on_health_check
|
86
|
+
}
|
87
|
+
run(health)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
end # class Engine
|
93
|
+
end # class Event
|
94
|
+
end # module Startback
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module Startback
|
2
|
+
class Operation
|
3
|
+
|
4
|
+
def self.emits(type, &bl)
|
5
|
+
after_call do
|
6
|
+
event_data = instance_exec(&bl)
|
7
|
+
event = type.new(type.to_s, event_data, context)
|
8
|
+
context.engine.bus.emit(event)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
end # class Operation
|
13
|
+
end # module Startback
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Startback
|
2
|
+
#
|
3
|
+
# An Event occuring a given context and having a type and attached data.
|
4
|
+
#
|
5
|
+
# Event instances have String types that are by default unrelated to ruby
|
6
|
+
# classes. Also, this Event class has a `json` information contract that
|
7
|
+
# allows dumping & reloading them easily. A context or context_factory may
|
8
|
+
# be provided in dress world to reload the event context from data, but
|
9
|
+
# that logic is opaque to this class.
|
10
|
+
#
|
11
|
+
# This class is intended to be subclassed if a more specific event protocol
|
12
|
+
# is wanted.
|
13
|
+
#
|
14
|
+
class Event
|
15
|
+
|
16
|
+
def initialize(type, data, context = nil)
|
17
|
+
@type = type.to_s
|
18
|
+
@data = OpenStruct.new(data)
|
19
|
+
@context = context
|
20
|
+
end
|
21
|
+
attr_reader :context, :type, :data
|
22
|
+
|
23
|
+
def self.json(src, context)
|
24
|
+
return src if src.is_a?(Event)
|
25
|
+
|
26
|
+
parsed = JSON.parse(src)
|
27
|
+
klass = Kernel.const_get(parsed['type'])
|
28
|
+
context = context.fork(parsed['context']) if context
|
29
|
+
klass.new(parsed['type'], parsed['data'], context)
|
30
|
+
end
|
31
|
+
|
32
|
+
def to_json(*args, &bl)
|
33
|
+
h = {
|
34
|
+
type: self.type,
|
35
|
+
data: data.to_h
|
36
|
+
}
|
37
|
+
h[:context] = context if context
|
38
|
+
h.to_json(*args, &bl)
|
39
|
+
end
|
40
|
+
|
41
|
+
end # class Event
|
42
|
+
end # module Startback
|
43
|
+
require_relative 'event/ext/context'
|
44
|
+
require_relative 'event/ext/operation'
|
45
|
+
require_relative 'event/agent'
|
46
|
+
require_relative 'event/bus'
|
47
|
+
require_relative 'event/engine'
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Startback
|
2
|
+
class Operation
|
3
|
+
class ErrorOperation < Operation
|
4
|
+
|
5
|
+
def initialize(details)
|
6
|
+
@details = details
|
7
|
+
end
|
8
|
+
attr_reader :details
|
9
|
+
|
10
|
+
def call
|
11
|
+
end
|
12
|
+
|
13
|
+
def bind(world)
|
14
|
+
self
|
15
|
+
end
|
16
|
+
|
17
|
+
end # class ErrorOperation
|
18
|
+
end # class Operation
|
19
|
+
end # module Startback
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Startback
|
2
|
+
class Operation
|
3
|
+
class MultiOperation < Operation
|
4
|
+
|
5
|
+
def initialize(ops = [])
|
6
|
+
@ops = ops
|
7
|
+
end
|
8
|
+
attr_reader :ops
|
9
|
+
|
10
|
+
def size
|
11
|
+
ops.size
|
12
|
+
end
|
13
|
+
|
14
|
+
def +(other)
|
15
|
+
MultiOperation.new(@ops + Array(other))
|
16
|
+
end
|
17
|
+
|
18
|
+
def bind(world)
|
19
|
+
MultiOperation.new(ops.map{|op| op.bind(world) })
|
20
|
+
end
|
21
|
+
|
22
|
+
def call
|
23
|
+
ops.map{|op| op.call }
|
24
|
+
end
|
25
|
+
|
26
|
+
end # class MultiOperation
|
27
|
+
end # class Operation
|
28
|
+
end # module Startback
|
@@ -0,0 +1,78 @@
|
|
1
|
+
module Startback
|
2
|
+
#
|
3
|
+
# High-level Operation abstraction, that is a piece of code that executes
|
4
|
+
# on demand and (generally) changes the state of the software system.
|
5
|
+
#
|
6
|
+
# An operation is basically an object that respond to `call`, but that
|
7
|
+
# executes within a given world (see `bind`). It also has before and
|
8
|
+
# after hooks that allows specifying what needs to be done before invoking
|
9
|
+
# call and after having invoked it. All this protocol is actually under
|
10
|
+
# the responsibility of an `OperationRunner`. Operations should not be
|
11
|
+
# called manually by third-party code.
|
12
|
+
#
|
13
|
+
# Example:
|
14
|
+
#
|
15
|
+
# class SayHello < Startback::Operation
|
16
|
+
#
|
17
|
+
# before_call do
|
18
|
+
# # e.g. check_some_permissions
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
# def call
|
22
|
+
# puts "Hello"
|
23
|
+
# end
|
24
|
+
#
|
25
|
+
# after_call do
|
26
|
+
# # e.g. log and/or emit something on a bus
|
27
|
+
# end
|
28
|
+
#
|
29
|
+
# end
|
30
|
+
#
|
31
|
+
class Operation
|
32
|
+
extend Support::TransactionPolicy
|
33
|
+
include Errors
|
34
|
+
include Support::OperationRunner
|
35
|
+
include Support::Hooks.new(:call)
|
36
|
+
|
37
|
+
attr_accessor :world
|
38
|
+
protected :world=
|
39
|
+
|
40
|
+
def initialize(input = {})
|
41
|
+
@input = input
|
42
|
+
end
|
43
|
+
attr_reader :input
|
44
|
+
|
45
|
+
def bind(world)
|
46
|
+
return self unless world
|
47
|
+
self.world = world
|
48
|
+
self
|
49
|
+
end
|
50
|
+
|
51
|
+
def method_missing(name, *args, &bl)
|
52
|
+
return super unless args.empty? and bl.nil?
|
53
|
+
return super unless world
|
54
|
+
world.fetch(name){ super }
|
55
|
+
end
|
56
|
+
|
57
|
+
def respond_to?(name, *args)
|
58
|
+
super || (world && world.has_key?(name))
|
59
|
+
end
|
60
|
+
|
61
|
+
def with_context(ctx = nil)
|
62
|
+
old_world = self.world
|
63
|
+
self.world = self.world.merge(context: ctx || old_world.context.dup)
|
64
|
+
result = ctx ? yield : yield(self.world.context)
|
65
|
+
self.world = old_world
|
66
|
+
result
|
67
|
+
end
|
68
|
+
|
69
|
+
protected
|
70
|
+
|
71
|
+
def operation_world(op)
|
72
|
+
self.world
|
73
|
+
end
|
74
|
+
|
75
|
+
end # class Operation
|
76
|
+
end # module Startback
|
77
|
+
require_relative 'operation/error_operation'
|
78
|
+
require_relative 'operation/multi_operation'
|
@@ -0,0 +1,71 @@
|
|
1
|
+
module Startback
|
2
|
+
module Support
|
3
|
+
module DataObject
|
4
|
+
|
5
|
+
def initialize(data = {})
|
6
|
+
@_data = data.dup.freeze
|
7
|
+
end
|
8
|
+
|
9
|
+
attr_writer :_data
|
10
|
+
protected :_data=
|
11
|
+
|
12
|
+
def method_missing(name, *args, &bl)
|
13
|
+
return super unless args.empty? && bl.nil?
|
14
|
+
return super unless pair = _data_key_for(name)
|
15
|
+
|
16
|
+
pair.last ? !!@_data[pair.first] : @_data[pair.first]
|
17
|
+
end
|
18
|
+
|
19
|
+
def [](name)
|
20
|
+
return nil unless pair = _data_key_for(name, false, false)
|
21
|
+
|
22
|
+
@_data[pair.first]
|
23
|
+
end
|
24
|
+
|
25
|
+
def respond_to?(name)
|
26
|
+
super || !_data_key_for(name).nil?
|
27
|
+
end
|
28
|
+
|
29
|
+
def to_data
|
30
|
+
@_data
|
31
|
+
end
|
32
|
+
alias :to_h :to_data
|
33
|
+
|
34
|
+
def to_json(*args, &bl)
|
35
|
+
to_data.to_json(*args, &bl)
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def _data_key_for(key, try_camelize = _data_allow_camelize, try_query = _data_allow_query)
|
41
|
+
if @_data.key?(key)
|
42
|
+
[key, false]
|
43
|
+
elsif @_data.key?(key.to_s)
|
44
|
+
[key.to_s, false]
|
45
|
+
elsif key.is_a?(String) && @_data.key?(key.to_sym)
|
46
|
+
[key.to_sym, false]
|
47
|
+
elsif try_camelize
|
48
|
+
cam = key.to_s.gsub(/_([a-z])/){ $1.upcase }.to_sym
|
49
|
+
_data_key_for(cam, false, true)
|
50
|
+
elsif try_query && key.to_s =~ /\?$/
|
51
|
+
got = _data_key_for(key[0...-1].to_sym, false, false)
|
52
|
+
got ? [got.first, true] : _data_key_not_found(key)
|
53
|
+
else
|
54
|
+
_data_key_not_found(key)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def _data_allow_camelize
|
59
|
+
true
|
60
|
+
end
|
61
|
+
|
62
|
+
def _data_allow_query
|
63
|
+
true
|
64
|
+
end
|
65
|
+
|
66
|
+
def _data_key_not_found(key)
|
67
|
+
nil
|
68
|
+
end
|
69
|
+
end # module DataObject
|
70
|
+
end # module Support
|
71
|
+
end # module Startback
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module Startback
|
2
|
+
module Support
|
3
|
+
# This method provides the `env` and `env!` methods that
|
4
|
+
# help querying environment variables easily.
|
5
|
+
module Env
|
6
|
+
|
7
|
+
# Returns an environment variable or raise an error if
|
8
|
+
# not set.
|
9
|
+
#
|
10
|
+
# The result is always a String with no leading/trailing
|
11
|
+
# spaces.
|
12
|
+
#
|
13
|
+
# If a block is given, the environment variable is yield
|
14
|
+
# and the result of the block returned.
|
15
|
+
def env!(key, default = nil, &bl)
|
16
|
+
v = ENV[key].to_s.strip
|
17
|
+
raise Startback::Error, "Missing ENV var `#{key}`" if v.empty?
|
18
|
+
|
19
|
+
env(key, default, &bl)
|
20
|
+
end
|
21
|
+
module_function :env!
|
22
|
+
|
23
|
+
# Returns an environment variable or the default value
|
24
|
+
# passed as second argument.
|
25
|
+
#
|
26
|
+
# The result is always a String with no leading/trailing
|
27
|
+
# spaces.
|
28
|
+
#
|
29
|
+
# If a block is given, the environment variable is yield
|
30
|
+
# and the result of the block returned.
|
31
|
+
def env(key, default = nil, &bl)
|
32
|
+
v = ENV[key].to_s.strip
|
33
|
+
v = v.empty? ? default : v
|
34
|
+
v = bl.call(v) if bl && v
|
35
|
+
v
|
36
|
+
end
|
37
|
+
module_function :env
|
38
|
+
|
39
|
+
end # module Env
|
40
|
+
end # module Support
|
41
|
+
end # module Startback
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Startback
|
2
|
+
module Support
|
3
|
+
class FakeLogger < Logger
|
4
|
+
|
5
|
+
def initialize(*args)
|
6
|
+
@last_msg = nil
|
7
|
+
end
|
8
|
+
attr_reader :last_msg
|
9
|
+
|
10
|
+
[:debug, :info, :warn, :error, :fatal].each do |meth|
|
11
|
+
define_method(meth) do |msg|
|
12
|
+
@last_msg = msg
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
end # class Logger
|
17
|
+
end # module Support
|
18
|
+
end # module Startback
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Startback
|
2
|
+
module Support
|
3
|
+
class Hooks < Module
|
4
|
+
|
5
|
+
def initialize(suffix)
|
6
|
+
@suffix = suffix
|
7
|
+
define_method :"before_#{suffix}" do
|
8
|
+
self.class.__befores.each do |bl|
|
9
|
+
instance_exec(&bl)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
define_method :"after_#{suffix}" do
|
13
|
+
self.class.__afters.each do |bl|
|
14
|
+
instance_exec(&bl)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
attr_reader :suffix
|
19
|
+
|
20
|
+
def included(by)
|
21
|
+
by.instance_eval %Q{
|
22
|
+
def __befores(create = false)
|
23
|
+
if create
|
24
|
+
@__befores ||= (superclass.respond_to?(:__befores, false) ? superclass.__befores.dup : [])
|
25
|
+
end
|
26
|
+
@__befores || (superclass.respond_to?(:__befores, false) ? superclass.__befores : [])
|
27
|
+
end
|
28
|
+
|
29
|
+
def __afters(create = false)
|
30
|
+
if create
|
31
|
+
@__afters ||= (superclass.respond_to?(:__afters, false) ? superclass.__afters.dup : [])
|
32
|
+
end
|
33
|
+
@__afters || (superclass.respond_to?(:__afters, false) ? superclass.__afters : [])
|
34
|
+
end
|
35
|
+
|
36
|
+
def before_#{suffix}(&bl)
|
37
|
+
__befores(true) << bl
|
38
|
+
end
|
39
|
+
|
40
|
+
def after_#{suffix}(&bl)
|
41
|
+
__afters(true) << bl
|
42
|
+
end
|
43
|
+
}
|
44
|
+
end
|
45
|
+
|
46
|
+
end # class Hooks
|
47
|
+
end # module Support
|
48
|
+
end # module Startback
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Startback
|
2
|
+
module Support
|
3
|
+
class LogFormatter
|
4
|
+
|
5
|
+
def call(severity, time, progname, msg)
|
6
|
+
msg = { message: msg } if msg.is_a?(String)
|
7
|
+
msg = { error: msg } if msg.is_a?(Exception)
|
8
|
+
{
|
9
|
+
severity: severity,
|
10
|
+
time: time
|
11
|
+
}.merge(msg)
|
12
|
+
.merge(error: error_to_json(msg[:error], severity))
|
13
|
+
.compact
|
14
|
+
.to_json << "\n"
|
15
|
+
end
|
16
|
+
|
17
|
+
def error_to_json(error, severity = nil)
|
18
|
+
return error if error.nil?
|
19
|
+
return error if error.is_a?(String)
|
20
|
+
return error.to_s unless error.is_a?(Exception)
|
21
|
+
|
22
|
+
backtrace = error.backtrace[0..25] if severity == "FATAL"
|
23
|
+
causes = error.causes.map{|c| error_to_json(c) } if error.respond_to?(:causes)
|
24
|
+
causes = nil if causes && causes.empty?
|
25
|
+
{
|
26
|
+
message: error.message,
|
27
|
+
backtrace: backtrace,
|
28
|
+
causes: causes
|
29
|
+
}.compact
|
30
|
+
end
|
31
|
+
|
32
|
+
end # class LogFormatter
|
33
|
+
end # module Support
|
34
|
+
end # module Startback
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Startback
|
2
|
+
module Support
|
3
|
+
#
|
4
|
+
# A Logger extension that sends info and debug messages to STDOUT
|
5
|
+
# and other messages to STDERR. This is not configurable.
|
6
|
+
#
|
7
|
+
class Logger < ::Logger
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
super(STDOUT)
|
11
|
+
@err_logger = ::Logger.new(STDERR)
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.level=(level)
|
15
|
+
super.tap{
|
16
|
+
@err_logger.level = level
|
17
|
+
}
|
18
|
+
end
|
19
|
+
|
20
|
+
def warn(*args, &bl)
|
21
|
+
@err_logger.warn(*args, &bl)
|
22
|
+
end
|
23
|
+
|
24
|
+
def error(*args, &bl)
|
25
|
+
@err_logger.error(*args, &bl)
|
26
|
+
end
|
27
|
+
|
28
|
+
def fatal(*args, &bl)
|
29
|
+
@err_logger.fatal(*args, &bl)
|
30
|
+
end
|
31
|
+
|
32
|
+
end # class Logger
|
33
|
+
end # module Support
|
34
|
+
end # module Startback
|