Skip to content

Commit

Permalink
Merge pull request #2862 from FlowFuse/chore-sentry-reporting
Browse files Browse the repository at this point in the history
Feature: Error tracking
  • Loading branch information
knolleary authored Oct 2, 2023
2 parents a521daf + 1e739db commit 3784d6a
Show file tree
Hide file tree
Showing 10 changed files with 1,014 additions and 4,036 deletions.
4 changes: 4 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Used for BUILD time sentry reporting
SENTRY_AUTH_TOKEN=
SENTRY_ORG=
SENTRY_PROJECT=
2 changes: 1 addition & 1 deletion .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
"ignoreDeclarationSort": true
}
],
"no-console": ["error", { "allow": ["info", "warn", "error"] }],
"no-console": ["error", { "allow": ["debug", "info", "warn", "error"] }],

// plugin:import
"import/order": [
Expand Down
263 changes: 144 additions & 119 deletions config/webpack.config.js
Original file line number Diff line number Diff line change
@@ -1,141 +1,166 @@
const path = require('path')

const { sentryWebpackPlugin } = require('@sentry/webpack-plugin')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const CopyPlugin = require('copy-webpack-plugin')
const Dotenv = require('dotenv-webpack')
const dotenv = require('dotenv')
const DotenvPlugin = require('dotenv-webpack')
const HTMLWebpackPlugin = require('html-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const { VueLoaderPlugin } = require('vue-loader')

require('dotenv').config()

function getPath (file) {
return path.resolve(__dirname, '..', file)
}

module.exports = {
entry: {
main: getPath('frontend/src/main.js'),
setup: getPath('frontend/src/setup.js')
},
output: {
path: getPath('frontend/dist/app'),
publicPath: '/app/',
assetModuleFilename: './assets/[hash][ext][query]'
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader'
}
},
{
test: /\.vue$/,
loader: 'vue-loader'
},
{
test: /\.css$/i,
include: getPath('frontend/src'),
use: [
{
loader: MiniCssExtractPlugin.loader,
options: {}
},
{
loader: 'css-loader',
options: { importLoaders: 1 }
},
{
loader: 'postcss-loader',
options: {
postcssOptions: {
config: path.resolve(__dirname, 'postcss.config.js')
module.exports = function (env, argv) {
const config = {
entry: {
main: getPath('frontend/src/main.js'),
setup: getPath('frontend/src/setup.js')
},
output: {
path: getPath('frontend/dist/app'),
publicPath: '/app/',
assetModuleFilename: './assets/[hash][ext][query]'
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader'
}
},
{
test: /\.vue$/,
loader: 'vue-loader'
},
{
test: /\.css$/i,
include: getPath('frontend/src'),
use: [
{
loader: MiniCssExtractPlugin.loader,
options: {}
},
{
loader: 'css-loader',
options: { importLoaders: 1 }
},
{
loader: 'postcss-loader',
options: {
postcssOptions: {
config: path.resolve(__dirname, 'postcss.config.js')
}
}
}
]
}, {
test: /\.css$/i,
exclude: getPath('frontend/src'),
use: [
{
loader: 'style-loader',
options: {}
},
{
loader: 'css-loader',
options: { importLoaders: 1 }
}
]
}, {
test: /\.scss$/,
use: [
'style-loader',
{
loader: 'css-loader',
options: { import: true, url: true }
},
'sass-loader'
]
},
{
test: /\.(eot|ttf|woff|woff2)(\?\S*)?$/,
loader: 'file-loader',
options: {
name: '[name][contenthash:8].[ext]'
}
]
}, {
test: /\.css$/i,
exclude: getPath('frontend/src'),
use: [
{
loader: 'style-loader',
options: {}
},
{
loader: 'css-loader',
options: { importLoaders: 1 }
}
]
}, {
test: /\.scss$/,
use: [
'style-loader',
{
loader: 'css-loader',
options: { import: true, url: true }
},
'sass-loader'
]
},
{
test: /\.(eot|ttf|woff|woff2)(\?\S*)?$/,
loader: 'file-loader',
},
{
test: /\.(png|jpe?g|gif|webm|mp4|svg)$/,
type: 'asset'
}
]
},
plugins: [
new VueLoaderPlugin(),
new CleanWebpackPlugin(),
new HTMLWebpackPlugin({
title: 'FlowFuse',
template: getPath('frontend/src/index.html'),
favicon: getPath('frontend/public/favicon.ico'),
filename: getPath('frontend/dist/index.html'),
chunks: ['main']
}),
new HTMLWebpackPlugin({
title: 'FlowFuse',
template: getPath('frontend/src/setup.html'),
favicon: getPath('frontend/public/favicon.ico'),
filename: getPath('frontend/dist-setup/setup.html'),
chunks: ['setup']
}),
new MiniCssExtractPlugin(),
new CopyPlugin({
patterns: [
{ from: getPath('frontend/public'), to: '..' }
],
options: {
name: '[name][contenthash:8].[ext]'
concurrency: 100
}
},
{
test: /\.(png|jpe?g|gif|webm|mp4|svg)$/,
type: 'asset'
}
]
},
plugins: [
new VueLoaderPlugin(),
new CleanWebpackPlugin(),
new HTMLWebpackPlugin({
title: 'FlowFuse',
template: getPath('frontend/src/index.html'),
favicon: getPath('frontend/public/favicon.ico'),
filename: getPath('frontend/dist/index.html'),
chunks: ['main']
}),
new HTMLWebpackPlugin({
title: 'FlowFuse',
template: getPath('frontend/src/setup.html'),
favicon: getPath('frontend/public/favicon.ico'),
filename: getPath('frontend/dist-setup/setup.html'),
chunks: ['setup']
}),
new MiniCssExtractPlugin(),
new CopyPlugin({
patterns: [
{ from: getPath('frontend/public'), to: '..' }
],
options: {
concurrency: 100
}
}),
new Dotenv()
],
optimization: {
moduleIds: 'deterministic',
runtimeChunk: 'single',
splitChunks: {
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
priority: -10,
chunks: 'all'
}),
new DotenvPlugin()
],
optimization: {
moduleIds: 'deterministic',
runtimeChunk: 'single',
splitChunks: {
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
priority: -10,
chunks: 'all'
}
}
}
},
devServer: {
port: 3000,
historyApiFallback: true
}
},
devServer: {
port: 3000,
historyApiFallback: true
}

// Add sentry only if ENV var is set
if (process.env.SENTRY_AUTH_TOKEN) {
config.plugins.push(
sentryWebpackPlugin({
authToken: process.env.SENTRY_AUTH_TOKEN,
org: process.env.SENTRY_ORG,
project: process.env.SENTRY_PROJECT,

telemetry: false,

errorHandler: (err) => {
console.warn(`Error with Sentry reporting: ${err.toString()}`)
}
})
)
}

return config
}
24 changes: 21 additions & 3 deletions docs/admin/telemetry.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,17 +108,19 @@ The FlowFuse UI can be configured to track usage to help understand how users ar
It supports integration with two different services:

