hanami 2.0.0.rc1 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6a1a430f511f0bda44248e88d4fb2c87d10dc3d8fbabf092b895ff2a3bbb78db
4
- data.tar.gz: 75c6e767584d34103505eb3637fc0fb72dde6118b35fa5f4a76a186681031e7a
3
+ metadata.gz: 2ff0a8cf3ce1df8c90acbb5feb8352cc0f05d0981ad9b08ac4f6f62351e8d775
4
+ data.tar.gz: ec3ca71f07e1f4120fb6c4792163a2ffbf926de2160264a294563b6d2a6c77d1
5
5
  SHA512:
6
- metadata.gz: 306e3261fb72d114ddcb9128de3a3c9a499bff57f1b2c51fd3cee54e644248c3bd981f54b626d7e5549e1fa9c467d86156a7c2c3e4c14c869501e698d07f6423
7
- data.tar.gz: a59bf43bf07700e282abe6e83919fd53a5e277b7efa5706b743a25537006eef76b49409bedf0af49cae8122bd39d5c396f705f1e148a01367565e1accb46d9a6
6
+ metadata.gz: ce1c3e798ba14c6ec3778beb25b47feaf87bc32dd995797836cdb8a7af9b8e13a6d401e7005f8ec78cf3da4ec5cc5be086f0ff2763eb2082939900ede5f4bae3
7
+ data.tar.gz: eb4f586e78f1afa3f674ca3e9f7499d7699e4d0c432aef744f98d788a8a77398f11ee27bd9619ed98463bc8065a28d35e9cb8e73b41b18c4044aaab7c9549507
data/CHANGELOG.md CHANGED
@@ -2,6 +2,23 @@
2
2
 
3
3
  The web, with simplicity.
4
4
 
5
+ ## v2.0.0 - 2022-11-22
6
+
7
+ ### Added
8
+
9
+ - [Tim Riley] Allow custom code from Hanami app `lib/` to be required before to load the app
10
+ - [Tim Riley] Support the new `Hanami::Action::Config#formats` config from hanami-controller 2.0.0
11
+ - [Tim Riley] Automatically use body_parser middleware when actions `:json` format is configured
12
+
13
+ ### Fixed
14
+
15
+ - [Luca Guidi] Ensure Hanami app to not crash when `hanami-controller` isn't bundled
16
+ - [Piotr Solnica] Several logger fixes
17
+
18
+ ### Changed
19
+
20
+ - [Tim Riley] Don't assign a default MIME Type for Hanami apps
21
+
5
22
  ## v2.0.0.rc1 - 2022-11-08
6
23
 
7
24
  ### Added
data/FEATURES.md CHANGED
@@ -4,27 +4,21 @@
4
4
 
5
5
  ## Features
6
6
 
7
- ## v2.0.0.beta1 - 2021-07-20
8
-
9
- - Generate new apps using `app/` directory
10
-
11
- ## v2.0.0.alpha8 - 2021-05-19
12
-
13
- ## v2.0.0.alpha7.1 - 2021-03-09
14
-
15
- ## v2.0.0.alpha7 - 2021-03-08
16
-
17
- ## v2.0.0.alpha6 - 2021-02-10
18
-
19
- ## v2.0.0.alpha5 - 2021-01-12
20
-
21
- ## v2.0.0.alpha4 - 2021-12-07
22
-
23
- ## v2.0.0.alpha3 - 2021-11-09
24
-
25
- ## v2.0.0.alpha2 - 2021-05-04
26
-
27
- ## v2.0.0.alpha1 - 2019-01-30
7
+ ## v2.0.0 - 2021-11-22
8
+
9
+ - Renamed _apps_ into _slices_, and _project_ into _app_
10
+ - The core of new applications is going to be `app/`. Slices are now optional.
11
+ - App class built around a container for managing app components; code from `app/` is loaded into the app
12
+ - Apps may have multiple Slices; code from `slices/[slice_name]/` is loaded into the corresponding slice
13
+ - `MyApp::Deps` or `MySlice::Deps` mixin for auto-registering dependencies from the app or a slice
14
+ - Code autoloading using Zeitwerk
15
+ - Code reloading via Guard
16
+ - Providers for setting up and managing the lifecycle of your app's critical components and integrations
17
+ - Type-safe app settings
18
+ - Rebuilt high-performance router
19
+ - Redesigned stateless `Hamami::Action` classes supporting the Deps mixin; add your behavior to `#handle(request, response)`
20
+ - Simplified action format configuration via `config.format` and `config.formats`
21
+ - Rewritten CLI and console
28
22
 
