startback-websocket 0.14.0
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.
- 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
|