hanami 2.3.2 → 3.0.0.rc1
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 +55 -19
- data/LICENSE +20 -0
- data/README.md +18 -35
- data/hanami.gemspec +36 -37
- data/lib/hanami/config/db.rb +2 -0
- data/lib/hanami/config/i18n.rb +138 -0
- data/lib/hanami/config/logger.rb +15 -7
- data/lib/hanami/config/null_config.rb +1 -1
- data/lib/hanami/config/views.rb +17 -0
- data/lib/hanami/config.rb +66 -22
- data/lib/hanami/errors.rb +6 -0
- data/lib/hanami/extensions/action/slice_configured_action.rb +1 -1
- data/lib/hanami/extensions/action.rb +2 -2
- data/lib/hanami/extensions/mailer/slice_configured_mailer.rb +120 -0
- data/lib/hanami/extensions/mailer.rb +28 -0
- data/lib/hanami/extensions/operation/slice_configured_db_operation.rb +2 -0
- data/lib/hanami/extensions/view/context.rb +26 -4
- data/lib/hanami/extensions/view/part.rb +2 -0
- data/lib/hanami/extensions/view/slice_configured_context.rb +7 -0
- data/lib/hanami/extensions/view/slice_configured_part.rb +2 -0
- data/lib/hanami/extensions/view/slice_configured_view.rb +8 -8
- data/lib/hanami/extensions/view/standard_helpers.rb +4 -0
- data/lib/hanami/extensions.rb +6 -1
- data/lib/hanami/helpers/assets_helper.rb +0 -4
- data/lib/hanami/helpers/form_helper.rb +1 -1
- data/lib/hanami/helpers/i18n_helper.rb +176 -0
- data/lib/hanami/logger/rack_formatter.rb +73 -0
- data/lib/hanami/logger/sql_formatter.rb +80 -0
- data/lib/hanami/logger/sql_logger.rb +48 -0
- data/lib/hanami/middleware/render_errors.rb +2 -2
- data/lib/hanami/providers/db.rb +7 -2
- data/lib/hanami/providers/db_logging.rb +4 -7
- data/lib/hanami/providers/i18n/backend.rb +369 -0
- data/lib/hanami/providers/i18n/locale/en.yml +57 -0
- data/lib/hanami/providers/i18n.rb +114 -0
- data/lib/hanami/providers/mailers.rb +101 -0
- data/lib/hanami/routes.rb +1 -0
- data/lib/hanami/settings/composite_store.rb +53 -0
- data/lib/hanami/settings.rb +4 -4
- data/lib/hanami/slice/router.rb +15 -10
- data/lib/hanami/slice.rb +71 -11
- data/lib/hanami/slice_registrar.rb +2 -2
- data/lib/hanami/universal_logger.rb +250 -0
- data/lib/hanami/version.rb +1 -1
- data/lib/hanami/web/rack_logger.rb +2 -80
- data/lib/hanami/web/welcome.html.erb +443 -58
- data/lib/hanami.rb +4 -2
- metadata +28 -276
- data/CODE_OF_CONDUCT.md +0 -74
- data/FEATURES.md +0 -269
- data/LICENSE.md +0 -22
- data/spec/integration/action/cookies_spec.rb +0 -58
- data/spec/integration/action/csrf_protection_spec.rb +0 -54
- data/spec/integration/action/format_config_spec.rb +0 -129
- data/spec/integration/action/routes_spec.rb +0 -71
- data/spec/integration/action/sessions_spec.rb +0 -50
- data/spec/integration/action/slice_configuration_spec.rb +0 -284
- data/spec/integration/action/view_rendering/automatic_rendering_spec.rb +0 -247
- data/spec/integration/action/view_rendering/paired_view_inference_spec.rb +0 -115
- data/spec/integration/action/view_rendering/view_context_spec.rb +0 -221
- data/spec/integration/action/view_rendering_spec.rb +0 -89
- data/spec/integration/assets/assets_spec.rb +0 -155
- data/spec/integration/assets/cross_slice_assets_helpers_spec.rb +0 -129
- data/spec/integration/assets/serve_static_assets_spec.rb +0 -152
- data/spec/integration/code_loading/loading_from_app_spec.rb +0 -152
- data/spec/integration/code_loading/loading_from_lib_spec.rb +0 -242
- data/spec/integration/code_loading/loading_from_slice_spec.rb +0 -165
- data/spec/integration/container/application_routes_helper_spec.rb +0 -48
- data/spec/integration/container/auto_injection_spec.rb +0 -53
- data/spec/integration/container/auto_registration_spec.rb +0 -86
- data/spec/integration/container/autoloader_spec.rb +0 -82
- data/spec/integration/container/imports_spec.rb +0 -253
- data/spec/integration/container/prepare_container_spec.rb +0 -125
- data/spec/integration/container/provider_environment_spec.rb +0 -52
- data/spec/integration/container/provider_lifecycle_spec.rb +0 -61
- data/spec/integration/container/shutdown_spec.rb +0 -91
- data/spec/integration/container/standard_providers/rack_provider_spec.rb +0 -44
- data/spec/integration/container/standard_providers_spec.rb +0 -124
- data/spec/integration/db/auto_registration_spec.rb +0 -39
- data/spec/integration/db/commands_spec.rb +0 -80
- data/spec/integration/db/db_inflector_spec.rb +0 -57
- data/spec/integration/db/db_slices_spec.rb +0 -398
- data/spec/integration/db/db_spec.rb +0 -245
- data/spec/integration/db/gateways_spec.rb +0 -361
- data/spec/integration/db/logging_spec.rb +0 -301
- data/spec/integration/db/mappers_spec.rb +0 -84
- data/spec/integration/db/provider_config_spec.rb +0 -88
- data/spec/integration/db/provider_spec.rb +0 -35
- data/spec/integration/db/relations_spec.rb +0 -60
- data/spec/integration/db/repo_spec.rb +0 -300
- data/spec/integration/db/slices_importing_from_parent.rb +0 -130
- data/spec/integration/dotenv_loading_spec.rb +0 -138
- data/spec/integration/logging/exception_logging_spec.rb +0 -120
- data/spec/integration/logging/notifications_spec.rb +0 -68
- data/spec/integration/logging/request_logging_spec.rb +0 -202
- data/spec/integration/operations/extension_spec.rb +0 -122
- data/spec/integration/rack_app/body_parser_spec.rb +0 -108
- data/spec/integration/rack_app/method_override_spec.rb +0 -97
- data/spec/integration/rack_app/middleware_spec.rb +0 -720
- data/spec/integration/rack_app/non_booted_rack_app_spec.rb +0 -104
- data/spec/integration/rack_app/rack_app_spec.rb +0 -442
- data/spec/integration/rake_tasks_spec.rb +0 -107
- data/spec/integration/router/resource_routes_spec.rb +0 -281
- data/spec/integration/settings/access_in_slice_class_body_spec.rb +0 -83
- data/spec/integration/settings/access_to_constants_spec.rb +0 -46
- data/spec/integration/settings/loading_from_env_spec.rb +0 -188
- data/spec/integration/settings/settings_component_loading_spec.rb +0 -113
- data/spec/integration/settings/slice_registration_spec.rb +0 -145
- data/spec/integration/settings/using_types_spec.rb +0 -80
- data/spec/integration/setup_spec.rb +0 -165
- data/spec/integration/slices/external_slice_spec.rb +0 -91
- data/spec/integration/slices/slice_configuration_spec.rb +0 -42
- data/spec/integration/slices/slice_loading_spec.rb +0 -171
- data/spec/integration/slices/slice_registrations_spec.rb +0 -80
- data/spec/integration/slices/slice_routing_spec.rb +0 -219
- data/spec/integration/slices_spec.rb +0 -471
- data/spec/integration/view/config/default_context_spec.rb +0 -149
- data/spec/integration/view/config/inflector_spec.rb +0 -57
- data/spec/integration/view/config/part_class_spec.rb +0 -147
- data/spec/integration/view/config/part_namespace_spec.rb +0 -103
- data/spec/integration/view/config/paths_spec.rb +0 -119
- data/spec/integration/view/config/scope_class_spec.rb +0 -147
- data/spec/integration/view/config/scope_namespace_spec.rb +0 -103
- data/spec/integration/view/config/template_spec.rb +0 -38
- data/spec/integration/view/context/assets_spec.rb +0 -79
- data/spec/integration/view/context/inflector_spec.rb +0 -40
- data/spec/integration/view/context/request_spec.rb +0 -57
- data/spec/integration/view/context/routes_spec.rb +0 -84
- data/spec/integration/view/helpers/form_helper_spec.rb +0 -174
- data/spec/integration/view/helpers/part_helpers_spec.rb +0 -124
- data/spec/integration/view/helpers/scope_helpers_spec.rb +0 -84
- data/spec/integration/view/helpers/user_defined_helpers/part_helpers_spec.rb +0 -162
- data/spec/integration/view/helpers/user_defined_helpers/scope_helpers_spec.rb +0 -119
- data/spec/integration/view/parts/default_rendering_spec.rb +0 -138
- data/spec/integration/view/slice_configuration_spec.rb +0 -289
- data/spec/integration/view/views_spec.rb +0 -103
- data/spec/integration/web/content_security_policy_nonce_spec.rb +0 -251
- data/spec/integration/web/render_detailed_errors_spec.rb +0 -107
- data/spec/integration/web/render_errors_spec.rb +0 -242
- data/spec/integration/web/welcome_view_spec.rb +0 -84
- data/spec/spec_helper.rb +0 -28
- data/spec/support/app_integration.rb +0 -157
- data/spec/support/coverage.rb +0 -1
- data/spec/support/matchers.rb +0 -32
- data/spec/support/rspec.rb +0 -27
- data/spec/unit/hanami/config/actions/content_security_policy_spec.rb +0 -96
- data/spec/unit/hanami/config/actions/cookies_spec.rb +0 -46
- data/spec/unit/hanami/config/actions/csrf_protection_spec.rb +0 -58
- data/spec/unit/hanami/config/actions/default_values_spec.rb +0 -43
- data/spec/unit/hanami/config/actions/sessions_spec.rb +0 -48
- data/spec/unit/hanami/config/actions_spec.rb +0 -52
- data/spec/unit/hanami/config/base_url_spec.rb +0 -25
- data/spec/unit/hanami/config/console_spec.rb +0 -22
- data/spec/unit/hanami/config/db_spec.rb +0 -38
- data/spec/unit/hanami/config/inflector_spec.rb +0 -35
- data/spec/unit/hanami/config/logger_spec.rb +0 -195
- data/spec/unit/hanami/config/render_detailed_errors_spec.rb +0 -25
- data/spec/unit/hanami/config/render_errors_spec.rb +0 -25
- data/spec/unit/hanami/config/router_spec.rb +0 -44
- data/spec/unit/hanami/config/slices_spec.rb +0 -34
- data/spec/unit/hanami/config/views_spec.rb +0 -80
- data/spec/unit/hanami/env_spec.rb +0 -37
- data/spec/unit/hanami/extensions/view/context_spec.rb +0 -59
- data/spec/unit/hanami/helpers/assets_helper/asset_url_spec.rb +0 -120
- data/spec/unit/hanami/helpers/assets_helper/audio_tag_spec.rb +0 -132
- data/spec/unit/hanami/helpers/assets_helper/favicon_tag_spec.rb +0 -87
- data/spec/unit/hanami/helpers/assets_helper/image_tag_spec.rb +0 -92
- data/spec/unit/hanami/helpers/assets_helper/javascript_tag_spec.rb +0 -143
- data/spec/unit/hanami/helpers/assets_helper/stylesheet_tag_spec.rb +0 -126
- data/spec/unit/hanami/helpers/assets_helper/video_tag_spec.rb +0 -136
- data/spec/unit/hanami/helpers/form_helper_spec.rb +0 -2857
- data/spec/unit/hanami/port_spec.rb +0 -117
- data/spec/unit/hanami/providers/db/config/default_config_spec.rb +0 -100
- data/spec/unit/hanami/providers/db/config/gateway_spec.rb +0 -73
- data/spec/unit/hanami/providers/db/config_spec.rb +0 -143
- data/spec/unit/hanami/router/errors/not_allowed_error_spec.rb +0 -27
- data/spec/unit/hanami/router/errors/not_found_error_spec.rb +0 -22
- data/spec/unit/hanami/settings/env_store_spec.rb +0 -52
- data/spec/unit/hanami/settings_spec.rb +0 -111
- data/spec/unit/hanami/slice_configurable_spec.rb +0 -141
- data/spec/unit/hanami/slice_name_spec.rb +0 -47
- data/spec/unit/hanami/slice_spec.rb +0 -99
- data/spec/unit/hanami/web/rack_logger_spec.rb +0 -99
data/lib/hanami/settings.rb
CHANGED
|
@@ -140,8 +140,8 @@ module Hanami
|
|
|
140
140
|
|
|
141
141
|
begin
|
|
142
142
|
require slice_settings_require_path
|
|
143
|
-
rescue LoadError =>
|
|
144
|
-
raise
|
|
143
|
+
rescue LoadError => exception
|
|
144
|
+
raise exception unless exception.path == slice_settings_require_path
|
|
145
145
|
end
|
|
146
146
|
end
|
|
147
147
|
end
|
|
@@ -167,8 +167,8 @@ module Hanami
|
|
|
167
167
|
else
|
|
168
168
|
public_send("#{name}=", value)
|
|
169
169
|
end
|
|
170
|
-
rescue =>
|
|
171
|
-
errs[name] =
|
|
170
|
+
rescue => exception # rubocop:disable Style/RescueStandardError
|
|
171
|
+
errs[name] = exception
|
|
172
172
|
end
|
|
173
173
|
|
|
174
174
|
raise InvalidSettingsError, errors if errors.any?
|
data/lib/hanami/slice/router.rb
CHANGED
|
@@ -23,7 +23,14 @@ module Hanami
|
|
|
23
23
|
attr_reader :path_prefix
|
|
24
24
|
|
|
25
25
|
# @api private
|
|
26
|
-
def initialize(
|
|
26
|
+
def initialize(
|
|
27
|
+
routes:,
|
|
28
|
+
inflector:,
|
|
29
|
+
middleware_stack: Routing::Middleware::Stack.new,
|
|
30
|
+
prefix: ::Hanami::Router::DEFAULT_PREFIX,
|
|
31
|
+
**kwargs,
|
|
32
|
+
&blk
|
|
33
|
+
)
|
|
27
34
|
@path_prefix = Hanami::Router::Prefix.new(prefix)
|
|
28
35
|
@inflector = inflector
|
|
29
36
|
@middleware_stack = middleware_stack
|
|
@@ -233,22 +240,20 @@ module Hanami
|
|
|
233
240
|
|
|
234
241
|
def route_suffix(suffix)
|
|
235
242
|
return suffix.sub(LEADING_ID_REGEX, "") if suffix && singular?
|
|
243
|
+
|
|
236
244
|
suffix
|
|
237
245
|
end
|
|
238
246
|
LEADING_ID_REGEX = %r{\A/:id}
|
|
239
247
|
|
|
240
248
|
def key_path_base
|
|
241
|
-
@key_path_base ||=
|
|
242
|
-
if @
|
|
243
|
-
@
|
|
249
|
+
@key_path_base ||= @options[:to] || begin
|
|
250
|
+
if @resource_scope.any?
|
|
251
|
+
prefix = @resource_scope.join(CONTAINER_KEY_DELIMITER)
|
|
252
|
+
"#{prefix}#{CONTAINER_KEY_DELIMITER}#{@name}"
|
|
244
253
|
else
|
|
245
|
-
@name.to_s
|
|
246
|
-
next name unless @resource_scope.any?
|
|
247
|
-
|
|
248
|
-
prefix = @resource_scope.join(CONTAINER_KEY_DELIMITER)
|
|
249
|
-
"#{prefix}#{CONTAINER_KEY_DELIMITER}#{name}"
|
|
250
|
-
}
|
|
254
|
+
@name.to_s
|
|
251
255
|
end
|
|
256
|
+
end
|
|
252
257
|
end
|
|
253
258
|
|
|
254
259
|
def route_name(action, prefix)
|
data/lib/hanami/slice.rb
CHANGED
|
@@ -46,7 +46,6 @@ module Hanami
|
|
|
46
46
|
end
|
|
47
47
|
end
|
|
48
48
|
|
|
49
|
-
# rubocop:disable Metrics/ModuleLength
|
|
50
49
|
module ClassMethods
|
|
51
50
|
# Returns the slice's parent.
|
|
52
51
|
#
|
|
@@ -797,12 +796,24 @@ module Hanami
|
|
|
797
796
|
#
|
|
798
797
|
# @return [Array] the three-element Rack response array
|
|
799
798
|
#
|
|
799
|
+
# @raise [Hanami::NoRoutesDefinedError] if the hanami-router gem is missing or
|
|
800
|
+
# no routes are defined.
|
|
801
|
+
#
|
|
800
802
|
# @see #rack_app
|
|
801
803
|
#
|
|
802
804
|
# @api public
|
|
803
805
|
# @since 2.0.0
|
|
804
806
|
def call(...)
|
|
805
|
-
rack_app
|
|
807
|
+
if rack_app
|
|
808
|
+
rack_app.call(...)
|
|
809
|
+
else
|
|
810
|
+
error_message = if Hanami.bundled?("hanami-router")
|
|
811
|
+
"Could not handle this rack request because no routes are defined"
|
|
812
|
+
else
|
|
813
|
+
"Could not handle this rack request because the hanami router gem is missing, please add it"
|
|
814
|
+
end
|
|
815
|
+
raise NoRoutesDefinedError, error_message
|
|
816
|
+
end
|
|
806
817
|
end
|
|
807
818
|
|
|
808
819
|
private
|
|
@@ -893,10 +904,27 @@ module Hanami
|
|
|
893
904
|
container.config.provider_dirs = [File.join("config", "providers")]
|
|
894
905
|
container.config.registrations_dir = File.join("config", "registrations")
|
|
895
906
|
|
|
907
|
+
container.config.component_dirs.memoize = memoize_policy
|
|
908
|
+
|
|
896
909
|
container.config.env = config.env
|
|
897
910
|
container.config.inflector = config.inflector
|
|
898
911
|
end
|
|
899
912
|
|
|
913
|
+
def memoize_policy
|
|
914
|
+
# Do not memoize components in the test env, so they can be stubbed if required.
|
|
915
|
+
return false if config.env == :test
|
|
916
|
+
|
|
917
|
+
no_memoize = config.no_memoize
|
|
918
|
+
|
|
919
|
+
if no_memoize.respond_to?(:call)
|
|
920
|
+
->(component) { !no_memoize.call(component) }
|
|
921
|
+
elsif no_memoize.is_a?(Array) && no_memoize.any?
|
|
922
|
+
->(component) { !component.key.start_with?(*no_memoize) }
|
|
923
|
+
else
|
|
924
|
+
true
|
|
925
|
+
end
|
|
926
|
+
end
|
|
927
|
+
|
|
900
928
|
def prepare_container_component_dirs
|
|
901
929
|
return unless root.directory?
|
|
902
930
|
|
|
@@ -935,6 +963,7 @@ module Hanami
|
|
|
935
963
|
)
|
|
936
964
|
end
|
|
937
965
|
|
|
966
|
+
# rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
938
967
|
def prepare_container_providers
|
|
939
968
|
# Check here for the `routes` definition only, not `router` itself, because the
|
|
940
969
|
# `router` requires the slice to be prepared before it can be loaded, and at this
|
|
@@ -956,19 +985,39 @@ module Hanami
|
|
|
956
985
|
|
|
957
986
|
if register_db_provider?
|
|
958
987
|
# Only register providers if the user hasn't provided their own
|
|
959
|
-
|
|
988
|
+
unless container.providers[:db]
|
|
960
989
|
register_provider(:db, namespace: true, source: Providers::DB)
|
|
961
990
|
end
|
|
962
991
|
|
|
963
|
-
|
|
992
|
+
unless container.providers[:relations]
|
|
964
993
|
register_provider(:relations, namespace: true, source: Providers::Relations)
|
|
965
994
|
end
|
|
966
995
|
end
|
|
967
996
|
end
|
|
997
|
+
|
|
998
|
+
if Hanami.bundled?("i18n")
|
|
999
|
+
require_relative "providers/i18n"
|
|
1000
|
+
|
|
1001
|
+
if register_i18n_provider? && !container.providers[:i18n]
|
|
1002
|
+
register_provider(:i18n, source: Providers::I18n)
|
|
1003
|
+
end
|
|
1004
|
+
end
|
|
1005
|
+
|
|
1006
|
+
if Hanami.bundled?("hanami-mailer")
|
|
1007
|
+
# Explicit require here to ensure the provider source registers itself, to allow the
|
|
1008
|
+
# user to configure it within their own concrete provider file.
|
|
1009
|
+
require_relative "providers/mailers"
|
|
1010
|
+
|
|
1011
|
+
# Only register the provider if the user hasn't provided their own.
|
|
1012
|
+
unless container.providers[:mailers]
|
|
1013
|
+
register_provider(:mailers, namespace: true, source: Providers::Mailers)
|
|
1014
|
+
end
|
|
1015
|
+
end
|
|
968
1016
|
end
|
|
1017
|
+
# rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
969
1018
|
|
|
970
1019
|
def prepare_autoloader
|
|
971
|
-
autoloader.tag = "hanami.slices.#{slice_name
|
|
1020
|
+
autoloader.tag = "hanami.slices.#{slice_name}"
|
|
972
1021
|
|
|
973
1022
|
# Component dirs are automatically pushed to the autoloader by dry-system's zeitwerk plugin.
|
|
974
1023
|
# This method adds other dirs that are not otherwise configured as component dirs.
|
|
@@ -1009,19 +1058,20 @@ module Hanami
|
|
|
1009
1058
|
begin
|
|
1010
1059
|
require_relative "./routes"
|
|
1011
1060
|
require routes_require_path
|
|
1012
|
-
rescue LoadError =>
|
|
1013
|
-
raise
|
|
1061
|
+
rescue LoadError => exception
|
|
1062
|
+
raise exception unless exception.path == routes_require_path
|
|
1014
1063
|
end
|
|
1015
1064
|
end
|
|
1016
1065
|
|
|
1017
1066
|
begin
|
|
1018
1067
|
routes_class = namespace.const_get(ROUTES_CLASS_NAME)
|
|
1019
1068
|
routes_class.routes
|
|
1020
|
-
rescue NameError =>
|
|
1021
|
-
raise
|
|
1069
|
+
rescue NameError => exception
|
|
1070
|
+
raise exception unless exception.name == ROUTES_CLASS_NAME.to_sym
|
|
1022
1071
|
end
|
|
1023
1072
|
end
|
|
1024
1073
|
|
|
1074
|
+
# rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
1025
1075
|
def load_router(inspector:)
|
|
1026
1076
|
return unless routes
|
|
1027
1077
|
|
|
@@ -1065,7 +1115,7 @@ module Hanami
|
|
|
1065
1115
|
use(Hanami::Webconsole::Middleware, config)
|
|
1066
1116
|
end
|
|
1067
1117
|
|
|
1068
|
-
if Hanami.bundled?("hanami-
|
|
1118
|
+
if Hanami.bundled?("hanami-action")
|
|
1069
1119
|
if config.actions.method_override
|
|
1070
1120
|
require "rack/method_override"
|
|
1071
1121
|
use(Rack::MethodOverride)
|
|
@@ -1088,6 +1138,7 @@ module Hanami
|
|
|
1088
1138
|
middleware_stack.update(config.middleware_stack)
|
|
1089
1139
|
end
|
|
1090
1140
|
end
|
|
1141
|
+
# rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
1091
1142
|
|
|
1092
1143
|
def render_errors?
|
|
1093
1144
|
config.render_errors
|
|
@@ -1111,6 +1162,16 @@ module Hanami
|
|
|
1111
1162
|
source_path.join("assets").directory?
|
|
1112
1163
|
end
|
|
1113
1164
|
|
|
1165
|
+
# Ensures an i18n provider is available in every slice.
|
|
1166
|
+
#
|
|
1167
|
+
# For the app, this will always be a standalone provider. For slices, this will be a
|
|
1168
|
+
# standalone provider unless the slice is configured to share the app's "i18n" component.
|
|
1169
|
+
def register_i18n_provider?
|
|
1170
|
+
return true if self == app
|
|
1171
|
+
|
|
1172
|
+
!config.shared_app_component_keys.include?("i18n")
|
|
1173
|
+
end
|
|
1174
|
+
|
|
1114
1175
|
def register_db_provider?
|
|
1115
1176
|
concrete_db_provider? ||
|
|
1116
1177
|
db_config_dir? ||
|
|
@@ -1143,6 +1204,5 @@ module Hanami
|
|
|
1143
1204
|
|
|
1144
1205
|
# rubocop:enable Metrics/AbcSize
|
|
1145
1206
|
end
|
|
1146
|
-
# rubocop:enable Metrics/ModuleLength
|
|
1147
1207
|
end
|
|
1148
1208
|
end
|
|
@@ -108,8 +108,8 @@ module Hanami
|
|
|
108
108
|
slice_class =
|
|
109
109
|
begin
|
|
110
110
|
inflector.constantize("#{slice_module_name(slice_name)}#{MODULE_DELIMITER}Slice")
|
|
111
|
-
rescue NameError =>
|
|
112
|
-
raise
|
|
111
|
+
rescue NameError => exception
|
|
112
|
+
raise exception unless exception.name.to_s == inflector.camelize(slice_name) || exception.name.to_s == :Slice
|
|
113
113
|
end
|
|
114
114
|
|
|
115
115
|
register(slice_name, slice_class)
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
|
|
5
|
+
module Hanami
|
|
6
|
+
# An adapter that optionally wraps the logger configured for a Hanami app. Ensures that both
|
|
7
|
+
# structured and tagged logging can be used across the Hanami framework.
|
|
8
|
+
#
|
|
9
|
+
# Provides `.call` as its main entrypoint, expecting a logger object. If a compatible logger is
|
|
10
|
+
# given (such as the Dry Logger instance provided by default in Hanami apps), then that logger is
|
|
11
|
+
# returned directly and not wrapped.
|
|
12
|
+
#
|
|
13
|
+
# If a non-compatible logger is given, then it will be wrapped by an instance of UniversalLogger,
|
|
14
|
+
# which adapts a structured and tagged logging API to the given logger.
|
|
15
|
+
#
|
|
16
|
+
# This leads to two levels of logger enhancement:
|
|
17
|
+
#
|
|
18
|
+
# 1. Structured-capable loggers (accepts keyword arguments, but no `#tagged` method): tags are are
|
|
19
|
+
# provided as a `:tags` keyword argument when logging.
|
|
20
|
+
# 2. Legacy loggers (such as the Ruby standard `Logger`, no keyword arguments, no `#tagged`
|
|
21
|
+
# method): messages are logged as JSON, with tags under a `"tags"` key.
|
|
22
|
+
#
|
|
23
|
+
# This adapter is used for all loggers configured in Hanami apps.
|
|
24
|
+
#
|
|
25
|
+
# @api public
|
|
26
|
+
# @since x.x.x
|
|
27
|
+
class UniversalLogger
|
|
28
|
+
class << self
|
|
29
|
+
# Wrap a logger if needed, or return it as-is if fully compatible.
|
|
30
|
+
#
|
|
31
|
+
# @param logger [Object] the logger to wrap
|
|
32
|
+
# @return [Object, UniversalLogger] the original logger or wrapped logger
|
|
33
|
+
#
|
|
34
|
+
# @api private
|
|
35
|
+
def call(logger)
|
|
36
|
+
return logger if compatible_logger?(logger)
|
|
37
|
+
|
|
38
|
+
new(logger)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# @api private
|
|
42
|
+
alias_method :[], :call
|
|
43
|
+
|
|
44
|
+
# @api private
|
|
45
|
+
def compatible_logger?(logger)
|
|
46
|
+
structured_logger?(logger) && tagged_logger?(logger)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# @api private
|
|
50
|
+
def tagged_logger?(logger)
|
|
51
|
+
logger.respond_to?(:tagged)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# @api private
|
|
55
|
+
def structured_logger?(logger)
|
|
56
|
+
logger.respond_to?(:info) &&
|
|
57
|
+
logger.method(:info).parameters.any? { |(type, _)| type == :keyrest }
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# @api public
|
|
62
|
+
# @since x.x.x
|
|
63
|
+
attr_reader :logger
|
|
64
|
+
|
|
65
|
+
# @api private
|
|
66
|
+
def initialize(logger)
|
|
67
|
+
@logger = logger
|
|
68
|
+
@structured_logger = self.class.structured_logger?(logger)
|
|
69
|
+
@tags_thread_key = :"hanami_universal_logger_tags_#{object_id}"
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# @api private
|
|
73
|
+
LOG_LEVEL_METHODS = %i[debug info warn error fatal unknown].freeze
|
|
74
|
+
private_constant :LOG_LEVEL_METHODS
|
|
75
|
+
|
|
76
|
+
# @!method debug(message = nil, **payload, &blk)
|
|
77
|
+
# Logs a debug message.
|
|
78
|
+
#
|
|
79
|
+
# @param message [String, nil] the log message
|
|
80
|
+
# @param payload [Hash] structured data to include in the log entry
|
|
81
|
+
# @yieldreturn [Hash] additional payload data to merge
|
|
82
|
+
# @return [void]
|
|
83
|
+
#
|
|
84
|
+
# @api public
|
|
85
|
+
# @since x.x.x
|
|
86
|
+
|
|
87
|
+
# @!method info(message = nil, **payload, &blk)
|
|
88
|
+
# Logs an info message.
|
|
89
|
+
#
|
|
90
|
+
# @param message [String, nil] the log message
|
|
91
|
+
# @param payload [Hash] structured data to include in the log entry
|
|
92
|
+
# @yieldreturn [Hash] additional payload data to merge
|
|
93
|
+
# @return [void]
|
|
94
|
+
# @api public
|
|
95
|
+
# @since x.x.x
|
|
96
|
+
|
|
97
|
+
# @!method warn(message = nil, **payload, &blk)
|
|
98
|
+
# Logs a warning message.
|
|
99
|
+
#
|
|
100
|
+
# @param message [String, nil] the log message
|
|
101
|
+
# @param payload [Hash] structured data to include in the log entry
|
|
102
|
+
# @yieldreturn [Hash] additional payload data to merge
|
|
103
|
+
# @return [void]
|
|
104
|
+
#
|
|
105
|
+
# @api public
|
|
106
|
+
# @since x.x.x
|
|
107
|
+
|
|
108
|
+
# @!method error(message = nil, **payload, &blk)
|
|
109
|
+
# Logs an error message.
|
|
110
|
+
#
|
|
111
|
+
# @param message [String, nil] the log message
|
|
112
|
+
# @param payload [Hash] structured data to include in the log entry
|
|
113
|
+
# @yieldreturn [Hash] additional payload data to merge
|
|
114
|
+
# @return [void]
|
|
115
|
+
#
|
|
116
|
+
# @api public
|
|
117
|
+
# @since x.x.x
|
|
118
|
+
|
|
119
|
+
# @!method fatal(message = nil, **payload, &blk)
|
|
120
|
+
# Logs a fatal message.
|
|
121
|
+
#
|
|
122
|
+
# @param message [String, nil] the log message
|
|
123
|
+
# @param payload [Hash] structured data to include in the log entry
|
|
124
|
+
# @yieldreturn [Hash] additional payload data to merge
|
|
125
|
+
# @return [void]
|
|
126
|
+
#
|
|
127
|
+
# @api public
|
|
128
|
+
# @since x.x.x
|
|
129
|
+
|
|
130
|
+
# @!method unknown(message = nil, **payload, &blk)
|
|
131
|
+
# Logs a message with unknown severity.
|
|
132
|
+
#
|
|
133
|
+
# @param message [String, nil] the log message
|
|
134
|
+
# @param payload [Hash] structured data to include in the log entry
|
|
135
|
+
# @yieldreturn [Hash] additional payload data to merge
|
|
136
|
+
# @return [void]
|
|
137
|
+
#
|
|
138
|
+
# @api public
|
|
139
|
+
# @since x.x.x
|
|
140
|
+
|
|
141
|
+
LOG_LEVEL_METHODS.each do |level|
|
|
142
|
+
define_method(level) do |message = nil, **payload, &blk|
|
|
143
|
+
_log(level, message, payload, &blk)
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# @api public
|
|
148
|
+
# @since x.x.x
|
|
149
|
+
def add(severity, message = nil, progname = nil, &blk)
|
|
150
|
+
# Convert severity to a level symbol if it's an integer (the standard Logger uses integers).
|
|
151
|
+
level = _severity_to_level(severity)
|
|
152
|
+
|
|
153
|
+
payload = {}
|
|
154
|
+
payload[:progname] = progname if progname
|
|
155
|
+
|
|
156
|
+
_log(level, message, payload, &blk)
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# @api public
|
|
160
|
+
# @since x.x.x
|
|
161
|
+
alias_method :log, :add
|
|
162
|
+
|
|
163
|
+
# @api public
|
|
164
|
+
# @since x.x.x
|
|
165
|
+
def tagged(*tags)
|
|
166
|
+
previous_tags = _current_tags
|
|
167
|
+
self._current_tags = tags
|
|
168
|
+
begin
|
|
169
|
+
yield
|
|
170
|
+
ensure
|
|
171
|
+
self._current_tags = previous_tags
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
private
|
|
176
|
+
|
|
177
|
+
# Delegates any other methods to the wrapped logger.
|
|
178
|
+
def method_missing(method, ...)
|
|
179
|
+
if logger.respond_to?(method)
|
|
180
|
+
logger.public_send(method, ...)
|
|
181
|
+
else
|
|
182
|
+
super
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
def respond_to_missing?(method, include_private = false)
|
|
187
|
+
logger.respond_to?(method, include_private) || super
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
# Maps a standard Logger severity integer (e.g. 1) to a level name (`:info`).
|
|
191
|
+
#
|
|
192
|
+
# We need this be able to support the basic `Logger#log` and `#add` methods in addition to the
|
|
193
|
+
# named severity methods.
|
|
194
|
+
def _severity_to_level(severity)
|
|
195
|
+
return severity if severity.is_a?(Symbol)
|
|
196
|
+
|
|
197
|
+
SEVERITY_MAP.fetch(severity, :unknown)
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
SEVERITY_MAP = {
|
|
201
|
+
0 => :debug, # Logger::DEBUG
|
|
202
|
+
1 => :info, # Logger::INFO
|
|
203
|
+
2 => :warn, # Logger::WARN
|
|
204
|
+
3 => :error, # Logger::ERROR
|
|
205
|
+
4 => :fatal, # Logger::FATAL
|
|
206
|
+
5 => :unknown # Logger::UNKNOWN
|
|
207
|
+
}.freeze
|
|
208
|
+
private_constant :SEVERITY_MAP
|
|
209
|
+
|
|
210
|
+
def _log(level, message, payload, &blk)
|
|
211
|
+
if @structured_logger
|
|
212
|
+
_log_structured(level, message, payload, &blk)
|
|
213
|
+
else
|
|
214
|
+
_log_json(level, message, payload, &blk)
|
|
215
|
+
end
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
def _log_structured(method, message, payload)
|
|
219
|
+
payload = payload.merge(yield) if block_given?
|
|
220
|
+
|
|
221
|
+
tags = _current_tags
|
|
222
|
+
payload[:tags] = tags if tags && !tags.empty?
|
|
223
|
+
|
|
224
|
+
logger.public_send(method, message, **payload)
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
def _log_json(method, message, payload)
|
|
228
|
+
json_data =
|
|
229
|
+
if block_given?
|
|
230
|
+
yield
|
|
231
|
+
else
|
|
232
|
+
payload[:message] = message if message
|
|
233
|
+
payload
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
tags = _current_tags
|
|
237
|
+
json_data[:tags] = tags if tags && !tags.empty?
|
|
238
|
+
|
|
239
|
+
logger.public_send(method, JSON.generate(json_data))
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
def _current_tags
|
|
243
|
+
Thread.current[@tags_thread_key]
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
def _current_tags=(tags)
|
|
247
|
+
Thread.current[@tags_thread_key] = tags
|
|
248
|
+
end
|
|
249
|
+
end
|
|
250
|
+
end
|
data/lib/hanami/version.rb
CHANGED
|
@@ -56,87 +56,10 @@ module Hanami
|
|
|
56
56
|
end
|
|
57
57
|
end
|
|
58
58
|
|
|
59
|
-
# @since 2.1.0
|
|
60
|
-
# @api private
|
|
61
|
-
class UniversalLogger
|
|
62
|
-
class << self
|
|
63
|
-
# @since 2.1.0
|
|
64
|
-
# @api private
|
|
65
|
-
def call(logger)
|
|
66
|
-
return logger if compatible_logger?(logger)
|
|
67
|
-
|
|
68
|
-
new(logger)
|
|
69
|
-
end
|
|
70
|
-
|
|
71
|
-
# @since 2.1.0
|
|
72
|
-
# @api private
|
|
73
|
-
alias_method :[], :call
|
|
74
|
-
|
|
75
|
-
private
|
|
76
|
-
|
|
77
|
-
def compatible_logger?(logger)
|
|
78
|
-
logger.respond_to?(:tagged) && accepts_entry_payload?(logger)
|
|
79
|
-
end
|
|
80
|
-
|
|
81
|
-
def accepts_entry_payload?(logger)
|
|
82
|
-
logger.method(:info).parameters.any? { |(type, _)| type == :keyrest }
|
|
83
|
-
end
|
|
84
|
-
end
|
|
85
|
-
|
|
86
|
-
# @since 2.1.0
|
|
87
|
-
# @api private
|
|
88
|
-
attr_reader :logger
|
|
89
|
-
|
|
90
|
-
# @since 2.1.0
|
|
91
|
-
# @api private
|
|
92
|
-
def initialize(logger)
|
|
93
|
-
@logger = logger
|
|
94
|
-
end
|
|
95
|
-
|
|
96
|
-
# @since 2.1.0
|
|
97
|
-
# @api private
|
|
98
|
-
def tagged(*, &blk)
|
|
99
|
-
blk.call
|
|
100
|
-
end
|
|
101
|
-
|
|
102
|
-
# Logs the entry as JSON.
|
|
103
|
-
#
|
|
104
|
-
# This ensures a reasonable (and parseable) representation of our log payload structures for
|
|
105
|
-
# loggers that are configured to wholly replace Hanami's default logger.
|
|
106
|
-
#
|
|
107
|
-
# @since 2.1.0
|
|
108
|
-
# @api private
|
|
109
|
-
def info(message = nil, **payload, &blk)
|
|
110
|
-
logger.info do
|
|
111
|
-
if blk
|
|
112
|
-
JSON.generate(blk.call)
|
|
113
|
-
else
|
|
114
|
-
payload[:message] = message if message
|
|
115
|
-
JSON.generate(payload)
|
|
116
|
-
end
|
|
117
|
-
end
|
|
118
|
-
end
|
|
119
|
-
|
|
120
|
-
# @see info
|
|
121
|
-
#
|
|
122
|
-
# @since 2.1.0
|
|
123
|
-
# @api private
|
|
124
|
-
def error(message = nil, **payload, &blk)
|
|
125
|
-
logger.error do
|
|
126
|
-
if blk
|
|
127
|
-
JSON.generate(blk.call)
|
|
128
|
-
else
|
|
129
|
-
payload[:message] = message if message
|
|
130
|
-
JSON.generate(payload)
|
|
131
|
-
end
|
|
132
|
-
end
|
|
133
|
-
end
|
|
134
|
-
end
|
|
135
|
-
|
|
136
59
|
# @api private
|
|
137
60
|
# @since 2.0.0
|
|
138
61
|
def initialize(logger, env: :development)
|
|
139
|
-
@logger = UniversalLogger[logger]
|
|
62
|
+
@logger = Hanami::UniversalLogger[logger]
|
|
140
63
|
extend(Development) if %i[development test].include?(env)
|
|
141
64
|
end
|
|
142
65
|
|
|
@@ -157,7 +80,6 @@ module Hanami
|
|
|
157
80
|
# @since 2.0.0
|
|
158
81
|
def log_request(env, status, elapsed)
|
|
159
82
|
logger.tagged(:rack) do
|
|
160
|
-
|
|
161
83
|
logger.info do
|
|
162
84
|
data(env, status: status, elapsed: elapsed)
|
|
163
85
|
end
|
|
@@ -189,7 +111,7 @@ module Hanami
|
|
|
189
111
|
length: extract_content_length(env),
|
|
190
112
|
params: env.fetch(ROUTER_PARAMS, EMPTY_PARAMS),
|
|
191
113
|
elapsed: elapsed,
|
|
192
|
-
elapsed_unit: MICROSECOND
|
|
114
|
+
elapsed_unit: MICROSECOND
|
|
193
115
|
}
|
|
194
116
|
end
|
|
195
117
|
|