- [PostHog](https://posthog.com/) _(recommended)_: You will require your own API key to pass into the `yml`, which will begin the logging of user interactions.
- [Plausible](https://plausible.io/): _(deprecated since 0.9 and will be removed in the future)_: You can setup your own account, and pass the relevant domain to the `yml` in the telemetry configuration. As this
option is deprecated, details of how to configure are no longer provided.
- [Sentry](https://sentry.io/) _(recommended)_: You will need to specify your Sentry DSN for the frontend and back-end
- [Plausible](https://plausible.io/): _(deprecated since 0.9 and will be removed in the future)_: You can setup your own account, and pass the relevant domain to the `yml` in the telemetry configuration. As this option is deprecated, details of how to configure are no longer provided.

### Configuring Telemetry

Option | Description
--------------|------------
`telemetry.enabled` | Enables the anonymous usage telemetry of the platform. Default: `true`
`telemetry.backend.sentry.dsn` | The API key provided to you from your own sentry account. Default: `null`
`telemetry.frontend.posthog.apikey` | The API key provided to you from your own PostHog account. Default: `null`
`telemetry.frontend.posthog.capture_pageview` | FlowFuse is designed as to provide custom posthog `$pageview` events that provide more detail on navigation than the default, and suit a single page application better. As such, we recommend setting this to false in order to prevent duplicate `pageleave`/`pageview` events firing. Default: `true`

`telemetry.frontend.sentry.dsn` | The API key provided to you from your own sentry account. Default: `null`
`telemetry.frontend.sentry.production_mode` | Should this instance be treated as production (lower session count recorded). Default: `false`

```yaml
telemetry:
Expand All @@ -127,4 +129,20 @@ telemetry:
posthog:
apikey: <api-key>
capture_pageview: false
sentry:
dsn: <dsn-key>
production_mode: true
backend:
sentry:
dsn: <dsn-key>
```
#### Telemetry During Build
Configure .env with the auth token, org and project name for the frontend project.
```yaml
# Used for BUILD time sentry reporting
SENTRY_AUTH_TOKEN=
SENTRY_ORG=
SENTRY_PROJECT=
```
11 changes: 11 additions & 0 deletions forge/forge.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ const postoffice = require('./postoffice')
const routes = require('./routes')
const settings = require('./settings')

require('dotenv').config()

// type defs for JSDoc and VSCode Intellisense

/**
Expand Down Expand Up @@ -75,6 +77,15 @@ module.exports = async (options = {}) => {
trustProxy: true,
logger: loggerConfig
})

if (runtimeConfig.telemetry.backend?.sentry?.dsn) {
server.register(require('@immobiliarelabs/fastify-sentry'), {
dsn: runtimeConfig.telemetry.backend.sentry.dsn,
environment: process.env.NODE_ENV,
release: `flowforge@${runtimeConfig.version}`
})
}

server.addHook('onError', async (request, reply, error) => {
// Useful for debugging when a route goes wrong
// console.error(error.stack)
Expand Down
4 changes: 4 additions & 0 deletions forge/routes/ui/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,10 @@ module.exports = async function (app) {
<!-- End of HubSpot Embed Code -->`
}

if (telemetry.frontend.sentry.dsn) {
injection += `<script>window.sentryConfig = { dsn: "${telemetry.frontend.sentry.dsn}", production_mode: "${telemetry.frontend.sentry.production_mode}" }</script>`
}

// inject into index.html
cachedIndex = data.replace(/<script>\/\*inject-ff-scripts\*\/<\/script>/g, injection)
}
Expand Down
Loading

0 comments on commit 3784d6a

Please sign in to comment.