前端監控進階篇 — Sentry 監控 Next.js 專案實踐

luffyZh發表於2020-02-15

前言

上一篇相關文章介紹了 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 地址。如下圖所示:

前端監控進階篇 — Sentry 監控  Next.js 專案實踐

初始化專案

  • 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 監控  Next.js 專案實踐

  • Sentry 專案 Issue 列表

如下圖所示, Sentry 正常捕獲到了程式碼裡所丟擲的異常。

前端監控進階篇 — Sentry 監控  Next.js 專案實踐

  • 郵件提醒

如下圖所示,錯誤還可以通過郵件傳送給開發者,更為方便及時的提醒。

前端監控進階篇 — Sentry 監控  Next.js 專案實踐

擴充套件專案 —— 捕獲資料請求

上面完成了初始化專案,可以正常捕獲客戶端、服務端、初始化以及執行中的異常錯誤。不過呢,在使用過程中發現 Sentry 並不能捕獲網路請求的錯誤。比如,專案裡使用的都是 Fetch,如下圖所示,初始化頁面的網路請求報錯404,但是 Sentry 的控制檯並沒有捕獲到錯誤。

fetch-error-not-report

正常的商業系統,當使用者多且業務邏輯複雜的時候,有一定需求需要對網路請求也進行簡單的監控。所以,簡單來擴充套件一下專案,使其能捕獲資料請求的異常。

  • 封裝 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 監控  Next.js 專案實踐

可以見到,Sentry 成功上報了資料請求的異常資訊。

這裡其實存在一個問題,如果恰好伺服器崩潰了怎麼辦?豈不是一天開發人員要收到無數封的郵件轟炸?不是很合理嘛,能不能 Fetch Error 不發郵件呢?這一點在下文會提到~

擴充套件專案 —— 原始碼定位

原始碼定位是什麼意思呢,我們先來看看現在正常的錯誤上報資訊,如下圖所示:

前端監控進階篇 — Sentry 監控  Next.js 專案實踐

從上圖可以看出來,因為程式碼是經過壓縮的,所以錯誤的上下文,堆疊資訊也是壓縮過後的,這樣的程式碼排查問題相當困難了,只是有個報錯而已。那麼眾所周知,我們的構建工具一般來說也就是 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 需要我們去生成,具體如下圖:

前端監控進階篇 — Sentry 監控  Next.js 專案實踐

生成 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 檔案,如下圖所示:

前端監控進階篇 — Sentry 監控  Next.js 專案實踐

build 過後,在 Sentry 專案控制檯可以檢視到對應版本的 source-map 檔案,如下圖所示:

前端監控進階篇 — Sentry 監控  Next.js 專案實踐

然後重新執行程式,會發現錯誤堆疊資訊已經可以檢視原始碼了,具體那一行程式碼丟擲的異常都可以檢視~

前端監控進階篇 — Sentry 監控  Next.js 專案實踐

擴充套件專案 —— 使用者反饋

關於使用者反饋,那就是仁者見仁智者見智了,如果你希望線上專案崩潰的時候顯得很優雅,並且你有訴求你的使用者能夠幫你排查問題,復現步驟,那還是可以的。不過呢,不是所有人都希望這樣,因為這就代表著你讓使用者發現了系統的問題,可能有的人不希望使用者發現,或者希望的是私下反饋及時解決避免更多的人發現~總而言之,這也算是一個知識點,就簡單配置一下玩耍玩耍:

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 監控  Next.js 專案實踐

  • 第二步:填寫返回資訊

    前端監控進階篇 — Sentry 監控  Next.js 專案實踐

  • 第三步:Sentry 控制使用者反饋檢視對應資訊

    前端監控進階篇 — Sentry 監控  Next.js 專案實踐

從上圖可以看出,出現了反饋建議,並且在 Sentry 的反饋列表裡,對應反饋內容也正常顯示~

擴充套件專案 —— 關聯 Github/Gitlab

繼續來擴充套件專案,那就是 Sentry 監控的錯誤還可以關聯 Github/Gitlab,然後直接新建 Issue,對於測試同學來說,簡直是福音啊~

第一次初始化配置我們需要在錯誤詳情頁與專案進行關聯,如下圖所示:

前端監控進階篇 — Sentry 監控  Next.js 專案實踐

然後安裝相應的外掛,這裡以 Github 為例:

前端監控進階篇 — Sentry 監控  Next.js 專案實踐

這裡是安裝不上的,因為這些外掛需要在安裝 Sentry 的同時一起安裝才可以,也就是通過 config.yml 檔案配置對應外掛。具體的就是上篇文章內容了,因為某些原因(疫情沒結束,家裡沒網路,4G 網路確實有點費勁),這裡就不進行安裝了,對應地址如下:Sentry 安裝 Github Integrations

安裝好之後關聯 Issue 效果如下:

下面這些截圖從網上獲取,對應文章 -> 另一篇 Sentry 文章

前端監控進階篇 — Sentry 監控  Next.js 專案實踐

前端監控進階篇 — Sentry 監控  Next.js 專案實踐

前端監控進階篇 — Sentry 監控  Next.js 專案實踐

其實公司專案沒有必要這麼新建 JIRA,有測試同學以及郵件提醒,所以夠用了,不過如果是個人專案,還是挺有用的。感興趣的可以自行配置一下~不配置也沒事,不耽誤使用。

擴充套件專案 —— 錯誤級別以及郵件設定

在這裡承接上文,上面提到了 Fetch Error 是捕獲資料請求的異常的,那麼如果伺服器崩潰了,重啟期間,假設有10000個訪問,那麼開發人員就會受到郵件轟炸,不是很合理,有沒有解決辦法呢?有~就是郵件提醒設定。

郵件設定不需要任何程式碼,使用起來也很簡單,當然也很重要,具體設定如下圖所示:

前端監控進階篇 — Sentry 監控  Next.js 專案實踐

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 級別的顏色是橙色,其他正常異常的是紅色

前端監控進階篇 — Sentry 監控  Next.js 專案實踐

  • 錯誤詳情裡,本次異常的 level 對應也是 warning

前端監控進階篇 — Sentry 監控  Next.js 專案實踐

最後重啟專案,就會發現,其他異常依然會有郵件警告,而 Fetch Error 已經沒有郵件警告了,但是我們依然會在 Sentry 控制檯看到它們~很完美!

總結

至此為止,繼上篇基礎篇之後,實踐篇也寫完了,基本包含了所有的可用場景。希望對大家有所幫助,前端監控在生產開發過程中還是比較重要的。感興趣的可以一起交流~

專案地址如下:next-sentry-easy,該專案優化了 Next.js_with-sentry-simple 的 bug,精簡了 Next.js_with-sentry 的複雜邏輯,個人感覺算是一個比較平均的專案,如果感興趣記得 Star,謝謝~

基礎程式碼是master分支,完整程式碼是full-demo分支。

相關文章