logster 2.21.0 → 3.0.1
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 +24 -0
- data/README.md +29 -14
- data/assets/javascript/main-C9t0S9He.js +1 -0
- data/assets/javascript/modules-4-12-BnbZfQQN.js +1 -0
- data/assets/javascript/vendor.js +11 -5260
- data/assets/logster-config.json +18 -0
- data/assets/manifest.json +23 -0
- data/assets/stylesheets/main-et5OPj6E.css +1 -0
- data/lib/logster/configuration.rb +1 -0
- data/lib/logster/middleware/reporter.rb +34 -3
- data/lib/logster/middleware/viewer.rb +204 -32
- data/lib/logster/rails/railtie.rb +4 -20
- data/lib/logster/redis_store.rb +0 -2
- data/lib/logster/version.rb +1 -1
- data/lib/logster.rb +1 -1
- metadata +17 -186
- data/.github/workflows/ci.yml +0 -140
- data/.gitignore +0 -3
- data/.rubocop.yml +0 -12
- data/.streerc +0 -2
- data/Gemfile +0 -10
- data/Guardfile +0 -10
- data/Rakefile +0 -24
- data/assets/javascript/.gitkeep +0 -0
- data/assets/javascript/chunk.524.b7d0b89d25457a72409c.js +0 -22
- data/assets/javascript/chunk.582.5a04ae4713e692cd335a.js +0 -22
- data/assets/javascript/chunk.747.a730c497582bb6742a6e.js +0 -579
- data/assets/javascript/chunk.761.64949d89942c829e5c1b.js +0 -1213
- data/assets/javascript/client-app.js +0 -1389
- data/assets/stylesheets/.gitkeep +0 -0
- data/assets/stylesheets/client-app.css +0 -1
- data/assets/stylesheets/vendor.css +0 -1
- data/build_client_app.sh +0 -10
- data/client-app/.editorconfig +0 -19
- data/client-app/.ember-cli +0 -9
- data/client-app/.eslintignore +0 -20
- data/client-app/.eslintrc +0 -10
- data/client-app/.gitignore +0 -23
- data/client-app/.prettierignore +0 -21
- data/client-app/.prettierrc +0 -1
- data/client-app/.template-lintrc.js +0 -10
- data/client-app/.watchmanconfig +0 -3
- data/client-app/README.md +0 -56
- data/client-app/app/app.js +0 -12
- data/client-app/app/components/actions-menu.js +0 -50
- data/client-app/app/components/back-to-site-link.js +0 -9
- data/client-app/app/components/back-trace.js +0 -153
- data/client-app/app/components/env-tab.js +0 -68
- data/client-app/app/components/message-info.js +0 -151
- data/client-app/app/components/message-row.js +0 -46
- data/client-app/app/components/page-nav.js +0 -42
- data/client-app/app/components/panel-resizer.js +0 -108
- data/client-app/app/components/patterns-list.js +0 -142
- data/client-app/app/components/tab-contents.js +0 -23
- data/client-app/app/components/tabbed-section.js +0 -45
- data/client-app/app/components/time-formatter.js +0 -36
- data/client-app/app/controllers/index.js +0 -288
- data/client-app/app/controllers/show.js +0 -39
- data/client-app/app/helpers/logster-url.js +0 -12
- data/client-app/app/helpers/or.js +0 -7
- data/client-app/app/index.html +0 -31
- data/client-app/app/initializers/app-init.js +0 -71
- data/client-app/app/lib/decorators.js +0 -13
- data/client-app/app/lib/preload.js +0 -43
- data/client-app/app/lib/utilities.js +0 -230
- data/client-app/app/models/group.js +0 -34
- data/client-app/app/models/message-collection.js +0 -303
- data/client-app/app/models/message.js +0 -128
- data/client-app/app/models/pattern-item.js +0 -32
- data/client-app/app/resolver.js +0 -3
- data/client-app/app/router.js +0 -13
- data/client-app/app/routes/index.js +0 -64
- data/client-app/app/routes/settings.js +0 -32
- data/client-app/app/routes/show.js +0 -16
- data/client-app/app/services/events.js +0 -4
- data/client-app/app/styles/app.css +0 -898
- data/client-app/app/templates/application.hbs +0 -1
- data/client-app/app/templates/components/actions-menu.hbs +0 -31
- data/client-app/app/templates/components/back-to-site-link.hbs +0 -8
- data/client-app/app/templates/components/back-trace.hbs +0 -15
- data/client-app/app/templates/components/env-tab.hbs +0 -10
- data/client-app/app/templates/components/message-info.hbs +0 -86
- data/client-app/app/templates/components/message-row.hbs +0 -32
- data/client-app/app/templates/components/page-nav.hbs +0 -37
- data/client-app/app/templates/components/panel-resizer.hbs +0 -3
- data/client-app/app/templates/components/patterns-list.hbs +0 -78
- data/client-app/app/templates/components/tabbed-section.hbs +0 -18
- data/client-app/app/templates/components/time-formatter.hbs +0 -1
- data/client-app/app/templates/index.hbs +0 -192
- data/client-app/app/templates/settings.hbs +0 -47
- data/client-app/app/templates/show.hbs +0 -13
- data/client-app/config/ember-cli-update.json +0 -18
- data/client-app/config/environment.js +0 -51
- data/client-app/config/icons.js +0 -28
- data/client-app/config/optional-features.json +0 -6
- data/client-app/config/targets.js +0 -26
- data/client-app/ember-cli-build.js +0 -34
- data/client-app/package.json +0 -76
- data/client-app/preload-json-manager.rb +0 -35
- data/client-app/public/assets/images/icon_144x144.png +0 -0
- data/client-app/public/assets/images/icon_64x64.png +0 -0
- data/client-app/testem.js +0 -23
- data/client-app/tests/index.html +0 -41
- data/client-app/tests/integration/components/back-to-site-link-test.js +0 -20
- data/client-app/tests/integration/components/back-trace-test.js +0 -138
- data/client-app/tests/integration/components/env-tab-test.js +0 -220
- data/client-app/tests/integration/components/message-info-test.js +0 -122
- data/client-app/tests/integration/components/patterns-list-test.js +0 -76
- data/client-app/tests/test-helper.js +0 -12
- data/client-app/tests/unit/controllers/index-test.js +0 -77
- data/client-app/tests/unit/controllers/show-test.js +0 -12
- data/client-app/tests/unit/routes/index-test.js +0 -11
- data/client-app/tests/unit/routes/show-test.js +0 -11
- data/client-app/yarn.lock +0 -9905
- data/gemfiles/rails_6.1.gemfile +0 -12
- data/gemfiles/rails_7.0.gemfile +0 -12
- data/gemfiles/rails_7.1.gemfile +0 -9
- data/gemfiles/rails_7.2.gemfile +0 -9
- data/logster.gemspec +0 -43
- data/test/dummy/config/application.rb +0 -31
- data/test/dummy/config/boot.rb +0 -6
- data/test/dummy/config/environment.rb +0 -6
- data/test/examples/test_sidekiq_reporter_example.rb +0 -47
- data/test/fake_data/Gemfile +0 -6
- data/test/fake_data/generate.rb +0 -23
- data/test/logster/middleware/test_reporter.rb +0 -84
- data/test/logster/middleware/test_viewer.rb +0 -495
- data/test/logster/test_base_store.rb +0 -224
- data/test/logster/test_cache.rb +0 -39
- data/test/logster/test_defer_logger.rb +0 -36
- data/test/logster/test_group.rb +0 -92
- data/test/logster/test_ignore_pattern.rb +0 -42
- data/test/logster/test_logger.rb +0 -177
- data/test/logster/test_message.rb +0 -269
- data/test/logster/test_pattern.rb +0 -144
- data/test/logster/test_railtie.rb +0 -21
- data/test/logster/test_redis_rate_limiter.rb +0 -246
- data/test/logster/test_redis_store.rb +0 -1165
- data/test/test_helper.rb +0 -66
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"modulePrefix": "client-app",
|
|
3
|
+
"environment": "production",
|
|
4
|
+
"rootURL": "/logs",
|
|
5
|
+
"locationType": "history",
|
|
6
|
+
"EmberENV": {
|
|
7
|
+
"FEATURES": {},
|
|
8
|
+
"EXTEND_PROTOTYPES": {
|
|
9
|
+
"Date": false
|
|
10
|
+
},
|
|
11
|
+
"_APPLICATION_TEMPLATE_WRAPPER": false,
|
|
12
|
+
"_DEFAULT_ASYNC_OBSERVERS": true,
|
|
13
|
+
"_JQUERY_INTEGRATION": false,
|
|
14
|
+
"_TEMPLATE_ONLY_GLIMMER_COMPONENTS": true,
|
|
15
|
+
"_USE_EMBER_MODULES": true
|
|
16
|
+
},
|
|
17
|
+
"APP": {}
|
|
18
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"index.html": {
|
|
3
|
+
"file": "assets/main-C9t0S9He.js",
|
|
4
|
+
"name": "main",
|
|
5
|
+
"src": "index.html",
|
|
6
|
+
"isEntry": true,
|
|
7
|
+
"dynamicImports": [
|
|
8
|
+
"node_modules/.pnpm/@embroider+legacy-inspector-support@0.1.3/node_modules/@embroider/legacy-inspector-support/src/modules-4-12.js"
|
|
9
|
+
],
|
|
10
|
+
"css": [
|
|
11
|
+
"assets/main-et5OPj6E.css"
|
|
12
|
+
]
|
|
13
|
+
},
|
|
14
|
+
"node_modules/.pnpm/@embroider+legacy-inspector-support@0.1.3/node_modules/@embroider/legacy-inspector-support/src/modules-4-12.js": {
|
|
15
|
+
"file": "assets/modules-4-12-BnbZfQQN.js",
|
|
16
|
+
"name": "modules-4-12",
|
|
17
|
+
"src": "node_modules/.pnpm/@embroider+legacy-inspector-support@0.1.3/node_modules/@embroider/legacy-inspector-support/src/modules-4-12.js",
|
|
18
|
+
"isDynamicEntry": true,
|
|
19
|
+
"imports": [
|
|
20
|
+
"index.html"
|
|
21
|
+
]
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
:root,:host{--fa-font-solid:normal 900 1em/1 "Font Awesome 7 Free";--fa-font-regular:normal 400 1em/1 "Font Awesome 7 Free";--fa-font-light:normal 300 1em/1 "Font Awesome 7 Pro";--fa-font-thin:normal 100 1em/1 "Font Awesome 7 Pro";--fa-font-duotone:normal 900 1em/1 "Font Awesome 7 Duotone";--fa-font-duotone-regular:normal 400 1em/1 "Font Awesome 7 Duotone";--fa-font-duotone-light:normal 300 1em/1 "Font Awesome 7 Duotone";--fa-font-duotone-thin:normal 100 1em/1 "Font Awesome 7 Duotone";--fa-font-brands:normal 400 1em/1 "Font Awesome 7 Brands";--fa-font-sharp-solid:normal 900 1em/1 "Font Awesome 7 Sharp";--fa-font-sharp-regular:normal 400 1em/1 "Font Awesome 7 Sharp";--fa-font-sharp-light:normal 300 1em/1 "Font Awesome 7 Sharp";--fa-font-sharp-thin:normal 100 1em/1 "Font Awesome 7 Sharp";--fa-font-sharp-duotone-solid:normal 900 1em/1 "Font Awesome 7 Sharp Duotone";--fa-font-sharp-duotone-regular:normal 400 1em/1 "Font Awesome 7 Sharp Duotone";--fa-font-sharp-duotone-light:normal 300 1em/1 "Font Awesome 7 Sharp Duotone";--fa-font-sharp-duotone-thin:normal 100 1em/1 "Font Awesome 7 Sharp Duotone";--fa-font-slab-regular:normal 400 1em/1 "Font Awesome 7 Slab";--fa-font-slab-press-regular:normal 400 1em/1 "Font Awesome 7 Slab Press";--fa-font-slab-duo-regular:normal 400 1em/1 "Font Awesome 7 Slab Duo";--fa-font-slab-press-duo-regular:normal 400 1em/1 "Font Awesome 7 Slab Press Duo";--fa-font-pixel-regular:normal 400 1em/1 "Font Awesome 7 Pixel";--fa-font-mosaic-solid:normal 900 1em/1 "Font Awesome 7 Mosaic";--fa-font-vellum-solid:normal 900 1em/1 "Font Awesome 7 Vellum";--fa-font-whiteboard-semibold:normal 600 1em/1 "Font Awesome 7 Whiteboard";--fa-font-thumbprint-light:normal 300 1em/1 "Font Awesome 7 Thumbprint";--fa-font-notdog-solid:normal 900 1em/1 "Font Awesome 7 Notdog";--fa-font-notdog-duo-solid:normal 900 1em/1 "Font Awesome 7 Notdog Duo";--fa-font-etch-solid:normal 900 1em/1 "Font Awesome 7 Etch";--fa-font-graphite-thin:normal 100 1em/1 "Font Awesome 7 Graphite";--fa-font-jelly-regular:normal 400 1em/1 "Font Awesome 7 Jelly";--fa-font-jelly-fill-regular:normal 400 1em/1 "Font Awesome 7 Jelly Fill";--fa-font-jelly-duo-regular:normal 400 1em/1 "Font Awesome 7 Jelly Duo";--fa-font-chisel-regular:normal 400 1em/1 "Font Awesome 7 Chisel";--fa-font-utility-semibold:normal 600 1em/1 "Font Awesome 7 Utility";--fa-font-utility-duo-semibold:normal 600 1em/1 "Font Awesome 7 Utility Duo";--fa-font-utility-fill-semibold:normal 600 1em/1 "Font Awesome 7 Utility Fill"}.svg-inline--fa{box-sizing:content-box;display:var(--fa-display,inline-block);vertical-align:-.125em;height:1em;width:var(--fa-width,1.25em);overflow:visible}.svg-inline--fa.fa-2xs{vertical-align:.1em}.svg-inline--fa.fa-xs{vertical-align:0}.svg-inline--fa.fa-sm{vertical-align:-.0714286em}.svg-inline--fa.fa-lg{vertical-align:-.2em}.svg-inline--fa.fa-xl{vertical-align:-.25em}.svg-inline--fa.fa-2xl{vertical-align:-.3125em}.svg-inline--fa.fa-pull-left,.svg-inline--fa .fa-pull-start{float:inline-start;margin-inline-end:var(--fa-pull-margin,.3em)}.svg-inline--fa.fa-pull-right,.svg-inline--fa .fa-pull-end{float:inline-end;margin-inline-start:var(--fa-pull-margin,.3em)}.svg-inline--fa.fa-li{width:var(--fa-li-width,2em);inset-block-start:.25em;inset-inline-start:calc(-1 * var(--fa-li-width,2em))}.fa-layers-counter,.fa-layers-text{text-align:center;display:inline-block;position:absolute}.fa-layers{text-align:center;vertical-align:-.125em;height:1em;width:var(--fa-width,1.25em);display:inline-block;position:relative}.fa-layers .svg-inline--fa{transform-origin:50%;margin:auto;position:absolute;inset:0}.fa-layers-text{transform-origin:50%;top:50%;left:50%;transform:translate(-50%,-50%)}.fa-layers-counter{background-color:var(--fa-counter-background-color,#ff253a);border-radius:var(--fa-counter-border-radius,1em);box-sizing:border-box;color:var(--fa-inverse,#fff);line-height:var(--fa-counter-line-height,1);max-width:var(--fa-counter-max-width,5em);min-width:var(--fa-counter-min-width,1.5em);padding:var(--fa-counter-padding,.25em .5em);right:var(--fa-right,0);text-overflow:ellipsis;top:var(--fa-top,0);transform:scale(var(--fa-counter-scale,.25));transform-origin:100% 0;overflow:hidden}.fa-layers-bottom-right{bottom:var(--fa-bottom,0);right:var(--fa-right,0);transform:scale(var(--fa-layers-scale,.25));transform-origin:100% 100%;top:auto}.fa-layers-bottom-left{bottom:var(--fa-bottom,0);left:var(--fa-left,0);transform:scale(var(--fa-layers-scale,.25));transform-origin:0 100%;top:auto;right:auto}.fa-layers-top-right{top:var(--fa-top,0);right:var(--fa-right,0);transform:scale(var(--fa-layers-scale,.25));transform-origin:100% 0}.fa-layers-top-left{left:var(--fa-left,0);right:auto;top:var(--fa-top,0);transform:scale(var(--fa-layers-scale,.25));transform-origin:0 0}.fa-1x{font-size:1em}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-6x{font-size:6em}.fa-7x{font-size:7em}.fa-8x{font-size:8em}.fa-9x{font-size:9em}.fa-10x{font-size:10em}.fa-2xs{vertical-align:.225em;font-size:.625em;line-height:.1em}.fa-xs{vertical-align:.125em;font-size:.75em;line-height:.0833333em}.fa-sm{vertical-align:.0535715em;font-size:.875em;line-height:.0714286em}.fa-lg{vertical-align:-.075em;font-size:1.25em;line-height:.05em}.fa-xl{vertical-align:-.125em;font-size:1.5em;line-height:.0416667em}.fa-2xl{vertical-align:-.1875em;font-size:2em;line-height:.03125em}.fa-width-auto{--fa-width:auto}.fa-fw,.fa-width-fixed{--fa-width:1.25em}.fa-canvas-square{margin-block-end:-.125em;padding-block:.125em}.fa-canvas-roomy{box-sizing:content-box;margin-block-end:-.25em;padding-block:.25em;padding-inline:.125em}.fa-ul{margin-inline-start:var(--fa-li-margin,2.5em);padding-inline-start:0;list-style-type:none}.fa-ul>li{position:relative}.fa-li{text-align:center;width:var(--fa-li-width,2em);line-height:inherit;position:absolute;inset-inline-start:calc(-1 * var(--fa-li-width,2em))}.fa-border{border-color:var(--fa-border-color,#eee);border-radius:var(--fa-border-radius,.1em);border-style:var(--fa-border-style,solid);border-width:var(--fa-border-width,.0625em);box-sizing:var(--fa-border-box-sizing,content-box);padding:var(--fa-border-padding,.1875em .25em)}.fa-pull-left,.fa-pull-start{float:inline-start;margin-inline-end:var(--fa-pull-margin,.3em)}.fa-pull-right,.fa-pull-end{float:inline-end;margin-inline-start:var(--fa-pull-margin,.3em)}.fa-beat{animation-name:fa-beat;animation-delay:var(--fa-animation-delay,0s);animation-direction:var(--fa-animation-direction,normal);animation-duration:var(--fa-animation-duration,1s);animation-iteration-count:var(--fa-animation-iteration-count,infinite);animation-timing-function:var(--fa-animation-timing,ease-in-out)}.fa-bounce{animation-name:fa-bounce;animation-delay:var(--fa-animation-delay,0s);animation-direction:var(--fa-animation-direction,normal);animation-duration:var(--fa-animation-duration,1s);animation-iteration-count:var(--fa-animation-iteration-count,infinite);animation-timing-function:var(--fa-animation-timing,cubic-bezier(.28, .84, .42, 1))}.fa-fade{animation-name:fa-fade;animation-delay:var(--fa-animation-delay,0s);animation-direction:var(--fa-animation-direction,normal);animation-duration:var(--fa-animation-duration,1s);animation-iteration-count:var(--fa-animation-iteration-count,infinite);animation-timing-function:var(--fa-animation-timing,ease-in-out)}.fa-beat-fade{animation-name:fa-beat-fade;animation-delay:var(--fa-animation-delay,0s);animation-direction:var(--fa-animation-direction,normal);animation-duration:var(--fa-animation-duration,1s);animation-iteration-count:var(--fa-animation-iteration-count,infinite);animation-timing-function:var(--fa-animation-timing,ease-in-out)}.fa-flip{animation-name:fa-flip;animation-delay:var(--fa-animation-delay,0s);animation-direction:var(--fa-animation-direction,normal);animation-duration:var(--fa-animation-duration,1.5s);animation-iteration-count:var(--fa-animation-iteration-count,infinite);animation-timing-function:var(--fa-animation-timing,ease-in-out)}.fa-flip-360{animation-name:fa-flip-360;animation-delay:var(--fa-animation-delay,0s);animation-direction:var(--fa-animation-direction,normal);animation-duration:var(--fa-animation-duration,1s);animation-iteration-count:var(--fa-animation-iteration-count,infinite);animation-timing-function:var(--fa-animation-timing,ease-in-out)}.fa-shake{animation-name:fa-shake;animation-delay:var(--fa-animation-delay,0s);animation-direction:var(--fa-animation-direction,normal);animation-duration:var(--fa-animation-duration,.75s);animation-iteration-count:var(--fa-animation-iteration-count,infinite);animation-timing-function:var(--fa-animation-timing,ease-in-out)}.fa-spin{animation-name:fa-spin;animation-delay:var(--fa-animation-delay,0s);animation-direction:var(--fa-animation-direction,normal);animation-duration:var(--fa-animation-duration,2s);animation-iteration-count:var(--fa-animation-iteration-count,infinite);animation-timing-function:var(--fa-animation-timing,linear)}.fa-spin-reverse{--fa-animation-direction:reverse}.fa-pulse,.fa-spin-pulse{animation-name:fa-spin;animation-direction:var(--fa-animation-direction,normal);animation-duration:var(--fa-animation-duration,1s);animation-iteration-count:var(--fa-animation-iteration-count,infinite);animation-timing-function:var(--fa-animation-timing,steps(8))}.fa-spin-snap{animation-name:fa-spin-snap;animation-delay:var(--fa-animation-delay,0s);animation-direction:var(--fa-animation-direction,normal);animation-duration:var(--fa-animation-duration,3s);animation-iteration-count:var(--fa-animation-iteration-count,infinite);animation-timing-function:var(--fa-animation-timing,linear)}.fa-spin-snap-4{animation-name:fa-spin-snap-4;animation-delay:var(--fa-animation-delay,0s);animation-direction:var(--fa-animation-direction,normal);animation-duration:var(--fa-animation-duration,2.4s);animation-iteration-count:var(--fa-animation-iteration-count,infinite);animation-timing-function:var(--fa-animation-timing,linear)}.fa-spin-snap-8{animation-name:fa-spin-snap-8;animation-delay:var(--fa-animation-delay,0s);animation-direction:var(--fa-animation-direction,normal);animation-duration:var(--fa-animation-duration,4s);animation-iteration-count:var(--fa-animation-iteration-count,infinite);animation-timing-function:var(--fa-animation-timing,linear)}.fa-buzz{animation-name:fa-buzz;animation-delay:var(--fa-animation-delay,0s);animation-direction:var(--fa-animation-direction,normal);animation-duration:var(--fa-animation-duration,.6s);animation-iteration-count:var(--fa-animation-iteration-count,infinite);animation-timing-function:var(--fa-animation-timing,linear)}.fa-wag{animation-name:fa-wag;animation-delay:var(--fa-animation-delay,0s);animation-direction:var(--fa-animation-direction,normal);animation-duration:var(--fa-animation-duration,.9s);animation-iteration-count:var(--fa-animation-iteration-count,infinite);animation-timing-function:var(--fa-animation-timing,ease-out);transform-origin:bottom}.fa-float{animation-name:fa-float;animation-delay:var(--fa-animation-delay,0s);animation-direction:var(--fa-animation-direction,normal);animation-duration:var(--fa-animation-duration,3s);animation-iteration-count:var(--fa-animation-iteration-count,infinite);animation-timing-function:var(--fa-animation-timing,ease-in-out);will-change:transform}.fa-swing{animation-name:fa-swing;animation-delay:var(--fa-animation-delay,0s);animation-direction:var(--fa-animation-direction,normal);animation-duration:var(--fa-animation-duration,1.2s);animation-iteration-count:var(--fa-animation-iteration-count,infinite);animation-timing-function:var(--fa-animation-timing,ease-out);transform-origin:top}.fa-jello{animation-name:fa-jello;animation-delay:var(--fa-animation-delay,0s);animation-direction:var(--fa-animation-direction,normal);animation-duration:var(--fa-animation-duration,.9s);animation-iteration-count:var(--fa-animation-iteration-count,infinite);animation-timing-function:var(--fa-animation-timing,ease-out)}@media (prefers-reduced-motion:reduce){.fa-beat,.fa-bounce,.fa-fade,.fa-beat-fade,.fa-flip,.fa-flip-360,.fa-pulse,.fa-shake,.fa-spin,.fa-spin-pulse,.fa-buzz,.fa-float,.fa-jello,.fa-spin-snap,.fa-spin-snap-4,.fa-spin-snap-8,.fa-swing,.fa-wag{transition:none!important;animation:none!important}}@keyframes fa-beat{0%{transform:scale(1)}25%{transform:scale(calc(1.25 * var(--fa-beat-scale,1.25)))}45%{transform:scale(calc(1.22 * var(--fa-beat-scale,1.22)))}65%{transform:scale(calc(1.25 * var(--fa-beat-scale,1.25)))}90%{transform:scale(1)}}@keyframes fa-bounce{0%{animation-timing-function:var(--fa-animation-timing);transform:scale(1)translateY(0)}14%{transform:scale(var(--fa-bounce-start-scale-x,1.06), var(--fa-bounce-start-scale-y,.94)) translateY(var(--fa-bounce-anticipation,3px));animation-timing-function:cubic-bezier(.33,0,.66,.33)}32%{transform:scale(var(--fa-bounce-jump-scale-x,.94), var(--fa-bounce-jump-scale-y,1.12)) translateY(calc(-1 * var(--fa-bounce-height,.5em)));animation-timing-function:cubic-bezier(.33,.66,.66,1)}52%{transform:scale(1, 1) translateY(calc(-1 * var(--fa-bounce-height,.5em) * 1.1));animation-timing-function:cubic-bezier(.5,0,1,.5)}70%{transform:scale(var(--fa-bounce-land-scale-x,1.06), var(--fa-bounce-land-scale-y,.92)) translateY(0);animation-timing-function:cubic-bezier(.33,.33,.66,1)}85%{transform:scale(.98, 1.04) translateY(calc(-2px * var(--fa-bounce-rebound,1)));animation-timing-function:cubic-bezier(.33,0,.66,1)}to{transform:scale(1)translateY(0)}}@keyframes fa-fade{0%{opacity:1;animation-timing-function:cubic-bezier(.2,0,.4,1);transform:scale(1)}40%{opacity:var(--fa-fade-opacity,.4);animation-timing-function:cubic-bezier(.4,0,.6,1);transform:scale(.98)}to{opacity:1;transform:scale(1)}}@keyframes fa-beat-fade{0%{opacity:var(--fa-beat-fade-opacity,.4);animation-timing-function:cubic-bezier(.2,0,.4,1);transform:scale(1)}25%{opacity:calc(var(--fa-beat-fade-opacity,.4) + .4);transform:scale(var(--fa-beat-fade-scale,1.28));animation-timing-function:cubic-bezier(.4,0,.6,1)}45%{opacity:1;transform:scale(var(--fa-beat-fade-scale,1.25));animation-timing-function:cubic-bezier(.4,0,.2,1)}65%{opacity:calc(var(--fa-beat-fade-opacity,.4) + .4);transform:scale(var(--fa-beat-fade-scale,1.28));animation-timing-function:cubic-bezier(.4,0,.6,1)}to{opacity:var(--fa-beat-fade-opacity,.4);transform:scale(1)}}@keyframes fa-flip{0%{transform:perspective(2em) scale(1) rotate3d(var(--fa-flip-x,0), var(--fa-flip-y,1), var(--fa-flip-z,0), 0deg);animation-timing-function:cubic-bezier(.2,0,.4,1)}8%{transform:perspective(2em) scale(var(--fa-flip-anticipation-scale,.95)) rotate3d(var(--fa-flip-x,0), var(--fa-flip-y,1), var(--fa-flip-z,0), 0deg);animation-timing-function:cubic-bezier(.33,0,.66,.33)}35%{transform:perspective(2em) scale(1) rotate3d(var(--fa-flip-x,0), var(--fa-flip-y,1), var(--fa-flip-z,0), calc(var(--fa-flip-angle,-360deg) * .6));animation-timing-function:linear}65%{transform:perspective(2em) scale(1) rotate3d(var(--fa-flip-x,0), var(--fa-flip-y,1), var(--fa-flip-z,0), calc(var(--fa-flip-angle,-360deg) * .5));animation-timing-function:cubic-bezier(.33,.66,.66,1)}92%{transform:perspective(2em) scale(1) rotate3d(var(--fa-flip-x,0), var(--fa-flip-y,1), var(--fa-flip-z,0), calc(var(--fa-flip-angle,-360deg) * var(--fa-flip-overshoot,1.04)));animation-timing-function:cubic-bezier(.33,0,.66,1)}to{transform:perspective(2em) scale(1) rotate3d(var(--fa-flip-x,0), var(--fa-flip-y,1), var(--fa-flip-z,0), var(--fa-flip-angle,-360deg))}}@keyframes fa-flip-360{0%{transform:perspective(2em) scale(1) rotate3d(var(--fa-flip-x,0), var(--fa-flip-y,1), var(--fa-flip-z,0), 0deg);animation-timing-function:cubic-bezier(.2,0,.4,1)}8%{transform:perspective(2em) scale(var(--fa-flip-anticipation-scale,.95)) rotate3d(var(--fa-flip-x,0), var(--fa-flip-y,1), var(--fa-flip-z,0), 0deg);animation-timing-function:cubic-bezier(.33,0,.66,.33)}50%{transform:perspective(2em) scale(1) rotate3d(var(--fa-flip-x,0), var(--fa-flip-y,1), var(--fa-flip-z,0), calc(var(--fa-flip-angle,-360deg) * .6));animation-timing-function:cubic-bezier(.33,.66,.66,1)}80%{transform:perspective(2em) scale(1) rotate3d(var(--fa-flip-x,0), var(--fa-flip-y,1), var(--fa-flip-z,0), calc(var(--fa-flip-angle,-360deg) * var(--fa-flip-overshoot,1.04)));animation-timing-function:cubic-bezier(.33,0,.66,1)}to{transform:perspective(2em) scale(1) rotate3d(var(--fa-flip-x,0), var(--fa-flip-y,1), var(--fa-flip-z,0), var(--fa-flip-angle,-360deg))}}@keyframes fa-shake{0%{animation-timing-function:cubic-bezier(.2,0,.8,1);transform:rotate(0)}8%{animation-timing-function:cubic-bezier(.3,0,.7,1);transform:rotate(35deg)translate(1px)}20%{animation-timing-function:cubic-bezier(.3,0,.7,1);transform:rotate(-22deg)translate(-1px)}35%{animation-timing-function:cubic-bezier(.3,0,.7,1);transform:rotate(15deg)translate(1px)}50%{animation-timing-function:cubic-bezier(.4,0,.6,1);transform:rotate(-9deg)}65%{animation-timing-function:cubic-bezier(.4,0,.6,1);transform:rotate(5deg)}78%{animation-timing-function:cubic-bezier(.4,0,.6,1);transform:rotate(-3deg)}90%{animation-timing-function:cubic-bezier(.4,0,.2,1);transform:rotate(1deg)}to{transform:rotate(0)}}@keyframes fa-spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}@keyframes fa-spin-snap{0%{animation-timing-function:cubic-bezier(0,0,.2,1);transform:rotate(0)}12%{animation-timing-function:cubic-bezier(.8,0,1,1);transform:rotate(60deg)}16.67%{animation-timing-function:cubic-bezier(0,0,.2,1);transform:rotate(60deg)}28.67%{animation-timing-function:cubic-bezier(.8,0,1,1);transform:rotate(120deg)}33.33%{animation-timing-function:cubic-bezier(0,0,.2,1);transform:rotate(120deg)}45.33%{animation-timing-function:cubic-bezier(.8,0,1,1);transform:rotate(180deg)}50%{animation-timing-function:cubic-bezier(0,0,.2,1);transform:rotate(180deg)}62%{animation-timing-function:cubic-bezier(.8,0,1,1);transform:rotate(240deg)}66.67%{animation-timing-function:cubic-bezier(0,0,.2,1);transform:rotate(240deg)}78.67%{animation-timing-function:cubic-bezier(.8,0,1,1);transform:rotate(300deg)}83.33%{animation-timing-function:cubic-bezier(0,0,.2,1);transform:rotate(300deg)}95.33%{animation-timing-function:cubic-bezier(.8,0,1,1);transform:rotate(360deg)}to{transform:rotate(360deg)}}@keyframes fa-spin-snap-4{0%{animation-timing-function:cubic-bezier(0,0,.2,1);transform:rotate(0)}15%{animation-timing-function:cubic-bezier(.8,0,1,1);transform:rotate(90deg)}25%{animation-timing-function:cubic-bezier(0,0,.2,1);transform:rotate(90deg)}40%{animation-timing-function:cubic-bezier(.8,0,1,1);transform:rotate(180deg)}50%{animation-timing-function:cubic-bezier(0,0,.2,1);transform:rotate(180deg)}65%{animation-timing-function:cubic-bezier(.8,0,1,1);transform:rotate(270deg)}75%{animation-timing-function:cubic-bezier(0,0,.2,1);transform:rotate(270deg)}90%{animation-timing-function:cubic-bezier(.8,0,1,1);transform:rotate(360deg)}to{transform:rotate(360deg)}}@keyframes fa-spin-snap-8{0%{animation-timing-function:cubic-bezier(0,0,.2,1);transform:rotate(0)}9%{animation-timing-function:cubic-bezier(.8,0,1,1);transform:rotate(45deg)}12.5%{animation-timing-function:cubic-bezier(0,0,.2,1);transform:rotate(45deg)}21.5%{animation-timing-function:cubic-bezier(.8,0,1,1);transform:rotate(90deg)}25%{animation-timing-function:cubic-bezier(0,0,.2,1);transform:rotate(90deg)}34%{animation-timing-function:cubic-bezier(.8,0,1,1);transform:rotate(135deg)}37.5%{animation-timing-function:cubic-bezier(0,0,.2,1);transform:rotate(135deg)}46.5%{animation-timing-function:cubic-bezier(.8,0,1,1);transform:rotate(180deg)}50%{animation-timing-function:cubic-bezier(0,0,.2,1);transform:rotate(180deg)}59%{animation-timing-function:cubic-bezier(.8,0,1,1);transform:rotate(225deg)}62.5%{animation-timing-function:cubic-bezier(0,0,.2,1);transform:rotate(225deg)}71.5%{animation-timing-function:cubic-bezier(.8,0,1,1);transform:rotate(270deg)}75%{animation-timing-function:cubic-bezier(0,0,.2,1);transform:rotate(270deg)}84%{animation-timing-function:cubic-bezier(.8,0,1,1);transform:rotate(315deg)}87.5%{animation-timing-function:cubic-bezier(0,0,.2,1);transform:rotate(315deg)}96.5%{animation-timing-function:cubic-bezier(.8,0,1,1);transform:rotate(360deg)}to{transform:rotate(360deg)}}@keyframes fa-buzz{0%{animation-timing-function:cubic-bezier(.1,0,.9,1);transform:translate(0)rotate(0)}5%{transform:translateX(var(--fa-buzz-distance,4px)) rotate(.5deg)}10%{transform:translateX(calc(-1 * var(--fa-buzz-distance,4px))) rotate(-.5deg)}15%{transform:translateX(var(--fa-buzz-distance,4px)) rotate(.3deg)}20%{transform:translateX(calc(-1 * var(--fa-buzz-distance,4px))) rotate(-.3deg)}25%{transform:translateX(calc(var(--fa-buzz-distance,4px) * .7)) rotate(.2deg)}30%{transform:translateX(calc(-1 * var(--fa-buzz-distance,4px) * .7)) rotate(-.2deg)}35%{transform:translateX(calc(var(--fa-buzz-distance,4px) * .4)) rotate(.1deg)}40%{transform:translate(0)rotate(0)}to{transform:translate(0)rotate(0)}}@keyframes fa-wag{0%{animation-timing-function:cubic-bezier(.2,0,.6,1);transform:rotate(0)}12%{transform:rotate(var(--fa-wag-angle,12deg));animation-timing-function:cubic-bezier(.4,0,.2,1)}24%{animation-timing-function:cubic-bezier(.2,0,.6,1);transform:rotate(2deg)}36%{transform:rotate(calc(var(--fa-wag-angle,12deg) * .85));animation-timing-function:cubic-bezier(.4,0,.2,1)}48%{animation-timing-function:cubic-bezier(.2,0,.6,1);transform:rotate(1deg)}58%{transform:rotate(calc(var(--fa-wag-angle,12deg) * .6));animation-timing-function:cubic-bezier(.4,0,.2,1)}68%{transform:rotate(0)}to{transform:rotate(0)}}@keyframes fa-float{0%{transform:translateY(0) translateX(0) rotate(0deg) scale(var(--fa-float-squash-x,1.02), var(--fa-float-squash-y,.98));animation-timing-function:cubic-bezier(.33,0,.66,.33)}15%{transform:translateY(calc(-.4 * var(--fa-float-height,6px))) translateX(var(--fa-float-drift,1px)) rotate(var(--fa-float-tilt,1deg)) scale(1, 1);animation-timing-function:cubic-bezier(.33,.66,.66,1)}35%{transform:translateY(calc(-1 * var(--fa-float-height,6px))) translateX(0) rotate(0deg) scale(var(--fa-float-stretch-x,.98), var(--fa-float-stretch-y,1.03));animation-timing-function:cubic-bezier(.5,0,.5,0)}50%{transform:translateY(calc(-.92 * var(--fa-float-height,6px))) translateX(calc(-.5 * var(--fa-float-drift,1px))) rotate(calc(-.5 * var(--fa-float-tilt,1deg))) scale(.995, 1.01);animation-timing-function:cubic-bezier(.33,0,.66,.33)}70%{transform:translateY(calc(-.3 * var(--fa-float-height,6px))) translateX(calc(-1 * var(--fa-float-drift,1px))) rotate(calc(-1 * var(--fa-float-tilt,1deg))) scale(1, 1);animation-timing-function:cubic-bezier(.33,.66,.66,1)}90%{transform:translateY(calc(.05 * var(--fa-float-height,6px))) translateX(0) rotate(0deg) scale(var(--fa-float-squash-x,1.02), var(--fa-float-squash-y,.98));animation-timing-function:cubic-bezier(.33,0,.66,1)}to{transform:translateY(0) translateX(0) rotate(0deg) scale(var(--fa-float-squash-x,1.02), var(--fa-float-squash-y,.98))}}@keyframes fa-swing{0%{animation-timing-function:cubic-bezier(.2,0,.8,1);transform:rotate(0)}8%{transform:rotate(var(--fa-swing-angle,22deg));animation-timing-function:cubic-bezier(.3,0,.7,1)}18%{transform:rotate(calc(-1 * var(--fa-swing-angle,22deg) * .85));animation-timing-function:cubic-bezier(.3,0,.7,1)}28%{transform:rotate(calc(var(--fa-swing-angle,22deg) * .65));animation-timing-function:cubic-bezier(.35,0,.65,1)}38%{transform:rotate(calc(-1 * var(--fa-swing-angle,22deg) * .45));animation-timing-function:cubic-bezier(.4,0,.6,1)}48%{transform:rotate(calc(var(--fa-swing-angle,22deg) * .25));animation-timing-function:cubic-bezier(.4,0,.6,1)}56%{transform:rotate(calc(-1 * var(--fa-swing-angle,22deg) * .1));animation-timing-function:cubic-bezier(.4,0,.6,1)}64%{transform:rotate(0)}to{transform:rotate(0)}}@keyframes fa-jello{0%{animation-timing-function:cubic-bezier(.2,0,.8,1);transform:scale(1)}12%{transform:scale(var(--fa-jello-scale-x,1.15), calc(2 - var(--fa-jello-scale-x,1.15)));animation-timing-function:cubic-bezier(.3,0,.7,1)}24%{transform:scale(calc(2 - var(--fa-jello-scale-y,1.12)), var(--fa-jello-scale-y,1.12));animation-timing-function:cubic-bezier(.3,0,.7,1)}36%{transform:scale(calc(1 + (var(--fa-jello-scale-x,1.15) - 1) * .5), calc(2 - (1 + (var(--fa-jello-scale-x,1.15) - 1) * .5)));animation-timing-function:cubic-bezier(.4,0,.6,1)}48%{transform:scale(calc(2 - (1 + (var(--fa-jello-scale-y,1.12) - 1) * .3)), calc(1 + (var(--fa-jello-scale-y,1.12) - 1) * .3));animation-timing-function:cubic-bezier(.4,0,.6,1)}58%{animation-timing-function:cubic-bezier(.4,0,.2,1);transform:scale(1.02,.98)}68%{transform:scale(1)}to{transform:scale(1)}}.fa-rotate-90{transform:rotate(90deg)}.fa-rotate-180{transform:rotate(180deg)}.fa-rotate-270{transform:rotate(270deg)}.fa-flip-horizontal{transform:scaleX(-1)}.fa-flip-vertical{transform:scaleY(-1)}.fa-flip-both,.fa-flip-horizontal.fa-flip-vertical{transform:scale(-1)}.fa-rotate-by{transform:rotate(var(--fa-rotate-angle,0))}.svg-inline--fa .fa-primary{fill:var(--fa-primary-color,currentColor);opacity:var(--fa-primary-opacity,1)}.svg-inline--fa .fa-secondary{fill:var(--fa-secondary-color,currentColor);opacity:var(--fa-secondary-opacity,.4)}.svg-inline--fa.fa-swap-opacity .fa-primary{opacity:var(--fa-secondary-opacity,.4)}.svg-inline--fa.fa-swap-opacity .fa-secondary{opacity:var(--fa-primary-opacity,1)}.svg-inline--fa mask .fa-primary,.svg-inline--fa mask .fa-secondary{fill:#000}.svg-inline--fa.fa-inverse{fill:var(--fa-inverse,#fff)}.fa-stack{vertical-align:middle;width:2.5em;height:2em;line-height:2em;display:inline-block;position:relative}.fa-inverse{color:var(--fa-inverse,#fff)}.svg-inline--fa.fa-stack-1x{--fa-width:1.25em;height:1em;width:var(--fa-width)}.svg-inline--fa.fa-stack-2x{--fa-width:2.5em;height:2em;width:var(--fa-width)}.fa-stack-1x,.fa-stack-2x{z-index:var(--fa-stack-z-index,auto);margin:auto;position:absolute;inset:0}body{font-family:Arial,Liberation Sans,DejaVu Sans,sans-serif;font-size:12px}body.mobile,body.mobile .message{font-size:14px}pre{font-family:Consolas,Monaco,Ubuntu Mono,monospace}table.env-table tbody tr td{vertical-align:top;border-top:none;height:18px;line-height:18px}table.env-table,table.env-table table{border-spacing:0;border-collapse:collapse}table.env-table td{padding-right:5px}tbody tr{width:98%}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.loading{border:4px solid #f3f3f3;border-top-color:#3498db;border-radius:50%;width:30px;height:30px;margin:40dvh auto;animation:1s linear infinite spin}.message-row-wrapper{width:inherit;display:flex}.message-row-wrapper .grouping-checkbox{margin:0}.message-row{width:inherit;display:flex}.message-row .severity,.message-row .protected{text-align:center;flex-grow:0;flex-shrink:0;width:25px;font-size:12px}.message-row div{border-top:.5px solid #e9e9e9;padding-top:1px;padding-bottom:1px;line-height:25px}.message-row .count{box-sizing:border-box;flex-grow:0;flex-shrink:0;width:30px;padding-right:4px;font-size:11px;font-weight:700}.message-row .message-body{white-space:nowrap;text-overflow:ellipsis;flex-grow:1;flex-shrink:1;font-size:13px;overflow:hidden}.message-row .time{color:#999;vertical-align:top;flex-grow:0;flex-shrink:0;padding-right:8px;font-size:12px}.message-row .count{text-align:right}.message-row:hover{cursor:pointer;background-color:#f8f8f8}.message-row.selected{background-color:#dfdfdf}.svg-inline--fa.fatal{color:#e00}.svg-inline--fa.error{color:#900}.svg-inline--fa.warning{color:#feb800}.debug{color:#777}.action-panel .search{vertical-align:middle;box-sizing:border-box;border:1px solid #ddd;padding:3px}#log-table .show-more{text-align:center;cursor:pointer;background-color:#ddd;height:30px;margin-top:8px;line-height:30px;text-decoration:none}#bottom-panel{z-index:2;background-color:#f1f1f1;height:300px;padding:0 8px 8px;position:fixed;bottom:0;left:0;right:0}#bottom-panel.full{background-color:inherit;height:90%;position:static}#bottom-panel.full>div{padding-bottom:40px}#bottom-panel.full .tabs{display:none}#bottom-panel.full .message-info{position:static}#bottom-panel.full .message-info .content{display:block;position:static}#bottom-panel.full .save,#bottom-panel.full .share{bottom:10px}#bottom-panel.full button.delete{display:none}#bottom-panel.full .message-actions button{margin-top:8px}@media (width<=382px){#bottom-panel.full .message-actions{height:73px}}@media (width>=383px){#bottom-panel.full .message-actions{height:40px}}#bottom-panel.full .message-actions{background-color:#eee;border-top:1px solid #dfdfdf;width:100%;padding-left:10px;position:fixed;bottom:0;left:0}.message-actions{margin-right:10px;position:absolute;bottom:5px;right:0}.message-actions button{margin-left:5px}.message-actions button.copied{color:#176b3a;background:#d5f5e3}.message-actions button.copy-failed{color:#9b1c1c;background:#fde2e2}.divider{cursor:row-resize;background-color:#fafafa;border-top:1px solid #ddd;border-bottom:1px solid #ddd;height:15px;position:fixed;bottom:310px;left:0;right:0}.divider div{background-color:#ccc;width:24px;height:1px;margin:auto;position:relative}.divider .line-1{top:5px}.divider .line-2{top:6px}.divider .line-3{top:7px}#top-panel{position:fixed;inset:0 0 320px;overflow:auto}#top-panel.with-top-menu{top:35px}#top-menu{z-index:900;width:100%;position:fixed;top:0;left:0}#back-to-site-panel{background-color:#f1f1f1;height:15px;padding:10px}.message-info{border-bottom:1px solid #ddd;position:absolute;top:0;left:0;right:0}#bottom-panel.group-view .message-info{top:43px}.nav-controls.group-nav{border-bottom:1px solid #ddd;position:absolute;left:0;right:0}.action-panel{font-weight:700;position:absolute;bottom:0;left:0;right:0}.action-panel input{margin:0}.action-panel .svg-inline--fa,input,label span{vertical-align:middle}.severity-filters label{margin-right:10px}.search-clear-all .clear{float:right;vertical-align:middle}#log-table{width:99%;margin:auto}.btn span{display:inline}.hidden{display:none}.message-info pre,.message-info .env-table{margin:5px 10px 10px;position:relative}#overlay{z-index:99999;cursor:row-resize;opacity:0;position:fixed;inset:0}.message-info .content{display:none;position:absolute;inset:0 0 35px;overflow:auto}.message-info .content.active{display:block}.tabs{border-top:1px solid #ddd;margin:0 0 5px;padding:0 0 0 10px;list-style-type:none;position:absolute;bottom:13px;left:0;right:0}.tabs li{float:left;margin:0;padding-right:5px;position:relative}.tabs a{color:#333;background-color:#e1e1e1;border:1px solid #ddd;border-top:none;border-bottom-right-radius:5px;border-bottom-left-radius:5px;padding:6px;text-decoration:none;position:relative;top:6px}.tabs a.active{background-color:#f1f1f1;border-top:1px solid #f1f1f1}.btn{text-align:center;cursor:pointer;color:#333;background-color:#ddd;border:none;margin:0;padding:5px 8px;font-size:1em;font-weight:400;line-height:0;text-decoration:none;transition:all .25s;display:inline-block}.btn:hover{color:#000;background-color:#ccc}.btn .svg-inline--fa{margin-right:7px}.btn:active{text-shadow:none}.btn.danger:hover{color:#eee;background-color:#c63c1b}.btn.ok{color:#fff;background-color:#3781dc}.btn.ok:hover{color:#fff;background-color:#286dc2}.search-clear-all .search,.search-clear-all .btn.clear{height:100%}.search-clear-all .btn.clear span{vertical-align:middle}.severity-filters label,.search-clear-all .search,.search-clear-all .btn.clear{align-self:center}.search-clear-all .footer-btns .settings{box-sizing:border-box;align-items:center;height:100%;display:inline-flex}.search-clear-all{justify-content:space-between;display:flex}.search-clear-all .search{flex-shrink:2;min-width:0}.footer-btns{gap:.7em;display:flex}@media (width>=871px){.severity-filters,.search-clear-all{height:100%}.severity-filters,.more-wrapping{display:flex}.severity-filters{float:left}.action-panel{box-sizing:border-box;height:45px;padding:10px}.message-info{bottom:45px}}@media (width<=870px){.severity-filters{padding:10px}.search-clear-all{padding:0 10px 10px}.action-panel{height:69px}.message-info{bottom:69px}}@media (width<=430px){.severity-filters{white-space:nowrap;overflow:scroll hidden}.more-wrapping:before,.more-wrapping:after{content:"";height:18px;position:absolute}.more-wrapping:before{background:linear-gradient(90deg,#f1f1f1 0%,#f1f1f100 100%);width:15px;margin-left:-10px}.more-wrapping:after{background:linear-gradient(270deg,#f1f1f1 0%,#f1f1f100 100%);width:23px;right:0}}.btn.no-text .svg-inline--fa{margin:0}.btn[disabled]{opacity:.5}.actions-menu{z-index:3;background:#fafafa;flex-direction:column;width:115px;padding:5px 5px 0;display:inline-flex;position:absolute;bottom:27px;right:45px;box-shadow:0 4px 14px #00000026}.actions-menu button{height:27px;margin:0 0 5px}.nav-controls{padding:10px}#bottom-panel:not(.full) .nav-controls.env-nav{position:-moz-sticky;position:-ms-sticky;position:-o-sticky;z-index:1;background:#f1f1f1;border-bottom:1px solid #ddd;position:sticky;top:0}.current-number{margin:0 7px}.recent-link,.expand-list{color:#00f;cursor:pointer;text-decoration:underline}.settings-page{max-width:1110px;margin-left:auto;margin-right:auto;padding:0 10px 70px;font-size:15px}.settings-header{align-items:center;margin-bottom:5px;display:flex}.settings-header .header-title{flex-grow:1}.settings-header .header-logo{width:50px;height:50px}.settings-section{padding-top:15px}.settings-section .section-title{margin-top:0}.settings-section .subsection-title{margin-bottom:10px}.settings-section .tip{color:gray;font-size:13px;font-style:italic}.settings-section .pattern-wrapper{height:33px;margin-top:10px;font-size:16px;display:flex}.settings-section .api-error{color:red;margin-top:5px;font-family:Consolas,Monaco,Ubuntu Mono,monospace}.pattern-wrapper .pattern-input{width:600px;font-family:Consolas,Monaco,Ubuntu Mono,monospace;font-size:inherit;box-sizing:border-box;vertical-align:middle;flex-grow:1;flex-shrink:1;height:100%;padding:5px 8px}.retro-checkbox .checkbox{margin:0}.retro-checkbox{margin-top:7px}.btn.new-pattern{height:100%;line-height:18px}.pattern-wrapper .shrink{text-align:center;box-sizing:border-box;vertical-align:middle;flex-grow:0;flex-shrink:0;width:40px;height:100%;margin-left:10px}.svg-inline--fa{opacity:.7}.pattern-wrapper .shrink.reset{background:unset;width:unset;margin-left:8px;padding:0}.grouping-patterns button.new-pattern{margin-top:10px}.grouping-pattern-dialog-overlay{z-index:100000;background:#00000080;justify-content:center;align-items:center;display:flex;position:fixed;inset:0}.grouping-pattern-dialog{background:#fff;border-radius:4px;min-width:600px;padding:20px;box-shadow:0 4px 16px #0000004d}.grouping-pattern-dialog h3{margin:0 0 8px;font-size:1em;font-weight:700}.grouping-pattern-dialog p{color:#555;margin:0 0 10px;font-size:.9em}.grouping-pattern-input{box-sizing:border-box;border:1px solid #ccc;border-radius:3px;width:100%;margin-bottom:14px;padding:8px;font-family:monospace;font-size:1.1em}.grouping-pattern-dialog .api-error{color:#c62828;white-space:pre-wrap;margin:0 0 14px}.grouping-pattern-dialog-buttons{justify-content:flex-end;gap:8px;display:flex}.grouping-pattern-dialog .btn{padding:12px}.backtrace-line .line-link{color:#9e9e9e;white-space:normal;margin-left:3px;text-decoration:none}@media (prefers-color-scheme:dark){body{color:#cecece;background:#181818}#bottom-panel{background:#181818}.message-row div{border-top-color:#404040}.message-row:hover{background:#262626}.message-row.selected{background:#303030}#log-table .show-more,.divider{background:#262626}.divider div{background:#707070}.divider,.tabs{border-top-color:#555}#bottom-panel:not(.full) .nav-controls.env-nav,.divider,.message-info,.nav-controls.group-nav{border-bottom-color:#555}.action-panel .search{background:#262626;border-color:#404040}.btn,.tabs a{color:#cecece}.tabs a{background:#323232;border-color:#404040}.tabs a.active{color:#f4f4f4;background:#555;border-color:#707070}.btn{background:#323232}#bottom-panel.full .message-actions{background:#181818;border-color:#555}#bottom-panel:not(.full) .nav-controls.env-nav{background:#181818}.recent-link,.expand-list{color:#44f}#back-to-site-panel{background-color:#181818}.grouping-pattern-dialog{color:#cecece;background:#262626}.grouping-pattern-dialog p{color:#aaa}.grouping-pattern-input{color:#cecece;background:#181818;border-color:#555}}
|
|
@@ -5,6 +5,12 @@ module Logster
|
|
|
5
5
|
class Reporter
|
|
6
6
|
PATH_INFO = "PATH_INFO"
|
|
7
7
|
SCRIPT_NAME = "SCRIPT_NAME"
|
|
8
|
+
REQUEST_METHOD = "REQUEST_METHOD"
|
|
9
|
+
SECURITY_HEADERS = {
|
|
10
|
+
"cache-control" => "no-store",
|
|
11
|
+
"referrer-policy" => "no-referrer",
|
|
12
|
+
"x-content-type-options" => "nosniff",
|
|
13
|
+
}.freeze
|
|
8
14
|
|
|
9
15
|
def initialize(app, config = {})
|
|
10
16
|
@app = app
|
|
@@ -20,7 +26,11 @@ module Logster
|
|
|
20
26
|
path = script_name + path if script_name && script_name.length > 0
|
|
21
27
|
|
|
22
28
|
if path == @error_path
|
|
23
|
-
|
|
29
|
+
if env[REQUEST_METHOD] != "POST"
|
|
30
|
+
return response(405, "Method not allowed", "allow" => "POST")
|
|
31
|
+
end
|
|
32
|
+
return response(403, "CSRF validation failed") unless valid_csrf_request?(env)
|
|
33
|
+
return response(403, "Access Denied") if !Logster.config.enable_js_error_reporting
|
|
24
34
|
|
|
25
35
|
Logster
|
|
26
36
|
.config
|
|
@@ -29,12 +39,12 @@ module Logster
|
|
|
29
39
|
if Logster.config.rate_limit_error_reporting
|
|
30
40
|
req = Rack::Request.new(env)
|
|
31
41
|
if Logster.store.rate_limited?(req.ip, perform: true)
|
|
32
|
-
return 429,
|
|
42
|
+
return response(429, "Rate Limited")
|
|
33
43
|
end
|
|
34
44
|
end
|
|
35
45
|
report_js_error(env)
|
|
36
46
|
end
|
|
37
|
-
return 200,
|
|
47
|
+
return response(200, "OK")
|
|
38
48
|
end
|
|
39
49
|
|
|
40
50
|
@app.call(env)
|
|
@@ -42,6 +52,27 @@ module Logster
|
|
|
42
52
|
Thread.current[Logster::Logger::LOGSTER_ENV] = nil
|
|
43
53
|
end
|
|
44
54
|
|
|
55
|
+
def response(status, body, headers = {})
|
|
56
|
+
[
|
|
57
|
+
status,
|
|
58
|
+
SECURITY_HEADERS.merge("content-type" => "text/plain; charset=utf-8").merge(headers),
|
|
59
|
+
[body],
|
|
60
|
+
]
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def valid_csrf_request?(env)
|
|
64
|
+
fetch_site = env["HTTP_SEC_FETCH_SITE"]
|
|
65
|
+
return false if fetch_site && !%w[same-origin same-site none].include?(fetch_site)
|
|
66
|
+
|
|
67
|
+
same_origin_fetch = fetch_site == "same-origin"
|
|
68
|
+
ajax_request = env["HTTP_X_REQUESTED_WITH"] == "XMLHttpRequest"
|
|
69
|
+
return false unless same_origin_fetch || ajax_request
|
|
70
|
+
return true if same_origin_fetch
|
|
71
|
+
|
|
72
|
+
origin = env["HTTP_ORIGIN"]
|
|
73
|
+
!origin || origin == Rack::Request.new(env).base_url
|
|
74
|
+
end
|
|
75
|
+
|
|
45
76
|
def report_js_error(env)
|
|
46
77
|
req = Rack::Request.new(env)
|
|
47
78
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "json"
|
|
4
|
+
require "uri"
|
|
4
5
|
|
|
5
6
|
module Logster
|
|
6
7
|
module Middleware
|
|
@@ -8,6 +9,39 @@ module Logster
|
|
|
8
9
|
PATH_INFO = "PATH_INFO".freeze
|
|
9
10
|
SCRIPT_NAME = "SCRIPT_NAME".freeze
|
|
10
11
|
REQUEST_METHOD = "REQUEST_METHOD".freeze
|
|
12
|
+
ASSET_NAME = /\A[a-zA-Z0-9._-]+\z/
|
|
13
|
+
LEGACY_SCRIPTS = %w[vendor.js].freeze
|
|
14
|
+
LOGSTER_RESPONSE = "logster.response".freeze
|
|
15
|
+
STATIC_RESPONSE = "logster.static_response".freeze
|
|
16
|
+
DYNAMIC_SECURITY_HEADERS = {
|
|
17
|
+
"cache-control" => "no-store",
|
|
18
|
+
"referrer-policy" => "no-referrer",
|
|
19
|
+
"x-content-type-options" => "nosniff",
|
|
20
|
+
"x-frame-options" => "DENY",
|
|
21
|
+
}.freeze
|
|
22
|
+
STATIC_SECURITY_HEADERS = {
|
|
23
|
+
"cache-control" => "public, max-age=0, must-revalidate",
|
|
24
|
+
"referrer-policy" => "no-referrer",
|
|
25
|
+
"x-content-type-options" => "nosniff",
|
|
26
|
+
}.freeze
|
|
27
|
+
MESSAGE_ROUTE = %r{\A/message/([0-9a-f]+)\z}
|
|
28
|
+
PROTECT_ROUTE = %r{\A/protect/([0-9a-f]+)\z}
|
|
29
|
+
UNPROTECT_ROUTE = %r{\A/unprotect/([0-9a-f]+)\z}
|
|
30
|
+
SOLVE_ROUTE = %r{\A/solve/([0-9a-f]+)\z}
|
|
31
|
+
CLEAR_ROUTE = %r{\A/clear\z}
|
|
32
|
+
PATTERNS_ROUTE = %r{\A/patterns/([a-zA-Z0-9_]+)\.json\z}
|
|
33
|
+
RESET_COUNT_ROUTE = %r{\A/reset-count\.json\z}
|
|
34
|
+
SOLVE_GROUP_ROUTE = %r{\A/solve-group\z}
|
|
35
|
+
MUTATING_ROUTES = [
|
|
36
|
+
[MESSAGE_ROUTE, %w[DELETE]],
|
|
37
|
+
[PROTECT_ROUTE, %w[PUT]],
|
|
38
|
+
[UNPROTECT_ROUTE, %w[DELETE]],
|
|
39
|
+
[SOLVE_ROUTE, %w[PUT]],
|
|
40
|
+
[CLEAR_ROUTE, %w[POST]],
|
|
41
|
+
[PATTERNS_ROUTE, %w[POST PUT DELETE]],
|
|
42
|
+
[RESET_COUNT_ROUTE, %w[PUT]],
|
|
43
|
+
[SOLVE_GROUP_ROUTE, %w[POST]],
|
|
44
|
+
].freeze
|
|
11
45
|
|
|
12
46
|
def initialize(app)
|
|
13
47
|
@app = app
|
|
@@ -21,60 +55,86 @@ module Logster
|
|
|
21
55
|
end
|
|
22
56
|
|
|
23
57
|
def call(env)
|
|
58
|
+
env.delete(LOGSTER_RESPONSE)
|
|
59
|
+
env.delete(STATIC_RESPONSE)
|
|
60
|
+
response = dispatch(env)
|
|
61
|
+
return response unless env.delete(LOGSTER_RESPONSE)
|
|
62
|
+
|
|
63
|
+
status, headers, body = response
|
|
64
|
+
security_headers =
|
|
65
|
+
env.delete(STATIC_RESPONSE) ? STATIC_SECURITY_HEADERS : DYNAMIC_SECURITY_HEADERS
|
|
66
|
+
[status, security_headers.merge(headers), body]
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def dispatch(env)
|
|
24
70
|
path = env[PATH_INFO]
|
|
25
71
|
script_name = env[SCRIPT_NAME]
|
|
26
72
|
|
|
27
73
|
path = script_name + path if script_name && script_name.length > 0
|
|
28
74
|
|
|
29
75
|
if resource = resolve_path(path)
|
|
30
|
-
|
|
31
|
-
|
|
76
|
+
env[LOGSTER_RESPONSE] = true
|
|
77
|
+
|
|
78
|
+
if (authorize_request = Logster.config.authorize_request) && !authorize_request.call(env)
|
|
79
|
+
return not_allowed("Not authorized")
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
return not_found if parent_directory_segment?(resource)
|
|
83
|
+
|
|
84
|
+
if allowed_methods = allowed_methods_for(resource)
|
|
85
|
+
if !allowed_methods.include?(env[REQUEST_METHOD])
|
|
86
|
+
return method_not_allowed(allowed_methods)
|
|
87
|
+
end
|
|
88
|
+
return not_allowed("CSRF validation failed") unless valid_csrf_request?(env)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
if resource =~ %r{\A/(?:.*\.(?:ico|js|png|handlebars|css|woff|ttf|woff2|svg|otf|eot))\z}
|
|
32
92
|
serve_file(env, resource)
|
|
33
|
-
elsif resource
|
|
93
|
+
elsif resource == "/messages.json" && env[REQUEST_METHOD] == "POST"
|
|
34
94
|
serve_messages(Rack::Request.new(env))
|
|
35
|
-
elsif
|
|
95
|
+
elsif (route_match = MESSAGE_ROUTE.match(resource))
|
|
36
96
|
return method_not_allowed("DELETE") if env[REQUEST_METHOD] != "DELETE"
|
|
37
97
|
|
|
38
|
-
key =
|
|
98
|
+
key = route_match[1]
|
|
39
99
|
message = Logster.store.get(key)
|
|
40
100
|
return 404, {}, ["Message not found"] unless message
|
|
41
101
|
|
|
42
102
|
Logster.store.delete(message)
|
|
43
|
-
[
|
|
44
|
-
elsif
|
|
45
|
-
off =
|
|
46
|
-
key =
|
|
103
|
+
[303, { "location" => "#{@logs_path}/" }, []]
|
|
104
|
+
elsif (route_match = PROTECT_ROUTE.match(resource) || UNPROTECT_ROUTE.match(resource))
|
|
105
|
+
off = resource.start_with?("/unprotect/")
|
|
106
|
+
key = route_match[1]
|
|
47
107
|
|
|
48
108
|
message = Logster.store.get(key)
|
|
49
109
|
return 404, {}, ["Message not found"] unless message
|
|
50
110
|
|
|
51
111
|
if off
|
|
52
112
|
if Logster.store.unprotect(key)
|
|
53
|
-
[
|
|
113
|
+
[303, { "location" => "#{@logs_path}/show/#{key}?protected=false" }, []]
|
|
54
114
|
else
|
|
55
115
|
[500, {}, ["Failed"]]
|
|
56
116
|
end
|
|
57
117
|
else
|
|
58
118
|
if Logster.store.protect(key)
|
|
59
|
-
[
|
|
119
|
+
[303, { "location" => "#{@logs_path}/show/#{key}?protected=true" }, []]
|
|
60
120
|
else
|
|
61
121
|
[500, {}, ["Failed"]]
|
|
62
122
|
end
|
|
63
123
|
end
|
|
64
|
-
elsif
|
|
65
|
-
key =
|
|
124
|
+
elsif (route_match = SOLVE_ROUTE.match(resource))
|
|
125
|
+
key = route_match[1]
|
|
66
126
|
|
|
67
127
|
message = Logster.store.get(key)
|
|
68
128
|
return 404, {}, ["Message not found"] unless message
|
|
69
129
|
|
|
70
130
|
Logster.store.solve(key)
|
|
71
131
|
|
|
72
|
-
[
|
|
73
|
-
elsif resource
|
|
132
|
+
[303, { "location" => "#{@logs_path}" }, []]
|
|
133
|
+
elsif CLEAR_ROUTE.match?(resource)
|
|
74
134
|
return method_not_allowed("POST") if env[REQUEST_METHOD] != "POST"
|
|
75
135
|
Logster.store.clear
|
|
76
136
|
[200, {}, ["Messages cleared"]]
|
|
77
|
-
elsif resource =~ %r{/show/([0-9a-f]+)(\.json)
|
|
137
|
+
elsif resource =~ %r{\A/show/([0-9a-f]+)(\.json)?\z}
|
|
78
138
|
key = $1
|
|
79
139
|
json = $2 == ".json"
|
|
80
140
|
|
|
@@ -87,7 +147,7 @@ module Logster
|
|
|
87
147
|
preload = { "/show/#{key}" => message }
|
|
88
148
|
js_app(preload)
|
|
89
149
|
end
|
|
90
|
-
elsif resource =~ %r{/settings(\.json)
|
|
150
|
+
elsif resource =~ %r{\A/settings(\.json)?\z}
|
|
91
151
|
json = $1 == ".json"
|
|
92
152
|
if json
|
|
93
153
|
ignore_count = Logster.store.get_all_ignore_count
|
|
@@ -116,7 +176,7 @@ module Logster
|
|
|
116
176
|
else
|
|
117
177
|
js_app
|
|
118
178
|
end
|
|
119
|
-
elsif
|
|
179
|
+
elsif (route_match = PATTERNS_ROUTE.match(resource))
|
|
120
180
|
unless Logster.config.enable_custom_patterns_via_ui
|
|
121
181
|
return(
|
|
122
182
|
not_allowed(
|
|
@@ -125,12 +185,12 @@ module Logster
|
|
|
125
185
|
)
|
|
126
186
|
end
|
|
127
187
|
|
|
128
|
-
set_name =
|
|
188
|
+
set_name = route_match[1]
|
|
129
189
|
req = Rack::Request.new(env)
|
|
130
190
|
return method_not_allowed(%w[POST PUT DELETE]) if req.request_method == "GET"
|
|
131
191
|
|
|
132
192
|
update_patterns(set_name, req)
|
|
133
|
-
elsif resource
|
|
193
|
+
elsif RESET_COUNT_ROUTE.match?(resource)
|
|
134
194
|
req = Rack::Request.new(env)
|
|
135
195
|
return method_not_allowed("PUT") if req.request_method != "PUT"
|
|
136
196
|
pattern = nil
|
|
@@ -152,7 +212,7 @@ module Logster
|
|
|
152
212
|
[200, {}, ["OK"]]
|
|
153
213
|
elsif resource == "/"
|
|
154
214
|
js_app
|
|
155
|
-
elsif resource =~ %r{/fetch-env/([0-9a-f]+)\.json
|
|
215
|
+
elsif resource =~ %r{\A/fetch-env/([0-9a-f]+)\.json\z}
|
|
156
216
|
key = $1
|
|
157
217
|
env = Logster.store.get_env(key)
|
|
158
218
|
if env
|
|
@@ -160,7 +220,7 @@ module Logster
|
|
|
160
220
|
else
|
|
161
221
|
not_found
|
|
162
222
|
end
|
|
163
|
-
elsif resource
|
|
223
|
+
elsif SOLVE_GROUP_ROUTE.match?(resource)
|
|
164
224
|
return not_allowed unless Logster.config.enable_custom_patterns_via_ui
|
|
165
225
|
req = Rack::Request.new(env)
|
|
166
226
|
return method_not_allowed("POST") if req.request_method != "POST"
|
|
@@ -185,7 +245,30 @@ module Logster
|
|
|
185
245
|
|
|
186
246
|
protected
|
|
187
247
|
|
|
248
|
+
def parent_directory_segment?(resource)
|
|
249
|
+
Rack::Utils.unescape_path(resource).split("/").include?("..")
|
|
250
|
+
rescue ArgumentError
|
|
251
|
+
true
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
def allowed_methods_for(resource)
|
|
255
|
+
route = MUTATING_ROUTES.find { |pattern, _| pattern.match?(resource) }
|
|
256
|
+
route&.last
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
def valid_csrf_request?(env)
|
|
260
|
+
return false unless env["HTTP_X_REQUESTED_WITH"] == "XMLHttpRequest"
|
|
261
|
+
|
|
262
|
+
fetch_site = env["HTTP_SEC_FETCH_SITE"]
|
|
263
|
+
return false if fetch_site && !%w[same-origin same-site none].include?(fetch_site)
|
|
264
|
+
return true if fetch_site == "same-origin"
|
|
265
|
+
|
|
266
|
+
origin = env["HTTP_ORIGIN"]
|
|
267
|
+
!origin || origin == Rack::Request.new(env).base_url
|
|
268
|
+
end
|
|
269
|
+
|
|
188
270
|
def serve_file(env, path)
|
|
271
|
+
env[STATIC_RESPONSE] = true
|
|
189
272
|
env[PATH_INFO] = path
|
|
190
273
|
# accl redirect is going to be trouble, ensure its bypassed
|
|
191
274
|
env["sendfile.type"] = ""
|
|
@@ -307,6 +390,93 @@ module Logster
|
|
|
307
390
|
"<script src='#{@logs_path}/javascript/#{name}' nonce='#{csp_nonce}'></script>"
|
|
308
391
|
end
|
|
309
392
|
|
|
393
|
+
def module_script(name, csp_nonce)
|
|
394
|
+
"<script type='module' src='#{@logs_path}/javascript/#{name}' nonce='#{csp_nonce}'></script>"
|
|
395
|
+
end
|
|
396
|
+
|
|
397
|
+
def modulepreload(name, csp_nonce)
|
|
398
|
+
"<link rel='modulepreload' href='#{@logs_path}/javascript/#{name}' nonce='#{csp_nonce}'>"
|
|
399
|
+
end
|
|
400
|
+
|
|
401
|
+
# Vite's own build manifest, plus the application config the build emits
|
|
402
|
+
# alongside it.
|
|
403
|
+
def asset_manifest
|
|
404
|
+
@asset_manifest ||=
|
|
405
|
+
begin
|
|
406
|
+
vite = JSON.parse(read_asset("manifest.json"))
|
|
407
|
+
entry = vite.values.find { |chunk| chunk.is_a?(Hash) && chunk["isEntry"] }
|
|
408
|
+
raise KeyError, "the Vite build emitted no entry chunk" unless entry
|
|
409
|
+
|
|
410
|
+
validate_asset_manifest(
|
|
411
|
+
"scripts" => plain_scripts,
|
|
412
|
+
"module" => asset_basename(entry.fetch("file")),
|
|
413
|
+
"modulepreload" => preloaded_chunks(vite, entry),
|
|
414
|
+
"stylesheets" => Array(entry["css"]).map { |href| asset_basename(href) },
|
|
415
|
+
"config" => JSON.parse(read_asset("logster-config.json")),
|
|
416
|
+
)
|
|
417
|
+
rescue JSON::ParserError, KeyError, TypeError, Errno::ENOENT => error
|
|
418
|
+
raise RuntimeError, "Logster asset manifest is invalid: #{error.message}"
|
|
419
|
+
end
|
|
420
|
+
end
|
|
421
|
+
|
|
422
|
+
def read_asset(name)
|
|
423
|
+
path = File.join(@assets_path, name)
|
|
424
|
+
unless File.file?(path)
|
|
425
|
+
raise RuntimeError,
|
|
426
|
+
"Logster asset manifest is missing; rebuild the gem with ./build_client_app.sh"
|
|
427
|
+
end
|
|
428
|
+
File.read(path)
|
|
429
|
+
end
|
|
430
|
+
|
|
431
|
+
def asset_basename(path)
|
|
432
|
+
File.basename(path.to_s)
|
|
433
|
+
end
|
|
434
|
+
|
|
435
|
+
# classicEmberSupport emits this to set EmberENV before the deferred
|
|
436
|
+
# module entry runs. It is absent once the app no longer needs the shim.
|
|
437
|
+
def plain_scripts
|
|
438
|
+
LEGACY_SCRIPTS.select { |name| File.file?(File.join(@assets_path, "javascript", name)) }
|
|
439
|
+
end
|
|
440
|
+
|
|
441
|
+
# Every chunk the entry pulls in eagerly, once each, in discovery order.
|
|
442
|
+
def preloaded_chunks(vite, chunk, names = [], seen = {})
|
|
443
|
+
Array(chunk["imports"]).each do |key|
|
|
444
|
+
next if seen[key]
|
|
445
|
+
seen[key] = true
|
|
446
|
+
|
|
447
|
+
imported = vite[key]
|
|
448
|
+
raise KeyError, "the Vite manifest is missing chunk #{key}" unless imported
|
|
449
|
+
|
|
450
|
+
names << asset_basename(imported.fetch("file"))
|
|
451
|
+
preloaded_chunks(vite, imported, names, seen)
|
|
452
|
+
end
|
|
453
|
+
names
|
|
454
|
+
end
|
|
455
|
+
|
|
456
|
+
def validate_asset_manifest(manifest)
|
|
457
|
+
%w[modulepreload stylesheets].each do |type|
|
|
458
|
+
names = manifest.fetch(type)
|
|
459
|
+
unless names.is_a?(Array) && names.all? { |name| ASSET_NAME.match?(name.to_s) }
|
|
460
|
+
raise JSON::ParserError, "Invalid #{type} asset manifest"
|
|
461
|
+
end
|
|
462
|
+
end
|
|
463
|
+
unless ASSET_NAME.match?(manifest.fetch("module").to_s)
|
|
464
|
+
raise JSON::ParserError, "Invalid module asset manifest"
|
|
465
|
+
end
|
|
466
|
+
unless manifest["config"].is_a?(Hash)
|
|
467
|
+
raise JSON::ParserError, "Invalid client application config"
|
|
468
|
+
end
|
|
469
|
+
|
|
470
|
+
manifest
|
|
471
|
+
end
|
|
472
|
+
|
|
473
|
+
def encoded_client_app_config
|
|
474
|
+
config = JSON.parse(JSON.generate(asset_manifest.fetch("config")))
|
|
475
|
+
config["environment"] = "production"
|
|
476
|
+
config["rootURL"] = "#{@logs_path}/"
|
|
477
|
+
Rack::Utils.escape_html(URI.encode_uri_component(JSON.generate(config)))
|
|
478
|
+
end
|
|
479
|
+
|
|
310
480
|
def to_json_and_escape(payload)
|
|
311
481
|
Rack::Utils.escape_html(JSON.generate(payload))
|
|
312
482
|
end
|
|
@@ -344,8 +514,14 @@ module Logster
|
|
|
344
514
|
def js_app(preload = {})
|
|
345
515
|
csp_nonce = SecureRandom.hex
|
|
346
516
|
preload = preloaded_data.merge(preload)
|
|
347
|
-
|
|
348
|
-
|
|
517
|
+
manifest = asset_manifest
|
|
518
|
+
stylesheets = manifest.fetch("stylesheets").map { |name| css(name, csp_nonce) }.join("\n")
|
|
519
|
+
javascript = [
|
|
520
|
+
# The plain scripts set up EmberENV, so they run before the module entry.
|
|
521
|
+
*manifest.fetch("scripts").map { |name| script(name, csp_nonce) },
|
|
522
|
+
*manifest.fetch("modulepreload").map { |name| modulepreload(name, csp_nonce) },
|
|
523
|
+
module_script(manifest.fetch("module"), csp_nonce),
|
|
524
|
+
].join("\n")
|
|
349
525
|
body = <<~HTML
|
|
350
526
|
<!doctype html>
|
|
351
527
|
<html>
|
|
@@ -353,18 +529,14 @@ module Logster
|
|
|
353
529
|
<link rel="shortcut icon" href="#{@logs_path}/images/icon_64x64.png">
|
|
354
530
|
<link rel="apple-touch-icon" href="#{@logs_path}/images/icon_144x144.png" />
|
|
355
531
|
<title>#{Logster.config.web_title || "Logs"}</title>
|
|
356
|
-
<link href='//fonts.googleapis.com/css?family=Roboto' rel='stylesheet' type='text/css' nonce='#{csp_nonce}'>
|
|
357
|
-
<link href='//fonts.googleapis.com/css?family=Roboto+Mono' rel='stylesheet' type='text/css' nonce='#{csp_nonce}'>
|
|
358
532
|
<meta name="viewport" content="width=device-width, minimum-scale=1.0, maximum-scale=1.0, user-scalable=yes">
|
|
359
533
|
<meta name="color-scheme" content="dark light">
|
|
360
|
-
#{
|
|
361
|
-
#{css("client-app.css", csp_nonce)}
|
|
362
|
-
#{script("vendor.js", csp_nonce)}
|
|
534
|
+
#{stylesheets}
|
|
363
535
|
<meta id="preloaded-data" data-root-path="#{@logs_path}" data-preloaded="#{to_json_and_escape(preload)}">
|
|
364
|
-
<meta name="client-app/config/environment" content="
|
|
536
|
+
<meta name="client-app/config/environment" content="#{encoded_client_app_config}" />
|
|
365
537
|
</head>
|
|
366
538
|
<body>
|
|
367
|
-
#{
|
|
539
|
+
#{javascript}
|
|
368
540
|
</body>
|
|
369
541
|
</html>
|
|
370
542
|
HTML
|
|
@@ -374,7 +546,7 @@ module Logster
|
|
|
374
546
|
{
|
|
375
547
|
"content-type" => "text/html; charset=utf-8",
|
|
376
548
|
"content-security-policy" =>
|
|
377
|
-
"script-src 'nonce-#{csp_nonce}'; style-src 'nonce-#{csp_nonce}'; object-src 'none'; base-uri 'none';",
|
|
549
|
+
"default-src 'none'; script-src 'nonce-#{csp_nonce}' 'strict-dynamic'; style-src 'self' 'nonce-#{csp_nonce}'; font-src 'self'; img-src 'self'; connect-src 'self'; object-src 'none'; base-uri 'none'; frame-ancestors 'none'; form-action 'self';",
|
|
378
550
|
},
|
|
379
551
|
[body],
|
|
380
552
|
]
|
|
@@ -25,13 +25,7 @@ module Logster::Rails
|
|
|
25
25
|
logger.level = ::Rails.logger.level
|
|
26
26
|
|
|
27
27
|
Logster.logger = config.logger = logger
|
|
28
|
-
|
|
29
|
-
if rails_71?
|
|
30
|
-
::Rails.logger.broadcast_to(logger)
|
|
31
|
-
else
|
|
32
|
-
logger.chain(::Rails.logger)
|
|
33
|
-
::Rails.logger = logger
|
|
34
|
-
end
|
|
28
|
+
::Rails.logger.broadcast_to(logger)
|
|
35
29
|
end
|
|
36
30
|
|
|
37
31
|
def initialize!(app)
|
|
@@ -42,14 +36,9 @@ module Logster::Rails
|
|
|
42
36
|
app.middleware.insert_before ActionDispatch::ShowExceptions, Logster::Middleware::Reporter
|
|
43
37
|
end
|
|
44
38
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
else
|
|
49
|
-
app.middleware.insert_before ActionDispatch::DebugExceptions,
|
|
50
|
-
Logster::Middleware::DebugExceptions,
|
|
51
|
-
Rails.application
|
|
52
|
-
end
|
|
39
|
+
app.middleware.insert_before ActionDispatch::DebugExceptions,
|
|
40
|
+
Logster::Middleware::DebugExceptions,
|
|
41
|
+
Rails.application
|
|
53
42
|
|
|
54
43
|
app.middleware.delete ActionDispatch::DebugExceptions
|
|
55
44
|
app.config.colorize_logging = false
|
|
@@ -63,13 +52,8 @@ module Logster::Rails
|
|
|
63
52
|
private
|
|
64
53
|
|
|
65
54
|
def logster_enabled?
|
|
66
|
-
return ::Rails.logger == Logster.logger unless rails_71?
|
|
67
55
|
::Rails.logger.broadcasts.include?(Logster.logger)
|
|
68
56
|
end
|
|
69
|
-
|
|
70
|
-
def rails_71?
|
|
71
|
-
::Rails.version >= "7.1"
|
|
72
|
-
end
|
|
73
57
|
end
|
|
74
58
|
|
|
75
59
|
class Railtie < ::Rails::Railtie
|
data/lib/logster/redis_store.rb
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "json"
|
|
4
|
-
# Disable rubocop because the require is only redundant for Ruby 3.2+ but we still support Ruby 3.1
|
|
5
|
-
require "set" # rubocop:disable Lint/RedundantRequireStatement
|
|
6
4
|
require "logster/base_store"
|
|
7
5
|
require "logster/redis_rate_limiter"
|
|
8
6
|
|
data/lib/logster/version.rb
CHANGED
data/lib/logster.rb
CHANGED
|
@@ -57,4 +57,4 @@ end
|
|
|
57
57
|
# check logster/configuration.rb for config options
|
|
58
58
|
# Logster.config.environments << :staging
|
|
59
59
|
|
|
60
|
-
require "logster/rails/railtie" if defined?(::Rails::VERSION)
|
|
60
|
+
require "logster/rails/railtie" if defined?(::Rails::VERSION)
|