hanami 2.0.0.alpha1 → 2.0.0.alpha5
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 +4 -4
- data/CHANGELOG.md +306 -5
- data/FEATURES.md +9 -1
- data/LICENSE.md +1 -1
- data/README.md +9 -6
- data/hanami.gemspec +12 -11
- data/lib/hanami/application/autoloader/inflector_adapter.rb +22 -0
- data/lib/hanami/application/container/boot/inflector.rb +7 -0
- data/lib/hanami/application/container/boot/logger.rb +7 -0
- data/lib/hanami/application/container/boot/rack_logger.rb +19 -0
- data/lib/hanami/application/container/boot/rack_monitor.rb +12 -0
- data/lib/hanami/application/container/boot/routes_helper.rb +9 -0
- data/lib/hanami/application/container/boot/settings.rb +7 -0
- data/lib/hanami/application/router.rb +59 -0
- data/lib/hanami/application/routes.rb +55 -0
- data/lib/hanami/application/routes_helper.rb +34 -0
- data/lib/hanami/application/routing/middleware/stack.rb +89 -0
- data/lib/hanami/application/routing/resolver/node.rb +50 -0
- data/lib/hanami/application/routing/resolver/trie.rb +59 -0
- data/lib/hanami/application/routing/resolver.rb +87 -0
- data/lib/hanami/application/routing/router.rb +36 -0
- data/lib/hanami/application/settings/dotenv_store.rb +60 -0
- data/lib/hanami/application/settings.rb +93 -0
- data/lib/hanami/application.rb +330 -34
- data/lib/hanami/assets/application_configuration.rb +63 -0
- data/lib/hanami/assets/configuration.rb +54 -0
- data/lib/hanami/boot/source_dirs.rb +44 -0
- data/lib/hanami/boot.rb +1 -2
- data/lib/hanami/cli/application/cli.rb +40 -0
- data/lib/hanami/cli/application/command.rb +47 -0
- data/lib/hanami/cli/application/commands/console.rb +81 -0
- data/lib/hanami/cli/application/commands.rb +16 -0
- data/lib/hanami/cli/base_command.rb +48 -0
- data/lib/hanami/cli/commands/command.rb +4 -4
- data/lib/hanami/cli/commands.rb +3 -2
- data/lib/hanami/configuration/logger.rb +84 -0
- data/lib/hanami/configuration/middleware.rb +4 -4
- data/lib/hanami/configuration/null_configuration.rb +14 -0
- data/lib/hanami/configuration/router.rb +52 -0
- data/lib/hanami/configuration/sessions.rb +5 -5
- data/lib/hanami/configuration/source_dirs.rb +42 -0
- data/lib/hanami/configuration.rb +122 -131
- data/lib/hanami/init.rb +5 -0
- data/lib/hanami/setup.rb +9 -0
- data/lib/hanami/slice.rb +189 -0
- data/lib/hanami/version.rb +1 -1
- data/lib/hanami/web/rack_logger.rb +96 -0
- data/lib/hanami.rb +17 -30
- metadata +116 -50
- data/bin/hanami +0 -8
- data/lib/hanami/configuration/cookies.rb +0 -24
- data/lib/hanami/configuration/security.rb +0 -141
- data/lib/hanami/container.rb +0 -107
- data/lib/hanami/frameworks.rb +0 -28
- data/lib/hanami/routes.rb +0 -31
data/lib/hanami/configuration.rb
CHANGED
@@ -3,7 +3,16 @@
|
|
3
3
|
require "uri"
|
4
4
|
require "concurrent/hash"
|
5
5
|
require "concurrent/array"
|
6
|
+
require "dry/configurable"
|
6
7
|
require "dry/inflector"
|
8
|
+
require "pathname"
|
9
|
+
|
10
|
+
require_relative "application/settings/dotenv_store"
|
11
|
+
require_relative "configuration/logger"
|
12
|
+
require_relative "configuration/middleware"
|
13
|
+
require_relative "configuration/router"
|
14
|
+
require_relative "configuration/sessions"
|
15
|
+
require_relative "configuration/source_dirs"
|
7
16
|
|
8
17
|
module Hanami
|
9
18
|
# Hanami application configuration
|
@@ -12,142 +21,149 @@ module Hanami
|
|
12
21
|
#
|
13
22
|
# rubocop:disable Metrics/ClassLength
|
14
23
|
class Configuration
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
24
|
+
include Dry::Configurable
|
25
|
+
|
26
|
+
DEFAULT_ENVIRONMENTS = Concurrent::Hash.new { |h, k| h[k] = Concurrent::Array.new }
|
27
|
+
private_constant :DEFAULT_ENVIRONMENTS
|
19
28
|
|
20
|
-
|
21
|
-
|
22
|
-
@settings = Concurrent::Hash.new
|
29
|
+
MODULE_DELIMITER = "::"
|
30
|
+
private_constant :MODULE_DELIMITER
|
23
31
|
|
24
|
-
|
25
|
-
|
32
|
+
attr_reader :actions
|
33
|
+
attr_reader :middleware
|
34
|
+
attr_reader :router
|
35
|
+
attr_reader :views, :assets
|
26
36
|
|
27
|
-
|
37
|
+
attr_reader :environments
|
38
|
+
private :environments
|
28
39
|
|
29
|
-
|
30
|
-
|
31
|
-
self.cookies = DEFAULT_COOKIES
|
32
|
-
self.sessions = DEFAULT_SESSIONS
|
40
|
+
def initialize(application_name:, env:)
|
41
|
+
@namespace = application_name.split(MODULE_DELIMITER)[0..-2].join(MODULE_DELIMITER)
|
33
42
|
|
34
|
-
|
35
|
-
|
43
|
+
@environments = DEFAULT_ENVIRONMENTS.clone
|
44
|
+
config.env = env
|
36
45
|
|
37
|
-
|
38
|
-
|
46
|
+
# Some default setting values must be assigned at initialize-time to ensure they
|
47
|
+
# have appropriate values for the current application
|
48
|
+
self.root = Dir.pwd
|
49
|
+
self.settings_store = Application::Settings::DotenvStore.new.with_dotenv_loaded
|
39
50
|
|
40
|
-
|
41
|
-
end
|
42
|
-
# rubocop:enable Metrics/MethodLength
|
51
|
+
config.logger = Configuration::Logger.new(env: env, application_name: method(:application_name))
|
43
52
|
|
44
|
-
|
45
|
-
|
46
|
-
|
53
|
+
@assets = begin
|
54
|
+
require_path = "hanami/assets/application_configuration"
|
55
|
+
require require_path
|
56
|
+
Hanami::Assets::ApplicationConfiguration.new
|
57
|
+
rescue LoadError => e
|
58
|
+
raise e unless e.path == require_path
|
59
|
+
require_relative "configuration/null_configuration"
|
60
|
+
NullConfiguration.new
|
47
61
|
end
|
48
62
|
|
49
|
-
|
50
|
-
|
63
|
+
# Config for actions (same for views, below) may not be available if the gem isn't
|
64
|
+
# loaded; fall back to a null config object if it's missing
|
65
|
+
@actions = begin
|
66
|
+
require_path = "hanami/action/application_configuration"
|
67
|
+
require require_path
|
68
|
+
Hanami::Action::ApplicationConfiguration.new(assets_server_url: assets.server_url)
|
69
|
+
rescue LoadError => e
|
70
|
+
raise e unless e.path == require_path
|
71
|
+
require_relative "configuration/null_configuration"
|
72
|
+
NullConfiguration.new
|
73
|
+
end
|
51
74
|
|
52
|
-
|
53
|
-
environment_for(name).push(blk)
|
54
|
-
end
|
75
|
+
@middleware = Middleware.new
|
55
76
|
|
56
|
-
|
57
|
-
settings[:env] = value
|
58
|
-
end
|
77
|
+
@router = Router.new(self)
|
59
78
|
|
60
|
-
|
61
|
-
|
62
|
-
|
79
|
+
@views = begin
|
80
|
+
require_path = "hanami/view/application_configuration"
|
81
|
+
require require_path
|
82
|
+
Hanami::View::ApplicationConfiguration.new
|
83
|
+
rescue LoadError => e
|
84
|
+
raise e unless e.path == require_path
|
85
|
+
require_relative "configuration/null_configuration"
|
86
|
+
NullConfiguration.new
|
87
|
+
end
|
63
88
|
|
64
|
-
|
65
|
-
settings[:base_url] = URI.parse(value)
|
89
|
+
yield self if block_given?
|
66
90
|
end
|
67
91
|
|
68
|
-
def
|
69
|
-
|
70
|
-
|
92
|
+
def environment(env_name, &block)
|
93
|
+
environments[env_name] << block
|
94
|
+
apply_env_config
|
71
95
|
|
72
|
-
|
73
|
-
settings[:logger] = options
|
96
|
+
self
|
74
97
|
end
|
75
98
|
|
76
|
-
def
|
77
|
-
|
78
|
-
end
|
99
|
+
def finalize!
|
100
|
+
apply_env_config
|
79
101
|
|
80
|
-
|
81
|
-
|
82
|
-
|
102
|
+
# Finalize nested configurations
|
103
|
+
assets.finalize!
|
104
|
+
actions.finalize!
|
105
|
+
views.finalize!
|
106
|
+
logger.finalize!
|
107
|
+
router.finalize!
|
83
108
|
|
84
|
-
|
85
|
-
settings.fetch(:routes)
|
109
|
+
super
|
86
110
|
end
|
87
111
|
|
88
|
-
def
|
89
|
-
|
112
|
+
def namespace
|
113
|
+
inflector.constantize(@namespace)
|
90
114
|
end
|
91
115
|
|
92
|
-
def
|
93
|
-
|
116
|
+
def application_name
|
117
|
+
inflector.underscore(@namespace).to_sym
|
94
118
|
end
|
95
119
|
|
96
|
-
|
97
|
-
settings[:sessions] = Sessions.new(args)
|
98
|
-
end
|
120
|
+
setting :env
|
99
121
|
|
100
|
-
def
|
101
|
-
|
122
|
+
def env=(new_env)
|
123
|
+
config.env = env
|
124
|
+
apply_env_config(new_env)
|
102
125
|
end
|
103
126
|
|
104
|
-
|
105
|
-
settings[:default_request_format] = value
|
106
|
-
end
|
127
|
+
setting :root, constructor: -> path { Pathname(path) }
|
107
128
|
|
108
|
-
|
109
|
-
settings.fetch(:default_request_format)
|
110
|
-
end
|
129
|
+
setting :inflector, default: Dry::Inflector.new, cloneable: true
|
111
130
|
|
112
|
-
def
|
113
|
-
|
131
|
+
def inflections(&block)
|
132
|
+
self.inflector = Dry::Inflector.new(&block)
|
114
133
|
end
|
115
134
|
|
116
|
-
|
117
|
-
settings.fetch(:default_response_format)
|
118
|
-
end
|
135
|
+
setting :logger, cloneable: true
|
119
136
|
|
120
|
-
def
|
121
|
-
|
137
|
+
def logger=(logger_instance)
|
138
|
+
@logger_instance = logger_instance
|
122
139
|
end
|
123
140
|
|
124
|
-
def
|
125
|
-
|
141
|
+
def logger_instance
|
142
|
+
@logger_instance || logger.instance
|
126
143
|
end
|
127
144
|
|
128
|
-
|
129
|
-
settings.fetch(:security)
|
130
|
-
end
|
145
|
+
setting :settings_path, default: File.join("config", "settings")
|
131
146
|
|
132
|
-
|
133
|
-
if blk.nil?
|
134
|
-
settings.fetch(:inflections)
|
135
|
-
else
|
136
|
-
settings[:inflections] = Dry::Inflector.new(&blk)
|
137
|
-
end
|
138
|
-
end
|
147
|
+
setting :settings_class_name, default: "Settings"
|
139
148
|
|
140
|
-
|
141
|
-
bu = base_url
|
149
|
+
setting :settings_store, default: Application::Settings::DotenvStore
|
142
150
|
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
151
|
+
setting :slices_dir, default: "slices"
|
152
|
+
|
153
|
+
setting :slices_namespace, default: Object
|
154
|
+
|
155
|
+
# TODO: convert into a dedicated object with explicit behaviour around blocks per
|
156
|
+
# slice, etc.
|
157
|
+
setting :slices, default: {}, constructor: :dup.to_proc
|
158
|
+
|
159
|
+
setting :source_dirs, default: Configuration::SourceDirs.new, cloneable: true
|
160
|
+
|
161
|
+
def slice(slice_name, &block)
|
162
|
+
slices[slice_name] = block
|
149
163
|
end
|
150
164
|
|
165
|
+
setting :base_url, default: "http://0.0.0.0:2300", constructor: -> url { URI(url) }
|
166
|
+
|
151
167
|
def for_each_middleware(&blk)
|
152
168
|
stack = middleware.stack.dup
|
153
169
|
stack += sessions.middleware if sessions.enabled?
|
@@ -155,51 +171,26 @@ module Hanami
|
|
155
171
|
stack.each(&blk)
|
156
172
|
end
|
157
173
|
|
158
|
-
|
174
|
+
setting :sessions, default: :null, constructor: -> *args { Sessions.new(*args) }
|
159
175
|
|
160
|
-
|
161
|
-
settings[:environments][name]
|
162
|
-
end
|
176
|
+
private
|
163
177
|
|
164
|
-
def
|
165
|
-
|
178
|
+
def apply_env_config(env = self.env)
|
179
|
+
environments[env].each do |block|
|
180
|
+
instance_eval(&block)
|
181
|
+
end
|
166
182
|
end
|
167
183
|
|
168
|
-
def
|
169
|
-
|
184
|
+
def method_missing(name, *args, &block)
|
185
|
+
if config.respond_to?(name)
|
186
|
+
config.public_send(name, *args, &block)
|
187
|
+
else
|
188
|
+
super
|
189
|
+
end
|
170
190
|
end
|
171
191
|
|
172
|
-
def
|
173
|
-
|
192
|
+
def respond_to_missing?(name, _incude_all = false)
|
193
|
+
config.respond_to?(name) || super
|
174
194
|
end
|
175
|
-
|
176
|
-
private
|
177
|
-
|
178
|
-
DEFAULT_ENVIRONMENTS = Concurrent::Hash.new { |h, k| h[k] = Concurrent::Array.new }
|
179
|
-
private_constant :DEFAULT_ENVIRONMENTS
|
180
|
-
|
181
|
-
DEFAULT_BASE_URL = "http://0.0.0.0:2300"
|
182
|
-
private_constant :DEFAULT_BASE_URL
|
183
|
-
|
184
|
-
DEFAULT_LOGGER = { level: :debug }.freeze
|
185
|
-
private_constant :DEFAULT_LOGGER
|
186
|
-
|
187
|
-
DEFAULT_ROUTES = File.join("config", "routes")
|
188
|
-
private_constant :DEFAULT_ROUTES
|
189
|
-
|
190
|
-
DEFAULT_COOKIES = Cookies.null
|
191
|
-
private_constant :DEFAULT_COOKIES
|
192
|
-
|
193
|
-
DEFAULT_SESSIONS = Sessions.null
|
194
|
-
private_constant :DEFAULT_SESSIONS
|
195
|
-
|
196
|
-
DEFAULT_REQUEST_FORMAT = :html
|
197
|
-
private_constant :DEFAULT_REQUEST_FORMAT
|
198
|
-
|
199
|
-
DEFAULT_RESPONSE_FORMAT = :html
|
200
|
-
private_constant :DEFAULT_RESPONSE_FORMAT
|
201
|
-
|
202
|
-
attr_reader :settings
|
203
195
|
end
|
204
|
-
# rubocop:enable Metrics/ClassLength
|
205
196
|
end
|
data/lib/hanami/init.rb
ADDED
data/lib/hanami/setup.rb
ADDED
data/lib/hanami/slice.rb
ADDED
@@ -0,0 +1,189 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "dry/system/container"
|
4
|
+
require "dry/system/loader/autoloading"
|
5
|
+
require "pathname"
|
6
|
+
|
7
|
+
module Hanami
|
8
|
+
# Distinct area of concern within an Hanami application
|
9
|
+
#
|
10
|
+
# @since 2.0.0
|
11
|
+
class Slice
|
12
|
+
attr_reader :application, :name, :namespace, :root
|
13
|
+
|
14
|
+
def initialize(application, name:, namespace: nil, root: nil, container: nil)
|
15
|
+
@application = application
|
16
|
+
@name = name.to_sym
|
17
|
+
@namespace = namespace
|
18
|
+
@root = root ? Pathname(root) : root
|
19
|
+
@container = container || define_container
|
20
|
+
end
|
21
|
+
|
22
|
+
def inflector
|
23
|
+
application.inflector
|
24
|
+
end
|
25
|
+
|
26
|
+
def namespace_path
|
27
|
+
@namespace_path ||= inflector.underscore(namespace.to_s)
|
28
|
+
end
|
29
|
+
|
30
|
+
def init
|
31
|
+
container.import application: application.container
|
32
|
+
|
33
|
+
slice_block = application.configuration.slices[name]
|
34
|
+
instance_eval(&slice_block) if slice_block
|
35
|
+
end
|
36
|
+
|
37
|
+
def boot
|
38
|
+
container.finalize! do
|
39
|
+
container.config.env = application.container.config.env
|
40
|
+
end
|
41
|
+
|
42
|
+
@booted = true
|
43
|
+
self
|
44
|
+
end
|
45
|
+
|
46
|
+
# rubocop:disable Style/DoubleNegation
|
47
|
+
def booted?
|
48
|
+
!!@booted
|
49
|
+
end
|
50
|
+
# rubocop:enable Style/DoubleNegation
|
51
|
+
|
52
|
+
def container
|
53
|
+
@container ||= define_container
|
54
|
+
end
|
55
|
+
|
56
|
+
def import(*slice_names)
|
57
|
+
raise "Cannot import after booting" if booted?
|
58
|
+
|
59
|
+
slice_names.each do |slice_name|
|
60
|
+
container.import slice_name.to_sym => application.slices.fetch(slice_name.to_sym).container
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def register(*args, &block)
|
65
|
+
container.register(*args, &block)
|
66
|
+
end
|
67
|
+
|
68
|
+
def register_bootable(*args, &block)
|
69
|
+
container.boot(*args, &block)
|
70
|
+
end
|
71
|
+
|
72
|
+
def init_bootable(*args)
|
73
|
+
container.init(*args)
|
74
|
+
end
|
75
|
+
|
76
|
+
def start_bootable(*args)
|
77
|
+
container.start(*args)
|
78
|
+
end
|
79
|
+
|
80
|
+
def key?(*args)
|
81
|
+
container.key?(*args)
|
82
|
+
end
|
83
|
+
|
84
|
+
def keys
|
85
|
+
container.keys
|
86
|
+
end
|
87
|
+
|
88
|
+
def [](*args)
|
89
|
+
container[*args]
|
90
|
+
end
|
91
|
+
|
92
|
+
def resolve(*args)
|
93
|
+
container.resolve(*args)
|
94
|
+
end
|
95
|
+
|
96
|
+
private
|
97
|
+
|
98
|
+
# rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
99
|
+
def define_container
|
100
|
+
container = Class.new(Dry::System::Container)
|
101
|
+
container.use :env
|
102
|
+
|
103
|
+
container.configure do |config|
|
104
|
+
config.name = name
|
105
|
+
config.inflector = application.configuration.inflector
|
106
|
+
|
107
|
+
config.component_dirs.loader = Dry::System::Loader::Autoloading
|
108
|
+
config.component_dirs.add_to_load_path = false
|
109
|
+
|
110
|
+
if root&.directory?
|
111
|
+
config.root = root
|
112
|
+
config.bootable_dirs = ["config/boot"]
|
113
|
+
|
114
|
+
# Add component dirs for each configured component path
|
115
|
+
application.configuration.source_dirs.component_dirs.each do |component_dir|
|
116
|
+
next unless root.join(component_dir.path).directory?
|
117
|
+
|
118
|
+
component_dir = component_dir.dup
|
119
|
+
|
120
|
+
# TODO: this `== "lib"` check should be codified into a method somewhere
|
121
|
+
if component_dir.path == "lib"
|
122
|
+
# Expect component files in the root of the lib/ component dir to define
|
123
|
+
# classes inside the slice's namespace.
|
124
|
+
#
|
125
|
+
# e.g. "lib/foo.rb" should define SliceNamespace::Foo, to be registered as
|
126
|
+
# "foo"
|
127
|
+
component_dir.namespaces.delete_root
|
128
|
+
component_dir.namespaces.add_root(key: nil, const: namespace_path)
|
129
|
+
|
130
|
+
config.component_dirs.add(component_dir)
|
131
|
+
|
132
|
+
application.autoloader.push_dir(root.join("lib"), namespace: namespace)
|
133
|
+
else
|
134
|
+
# Expect component files in the root of non-lib/ component dirs to define
|
135
|
+
# classes inside a namespace matching that dir.
|
136
|
+
#
|
137
|
+
# e.g. "actions/foo.rb" should define SliceNamespace::Actions::Foo, to be
|
138
|
+
# registered as "actions.foo"
|
139
|
+
|
140
|
+
dir_namespace_path = File.join(namespace_path, component_dir.path)
|
141
|
+
|
142
|
+
autoloader_namespace = begin
|
143
|
+
inflector.constantize(inflector.camelize(dir_namespace_path))
|
144
|
+
rescue NameError
|
145
|
+
namespace.const_set(inflector.camelize(component_dir.path), Module.new)
|
146
|
+
end
|
147
|
+
|
148
|
+
component_dir.namespaces.delete_root
|
149
|
+
component_dir.namespaces.add_root(const: dir_namespace_path, key: component_dir.path) # TODO: do we need to swap path delimiters for key delimiters here?
|
150
|
+
|
151
|
+
config.component_dirs.add(component_dir)
|
152
|
+
|
153
|
+
application.autoloader.push_dir(
|
154
|
+
container.root.join(component_dir.path),
|
155
|
+
namespace: autoloader_namespace
|
156
|
+
)
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
# Pass configured autoload dirs to the autoloader
|
161
|
+
application.configuration.source_dirs.autoload_paths.each do |autoload_path|
|
162
|
+
next unless root.join(autoload_path).directory?
|
163
|
+
|
164
|
+
dir_namespace_path = File.join(namespace_path, autoload_path)
|
165
|
+
|
166
|
+
autoloader_namespace = begin
|
167
|
+
inflector.constantize(inflector.camelize(dir_namespace_path))
|
168
|
+
rescue NameError
|
169
|
+
namespace.const_set(inflector.camelize(autoload_path), Module.new)
|
170
|
+
end
|
171
|
+
|
172
|
+
application.autoloader.push_dir(
|
173
|
+
container.root.join(autoload_path),
|
174
|
+
namespace: autoloader_namespace
|
175
|
+
)
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
if namespace
|
181
|
+
namespace.const_set :Container, container
|
182
|
+
namespace.const_set :Deps, container.injector
|
183
|
+
end
|
184
|
+
|
185
|
+
container
|
186
|
+
end
|
187
|
+
# rubocop:enable Metrics/AbcSize, Metrics/MethodLength
|
188
|
+
end
|
189
|
+
end
|
data/lib/hanami/version.rb
CHANGED
@@ -0,0 +1,96 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "json"
|
4
|
+
require "rack/request"
|
5
|
+
require "hanami/utils/hash"
|
6
|
+
|
7
|
+
module Hanami
|
8
|
+
module Web
|
9
|
+
# Rack logger for Hanami applications
|
10
|
+
class RackLogger
|
11
|
+
attr_reader :logger
|
12
|
+
attr_reader :filter_params
|
13
|
+
|
14
|
+
def initialize(logger, filter_params: [])
|
15
|
+
@logger = logger
|
16
|
+
@filter_params = filter_params
|
17
|
+
end
|
18
|
+
|
19
|
+
def attach(rack_monitor)
|
20
|
+
rack_monitor.on :stop do |event|
|
21
|
+
log_request event[:env], event[:status], event[:time]
|
22
|
+
end
|
23
|
+
|
24
|
+
rack_monitor.on :error do |event|
|
25
|
+
log_exception event[:exception]
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# rubocop:disable Metrics/MethodLength
|
30
|
+
def log_request(env, status, time)
|
31
|
+
data = {
|
32
|
+
http: env[HTTP_VERSION],
|
33
|
+
verb: env[REQUEST_METHOD],
|
34
|
+
status: status,
|
35
|
+
ip: env[HTTP_X_FORWARDED_FOR] || env[REMOTE_ADDR],
|
36
|
+
path: env[SCRIPT_NAME] + env[PATH_INFO].to_s,
|
37
|
+
length: extract_content_length(env),
|
38
|
+
params: extract_params(env),
|
39
|
+
elapsed: time,
|
40
|
+
}
|
41
|
+
|
42
|
+
logger.info JSON.generate(data)
|
43
|
+
end
|
44
|
+
# rubocop:enable Metrics/MethodLength
|
45
|
+
|
46
|
+
def log_exception(exception)
|
47
|
+
logger.error exception.message
|
48
|
+
logger.error exception.backtrace.join("\n")
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
HTTP_VERSION = "HTTP_VERSION"
|
54
|
+
REQUEST_METHOD = "REQUEST_METHOD"
|
55
|
+
HTTP_X_FORWARDED_FOR = "HTTP_X_FORWARDED_FOR"
|
56
|
+
REMOTE_ADDR = "REMOTE_ADDR"
|
57
|
+
SCRIPT_NAME = "SCRIPT_NAME"
|
58
|
+
PATH_INFO = "PATH_INFO"
|
59
|
+
RACK_ERRORS = "rack.errors"
|
60
|
+
QUERY_HASH = "rack.request.query_hash"
|
61
|
+
FORM_HASH = "rack.request.form_hash"
|
62
|
+
ROUTER_PARAMS = "router.params"
|
63
|
+
CONTENT_LENGTH = "Content-Length"
|
64
|
+
|
65
|
+
def extract_content_length(env)
|
66
|
+
value = env[CONTENT_LENGTH]
|
67
|
+
!value || value.to_s == "0" ? "-" : value
|
68
|
+
end
|
69
|
+
|
70
|
+
def extract_params(env)
|
71
|
+
result = env.fetch(QUERY_HASH, {})
|
72
|
+
result.merge!(env.fetch(FORM_HASH, {}))
|
73
|
+
result.merge!(Hanami::Utils::Hash.deep_stringify(env.fetch(ROUTER_PARAMS, {})))
|
74
|
+
result
|
75
|
+
end
|
76
|
+
|
77
|
+
FILTERED = "[FILTERED]"
|
78
|
+
|
79
|
+
# rubocop:disable Metrics/MethodLength
|
80
|
+
def filter(params)
|
81
|
+
params.each_with_object({}) do |(k, v), h|
|
82
|
+
if filter_params.include?(k)
|
83
|
+
h.update(k => FILTERED)
|
84
|
+
elsif v.is_a?(Hash)
|
85
|
+
h.update(k => filter(v))
|
86
|
+
elsif v.is_a?(Array)
|
87
|
+
h.update(k => v.map { |m| m.is_a?(Hash) ? filter(m) : m })
|
88
|
+
else
|
89
|
+
h[k] = v
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
# rubocop:enable Metrics/MethodLength
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|