Skip to content

Commit

Permalink
Merge pull request #1283 from MarcelRobitaille/1275-add-logging
Browse files Browse the repository at this point in the history
 Add logging to diagnose bugs in production
  • Loading branch information
christianlupus authored Oct 25, 2022
2 parents 08e7930 + 484c903 commit 511d830
Show file tree
Hide file tree
Showing 12 changed files with 140 additions and 6 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@
[1277](https://github.com/nextcloud/cookbook/pull/1277)
- Prepare the GitHub action scripts to be compatible with the upcoming version split in version 0.10.0
[#1285](https://github.com/nextcloud/cookbook/pull/1285) @christianlupus
- Add logging to diagnose bugs in production
[#1283](https://github.com/nextcloud/cookbook/pull/1283) @MarcelRobitaille

### Documentation
- Fix bad writing
Expand Down
21 changes: 21 additions & 0 deletions docs/dev/frontend/logging.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Debugging in production through logging

Sometimes, it is necessary to debug problems in production
where it is not possible to use a debugger or other developer tools to find the
source of the problem.
For example, if a user reports an issue that cannot be reproduced locally,
you can instruct them to enable logging to narrow down the source of the issue.

To enable logging, a user only has to run the following before loading the app/refreshing the page:
```js
localStorage.setItem('COOKBOOK_LOGGING_ENABLED', 'true')
```

This will automatically be reset after 30 minutes so verbose logging is not enabled permanently for the user.

The log level is also configurable. For example:
```js
localStorage.setItem('COOKBOOK_LOGGING_LEVEL', 'debug')
```

The documentation for the logging library used is available [here](https://www.npmjs.com/package/vuejs-logger).
1 change: 1 addition & 0 deletions docs/dev/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ If you want to **help contributing**, please see the [page about contributing](c

- [Webpack `BundleAnalyzer` Plugin](frontend/webpack-bundle-analyzer)
- [Hot reload capability for faster frontend development](frontend/hot-reload)
- [Debugging in production through logging](frontend/logging)

## Backend

Expand Down
39 changes: 33 additions & 6 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
"vue-modal-dialogs": "3.0.0",
"vue-router": "^3.1.6",
"vue-showdown": "^2.4.1",
"vuejs-logger": "1.5.5",
"vuex": "^3.1.3"
},
"devDependencies": {
Expand Down
1 change: 1 addition & 0 deletions src/components/AppIndex.vue
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export default {
},
},
mounted() {
this.$log.info("AppIndex mounted")
this.loadAll()
},
methods: {
Expand Down
1 change: 1 addition & 0 deletions src/components/AppMain.vue
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ export default {
*/
},
mounted() {
this.$log.info("AppMain mounted")
subscribe("navigation-toggled", this.updateAppNavigationOpen)
},
unmounted() {
Expand Down
6 changes: 6 additions & 0 deletions src/components/AppNavi.vue
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ export default {
},
computed: {
totalRecipeCount() {
this.$log.debug("Calling totalRecipeCount")
let total = this.uncatRecipes
for (let i = 0; i < this.categories.length; i++) {
total += this.categories[i].recipeCount
Expand All @@ -168,11 +169,13 @@ export default {
// Register a method hook for navigation refreshing
refreshRequired(newVal, oldVal) {
if (newVal !== oldVal && newVal === true) {
this.$log.debug("Calling getCategories from refreshRequired")
this.getCategories()
}
},
},
mounted() {
this.$log.info("AppNavi mounted")
this.getCategories()
},
methods: {
Expand Down Expand Up @@ -303,6 +306,7 @@ export default {
* Fetch and display recipe categories
*/
async getCategories() {
this.$log.debug("Calling getCategories")
const $this = this
this.loading.categories = true
try {
Expand Down Expand Up @@ -369,6 +373,7 @@ export default {
* Reindex all recipes
*/
reindex() {
this.$log.debug("Calling reindex")
const $this = this
if (this.scanningLibrary) {
// No repeat clicks until we're done
Expand All @@ -387,6 +392,7 @@ export default {
// This refreshes the current router view in case items in it changed during reindex
$this.$router.go()
} else {
this.$log.debug("Calling getCategories from reindex")
$this.getCategories()
}
})
Expand Down
1 change: 1 addition & 0 deletions src/components/RecipeEdit.vue
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,7 @@ export default {
},
},
mounted() {
this.$log.info("RecipeEdit mounted")
const $this = this
// Store the initial recipe configuration for possible later use
Expand Down
1 change: 1 addition & 0 deletions src/components/RecipeView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -522,6 +522,7 @@ export default {
},
},
mounted() {
this.$log.info("RecipeView mounted")
this.setup()
// Register data load method hook for access from the controls components
this.$root.$off("reloadRecipeView")
Expand Down
68 changes: 68 additions & 0 deletions src/js/logging.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// TODO: Switch to vuejs3-logger when we switch to Vue 3
import VueLogger from "vuejs-logger"
import moment from "@nextcloud/moment"

const DEFAULT_LOG_LEVEL = "info"
// How many minutes the logging configuration is valid for
const EXPIRY_MINUTES = 30
// localStorage keys
const KEY_ENABLED = "COOKBOOK_LOGGING_ENABLED"
const KEY_EXPIRY = "COOKBOOK_LOGGING_EXPIRY"
const KEY_LOG_LEVEL = "COOKBOOK_LOGGING_LEVEL"

// Check if the logging configuration in local storage has expired
//
// Since the expiry entry is added by us after the first run where
// the enabled entry is detected, this only checks if it has been EXPIRY_MINUTES
// since the first run, not EXPIRY_MINUTES since the user added the entry
// This is a reasonable comprimise to simplify what the user has to do to enable
// logging. We don't want them to have to setup the expiry as well
const isExpired = (timestamp) => {
if (timestamp === null) {
return false
}

return moment().isAfter(parseInt(timestamp, 10))
}

const isEnabled = () => {
const DEFAULT = false
const userValue = localStorage.getItem(KEY_ENABLED)
const expiry = localStorage.getItem(KEY_EXPIRY)

// Detect the first load after the user has enabled logging
// Set the expiry so the logging isn't enabled forever
if (userValue !== null && expiry === null) {
localStorage.setItem(
KEY_EXPIRY,
moment().add(EXPIRY_MINUTES, "m").valueOf()
)
}

if (isExpired(expiry)) {
localStorage.removeItem(KEY_ENABLED)
localStorage.removeItem(KEY_EXPIRY)

return DEFAULT
}

// Local storage converts everything to string
// Use JSON.parse to transform "false" -> false
return JSON.parse(userValue) ?? DEFAULT
}

export default function setupLogging(Vue) {
const logLevel = localStorage.getItem(KEY_LOG_LEVEL) ?? DEFAULT_LOG_LEVEL

Vue.use(VueLogger, {
isEnabled: isEnabled(),
logLevel,
stringifyArguments: false,
showLogLevel: true,
showMethodName: true,
separator: "|",
showConsoleColors: true,
})

Vue.$log.info(`Setting up logging with log level ${logLevel}`)
}
4 changes: 4 additions & 0 deletions src/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import Vue from "vue"
import * as ModalDialogs from "vue-modal-dialogs"

import helpers from "cookbook/js/helper"
import setupLogging from "cookbook/js/logging"

import router from "./router"
import store from "./store"
Expand Down Expand Up @@ -52,10 +53,13 @@ Vue.use(VueShowdown, {
// https://github.com/rlemaigre/vue3-promise-dialog
Vue.use(ModalDialogs)

setupLogging(Vue)

// Pass translation engine to Vue
Vue.prototype.t = window.t

// Start the app once document is done loading
Vue.$log.info("Main is done. Creating App.")
const App = Vue.extend(AppMain)
new App({
store,
Expand Down

0 comments on commit 511d830

Please sign in to comment.