前言
上一篇相關文章介紹了 Sentry 的基礎篇 —— 本地安裝,本篇文章以實際專案出發使用 Sentry 進行前端監控實踐。實踐案例選擇 Next.js 專案,具體專案地址next-sentry-easy。
上一篇:前端監控基礎篇 —— Docker + Sentry 搭建前端監控系統
開源社群存在很多搭建 Sentry 的文章,但是關於相關細節配置使用的實踐文章其實並不多,對於新手來說不是很友好,Next.js 相關的就更少之又少了。 Next.js 國內社群並不算龐大,關於 Next.js 的監控說實話我也沒找到太完美的方案,所以自己也嘗試做了幾個方案,Sentry 就是其中的一個選擇。寫這篇文章的原因還有如下的原因:
Next.js 官方示例倉庫裡有兩個關於 sentry 的 Demo,這兩個 Demo 都存在一些問題。
-
第一個with-sentry-simple是一個最基礎的簡單示例,但是此 Demo 存在 bug —— 只在客戶端可以正常上報,而服務端並沒有成功上報。
-
第二個with-sentry是一個比較複雜的案例,問題呢就是太複雜了,不適合新手上手學習。
所以,這裡就綜合一下,弄一個比較適合新手又比較完整的 Demo。
新建 Sentry 專案
新建立一個 Node.js 的專案,新建好之後 Sentry 就會為專案分配一個 DSN 地址。如下圖所示:
初始化專案
- package.json
"@sentry/browser": "^5.11.0",
"@sentry/node": "^5.11.0",
複製程式碼
- next.config.js
const withSourceMaps = require('@zeit/next-source-maps')
module.exports = withSourceMaps({
env: {
SENTRY_DSN: 'http://e69fa8afd38e43e59fc3baf48ec0c681@localhost:9000/11'
},
webpack: (config, options) => {
if (!options.isServer) {
config.resolve.alias['@sentry/node'] = '@sentry/browser'
}
return config
},
})
複製程式碼
上面這麼配置是因為 Next.js 的特殊性,服務端渲染框架,既存在服務端也存在客戶端場景,專案裡引入的只能是一種,通用引入@sentry/node
,所以當客戶端的時候,要替換成@sentry/browser
。
- _app.js
import * as Sentry from '@sentry/node'
Sentry.init({
// Replace with your project's Sentry DSN
dsn: process.env.SENTRY_DSN,
});
static async getInitialProps ({ Component, ctx }) {
let pageProps = {};
try {
if (Component.getInitialProps) {
pageProps = await Component.getInitialProps({ ctx })
}
return { pageProps }
} catch (err) {
// This will work on both client and server sides.
console.log('The Error happened in: ', typeof window === 'undefined' ? 'Server' : 'Client');
Sentry.captureException(err)
return { pageProps };
}
}
複製程式碼
在 _app.js 裡對 Sentry 進行初始化配置,這裡簡單隻配置了 dsn 引數。另外,在 getInitialProps 函式裡進行了操作,如果發生錯誤,使用 Sentry.captureException()
進行上報。
注意,此處主要負責頁面渲染,路由跳轉時的錯誤攔截,可以攔截到客戶端和服務端兩種場景的錯誤。具體為什麼可以去檢視我之前寫的 Next.js 相關文章。
- _error.js
import * as Sentry from '@sentry/node';
const MyError = ({ statusCode, err }) => {
if (err) {
// This will work on both client and server sides in production.
Sentry.captureException(err);
}
return <Error statusCode={statusCode} />
}
複製程式碼
_error.js 我們都知道,是 Next.js 內建的錯誤元件,程式在執行時出現錯誤就會渲染該元件,因此,在這裡主要負責程式執行時的錯誤攔截,比如點選按鈕、提交表單等互動操作的錯誤。
效果
上面初始化工作已經完成,來執行專案簡單檢視一下效果:
- 執行時出現錯誤
如下圖所示,分別點選客戶端和服務端的錯誤頁面。
- Sentry 專案 Issue 列表
如下圖所示, Sentry 正常捕獲到了程式碼裡所丟擲的異常。
- 郵件提醒
如下圖所示,錯誤還可以通過郵件傳送給開發者,更為方便及時的提醒。
擴充套件專案 —— 捕獲資料請求
上面完成了初始化專案,可以正常捕獲客戶端、服務端、初始化以及執行中的異常錯誤。不過呢,在使用過程中發現 Sentry 並不能捕獲網路請求的錯誤。比如,專案裡使用的都是 Fetch,如下圖所示,初始化頁面的網路請求報錯404,但是 Sentry 的控制檯並沒有捕獲到錯誤。
正常的商業系統,當使用者多且業務邏輯複雜的時候,有一定需求需要對網路請求也進行簡單的監控。所以,簡單來擴充套件一下專案,使其能捕獲資料請求的異常。
- 封裝 FetchError
class FetchError extends Error {
constructor(url = 'http://localhost', props) {
super(props);
const { message, traceId, userId } = props;
if (Error.captureStackTrace) {
Error.captureStackTrace(this, FetchError)
}
this.name = 'FetchError'
// Custom debugging information
this.url = url
this.message = message
traceId && (this.traceId = traceId)
userId && (this.userId = userId)
this.date = new Date()
console.log(this)
}
}
export default FetchError
複製程式碼
- 封裝 fetch
import fetch from 'isomorphic-unfetch';
import * as Sentry from '@sentry/node';
import FetchError from './FetchError';
function dealStatus(res) {
if (res.status !== 200) {
const err = new FetchError(res.url, {
traceId: Math.random() * 10000, // create your application traceId
userId: 26,
message: `${typeof window !== 'undefined' ? 'Client' : 'Server'} fetch error - ${res.status}`
})
// This will work on both client and server sides in production.
Sentry.captureException(err);
}
return res;
}
// initial fetch
const unfetch = Object.create(null);
...
HTTP_METHOD.forEach(method => {
// is can send data in opt.body
const bodyData = BODY_METHOD.includes(method);
unfetch[method] = (path, { data } = {}) => {
let url = path;
const opts = {
method,
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
mode: 'cors',
cache: 'no-cache'
};
...
return fetch(url, opts)
.then(dealStatus)
.then(res => res.json());
};
});
export default unfetch;
複製程式碼
上面,簡單的封裝了 Fetch 函式,然後在裡面處理了錯誤異常,如果返回碼不是 200,表示本次請求出現錯誤,使用Sentry.captureException(err);
上報錯誤。
具體效果如下圖所示:
可以見到,Sentry 成功上報了資料請求的異常資訊。
這裡其實存在一個問題,如果恰好伺服器崩潰了怎麼辦?豈不是一天開發人員要收到無數封的郵件轟炸?不是很合理嘛,能不能 Fetch Error 不發郵件呢?這一點在下文會提到~
擴充套件專案 —— 原始碼定位
原始碼定位是什麼意思呢,我們先來看看現在正常的錯誤上報資訊,如下圖所示:
從上圖可以看出來,因為程式碼是經過壓縮的,所以錯誤的上下文,堆疊資訊也是壓縮過後的,這樣的程式碼排查問題相當困難了,只是有個報錯而已。那麼眾所周知,我們的構建工具一般來說也就是 webpack,可以配置 source-map 來對程式碼進行對映。在這裡,Sentry 也可以,通過配置 release 版本資訊就可以更精確的定位錯誤資訊。
Sentry官方給出來的配置方案有三種,release API、sentry-cli和sentry-webpack-plugin,前兩個都需要手動上傳專案的 source-map 檔案,第三個通過程式碼配置在專案 build 的時候進行上傳,所以這裡採用第三種,更為方便。其他的社群都有對應文章,可以去看。
- 第一步:安裝依賴
yarn add @zeit/next-source-maps @sentry/webpack-plugin
複製程式碼
- 第二步:配置 .sentryclirc
[defaults]
url = http://localhost:9000/
org = luffyzh
project = next-sentry-example
[auth]
token = your token api
複製程式碼
這個配置檔案是配置 release 的關鍵檔案,預設引數是上報路徑,組織名稱以及專案名稱,通過這三個資訊就可以準確定位到具體的專案了。然後 auth 下面的 token 需要我們去生成,具體如下圖:
生成 token 的時候一定要勾選 project:write 許可權才可以!!!
- 第三步:配置 next.config.js
const webpack = require('webpack')
const withSourceMaps = require('@zeit/next-source-maps')()
const SentryCliPlugin = require('@sentry/webpack-plugin')
module.exports = withSourceMaps({
webpack: (config, { isServer, buildId }) => {
config.plugins.push(
...[
new webpack.DefinePlugin({
'process.env.SENTRY_RELEASE': JSON.stringify(buildId)
}),
new SentryCliPlugin({
include: ['.next'], // 上傳的資料夾,next專案傳.next資料夾就行
ignore: ['node_modules', 'next.config.js'], // 忽略的檔案
configFile: '.sentryclirc', // 上傳相關的配置檔案
release: buildId, // 版本號
urlPrefix: '~/_next' // 最關鍵的,相對路徑
})
]
)
...
return config
},
})
複製程式碼
這裡有非常重要的一點,就是
urlPrefix
這個引數,它預設是~
也就是域名加上上傳的檔案路徑,但是 Next.js 專案位元殊,檔案路徑前面都會加上一個/_next
,所以需要加上這個配置項才可以正常訪問,要不然 Sentry 獲取不到對應的 source-map 檔案。
- 第四步:程式碼配置
// _app.js
Sentry.init({
// Replace with your project's Sentry DSN
dsn: process.env.SENTRY_DSN,
release: process.env.SENTRY_RELEASE, // 版本號
});
複製程式碼
這裡同樣有個注意點,就是
Sentry.init({})
的版本號必須與next.config.js
的配置項裡的版本號一致,這樣 Sentry 才可以關聯上。在 Next.js 專案中非常簡單,使用內建的 buildId 即可~
- 第五步:build + 檢視效果
執行yarn build
命令會發現控制檯在不斷的上傳 source-map 檔案,如下圖所示:
build 過後,在 Sentry 專案控制檯可以檢視到對應版本的 source-map 檔案,如下圖所示:
然後重新執行程式,會發現錯誤堆疊資訊已經可以檢視原始碼了,具體那一行程式碼丟擲的異常都可以檢視~
擴充套件專案 —— 使用者反饋
關於使用者反饋,那就是仁者見仁智者見智了,如果你希望線上專案崩潰的時候顯得很優雅,並且你有訴求你的使用者能夠幫你排查問題,復現步驟,那還是可以的。不過呢,不是所有人都希望這樣,因為這就代表著你讓使用者發現了系統的問題,可能有的人不希望使用者發現,或者希望的是私下反饋及時解決避免更多的人發現~總而言之,這也算是一個知識點,就簡單配置一下玩耍玩耍:
Sentry.init({
// Replace with your project's Sentry DSN
dsn: process.env.SENTRY_DSN,
beforeSend(event) {
// Check if it is an exception, if so, show the report dialog
if (process.browser && event.exception) {
Sentry.showReportDialog({
eventId: event.event_id
});
}
return event;
}
});
複製程式碼
其實就一個 API,Sentry.showReportDialog()
,我們在出現異常的時候呼叫它,就可以讓使用者填寫對應的內容進行展示。具體過程如下圖所示:
-
第一步:執行程式出現錯誤
-
第二步:填寫返回資訊
-
第三步:Sentry 控制使用者反饋檢視對應資訊
從上圖可以看出,出現了反饋建議,並且在 Sentry 的反饋列表裡,對應反饋內容也正常顯示~
擴充套件專案 —— 關聯 Github/Gitlab
繼續來擴充套件專案,那就是 Sentry 監控的錯誤還可以關聯 Github/Gitlab,然後直接新建 Issue,對於測試同學來說,簡直是福音啊~
第一次初始化配置我們需要在錯誤詳情頁與專案進行關聯,如下圖所示:
然後安裝相應的外掛,這裡以 Github 為例:
這裡是安裝不上的,因為這些外掛需要在安裝 Sentry 的同時一起安裝才可以,也就是通過 config.yml 檔案配置對應外掛。具體的就是上篇文章內容了,因為某些原因(疫情沒結束,家裡沒網路,4G 網路確實有點費勁),這裡就不進行安裝了,對應地址如下:Sentry 安裝 Github Integrations。
安裝好之後關聯 Issue 效果如下:
下面這些截圖從網上獲取,對應文章 -> 另一篇 Sentry 文章
其實公司專案沒有必要這麼新建 JIRA,有測試同學以及郵件提醒,所以夠用了,不過如果是個人專案,還是挺有用的。感興趣的可以自行配置一下~不配置也沒事,不耽誤使用。
擴充套件專案 —— 錯誤級別以及郵件設定
在這裡承接上文,上面提到了 Fetch Error 是捕獲資料請求的異常的,那麼如果伺服器崩潰了,重啟期間,假設有10000個訪問,那麼開發人員就會受到郵件轟炸,不是很合理,有沒有解決辦法呢?有~就是郵件提醒設定。
郵件設定不需要任何程式碼,使用起來也很簡單,當然也很重要,具體設定如下圖所示:
Project -> Alerts -> Rules -> Edit
進行設定。
上面我設定了,只有 error 以上級別的異常才會發郵件,如果是 error 以下的級別(warning/info)就不發郵件了,但是仍然上報。接下來我們就是需要將我們的 Fetch Error 重新設定級別,因為預設上報的就是 error 級別。
// unfetch.js
/* config the fetch error is warning */
Sentry.withScope(function(scope) {
scope.setLevel('warning');
Sentry.captureException(err);
});
複製程式碼
通過上面的程式碼,我們將 Fetch Error 設定成了 warning 級別,具體如下圖:
- warning 級別的顏色是橙色,其他正常異常的是紅色
- 錯誤詳情裡,本次異常的 level 對應也是 warning
最後重啟專案,就會發現,其他異常依然會有郵件警告,而 Fetch Error 已經沒有郵件警告了,但是我們依然會在 Sentry 控制檯看到它們~很完美!
總結
至此為止,繼上篇基礎篇之後,實踐篇也寫完了,基本包含了所有的可用場景。希望對大家有所幫助,前端監控在生產開發過程中還是比較重要的。感興趣的可以一起交流~
專案地址如下:next-sentry-easy,該專案優化了 Next.js_with-sentry-simple 的 bug,精簡了 Next.js_with-sentry 的複雜邏輯,個人感覺算是一個比較平均的專案,如果感興趣記得 Star,謝謝~
基礎程式碼是
master
分支,完整程式碼是full-demo
分支。