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.
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