startback-websocket 0.14.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (95) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +3 -0
  3. data/README.md +13 -0
  4. data/Rakefile +18 -0
  5. data/lib/startback/audit/prometheus.rb +87 -0
  6. data/lib/startback/audit/shared.rb +17 -0
  7. data/lib/startback/audit/trailer.rb +129 -0
  8. data/lib/startback/audit.rb +3 -0
  9. data/lib/startback/caching/entity_cache.rb +157 -0
  10. data/lib/startback/caching/no_store.rb +28 -0
  11. data/lib/startback/caching/store.rb +34 -0
  12. data/lib/startback/context/h_factory.rb +43 -0
  13. data/lib/startback/context/middleware.rb +53 -0
  14. data/lib/startback/context.rb +122 -0
  15. data/lib/startback/errors.rb +197 -0
  16. data/lib/startback/event/agent.rb +84 -0
  17. data/lib/startback/event/bus/bunny/async.rb +162 -0
  18. data/lib/startback/event/bus/bunny.rb +1 -0
  19. data/lib/startback/event/bus/memory/async.rb +45 -0
  20. data/lib/startback/event/bus/memory/sync.rb +35 -0
  21. data/lib/startback/event/bus/memory.rb +2 -0
  22. data/lib/startback/event/bus.rb +100 -0
  23. data/lib/startback/event/engine.rb +94 -0
  24. data/lib/startback/event/ext/context.rb +5 -0
  25. data/lib/startback/event/ext/operation.rb +13 -0
  26. data/lib/startback/event.rb +47 -0
  27. data/lib/startback/ext/date_time.rb +9 -0
  28. data/lib/startback/ext/time.rb +9 -0
  29. data/lib/startback/ext.rb +2 -0
  30. data/lib/startback/model.rb +6 -0
  31. data/lib/startback/operation/error_operation.rb +19 -0
  32. data/lib/startback/operation/multi_operation.rb +28 -0
  33. data/lib/startback/operation.rb +78 -0
  34. data/lib/startback/services.rb +11 -0
  35. data/lib/startback/support/data_object.rb +71 -0
  36. data/lib/startback/support/env.rb +41 -0
  37. data/lib/startback/support/fake_logger.rb +18 -0
  38. data/lib/startback/support/hooks.rb +48 -0
  39. data/lib/startback/support/log_formatter.rb +34 -0
  40. data/lib/startback/support/logger.rb +34 -0
  41. data/lib/startback/support/operation_runner.rb +150 -0
  42. data/lib/startback/support/robustness.rb +157 -0
  43. data/lib/startback/support/transaction_manager.rb +25 -0
  44. data/lib/startback/support/transaction_policy.rb +33 -0
  45. data/lib/startback/support/world.rb +54 -0
  46. data/lib/startback/support.rb +26 -0
  47. data/lib/startback/version.rb +8 -0
  48. data/lib/startback/web/api.rb +99 -0
  49. data/lib/startback/web/auto_caching.rb +85 -0
  50. data/lib/startback/web/catch_all.rb +52 -0
  51. data/lib/startback/web/cors_headers.rb +80 -0
  52. data/lib/startback/web/health_check.rb +49 -0
  53. data/lib/startback/web/magic_assets/ng_html_transformer.rb +80 -0
  54. data/lib/startback/web/magic_assets/rake_tasks.rb +64 -0
  55. data/lib/startback/web/magic_assets.rb +98 -0
  56. data/lib/startback/web/middleware.rb +13 -0
  57. data/lib/startback/web/prometheus.rb +16 -0
  58. data/lib/startback/web/shield.rb +58 -0
  59. data/lib/startback.rb +43 -0
  60. data/spec/spec_helper.rb +49 -0
  61. data/spec/unit/audit/test_prometheus.rb +72 -0
  62. data/spec/unit/audit/test_trailer.rb +105 -0
  63. data/spec/unit/caching/test_entity_cache.rb +136 -0
  64. data/spec/unit/context/test_abstraction_factory.rb +64 -0
  65. data/spec/unit/context/test_dup.rb +42 -0
  66. data/spec/unit/context/test_fork.rb +37 -0
  67. data/spec/unit/context/test_h_factory.rb +31 -0
  68. data/spec/unit/context/test_middleware.rb +45 -0
  69. data/spec/unit/context/test_with_world.rb +20 -0
  70. data/spec/unit/context/test_world.rb +17 -0
  71. data/spec/unit/event/bus/memory/test_async.rb +43 -0
  72. data/spec/unit/event/bus/memory/test_sync.rb +43 -0
  73. data/spec/unit/support/hooks/test_after_hook.rb +54 -0
  74. data/spec/unit/support/hooks/test_before_hook.rb +54 -0
  75. data/spec/unit/support/operation_runner/test_around_run.rb +156 -0
  76. data/spec/unit/support/operation_runner/test_before_after_call.rb +48 -0
  77. data/spec/unit/support/test_data_object.rb +156 -0
  78. data/spec/unit/support/test_env.rb +75 -0
  79. data/spec/unit/support/test_robusteness.rb +229 -0
  80. data/spec/unit/support/test_transaction_manager.rb +64 -0
  81. data/spec/unit/support/test_world.rb +72 -0
  82. data/spec/unit/test_event.rb +62 -0
  83. data/spec/unit/test_operation.rb +55 -0
  84. data/spec/unit/test_support.rb +40 -0
  85. data/spec/unit/web/fixtures/assets/app/hello.es6 +4 -0
  86. data/spec/unit/web/fixtures/assets/app/hello.html +1 -0
  87. data/spec/unit/web/fixtures/assets/index.es6 +1 -0
  88. data/spec/unit/web/test_api.rb +82 -0
  89. data/spec/unit/web/test_auto_caching.rb +81 -0
  90. data/spec/unit/web/test_catch_all.rb +77 -0
  91. data/spec/unit/web/test_cors_headers.rb +88 -0
  92. data/spec/unit/web/test_healthcheck.rb +59 -0
  93. data/spec/unit/web/test_magic_assets.rb +82 -0
  94. data/tasks/test.rake +14 -0
  95. 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,5 @@
1
+ module Startback
2
+ class Context
3
+ attr_accessor :engine
4
+ end # class Context
5
+ 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,9 @@
1
+ class DateTime
2
+
3
+ # Makes sure that DateTime are exported with ISO8601
4
+ # conventions when using to_json and to_csv
5
+ def to_s(*args)
6
+ iso8601
7
+ end
8
+
9
+ end
@@ -0,0 +1,9 @@
1
+ class Time
2
+
3
+ # Makes sure that Time are exported with ISO8601
4
+ # conventions when using to_json and to_csv
5
+ def to_s(*args)
6
+ iso8601
7
+ end
8
+
9
+ end
@@ -0,0 +1,2 @@
1
+ require_relative 'ext/date_time'
2
+ require_relative 'ext/time'
@@ -0,0 +1,6 @@
1
+ module Startback
2
+ class Model
3
+ include Support::DataObject
4
+
5
+ end # class Model
6
+ end # module Startback
@@ -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,11 @@
1
+ module Startback
2
+ class Services
3
+ include Startback::Errors
4
+
5
+ def initialize(context)
6
+ @context = context
7
+ end
8
+ attr_reader :context
9
+
10
+ end # class Services
11
+ end # module Startback
@@ -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