29
23
  ## v1.3.3 - 2019-09-20
30
24
 
data/README.md CHANGED
@@ -8,19 +8,16 @@ The web, with simplicity.
8
8
 
9
9
  ## Frameworks
10
10
 
11
- Hanami is a **full-stack** Ruby web framework.
12
- It's made up of smaller, single-purpose libraries.
11
+ Hanami is a **full-stack** Ruby web framework. It's made up of smaller, single-purpose libraries.
13
12
 
14
- This repository is for the full-stack framework,
15
- which provides the glue that ties all the parts together:
13
+ This repository is for the full-stack framework, which provides the glue that ties all the parts together:
16
14
 
17
- * [**Hanami::View**](https://github.com/hanami/view) - Presentation with a separation between views and templates
18
- * [**Hanami::Controller**](https://github.com/hanami/controller) - Full featured, fast and testable actions for Rack
19
15
  * [**Hanami::Router**](https://github.com/hanami/router) - Rack compatible HTTP router for Ruby
16
+ * [**Hanami::Controller**](https://github.com/hanami/controller) - Full featured, fast and testable actions for Rack
17
+ * [**Hanami::View**](https://github.com/hanami/view) - Presentation with a separation between views and templates
20
18
  * [**Hanami::Helpers**](https://github.com/hanami/helpers) - View helpers for Ruby applications
21
19
  * [**Hanami::Mailer**](https://github.com/hanami/mailer) - Mail for Ruby applications
22
20
  * [**Hanami::Assets**](https://github.com/hanami/assets) - Assets management for Ruby
23
- * [**Hanami::Utils**](https://github.com/hanami/utils) - Ruby core extensions and class utilities
24
21
 
25
22
  These components are designed to be used independently or together in a Hanami application.
26
23
 
@@ -28,9 +25,7 @@ These components are designed to be used independently or together in a Hanami a
28
25
 
29
26
  [![Gem Version](https://badge.fury.io/rb/hanami.svg)](https://badge.fury.io/rb/hanami)
30
27
  [![CI](https://github.com/hanami/hanami/workflows/ci/badge.svg?branch=main)](https://github.com/hanami/hanami/actions?query=workflow%3Aci+branch%3Amain)
31
- [![Test Coverage](https://codecov.io/gh/hanami/hanami/branch/main/graph/badge.svg)](https://codecov.io/gh/hanami/hanami)
32
28
  [![Depfu](https://badges.depfu.com/badges/ba000e0f69e6ef1c44cd3038caaa1841/overview.svg)](https://depfu.com/github/hanami/hanami?project=Bundler)
33
- [![Inline Docs](http://inch-ci.org/github/hanami/hanami.svg)](http://inch-ci.org/github/hanami/hanami)
34
29
 
35
30
  ## Installation
36
31
 
@@ -75,10 +70,7 @@ You can give back to Open Source, by supporting Hanami development via [GitHub S
75
70
 
76
71
  ## Community
77
72
 
78
- We strive for an inclusive and helpful community.
79
- We have a [Code of Conduct](http://hanamirb.org/community/#code-of-conduct) to handle controversial cases.
80
- In general, we expect **you** to be **nice** with other people.
81
- Our hope is for a great software and a great Community.
73
+ We strive for an inclusive and helpful community. We have a [Code of Conduct](http://hanamirb.org/community/#code-of-conduct) to handle controversial cases. In general, we expect **you** to be **nice** with other people. Our hope is for a great software and a great Community.
82
74
 
83
75
  ## Contributing [![Open Source Helpers](https://www.codetriage.com/hanami/hanami/badges/users.svg)](https://www.codetriage.com/hanami/hanami)
84
76
 
@@ -90,44 +82,22 @@ Our hope is for a great software and a great Community.
90
82
 
91
83
  In addition to contributing code, you can help to triage issues. This can include reproducing bug reports, or asking for vital information such as version numbers or reproduction instructions. If you would like to start triaging issues, one easy way to get started is to [subscribe to hanami on CodeTriage](https://www.codetriage.com/hanami/hanami).
92
84
 
93
- ### How To Use Hanami HEAD
94
-
95
- If you want to test Hanami's HEAD to try a new feature or to test a bug fix, here's how to do:
96
-
97
- ```
98
- git clone https://github.com/hanami/hanami.git
99
- cd hanami && bundle
100
- bundle exec hanami new bookshelf --hanami-head
101
- cd bookshelf
102
- vim Gemfile # edit with: gem 'hanami', path: '..'
103
- bundle
104
- ```
105
-
106
85
  ### Development Requirements
107
86
 
108
- * Ruby 2.3+ / JRuby 9.1.5.0+
87
+ * Ruby >= 3.0
109
88
  * Bundler
110
- * [PhantomJS](http://phantomjs.org/download.html)
111
89
  * Node.js (MacOS)
112
90
 
113
91
  ### Testing
114
92
 
115
- In order to simulate installed gems on developers' computers, the build installs
116
- all the gems locally in `vendor/cache`, including `hanami` code from `lib/`.
93
+ In order to simulate installed gems on developers' computers, the build installs all the gems locally in `vendor/cache`, including `hanami` code from `lib/`.
117
94
 
118
95
  **Before running a test, please make sure you have a fresh version of the code:**
119
96
 
120
97
  ```shell
121
- ./script/setup
122
98
  bundle exec rspec spec/path/to/file_spec.rb
123
99
  ```
124
100
 
125
- To run all the tests, please use:
126
-
127
- ```shell
128
- ./script/ci
129
- ```
130
-
131
101
  ## Versioning
132
102
 
133
103
  __Hanami__ uses [Semantic Versioning 2.0.0](http://semver.org)
data/hanami.gemspec CHANGED
@@ -35,11 +35,11 @@ Gem::Specification.new do |spec|
35
35
  spec.add_dependency "dry-configurable", "~> 1.0", "< 2"
36
36
  spec.add_dependency "dry-core", "~> 1.0", "< 2"
37
37
  spec.add_dependency "dry-inflector", "~> 1.0", "< 2"
38
- spec.add_dependency "dry-monitor", "~> 1.0", "< 2"
39
- spec.add_dependency "dry-system", "~> 1.0.rc"
40
- spec.add_dependency "dry-logger", "~> 1.0.rc"
41
- spec.add_dependency "hanami-cli", "~> 2.0.rc"
42
- spec.add_dependency "hanami-utils", "~> 2.0.rc"
38
+ spec.add_dependency "dry-monitor", "~> 1.0", ">= 1.0.1", "< 2"
39
+ spec.add_dependency "dry-system", "~> 1.0", "< 2"
40
+ spec.add_dependency "dry-logger", "~> 1.0", "< 2"
41
+ spec.add_dependency "hanami-cli", "~> 2.0"
42
+ spec.add_dependency "hanami-utils", "~> 2.0"
43
43
  spec.add_dependency "zeitwerk", "~> 2.6"
44
44
 
45
45
  spec.add_development_dependency "rspec", "~> 3.8"
data/lib/hanami/app.rb CHANGED
@@ -29,11 +29,6 @@ module Hanami
29
29
  subclass.class_eval do
30
30
  @config = Hanami::Config.new(app_name: slice_name, env: Hanami.env)
31
31
 
32
- # Prepare the load path (based on the default root of `Dir.pwd`) as early as possible, so
33
- # you can make a `require` inside the body of an `App` subclass, which may be useful for
34
- # certain kinds of app configuration.
35
- prepare_load_path
36
-
37
32
  load_dotenv
38
33
  end
39
34
  end
@@ -136,9 +136,6 @@ module Hanami
136
136
 
137
137
  # Apply defaults for base config
138
138
  def configure_defaults
139
- self.default_request_format = :html
140
- self.default_response_format = :html
141
-
142
139
  self.default_headers = {
143
140
  "X-Frame-Options" => "DENY",
144
141
  "X-Content-Type-Options" => "nosniff",
@@ -15,9 +15,15 @@ module Hanami
15
15
  # @return [Hanami::SliceName]
16
16
  #
17
17
  # @api private
18
- # @since 2.0.o
18
+ # @since 2.0.0
19
19
  attr_reader :app_name
20
20
 
21
+ # @return [Symbol]
22
+ #
23
+ # @api private
24
+ # @since 2.0.0
25
+ attr_reader :env
26
+
21
27
  # @!attribute [rw] level
22
28
  # Sets or returns the logger level.
23
29
  #
@@ -40,7 +46,7 @@ module Hanami
40
46
  #
41
47
  # @api public
42
48
  # @since 2.0.0
43
- setting :stream
49
+ setting :stream, default: $stdout
44
50
 
45
51
  # @!attribute [rw] formatter
46
52
  # Sets or returns the logger's formatter.
@@ -57,7 +63,7 @@ module Hanami
57
63
  #
58
64
  # @api public
59
65
  # @since 2.0.0
60
- setting :formatter
66
+ setting :formatter, default: :string
61
67
 
62
68
  # @!attribute [rw] template
63
69
  # Sets or returns log entry string template
@@ -68,7 +74,7 @@ module Hanami
68
74
  #
69
75
  # @api public
70
76
  # @since 2.0.0
71
- setting :template, default: "[%<progname>s] [%<severity>s] [%<time>s] %<message>s"
77
+ setting :template, default: :details
72
78
 
73
79
  # @!attribute [rw] filters
74
80
  # Sets or returns an array of attribute names to filter from logs.
@@ -85,11 +91,11 @@ module Hanami
85
91
  # @!attribute [rw] logger_constructor
86
92
  # Sets or returns the constructor proc to use for the logger instantiation.
87
93
  #
88
- # Defaults to `Dry.method(:Logger)`.
94
+ # Defaults to either `Config#production_logger` or `Config#development_logger`
89
95
  #
90
96
  # @api public
91
97
  # @since 2.0.0
92
- setting :logger_constructor, default: Dry.method(:Logger)
98
+ setting :logger_constructor
93
99
 
94
100
  # @!attribute [rw] options
95
101
  # Sets or returns a hash of options to pass to the {logger_constructor} when initializing
@@ -113,27 +119,18 @@ module Hanami
113
119
  # @api private
114
120
  def initialize(env:, app_name:)
115
121
  @app_name = app_name
116
-
117
- config.level = case env
118
- when :production
119
- :info
120
- else
121
- :debug
122
- end
123
-
124
- config.stream = case env
125
- when :test
126
- File.join("log", "#{env}.log")
127
- else
128
- $stdout
129
- end
130
-
131
- config.formatter = case env
132
- when :production
133
- :json
134
- else
135
- :rack
136
- end
122
+ @env = env
123
+
124
+ case env
125
+ when :development, :test
126
+ config.level = :debug
127
+ config.stream = File.join("log", "#{env}.log") if env == :test
128
+ config.logger_constructor = method(:development_logger)
129
+ when :production
130
+ config.level = :info
131
+ config.formatter = :json
132
+ config.logger_constructor = method(:production_logger)
133
+ end
137
134
  end
138
135
 
139
136
  # Returns a new instance of the logger.
@@ -143,13 +140,39 @@ module Hanami
143
140
  # @api public
144
141
  # @since 2.0.0
145
142
  def instance
146
- logger_constructor.call(app_name.name, **logger_constructor_opts)
143
+ logger_constructor.call(env, app_name.name, **logger_constructor_options)
144
+ end
145
+
146
+ # Build an instance of a development logger
147
+ #
148
+ # This logger is used in both development and test
149
+ #
150
+ # @return [Dry::Logger::Dispatcher]
151
+ # @since 2.0.0
152
+ # @api private
153
+ def development_logger(_env, app_name, **options)
154
+ Dry.Logger(app_name, **options) do |setup|
155
+ setup
156
+ .add_backend(log_if: -> entry { !entry.tag?(:rack) })
157
+ .add_backend(formatter: :rack, log_if: -> entry { entry.tag?(:rack) })
158
+ end
159
+ end
160
+
161
+ # Build an instance of a production logger
162
+ #
163
+ # This logger is used in both development and test
164
+ #
165
+ # @return [Dry::Logger::Dispatcher]
166
+ # @since 2.0.0
167
+ # @api private
168
+ def production_logger(_env, app_name, **options)
169
+ Dry.Logger(app_name, **options)
147
170
  end
148
171
 
149
172
  private
150
173
 
151
174
  # @api private
152
- def logger_constructor_opts
175
+ def logger_constructor_options
153
176
  {stream: stream,
154
177
  level: level,
155
178
  formatter: formatter,
data/lib/hanami/config.rb CHANGED
@@ -274,6 +274,8 @@ module Hanami
274
274
  logger.finalize!
275
275
  router.finalize!
276
276
 
277
+ use_body_parser_middleware
278
+
277
279
  super
278
280
  end
279
281
 
@@ -371,7 +373,26 @@ module Hanami
371
373
  self.slices = ENV["HANAMI_SLICES"]&.split(",")&.map(&:strip)
372
374
  end
373
375
 
374
- # @api private
376
+ SUPPORTED_MIDDLEWARE_PARSERS = %i[json].freeze
377
+ private_constant :SUPPORTED_MIDDLEWARE_PARSERS
378
+
379
+ def use_body_parser_middleware
380
+ return unless Hanami.bundled?("hanami-controller")
381
+
382
+ return if actions.formats.empty?
383
+ return if middleware.stack["/"].map(&:first).any? { |klass| klass == "Hanami::Middleware::BodyParser" }
384
+
385
+ parsers = SUPPORTED_MIDDLEWARE_PARSERS & actions.formats.values
386
+ return if parsers.empty?
387
+
388
+ middleware.use(
389
+ :body_parser,
390
+ [parsers.to_h { |parser_format|
391
+ [parser_format, actions.formats.mime_types_for(parser_format)]
392
+ }]
393
+ )
394
+ end
395
+
375
396
  def load_dependent_config(gem_name)
376
397
  if Hanami.bundled?(gem_name)
377
398
  yield
@@ -60,7 +60,7 @@ module Hanami
60
60
  #
61
61
  # module MyApp
62
62
  # class Action < Hanami::Action
63
- # config.default_response_format = :json
63
+ # config.format :json
64
64
  # end
65
65
  # end
66
66
  #
@@ -91,7 +91,10 @@ module Hanami
91
91
 
92
92
  next if slice.parent && slice_value == parent_value
93
93
 
94
- action_class.config.public_send(:"#{setting.name}=", slice_value)
94
+ action_class.config.public_send(
95
+ :"#{setting.name}=",
96
+ setting.mutable? ? slice_value.dup : slice_value
97
+ )
95
98
  end
96
99
  end
97
100
 
@@ -77,7 +77,10 @@ module Hanami
77
77
 
78
78
  next if slice.parent && slice_value == parent_value
79
79
 
80
- view_class.config.public_send(:"#{setting.name}=", slice_value)
80
+ view_class.config.public_send(
81
+ :"#{setting.name}=",
82
+ setting.mutable? ? slice_value.dup : slice_value
83
+ )
81
84
  end
82
85
 
83
86
  view_class.config.inflector = inflector
@@ -27,9 +27,10 @@ module Hanami
27
27
 
28
28
  notifications = target[:notifications]
29
29
 
30
- monitor_middleware = Dry::Monitor::Rack::Middleware.new(notifications)
30
+ clock = Dry::Monitor::Clock.new(unit: :microsecond)
31
+ monitor_middleware = Dry::Monitor::Rack::Middleware.new(notifications, clock: clock)
31
32
 
32
- rack_logger = Hanami::Web::RackLogger.new(target[:logger])
33
+ rack_logger = Hanami::Web::RackLogger.new(target[:logger], env: target.env)
33
34
  rack_logger.attach(monitor_middleware)
34
35
 
35
36
  register "monitor", monitor_middleware
@@ -1,11 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "hanami/cli/rake_tasks"
4
- require "hanami/cli/command_line"
3
+ require "hanami/cli"
5
4
 
6
5
  Hanami::CLI::RakeTasks.register_tasks do
7
- @_hanami_command_line = Hanami::CLI::CommandLine.new
8
-
9
6
  desc "Load the app environment"
10
7
  task :environment do
11
8
  require "hanami/prepare"
@@ -53,8 +50,10 @@ Hanami::CLI::RakeTasks.register_tasks do
53
50
 
54
51
  private
55
52
 
53
+ @_hanami_cli_bundler = Hanami::CLI::Bundler.new
54
+
56
55
  def run_hanami_command(command)
57
- @_hanami_command_line.call(command)
56
+ @_hanami_cli_bundler.exec(command)
58
57
  end
59
58
  end
60
59
 
data/lib/hanami/slice.rb CHANGED
@@ -957,7 +957,9 @@ module Hanami
957
957
  **config.router.options
958
958
  ) do
959
959
  use(rack_monitor)
960
- use(*config.actions.sessions.middleware) if config.actions.sessions.enabled?
960
+ if Hanami.bundled?("hanami-controller")
961
+ use(*config.actions.sessions.middleware) if config.actions.sessions.enabled?
962
+ end
961
963
 
962
964
  middleware_stack.update(config.middleware_stack)
963
965
  end
@@ -7,7 +7,7 @@ module Hanami
7
7
  # @api private
8
8
  module Version
9
9
  # @api public
10
- VERSION = "2.0.0.rc1"
10
+ VERSION = "2.0.0"
11
11
 
12
12
  # @since 0.9.0
13
13
  # @api private
@@ -8,6 +8,9 @@ module Hanami
8
8
  # @api private
9
9
  # @since 2.0.0
10
10
  class RackLogger
11
+ EMPTY_PARAMS = {}.freeze
12
+ private_constant :EMPTY_PARAMS
13
+
11
14
  REQUEST_METHOD = "REQUEST_METHOD"
12
15
  private_constant :REQUEST_METHOD
13
16
 
@@ -29,10 +32,46 @@ module Hanami
29
32
  CONTENT_LENGTH = "Content-Length"
30
33
  private_constant :CONTENT_LENGTH
31
34
 
35
+ MILISECOND = "ms"
36
+ private_constant :MILISECOND
37
+
38
+ MICROSECOND = "µs"
39
+ private_constant :MICROSECOND
40
+
41
+ # Dynamic extension used in production environments
42
+ # @api private
43
+ module Production
44
+ private
45
+
46
+ # @since 1.0.0
47
+ # @api private
48
+ def data(env, status:, elapsed:)
49
+ payload = super
50
+ payload[:elapsed] = elapsed
51
+ payload[:elapsed_unit] = MICROSECOND
52
+ payload
53
+ end
54
+ end
55
+
56
+ # Dynamic extension used in non-production environments
57
+ # @api private
58
+ module Development
59
+ private
60
+
61
+ # @since 1.0.0
62
+ # @api private
63
+ def data(env, status:, elapsed:)
64
+ payload = super
65
+ payload[:elapsed] = elapsed > 1000 ? "#{elapsed / 1000}ms" : "#{elapsed}#{MICROSECOND}"
66
+ payload
67
+ end
68
+ end
69
+
32
70
  # @api private
33
71
  # @since 2.0.0
34
- def initialize(logger)
72
+ def initialize(logger, env: :development)
35
73
  @logger = logger
74
+ extend(env == :production ? Production : Development)
36
75
  end
37
76
 
38
77
  # @api private
@@ -43,36 +82,46 @@ module Hanami
43
82
  end
44
83
 
45
84
  rack_monitor.on :error do |event|
46
- log_exception event[:exception]
85
+ # TODO: why we don't provide time on error?
86
+ log_exception event[:env], event[:exception], 500, 0
47
87
  end
48
88
  end
49
89
 
50
90
  # @api private
51
91
  # @since 2.0.0
52
92
  def log_request(env, status, elapsed)
53
- data = {
54
- verb: env[REQUEST_METHOD],
55
- status: status,
56
- elapsed: "#{elapsed}ms",
57
- ip: env[HTTP_X_FORWARDED_FOR] || env[REMOTE_ADDR],
58
- path: env[SCRIPT_NAME] + env[PATH_INFO].to_s,
59
- length: extract_content_length(env),
60
- params: env[ROUTER_PARAMS]
61
- }
62
-
63
- logger.info(data)
93
+ logger.tagged(:rack) do
94
+ logger.info(data(env, status: status, elapsed: elapsed))
95
+ end
64
96
  end
65
97
 
66
98
  # @api private
67
99
  # @since 2.0.0
68
- def log_exception(exception)
69
- logger.error(exception)
100
+ def log_exception(env, exception, status, elapsed)
101
+ logger.tagged(:rack) do
102
+ logger.error(exception, **data(env, status: status, elapsed: elapsed))
103
+ end
70
104
  end
71
105
 
72
106
  private
73
107
 
74
108
  attr_reader :logger
75
109
 
110
+ # @api private
111
+ # @since 2.0.0
112
+ def data(env, status:, elapsed:)
113
+ {
114
+ verb: env[REQUEST_METHOD],
115
+ status: status,
116
+ ip: env[HTTP_X_FORWARDED_FOR] || env[REMOTE_ADDR],
117
+ path: "#{env[SCRIPT_NAME]}#{env[PATH_INFO]}",
118
+ length: extract_content_length(env),
119
+ params: env.fetch(ROUTER_PARAMS, EMPTY_PARAMS)
120
+ }
121
+ end
122
+
123
+ # @api private
124
+ # @since 2.0.0
76
125
  def extract_content_length(env)
77
126
  value = env[CONTENT_LENGTH]
78
127
  !value || value.to_s == "0" ? "-" : value
data/lib/hanami.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "pathname"
3
4
  require "zeitwerk"
4
5
  require_relative "hanami/constants"
5
6
 
@@ -36,7 +37,8 @@ module Hanami
36
37
  app_path = self.app_path
37
38
 
38
39
  if app_path
39
- require(app_path)
40
+ prepare_load_path
41
+ require(app_path.to_s)
40
42
  app
41
43
  elsif raise_exception
42
44
  raise(
@@ -47,6 +49,23 @@ module Hanami
47
49
  end
48
50
  end
49
51
 
52
+ # Prepare the load path as early as possible (based on the default root inferred from the location
53
+ # of `config/app.rb`), so `require` can work at the top of `config/app.rb`. This may be useful
54
+ # when external classes are needed for configuring certain aspects of the app.
55
+ #
56
+ # @api private
57
+ # @since 2.0.0
58
+ private_class_method def self.prepare_load_path
59
+ lib_path = app_path&.join("..", "..", LIB_DIR)
60
+
61
+ if lib_path&.directory?
62
+ path = lib_path.realpath.to_s
63
+ $LOAD_PATH.prepend(path) unless $LOAD_PATH.include?(path)
64
+ end
65
+
66
+ lib_path
67
+ end
68
+
50
69
  # Returns the Hamami app class.
51
70
  #
52
71
  # To ensure your Hanami app is loaded, run {.setup} (or `require "hanami/setup"`) first.
@@ -98,10 +117,10 @@ module Hanami
98
117
  # Searches within the given directory, then searches upwards through parent directories until the
99
118
  # app file can be found.
100
119
  #
101
- # @param dir [String] The directory from which to start searching. Defaults to the current
102
- # directory.
120
+ # @param dir [String, Pathname] The directory from which to start searching. Defaults to the
121
+ # current directory.
103
122
  #
104
- # @return [String, nil] the app file path, or nil if not found.
123
+ # @return [Pathname, nil] the app file path, or nil if not found.
105
124
  #
106
125
  # @api public
107
126
  # @since 2.0.0
@@ -110,7 +129,7 @@ module Hanami
110
129
  path = dir.join(APP_PATH)
111
130
 
112
131
  if path.file?
113
- path.to_s
132
+ path
114
133
  elsif !dir.root?
115
134
  app_path(dir.parent)
116
135
  end