brut 0.10.0 → 0.12.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +20 -0
- data/Gemfile.lock +21 -21
- data/assets/YouTubeThumb.pxd +0 -0
- data/bin/new-version +3 -3
- data/brut-css/package-lock.json +94 -97
- data/brut-css/package.json +2 -2
- data/brut-js/package-lock.json +3 -3
- data/brut-js/package.json +6 -3
- data/brut-js/specs/AjaxSubmit.spec.js +62 -6
- data/brut-js/src/AjaxSubmit.js +26 -6
- data/brutrb.com/forms.md +1 -0
- data/brutrb.com/getting-started.md +3 -0
- data/brutrb.com/images/tutorial/02-confirmation-dialog-browser-element-styled.png +0 -0
- data/brutrb.com/images/tutorial/02-confirmation-dialog-browser-element.png +0 -0
- data/brutrb.com/images/tutorial/02-confirmation-dialog-browser.png +0 -0
- data/brutrb.com/images/tutorial/02-confirmation-flow.graffle +0 -0
- data/brutrb.com/images/tutorial/02-confirmation-flow.png +0 -0
- data/brutrb.com/instrumentation.md +142 -3
- data/brutrb.com/recipes/authentication.md +1 -0
- data/brutrb.com/tutorial.md +29 -1627
- data/brutrb.com/tutorials/01-intro.md +1654 -0
- data/brutrb.com/tutorials/02-dialog.md +569 -0
- data/docs/404.html +2 -2
- data/docs/adrs.html +4 -4
- data/docs/ai.html +4 -4
- data/docs/api/Brut/BackEnd/SeedData.html +1 -1
- data/docs/api/Brut/BackEnd/Sidekiq/Middlewares/Server/FlushSpans.html +1 -1
- data/docs/api/Brut/BackEnd/Sidekiq/Middlewares/Server.html +1 -1
- data/docs/api/Brut/BackEnd/Sidekiq/Middlewares.html +1 -1
- data/docs/api/Brut/BackEnd/Sidekiq.html +1 -1
- data/docs/api/Brut/BackEnd/Validators/FormValidator.html +1 -1
- data/docs/api/Brut/BackEnd/Validators.html +1 -1
- data/docs/api/Brut/BackEnd.html +1 -1
- data/docs/api/Brut/CLI/App.html +1 -1
- data/docs/api/Brut/CLI/AppRunner.html +1 -1
- data/docs/api/Brut/CLI/Apps/BuildAssets/All.html +1 -1
- data/docs/api/Brut/CLI/Apps/BuildAssets/CSS.html +1 -1
- data/docs/api/Brut/CLI/Apps/BuildAssets/Images.html +1 -1
- data/docs/api/Brut/CLI/Apps/BuildAssets/JS.html +1 -1
- data/docs/api/Brut/CLI/Apps/BuildAssets.html +1 -1
- data/docs/api/Brut/CLI/Apps/DB/Create.html +1 -1
- data/docs/api/Brut/CLI/Apps/DB/Drop.html +1 -1
- data/docs/api/Brut/CLI/Apps/DB/Migrate.html +1 -1
- data/docs/api/Brut/CLI/Apps/DB/NewMigration.html +1 -1
- data/docs/api/Brut/CLI/Apps/DB/Rebuild.html +1 -1
- data/docs/api/Brut/CLI/Apps/DB/Seed.html +1 -1
- data/docs/api/Brut/CLI/Apps/DB/Status.html +1 -1
- data/docs/api/Brut/CLI/Apps/DB.html +1 -1
- data/docs/api/Brut/CLI/Apps/DeployBase/GitChecks.html +1 -1
- data/docs/api/Brut/CLI/Apps/DeployBase.html +1 -1
- data/docs/api/Brut/CLI/Apps/HerokuContainerBasedDeploy/Deploy.html +1 -1
- data/docs/api/Brut/CLI/Apps/HerokuContainerBasedDeploy.html +1 -1
- data/docs/api/Brut/CLI/Apps/Scaffold/Action/Route.html +1 -1
- data/docs/api/Brut/CLI/Apps/Scaffold/Action.html +1 -1
- data/docs/api/Brut/CLI/Apps/Scaffold/Component.html +1 -1
- data/docs/api/Brut/CLI/Apps/Scaffold/CustomElementTest.html +1 -1
- data/docs/api/Brut/CLI/Apps/Scaffold/DbModel.html +1 -1
- data/docs/api/Brut/CLI/Apps/Scaffold/E2ETest.html +1 -1
- data/docs/api/Brut/CLI/Apps/Scaffold/Form.html +1 -1
- data/docs/api/Brut/CLI/Apps/Scaffold/Page/Route.html +1 -1
- data/docs/api/Brut/CLI/Apps/Scaffold/Page.html +1 -1
- data/docs/api/Brut/CLI/Apps/Scaffold/RoutesEditor.html +1 -1
- data/docs/api/Brut/CLI/Apps/Scaffold/Test.html +1 -1
- data/docs/api/Brut/CLI/Apps/Scaffold.html +1 -1
- data/docs/api/Brut/CLI/Apps/Test/Audit.html +1 -1
- data/docs/api/Brut/CLI/Apps/Test/E2e.html +1 -1
- data/docs/api/Brut/CLI/Apps/Test/JS.html +1 -1
- data/docs/api/Brut/CLI/Apps/Test/Run.html +1 -1
- data/docs/api/Brut/CLI/Apps/Test.html +1 -1
- data/docs/api/Brut/CLI/Apps.html +1 -1
- data/docs/api/Brut/CLI/Command.html +1 -1
- data/docs/api/Brut/CLI/Error.html +1 -1
- data/docs/api/Brut/CLI/ExecutionResults/Result.html +1 -1
- data/docs/api/Brut/CLI/ExecutionResults.html +1 -1
- data/docs/api/Brut/CLI/Executor.html +1 -1
- data/docs/api/Brut/CLI/InvalidOption.html +1 -1
- data/docs/api/Brut/CLI/Options.html +1 -1
- data/docs/api/Brut/CLI/Output.html +1 -1
- data/docs/api/Brut/CLI/SystemExecError.html +1 -1
- data/docs/api/Brut/CLI.html +1 -1
- data/docs/api/Brut/FactoryBot.html +1 -1
- data/docs/api/Brut/Framework/App.html +1 -1
- data/docs/api/Brut/Framework/Config.html +1 -1
- data/docs/api/Brut/Framework/Container.html +1 -1
- data/docs/api/Brut/Framework/Error.html +1 -1
- data/docs/api/Brut/Framework/Errors/AbstractMethod.html +1 -1
- data/docs/api/Brut/Framework/Errors/Bug.html +1 -1
- data/docs/api/Brut/Framework/Errors/MissingConfiguration.html +1 -1
- data/docs/api/Brut/Framework/Errors/MissingParameter.html +1 -1
- data/docs/api/Brut/Framework/Errors/NoClassForPath.html +1 -1
- data/docs/api/Brut/Framework/Errors/NotFound.html +1 -1
- data/docs/api/Brut/Framework/Errors/NotImplemented.html +1 -1
- data/docs/api/Brut/Framework/Errors.html +1 -1
- data/docs/api/Brut/Framework/FussyTypeEnforcement.html +2 -2
- data/docs/api/Brut/Framework/MCP.html +1 -1
- data/docs/api/Brut/Framework/ProjectEnvironment.html +1 -1
- data/docs/api/Brut/Framework.html +1 -1
- data/docs/api/Brut/FrontEnd/AssetPathResolver.html +1 -1
- data/docs/api/Brut/FrontEnd/Component/Helpers.html +1 -1
- data/docs/api/Brut/FrontEnd/Component.html +1 -1
- data/docs/api/Brut/FrontEnd/Components/ConstraintViolations.html +48 -27
- data/docs/api/Brut/FrontEnd/Components/FormTag.html +1 -1
- data/docs/api/Brut/FrontEnd/Components/I18nTranslations.html +1 -1
- data/docs/api/Brut/FrontEnd/Components/Input.html +2 -2
- data/docs/api/Brut/FrontEnd/Components/Inputs/ButtonTag.html +443 -0
- data/docs/api/Brut/FrontEnd/Components/Inputs/CsrfToken.html +1 -1
- data/docs/api/Brut/FrontEnd/Components/Inputs/InputTag.html +14 -9
- data/docs/api/Brut/FrontEnd/Components/Inputs/RadioButton.html +1 -1
- data/docs/api/Brut/FrontEnd/Components/Inputs/SelectTagWithOptions.html +8 -9
- data/docs/api/Brut/FrontEnd/Components/Inputs/TextareaTag.html +10 -11
- data/docs/api/Brut/FrontEnd/Components/Inputs.html +2 -2
- data/docs/api/Brut/FrontEnd/Components/LocaleDetection.html +1 -1
- data/docs/api/Brut/FrontEnd/Components/PageIdentifier.html +1 -1
- data/docs/api/Brut/FrontEnd/Components/TimeTag.html +1 -1
- data/docs/api/Brut/FrontEnd/Components/Traceparent.html +1 -1
- data/docs/api/Brut/FrontEnd/Components.html +1 -1
- data/docs/api/Brut/FrontEnd/CsrfProtector.html +1 -1
- data/docs/api/Brut/FrontEnd/Download.html +1 -1
- data/docs/api/Brut/FrontEnd/Flash.html +1 -1
- data/docs/api/Brut/FrontEnd/Form.html +39 -39
- data/docs/api/Brut/FrontEnd/Forms/Button.html +331 -0
- data/docs/api/Brut/FrontEnd/Forms/ButtonInputDefinition.html +544 -0
- data/docs/api/Brut/FrontEnd/Forms/ConstraintViolation.html +1 -1
- data/docs/api/Brut/FrontEnd/Forms/Input/Color.html +1 -1
- data/docs/api/Brut/FrontEnd/Forms/Input/TimeOfDay.html +1 -1
- data/docs/api/Brut/FrontEnd/Forms/Input.html +6 -2
- data/docs/api/Brut/FrontEnd/Forms/InputDeclarations.html +111 -23
- data/docs/api/Brut/FrontEnd/Forms/InputDefinition.html +20 -12
- data/docs/api/Brut/FrontEnd/Forms/RadioButtonGroupInput.html +1 -1
- data/docs/api/Brut/FrontEnd/Forms/RadioButtonGroupInputDefinition.html +1 -1
- data/docs/api/Brut/FrontEnd/Forms/SelectInput.html +1 -1
- data/docs/api/Brut/FrontEnd/Forms/SelectInputDefinition.html +1 -1
- data/docs/api/Brut/FrontEnd/Forms/ValidityState.html +1 -1
- data/docs/api/Brut/FrontEnd/Forms.html +2 -2
- data/docs/api/Brut/FrontEnd/GenericResponse.html +1 -1
- data/docs/api/Brut/FrontEnd/Handler.html +1 -1
- data/docs/api/Brut/FrontEnd/Handlers/CspReportingHandler.html +1 -1
- data/docs/api/Brut/FrontEnd/Handlers/InstrumentationHandler/TraceParent.html +1 -1
- data/docs/api/Brut/FrontEnd/Handlers/InstrumentationHandler.html +1 -1
- data/docs/api/Brut/FrontEnd/Handlers/LocaleDetectionHandler.html +1 -1
- data/docs/api/Brut/FrontEnd/Handlers/MissingHandler/Form.html +2 -2
- data/docs/api/Brut/FrontEnd/Handlers/MissingHandler.html +1 -1
- data/docs/api/Brut/FrontEnd/Handlers.html +1 -1
- data/docs/api/Brut/FrontEnd/HandlingResults.html +1 -1
- data/docs/api/Brut/FrontEnd/HttpMethod.html +1 -1
- data/docs/api/Brut/FrontEnd/HttpStatus.html +1 -1
- data/docs/api/Brut/FrontEnd/InlineSvgLocator.html +1 -1
- data/docs/api/Brut/FrontEnd/Layout.html +1 -1
- data/docs/api/Brut/FrontEnd/Middleware.html +1 -1
- data/docs/api/Brut/FrontEnd/Middlewares/AnnotateBrutOwnedPaths.html +1 -1
- data/docs/api/Brut/FrontEnd/Middlewares/Favicon.html +1 -1
- data/docs/api/Brut/FrontEnd/Middlewares/OpenTelemetrySpan.html +1 -1
- data/docs/api/Brut/FrontEnd/Middlewares/ReloadApp.html +1 -1
- data/docs/api/Brut/FrontEnd/Middlewares.html +1 -1
- data/docs/api/Brut/FrontEnd/Page.html +1 -1
- data/docs/api/Brut/FrontEnd/Pages/MissingPage.html +1 -1
- data/docs/api/Brut/FrontEnd/Pages.html +1 -1
- data/docs/api/Brut/FrontEnd/RequestContext.html +1 -1
- data/docs/api/Brut/FrontEnd/RouteHook.html +1 -1
- data/docs/api/Brut/FrontEnd/RouteHooks/AgeFlash.html +1 -1
- data/docs/api/Brut/FrontEnd/RouteHooks/CSPNoInlineScripts.html +1 -1
- data/docs/api/Brut/FrontEnd/RouteHooks/CSPNoInlineStylesOrScripts/ReportOnly.html +1 -1
- data/docs/api/Brut/FrontEnd/RouteHooks/CSPNoInlineStylesOrScripts.html +1 -1
- data/docs/api/Brut/FrontEnd/RouteHooks/LocaleDetection.html +1 -1
- data/docs/api/Brut/FrontEnd/RouteHooks/SetupRequestContext.html +1 -1
- data/docs/api/Brut/FrontEnd/RouteHooks.html +1 -1
- data/docs/api/Brut/FrontEnd/Routing/FormHandlerRoute.html +1 -1
- data/docs/api/Brut/FrontEnd/Routing/FormRoute.html +1 -1
- data/docs/api/Brut/FrontEnd/Routing/MissingForm.html +1 -1
- data/docs/api/Brut/FrontEnd/Routing/MissingHandler.html +1 -1
- data/docs/api/Brut/FrontEnd/Routing/MissingPage.html +1 -1
- data/docs/api/Brut/FrontEnd/Routing/MissingPath.html +1 -1
- data/docs/api/Brut/FrontEnd/Routing/PageRoute.html +1 -1
- data/docs/api/Brut/FrontEnd/Routing/Route.html +1 -1
- data/docs/api/Brut/FrontEnd/Routing.html +1 -1
- data/docs/api/Brut/FrontEnd/Session.html +1 -1
- data/docs/api/Brut/FrontEnd.html +1 -1
- data/docs/api/Brut/I18n/BaseMethods.html +1 -1
- data/docs/api/Brut/I18n/ForBackEnd.html +1 -1
- data/docs/api/Brut/I18n/ForCLI.html +1 -1
- data/docs/api/Brut/I18n/ForHTML.html +1 -1
- data/docs/api/Brut/I18n/HTTPAcceptLanguage/AlwaysEnglish.html +1 -1
- data/docs/api/Brut/I18n/HTTPAcceptLanguage.html +1 -1
- data/docs/api/Brut/I18n.html +1 -1
- data/docs/api/Brut/Instrumentation/LoggerSpanExporter.html +1 -1
- data/docs/api/Brut/Instrumentation/Methods/ClassMethods.html +596 -0
- data/docs/api/Brut/Instrumentation/Methods.html +173 -0
- data/docs/api/Brut/Instrumentation/OpenTelemetry/NormalizedAttributes.html +7 -7
- data/docs/api/Brut/Instrumentation/OpenTelemetry/Span.html +7 -7
- data/docs/api/Brut/Instrumentation/OpenTelemetry.html +19 -17
- data/docs/api/Brut/Instrumentation.html +3 -1
- data/docs/api/Brut/RubocopConfig.html +1 -1
- data/docs/api/Brut/SinatraHelpers/ClassMethods.html +1 -1
- data/docs/api/Brut/SinatraHelpers.html +1 -1
- data/docs/api/Brut/SpecSupport/ClockSupport.html +1 -1
- data/docs/api/Brut/SpecSupport/ComponentSupport.html +1 -1
- data/docs/api/Brut/SpecSupport/E2ETestServer.html +1 -1
- data/docs/api/Brut/SpecSupport/E2eSupport.html +1 -1
- data/docs/api/Brut/SpecSupport/EnhancedNode.html +1 -1
- data/docs/api/Brut/SpecSupport/FlashSupport.html +1 -1
- data/docs/api/Brut/SpecSupport/GeneralSupport/ClassMethods.html +1 -1
- data/docs/api/Brut/SpecSupport/GeneralSupport.html +1 -1
- data/docs/api/Brut/SpecSupport/HandlerSupport.html +1 -1
- data/docs/api/Brut/SpecSupport/Matchers/BeABug.html +1 -1
- data/docs/api/Brut/SpecSupport/Matchers/BePageFor.html +1 -1
- data/docs/api/Brut/SpecSupport/Matchers/BeRoutingFor.html +1 -1
- data/docs/api/Brut/SpecSupport/Matchers/HaveConstraintViolation.html +1 -1
- data/docs/api/Brut/SpecSupport/Matchers/HaveGenerated.html +1 -1
- data/docs/api/Brut/SpecSupport/Matchers/HaveHTMLAttribute.html +1 -1
- data/docs/api/Brut/SpecSupport/Matchers/HaveI18nString.html +1 -1
- data/docs/api/Brut/SpecSupport/Matchers/HaveLinkTo.html +1 -1
- data/docs/api/Brut/SpecSupport/Matchers/HaveRedirectedTo.html +1 -1
- data/docs/api/Brut/SpecSupport/Matchers/HaveReturnedHttpStatus.html +1 -1
- data/docs/api/Brut/SpecSupport/Matchers/HaveReturnedRackResponse.html +1 -1
- data/docs/api/Brut/SpecSupport/Matchers.html +1 -1
- data/docs/api/Brut/SpecSupport/RSpecSetup/OptionalSidekiqSupport.html +1 -1
- data/docs/api/Brut/SpecSupport/RSpecSetup.html +1 -1
- data/docs/api/Brut/SpecSupport/SessionSupport.html +1 -1
- data/docs/api/Brut/SpecSupport.html +1 -1
- data/docs/api/Brut.html +1 -1
- data/docs/api/Clock.html +1 -1
- data/docs/api/ModuleName.html +1 -1
- data/docs/api/RichString.html +1 -1
- data/docs/api/SemanticLogger/Appender/Async.html +1 -1
- data/docs/api/Sequel/Extensions/BrutInstrumentation.html +1 -1
- data/docs/api/Sequel/Extensions/BrutMigrations.html +1 -1
- data/docs/api/Sequel/Extensions.html +1 -1
- data/docs/api/Sequel/Plugins/CreatedAt/InstanceMethods.html +1 -1
- data/docs/api/Sequel/Plugins/CreatedAt.html +1 -1
- data/docs/api/Sequel/Plugins/ExternalId/ClassMethods.html +1 -1
- data/docs/api/Sequel/Plugins/ExternalId/InstanceMethods.html +1 -1
- data/docs/api/Sequel/Plugins/ExternalId.html +1 -1
- data/docs/api/Sequel/Plugins/FindBang/ClassMethods.html +1 -1
- data/docs/api/Sequel/Plugins/FindBang.html +1 -1
- data/docs/api/Sequel/Plugins.html +1 -1
- data/docs/api/Sequel.html +1 -1
- data/docs/api/_index.html +36 -1
- data/docs/api/class_list.html +1 -1
- data/docs/api/file.README.html +1 -1
- data/docs/api/index.html +1 -1
- data/docs/api/method_list.html +364 -252
- data/docs/api/top-level-namespace.html +1 -1
- data/docs/assets/02-confirmation-dialog-browser-element-styled.3NEGM20-.png +0 -0
- data/docs/assets/02-confirmation-dialog-browser-element.DPsf0xUW.png +0 -0
- data/docs/assets/02-confirmation-dialog-browser.DH8ALFO4.png +0 -0
- data/docs/assets/02-confirmation-flow.D9gZ0S5U.png +0 -0
- data/docs/assets/{app.vjGWMSnJ.js → app.V2GOcOrg.js} +1 -1
- data/docs/assets/chunks/@localSearchIndexroot.3Lsq4QTb.js +1 -0
- data/docs/assets/chunks/{VPLocalSearchBox.C-ymMW2k.js → VPLocalSearchBox.CZTadcAy.js} +1 -1
- data/docs/assets/chunks/{theme.pJUosGlI.js → theme.FeuYNxyp.js} +2 -2
- data/docs/assets/{components.md.B543a3Lm.js → components.md.f4cdTyvV.js} +3 -3
- data/docs/assets/{configuration.md.-foE_iVv.js → configuration.md.Bs4-rxnS.js} +1 -1
- data/docs/assets/{form-constraints.md.DK5adCgM.js → form-constraints.md.KTv5cdR4.js} +6 -6
- data/docs/assets/{forms.md.D5-2rgHh.js → forms.md.Sys-XxVf.js} +3 -3
- data/docs/assets/{forms.md.D5-2rgHh.lean.js → forms.md.Sys-XxVf.lean.js} +1 -1
- data/docs/assets/{getting-started.md.Cd4XSZb_.js → getting-started.md.CFIW0bcE.js} +6 -3
- data/docs/assets/{getting-started.md.Cd4XSZb_.lean.js → getting-started.md.CFIW0bcE.lean.js} +1 -1
- data/docs/assets/instrumentation.md._lNSriEZ.js +90 -0
- data/docs/assets/instrumentation.md._lNSriEZ.lean.js +1 -0
- data/docs/assets/{recipes_authentication.md.Dzvi_g69.js → recipes_authentication.md.BAISoxmN.js} +1 -0
- data/docs/assets/recipes_form-errors.md.Bv5RCKqH.js +66 -0
- data/docs/assets/recipes_form-errors.md.Bv5RCKqH.lean.js +1 -0
- data/docs/assets/tutorial.md.BM40jnoq.js +27 -0
- data/docs/assets/tutorial.md.BM40jnoq.lean.js +1 -0
- data/docs/assets/{tutorial.md.C4zR5XPG.js → tutorials_01-intro.md.B4sUBY3X.js} +13 -33
- data/docs/assets/tutorials_01-intro.md.B4sUBY3X.lean.js +1 -0
- data/docs/assets/tutorials_02-dialog.md.QTFeHdiA.js +274 -0
- data/docs/assets/tutorials_02-dialog.md.QTFeHdiA.lean.js +1 -0
- data/docs/assets.html +4 -4
- data/docs/brut-js/api/AjaxSubmit.html +16 -6
- data/docs/brut-js/api/AjaxSubmit.js.html +27 -7
- data/docs/brut-js/api/Autosubmit.html +1 -1
- data/docs/brut-js/api/Autosubmit.js.html +1 -1
- data/docs/brut-js/api/BaseCustomElement.html +1 -1
- data/docs/brut-js/api/BaseCustomElement.js.html +1 -1
- data/docs/brut-js/api/BrutCustomElements.html +1 -1
- data/docs/brut-js/api/BufferedLogger.html +1 -1
- data/docs/brut-js/api/ConfirmSubmit.html +1 -1
- data/docs/brut-js/api/ConfirmSubmit.js.html +1 -1
- data/docs/brut-js/api/ConfirmationDialog.html +1 -1
- data/docs/brut-js/api/ConfirmationDialog.js.html +1 -1
- data/docs/brut-js/api/ConstraintViolationMessage.html +55 -5
- data/docs/brut-js/api/ConstraintViolationMessage.js.html +18 -3
- data/docs/brut-js/api/ConstraintViolationMessages.html +1 -1
- data/docs/brut-js/api/ConstraintViolationMessages.js.html +1 -1
- data/docs/brut-js/api/CopyToClipboard.html +1 -1
- data/docs/brut-js/api/CopyToClipboard.js.html +1 -1
- data/docs/brut-js/api/Form.html +7 -10
- data/docs/brut-js/api/Form.js.html +20 -24
- data/docs/brut-js/api/I18nTranslation.html +1 -1
- data/docs/brut-js/api/I18nTranslation.js.html +1 -1
- data/docs/brut-js/api/LocaleDetection.html +1 -1
- data/docs/brut-js/api/LocaleDetection.js.html +1 -1
- data/docs/brut-js/api/Logger.html +1 -1
- data/docs/brut-js/api/Logger.js.html +1 -1
- data/docs/brut-js/api/Message.html +1 -1
- data/docs/brut-js/api/Message.js.html +1 -1
- data/docs/brut-js/api/PrefixedLogger.html +1 -1
- data/docs/brut-js/api/RichString.html +1 -1
- data/docs/brut-js/api/RichString.js.html +1 -1
- data/docs/brut-js/api/Tabs.html +1 -1
- data/docs/brut-js/api/Tabs.js.html +1 -1
- data/docs/brut-js/api/Tracing.html +1 -1
- data/docs/brut-js/api/Tracing.js.html +1 -1
- data/docs/brut-js/api/external-CustomElementRegistry.html +1 -1
- data/docs/brut-js/api/external-Performance.html +1 -1
- data/docs/brut-js/api/external-Promise.html +1 -1
- data/docs/brut-js/api/external-ValidityState.html +1 -1
- data/docs/brut-js/api/external-Window.html +1 -1
- data/docs/brut-js/api/external-fetch.html +1 -1
- data/docs/brut-js/api/global.html +1 -1
- data/docs/brut-js/api/index.html +1 -1
- data/docs/brut-js/api/index.js.html +1 -1
- data/docs/brut-js/api/module-testing.html +1 -1
- data/docs/brut-js/api/testing.AssetMetadata.html +1 -1
- data/docs/brut-js/api/testing.AssetMetadataLoader.html +1 -1
- data/docs/brut-js/api/testing.CustomElementTest.html +1 -1
- data/docs/brut-js/api/testing.DOMCreator.html +1 -1
- data/docs/brut-js/api/testing_AssetMetadata.js.html +1 -1
- data/docs/brut-js/api/testing_AssetMetadataLoader.js.html +1 -1
- data/docs/brut-js/api/testing_CustomElementTest.js.html +1 -1
- data/docs/brut-js/api/testing_DOMCreator.js.html +1 -1
- data/docs/brut-js/api/testing_index.js.html +1 -1
- data/docs/brut-js.html +4 -4
- data/docs/business-logic.html +4 -4
- data/docs/cli.html +4 -4
- data/docs/components.html +8 -8
- data/docs/configuration.html +5 -5
- data/docs/css.html +4 -4
- data/docs/custom-element-tests.html +4 -4
- data/docs/database-access.html +4 -4
- data/docs/database-schema.html +4 -4
- data/docs/deployment.html +4 -4
- data/docs/dev-environment.html +4 -4
- data/docs/dir-structure.html +4 -4
- data/docs/doc-conventions.html +4 -4
- data/docs/end-to-end-tests.html +4 -4
- data/docs/features.html +4 -4
- data/docs/flash-and-session.html +4 -4
- data/docs/form-constraints.html +11 -11
- data/docs/forms.html +7 -7
- data/docs/getting-started.html +10 -7
- data/docs/handlers.html +4 -4
- data/docs/hashmap.json +1 -1
- data/docs/hooks.html +4 -4
- data/docs/i18n.html +4 -4
- data/docs/index.html +3 -3
- data/docs/instrumentation.html +61 -6
- data/docs/javascript.html +4 -4
- data/docs/jobs.html +4 -4
- data/docs/keyword-injection.html +4 -4
- data/docs/layouts.html +4 -4
- data/docs/lsp.html +4 -4
- data/docs/markdown-examples.html +4 -4
- data/docs/middleware.html +4 -4
- data/docs/overview.html +4 -4
- data/docs/pages.html +4 -4
- data/docs/recipes/alternate-layouts.html +4 -4
- data/docs/recipes/authentication.html +7 -6
- data/docs/recipes/blank-layouts.html +4 -4
- data/docs/recipes/custom-flash.html +4 -4
- data/docs/recipes/form-errors.html +94 -0
- data/docs/recipes/indexed-forms.html +4 -4
- data/docs/recipes/migrations.html +5 -5
- data/docs/recipes/text-field-component.html +4 -4
- data/docs/roadmap.html +4 -4
- data/docs/routes.html +4 -4
- data/docs/security.html +4 -4
- data/docs/seed-data.html +4 -4
- data/docs/space-time-continuum.html +4 -4
- data/docs/tutorial.html +12 -713
- data/docs/tutorials/01-intro.html +736 -0
- data/docs/tutorials/02-dialog.html +302 -0
- data/docs/unit-tests.html +4 -4
- data/docs/why.html +4 -4
- data/lib/brut/front_end/components/input.rb +1 -0
- data/lib/brut/front_end/components/inputs/button_tag.rb +40 -0
- data/lib/brut/front_end/components/inputs/input_tag.rb +3 -1
- data/lib/brut/front_end/components/inputs/select_tag_with_options.rb +0 -1
- data/lib/brut/front_end/components/inputs/textarea_tag.rb +0 -1
- data/lib/brut/front_end/form.rb +7 -5
- data/lib/brut/front_end/forms/button.rb +21 -0
- data/lib/brut/front_end/forms/button_input_definition.rb +37 -0
- data/lib/brut/front_end/forms/input_declarations.rb +10 -0
- data/lib/brut/front_end/forms/input_definition.rb +7 -2
- data/lib/brut/instrumentation/methods.rb +153 -0
- data/lib/brut/instrumentation/open_telemetry.rb +1 -0
- data/lib/brut/instrumentation.rb +1 -0
- data/lib/brut/version.rb +1 -1
- data/mkbrut/Gemfile.lock +1 -1
- data/mkbrut/bin/publish +1 -1
- data/mkbrut/lib/mkbrut/version.rb +1 -1
- data/specs/brut/instrumentation/methods.spec.rb +399 -0
- metadata +51 -21
- data/docs/assets/chunks/@localSearchIndexroot.Dn1xGMv_.js +0 -1
- data/docs/assets/instrumentation.md.BgcaGVYH.js +0 -35
- data/docs/assets/instrumentation.md.BgcaGVYH.lean.js +0 -1
- data/docs/assets/tutorial.md.C4zR5XPG.lean.js +0 -1
- /data/docs/assets/{components.md.B543a3Lm.lean.js → components.md.f4cdTyvV.lean.js} +0 -0
- /data/docs/assets/{configuration.md.-foE_iVv.lean.js → configuration.md.Bs4-rxnS.lean.js} +0 -0
- /data/docs/assets/{form-constraints.md.DK5adCgM.lean.js → form-constraints.md.KTv5cdR4.lean.js} +0 -0
- /data/docs/assets/{recipes_authentication.md.Dzvi_g69.lean.js → recipes_authentication.md.BAISoxmN.lean.js} +0 -0
data/brut-js/package.json
CHANGED
@@ -1,8 +1,11 @@
|
|
1
1
|
{
|
2
2
|
"name": "brut-js",
|
3
|
-
"version": "0.
|
3
|
+
"version": "0.12.0",
|
4
4
|
"type": "module",
|
5
|
-
"keywords": [
|
5
|
+
"keywords": [
|
6
|
+
"WebComponents",
|
7
|
+
"Custom Elements"
|
8
|
+
],
|
6
9
|
"bugs": {
|
7
10
|
"url": "https://github.com/thirdtank/brut-js/issues",
|
8
11
|
"email": "davec@naildrivin5.com"
|
@@ -25,7 +28,7 @@
|
|
25
28
|
"bundle": "esbuild --sourcemap --bundle src/appForTestingOnly.js --outfile=specs/public/js/bundle.js"
|
26
29
|
},
|
27
30
|
"devDependencies": {
|
28
|
-
"esbuild": "^0.24.
|
31
|
+
"esbuild": "^0.24.2",
|
29
32
|
"jsdom": "^25.0.1",
|
30
33
|
"mocha": "^10.8.2"
|
31
34
|
}
|
@@ -6,7 +6,7 @@ describe("<brut-ajax-submit>", () => {
|
|
6
6
|
<input required type="text" name="some-text">
|
7
7
|
<input required type="number" name="some-number">
|
8
8
|
<brut-ajax-submit submitted-lifetime="10">
|
9
|
-
<button>Submit</button>
|
9
|
+
<button name="button-name" value="button-value">Submit</button>
|
10
10
|
</brut-ajax-submit>
|
11
11
|
</form>
|
12
12
|
`).onFetch( "/foo", [
|
@@ -43,12 +43,13 @@ describe("<brut-ajax-submit>", () => {
|
|
43
43
|
assert.equal(detailReceived.body.innerHTML,"<div>some html</div>")
|
44
44
|
assert(element.getAttribute("requesting") == null)
|
45
45
|
assert(element.getAttribute("submitted") != null)
|
46
|
-
waitForSetTimeout(11).then( () => {
|
46
|
+
return waitForSetTimeout(11).then( () => {
|
47
47
|
assert(element.getAttribute("submitted") == null)
|
48
48
|
return readRequestBodyIntoString(fetchRequests[0]).then( (string) => {
|
49
49
|
const params = new URLSearchParams(string)
|
50
50
|
assert.equal(params.get("some-text"),"Some Text")
|
51
51
|
assert.equal(params.get("some-number"),"11")
|
52
|
+
assert.equal(params.get("button-name"),"button-value")
|
52
53
|
})
|
53
54
|
})
|
54
55
|
|
@@ -79,7 +80,7 @@ describe("<brut-ajax-submit>", () => {
|
|
79
80
|
<input required type="text" name="some-text">
|
80
81
|
<input required type="number" name="some-number">
|
81
82
|
<brut-ajax-submit submitted-lifetime="10">
|
82
|
-
<button>Submit</button>
|
83
|
+
<button name="button-name" value="button-value">Submit</button>
|
83
84
|
</brut-ajax-submit>
|
84
85
|
</form>
|
85
86
|
`).onFetch( "/foo", [
|
@@ -114,6 +115,7 @@ describe("<brut-ajax-submit>", () => {
|
|
114
115
|
const params = new URLSearchParams(string)
|
115
116
|
assert.equal(params.get("some-text"),"Some Text")
|
116
117
|
assert.equal(params.get("some-number"),"11")
|
118
|
+
assert.equal(params.get("button-name"),"button-value")
|
117
119
|
})
|
118
120
|
})
|
119
121
|
})
|
@@ -123,7 +125,7 @@ describe("<brut-ajax-submit>", () => {
|
|
123
125
|
<input required type="text" name="some-text">
|
124
126
|
<input required type="number" name="some-number">
|
125
127
|
<brut-ajax-submit submitted-lifetime="10" max-retry-attempts=2>
|
126
|
-
<button>Submit</button>
|
128
|
+
<button name="button-name" value="button-value">Submit</button>
|
127
129
|
</brut-ajax-submit>
|
128
130
|
</form>
|
129
131
|
`).onFetch( "/foo", [
|
@@ -183,7 +185,7 @@ describe("<brut-ajax-submit>", () => {
|
|
183
185
|
</brut-cv-messages>
|
184
186
|
|
185
187
|
<brut-ajax-submit submitted-lifetime="10">
|
186
|
-
<button>Submit</button>
|
188
|
+
<button name="button-name" value="button-value">Submit</button>
|
187
189
|
</brut-ajax-submit>
|
188
190
|
</form>
|
189
191
|
`).onFetch( "/foo", [
|
@@ -283,7 +285,7 @@ Error that should be ignored
|
|
283
285
|
</brut-cv-messages>
|
284
286
|
|
285
287
|
<brut-ajax-submit submitted-lifetime="10" no-server-side-error-parsing>
|
286
|
-
<button>Submit</button>
|
288
|
+
<button name="button-name" value="button-value">Submit</button>
|
287
289
|
</brut-ajax-submit>
|
288
290
|
</form>
|
289
291
|
`).onFetch( "/foo", [
|
@@ -346,4 +348,58 @@ Error that should be ignored
|
|
346
348
|
})
|
347
349
|
})
|
348
350
|
})
|
351
|
+
withHTML(`
|
352
|
+
<form action="http://example.net/foo" method="POST">
|
353
|
+
<input required type="text" name="some-text">
|
354
|
+
<input required type="number" name="some-number">
|
355
|
+
<brut-ajax-submit submitted-lifetime="10">
|
356
|
+
<input type="submit" name="button-name" value="button-value">
|
357
|
+
</brut-ajax-submit>
|
358
|
+
</form>
|
359
|
+
`).onFetch( "/foo", [
|
360
|
+
{ then: { status: 200, text: "<div>some html</div>" }},
|
361
|
+
]
|
362
|
+
).test("submits the form, when wrapped around an input type=submi, setting various attributes during the lifecycle",
|
363
|
+
({document,assert,fetchRequests,waitForSetTimeout,readRequestBodyIntoString}) => {
|
364
|
+
|
365
|
+
const element = document.querySelector("brut-ajax-submit")
|
366
|
+
const button = element.querySelector("input[type=submit]")
|
367
|
+
const text = document.querySelector("input[name=some-text]")
|
368
|
+
const number = document.querySelector("input[name=some-number]")
|
369
|
+
|
370
|
+
let okReceived = false
|
371
|
+
let detailReceived = null
|
372
|
+
element.addEventListener("brut:submitok", (event) => {
|
373
|
+
okReceived = true
|
374
|
+
detailReceived = event.detail
|
375
|
+
})
|
376
|
+
|
377
|
+
text.value = "Some Text"
|
378
|
+
number.value = "11"
|
379
|
+
|
380
|
+
button.click()
|
381
|
+
assert(element.getAttribute("requesting") != null)
|
382
|
+
|
383
|
+
const promises = fetchRequests.
|
384
|
+
filter( (fetchRequest) => fetchRequest.promiseReturned ).
|
385
|
+
map( (fetchRequest) => fetchRequest.promiseReturned )
|
386
|
+
|
387
|
+
return Promise.all(promises).then( () => {
|
388
|
+
assert(okReceived)
|
389
|
+
assert(detailReceived)
|
390
|
+
assert.equal(detailReceived.body.innerHTML,"<div>some html</div>")
|
391
|
+
assert(element.getAttribute("requesting") == null)
|
392
|
+
assert(element.getAttribute("submitted") != null)
|
393
|
+
return waitForSetTimeout(11).then( () => {
|
394
|
+
assert(element.getAttribute("submitted") == null)
|
395
|
+
return readRequestBodyIntoString(fetchRequests[0]).then( (string) => {
|
396
|
+
const params = new URLSearchParams(string)
|
397
|
+
assert.equal(params.get("some-text"),"Some Text")
|
398
|
+
assert.equal(params.get("some-number"),"11")
|
399
|
+
assert.equal(params.get("button-name"),"button-value")
|
400
|
+
})
|
401
|
+
})
|
402
|
+
|
403
|
+
})
|
404
|
+
})
|
349
405
|
})
|
data/brut-js/src/AjaxSubmit.js
CHANGED
@@ -10,6 +10,8 @@ import ConstraintViolationMessage from "./ConstraintViolationMessage"
|
|
10
10
|
* 1. When the button is clicked, the form's validity is checked. If it's not valid, nothing happens.
|
11
11
|
* 2. If the form is valid, this element will be given the `requesting` attribute.
|
12
12
|
* 3. The request will be initiated, set to abort after `request-timeout` ms (see below).
|
13
|
+
* The data submitted will be the contents of `new FormData(form)`, along with the
|
14
|
+
* name/value of the button that was clicked, if it has a name and value.
|
13
15
|
* 4. If the request returns OK:
|
14
16
|
* - `requesting` will be removed and `submitted` will be added.
|
15
17
|
* - `submitted` will be removed after `submitted-lifetime` ms.
|
@@ -67,13 +69,21 @@ import ConstraintViolationMessage from "./ConstraintViolationMessage"
|
|
67
69
|
*
|
68
70
|
* @example
|
69
71
|
* <form action="/widgets" method="post">
|
70
|
-
* <input type=text name=name>
|
72
|
+
* <input type="text" name="name">
|
71
73
|
*
|
72
74
|
* <brut-ajax-submit>
|
73
|
-
* <button>Save</button>
|
75
|
+
* <button name="button" value="save">Save</button>
|
76
|
+
* </brut-ajax-submit>
|
77
|
+
* <brut-ajax-submit>
|
78
|
+
* <button name="button" value="analyze">Analyze</button>
|
74
79
|
* </brut-ajax-submit>
|
75
80
|
* </form>
|
76
81
|
*
|
82
|
+
* <!-- When "Save" is clicked, "name" will have the value from the text field,
|
83
|
+
* and "button" will have the value "save".
|
84
|
+
* When "Analyze" is clicked, "name" will have the value from the text
|
85
|
+
* field, and "button" will have the value "analyze". -->
|
86
|
+
*
|
77
87
|
* @customelement brut-ajax-submit
|
78
88
|
*/
|
79
89
|
class AjaxSubmit extends BaseCustomElement {
|
@@ -179,17 +189,20 @@ class AjaxSubmit extends BaseCustomElement {
|
|
179
189
|
if (submitter == this.#button()) {
|
180
190
|
event.preventDefault()
|
181
191
|
const now = Date.now()
|
182
|
-
this.#submitForm(event.target, now, 0)
|
192
|
+
this.#submitForm(event.target, event.submitter, now, 0)
|
183
193
|
}
|
184
194
|
}
|
185
195
|
|
186
|
-
#submitForm(form, firstSubmittedAt, numAttempts) {
|
196
|
+
#submitForm(form, submitter, firstSubmittedAt, numAttempts) {
|
187
197
|
|
188
198
|
const headers = new Headers()
|
189
199
|
headers.append("X-Requested-With","XMLHttpRequest")
|
190
200
|
headers.append("Content-Type","application/x-www-form-urlencoded")
|
191
201
|
|
192
202
|
const formData = new FormData(form)
|
203
|
+
if (submitter && submitter.name) {
|
204
|
+
formData.append(submitter.name, submitter.value)
|
205
|
+
}
|
193
206
|
const urlSearchParams = new URLSearchParams(formData)
|
194
207
|
|
195
208
|
const timeoutSignal = AbortSignal.timeout(this.#requestTimeout)
|
@@ -251,7 +264,7 @@ class AjaxSubmit extends BaseCustomElement {
|
|
251
264
|
}
|
252
265
|
if (retry) {
|
253
266
|
this.#requestErrorLogger("Trying again (attempt %d)",numAttempts +1)
|
254
|
-
setTimeout( () => this.#submitForm(form, firstSubmittedAt, numAttempts + 1), numAttempts * 10)
|
267
|
+
setTimeout( () => this.#submitForm(form, submitter, firstSubmittedAt, numAttempts + 1), numAttempts * 10)
|
255
268
|
}
|
256
269
|
else if (resubmit) {
|
257
270
|
this.#requestErrorLogger("'retry' was marked false, but resubmit is 'true', so submitting through browser")
|
@@ -265,7 +278,14 @@ class AjaxSubmit extends BaseCustomElement {
|
|
265
278
|
})
|
266
279
|
}
|
267
280
|
|
268
|
-
#button = () => {
|
281
|
+
#button = () => {
|
282
|
+
let button = this.querySelector("button")
|
283
|
+
if (!button) {
|
284
|
+
button = this.querySelector("input[type='submit']")
|
285
|
+
}
|
286
|
+
return button
|
287
|
+
}
|
288
|
+
|
269
289
|
|
270
290
|
#submitFormThroughBrowser(form) {
|
271
291
|
form.removeEventListener("submit",this.#formSubmitted)
|
data/brutrb.com/forms.md
CHANGED
@@ -83,6 +83,7 @@ for use in a radio button group. |
|
|
83
83
|
|`Brut::FrontEnd::Components::SelectTagWithOptions` | Creates a `<select>` tag with
|
84
84
|
`<option>` tags inside. |
|
85
85
|
|`Brut::FrontEnd::Components::TextareaTag` | Creates a `<textarea>` tag. |
|
86
|
+
|`Brut::FrontEnd::Components::ButtonTag` | Creates a `<button>` tag to submit the form. |
|
86
87
|
|
87
88
|
All of these classes have an initializer that accepts:
|
88
89
|
|
@@ -9,6 +9,7 @@ The simplest way to use `mkbrut` is to use an existing [Docker image](https://hu
|
|
9
9
|
|
10
10
|
```
|
11
11
|
docker run \
|
12
|
+
--pull always \
|
12
13
|
-v "$PWD":"$PWD" \
|
13
14
|
-w "$PWD" \
|
14
15
|
-u $(id -u):$(id -g) \
|
@@ -33,6 +34,7 @@ For now:
|
|
33
34
|
|
34
35
|
``` [Docker-based]
|
35
36
|
docker run \
|
37
|
+
--pull always \
|
36
38
|
-v "$PWD":"$PWD" \
|
37
39
|
-w "$PWD" \
|
38
40
|
-u $(id -u):$(id -g) \
|
@@ -55,6 +57,7 @@ To create your app without the demo components:
|
|
55
57
|
|
56
58
|
``` [Docker-based]
|
57
59
|
docker run \
|
60
|
+
--pull always \
|
58
61
|
-v "$PWD":"$PWD" \
|
59
62
|
-w "$PWD" \
|
60
63
|
-u $(id -u):$(id -g) \
|
Binary file
|
Binary file
|
Binary file
|
@@ -80,7 +80,99 @@ Here is a non-exhaustive list of what Brut automatically instruments:
|
|
80
80
|
|
81
81
|
### Adding Your Own Instrumentation
|
82
82
|
|
83
|
-
You can add instrumentation in
|
83
|
+
You can add instrumentation in two main ways, both of which can be used together.
|
84
|
+
|
85
|
+
#### Instrumenting Existing Methods
|
86
|
+
|
87
|
+
Although Brut instruments the entrypoints to pages, handlers, and components, you will likely have your own set of back-end business logic that needs to be instrumented. If you aren't trying to diagnose a specific problem and just want to see your back-end class' methods show up in your instrumentation vendor's dashboard, `Brut::Instrumentation::Methods` will be the easiest way to do that.
|
88
|
+
|
89
|
+
`Brut::Instrumentation::Methods` can be included in any class, and provides three class methods, which are *mutually exclusive*:
|
90
|
+
|
91
|
+
* `instrument_all` instruments all methods, public and private.
|
92
|
+
* `instrument_public` instruments only public methods.
|
93
|
+
* `instrument` instruments one or more named methods.
|
94
|
+
|
95
|
+
`initialize` is never instrumented.
|
96
|
+
|
97
|
+
Consider this class:
|
98
|
+
|
99
|
+
```ruby
|
100
|
+
class Widget
|
101
|
+
def initialize
|
102
|
+
# ...
|
103
|
+
end
|
104
|
+
|
105
|
+
def search
|
106
|
+
# ...
|
107
|
+
end
|
108
|
+
|
109
|
+
def save
|
110
|
+
# ...
|
111
|
+
end
|
112
|
+
|
113
|
+
private
|
114
|
+
|
115
|
+
def delete_orphans
|
116
|
+
# ...
|
117
|
+
end
|
118
|
+
end
|
119
|
+
```
|
120
|
+
|
121
|
+
If we use `instrument_all`…
|
122
|
+
|
123
|
+
```ruby
|
124
|
+
class Widget
|
125
|
+
include Brut::Instrumentation::Methods
|
126
|
+
instrument_all
|
127
|
+
|
128
|
+
# ...
|
129
|
+
end
|
130
|
+
```
|
131
|
+
|
132
|
+
…`search`, `save`, and `delete_orphans` will be instrumented. If we use `instrument_public`…
|
133
|
+
|
134
|
+
```ruby
|
135
|
+
class Widget
|
136
|
+
include Brut::Instrumentation::Methods
|
137
|
+
instrument_public
|
138
|
+
|
139
|
+
# ...
|
140
|
+
end
|
141
|
+
```
|
142
|
+
|
143
|
+
…then only `search` and `save` are instrumented.
|
144
|
+
|
145
|
+
We can pick and choose by using `instrument`.
|
146
|
+
|
147
|
+
```ruby{2,7,20}
|
148
|
+
class Widget
|
149
|
+
include Brut::Instrumentation::Methods
|
150
|
+
def initialize
|
151
|
+
# ...
|
152
|
+
end
|
153
|
+
|
154
|
+
instrument def search
|
155
|
+
# ...
|
156
|
+
end
|
157
|
+
|
158
|
+
def save
|
159
|
+
# ...
|
160
|
+
end
|
161
|
+
|
162
|
+
private
|
163
|
+
|
164
|
+
def delete_orphans
|
165
|
+
# ...
|
166
|
+
end
|
167
|
+
instrument :delete_orphans
|
168
|
+
end
|
169
|
+
```
|
170
|
+
|
171
|
+
Above, `search` and `delete_orphans` are instrumented. Since `def` in Ruby returns a symbol, `instrument def search` is the same as `instrument :search`.
|
172
|
+
|
173
|
+
#### Explicit Instrumentation with Spans, Attributes, and Events
|
174
|
+
|
175
|
+
To add explicit instrumentation, you'll create one or more of the following:
|
84
176
|
|
85
177
|
* *Spans* record a block of code. They are shown as a sub-span if one is already in effect. When you
|
86
178
|
create a span, that means it will be shown in the context of the HTTP request.
|
@@ -164,6 +256,39 @@ aforementioned `InstrumentationHandler`.
|
|
164
256
|
|
165
257
|
You should then see client-side tracing information as a sub-span of your HTTP request. The information available depends on the browser, and some browsers don't send much. Also keep in mind that clock drift is real and while client-side timings are accurate, the timestamps will not be.
|
166
258
|
|
259
|
+
### Logging
|
260
|
+
|
261
|
+
Brut configures [SemanticLogger](https://logger.rocketjob.io/), but uses it sparingly. Currently, Brut performs very little logging, and no request logging. You may have noticed that your app doesn't produce a lot of output in development. Brut's assumption is that you will use an OpenTelemetry vendor to understand your app in production or the otel-desktop-viewer in developoment.
|
262
|
+
|
263
|
+
That said, since SemanticLogger is configured, you can use it at will:
|
264
|
+
|
265
|
+
```ruby
|
266
|
+
class HomePage
|
267
|
+
def page_template
|
268
|
+
SemanticLogger[self.class].debug "page being rendered"
|
269
|
+
|
270
|
+
# ...
|
271
|
+
|
272
|
+
end
|
273
|
+
end
|
274
|
+
```
|
275
|
+
|
276
|
+
The logging system is currently not very configurable, and works as follows:
|
277
|
+
|
278
|
+
* In development, log messages are written to the standard output and to `logs/development.log`
|
279
|
+
* In test, log messages are written to `logs/test.log`
|
280
|
+
* In production, log messages are written to the standard output
|
281
|
+
|
282
|
+
The default log level is "debug" for the web app at "fatal" for CLI apps. You can set `LOG_LEVEL` in the environment to change this:
|
283
|
+
|
284
|
+
* `"debug"` - Show all messages
|
285
|
+
* `"info"` - Show info and above (not debug messages)
|
286
|
+
* `"warn"` - Show warnings and above (not info, not debug)
|
287
|
+
* `"error"` - Show errors and fatals only
|
288
|
+
* `"fatal"` - Show fatals only
|
289
|
+
|
290
|
+
Most CLIs also allow `--log-level` to accept one of these strings as wel ass `--verbose` to set the log level to debug.
|
291
|
+
|
167
292
|
## Testing
|
168
293
|
|
169
294
|
Generally you don't want to test instrumentation unless it's highly complex and critical to the app's
|
@@ -182,11 +307,25 @@ specific issues.
|
|
182
307
|
> Technical Notes are for deeper understanding and debugging. While we will try to keep them up-to-date with changes to Brut's
|
183
308
|
> internals, the source code is always more correct.
|
184
309
|
|
185
|
-
_Last Updated
|
310
|
+
_Last Updated Aug 27, 2025_
|
311
|
+
|
312
|
+
OpenTelemetry is notoriously opaque and, ironically, unobservable in its own behavior. Thus, the implementation is subject to change as I figure out what actually does what.
|
186
313
|
|
314
|
+
### Web Requests
|
187
315
|
|
188
|
-
Brut
|
316
|
+
`Brut::FrontEnd::Middlewares::OpenTelemetrySpan` is configured in `Brut::Framework::MCP` as the first middleware. It sets up the outer span for all web requests. Inside each request block (the code internal to Brut that calls handlers or pages), this span's name is modified with the HTTP method and path via the `brut.otel.root_span` in the Rack environment.
|
317
|
+
|
318
|
+
### Client-Side
|
189
319
|
|
190
320
|
The client-side portion of this is highly customized. The Otel open source code for the client side is
|
191
321
|
massive and hugely complex, so Brut decided to try to produce something simple and straightforward as a
|
192
322
|
start. This can and will evolve over time.
|
323
|
+
|
324
|
+
### CLI Commands
|
325
|
+
|
326
|
+
Brut CLI commands are instrumented as well,
|
327
|
+
in `Brut::CLI::App` in `execute!`, however the trace only begins if the underlying command is going to be executed. This may change.
|
328
|
+
|
329
|
+
### Sidekiq Jobs
|
330
|
+
|
331
|
+
Although Brut currently does not provide a default Sidekiq configuration, if you set up Sidekiq and include the `opentelemetry-instrumentation-sidekiq` gem in your app's `Gemfile`, you should see instrumentation of your Sidekiq jobs. In practice, this default set up doesn't seem to work very well, so expect this to change for the better.
|