😇

Nuxtで生成したsourcemapをNew RelicにPublishするGitHub Actionsを用意する

2024/03/07に公開

概要

  • NuxtとFirebaseとNew Relicを利用しています。デプロイパイプラインにGitHub Actionsを使っています
  • New RelicのBrowser monitoringのErrorがトランスパイル後のスタックトレースのため、調査しづらかった
  • sourcemapを連携する術を学び、GitHub Actionsに組み込んだ

設定するまでの流れ

デプロイパイプラインを用意する

これです

https://zenn.dev/cbcloud_blog/articles/47b8df83a72b78

New Relicで諸々用意する

手順はぐぐればでてくるのでここでは省略します

  • Browser monitoringの設定
    • scriptファイルの方を選択したので、JavaScriptファイルを ~/public 以下に保存します
  • Nuxtにscriptファイルをロードする仕組みの用意(例は下記に貼り付けている)
  • API Keyの発行
    • sourcemapをPublishするときに利用します
nuxt.config.ts
export default defineNuxtConfig({
  // ...
  app: {
    head: {
      script: [
        {
          key: 'NewRelic',
          src: `/js/newRelic/newrelic.js`,
          defer: true,
          type: 'text/javascript'
        }
      ]
    }
  },
  // ...

この設定をすることで、トランスパイル後のコードで、 window.newrelic.xxx と呼び出すことができます

プロジェクトに @newrelic/publish-sourcemap@newrelic/publish-sourcemapをインストールする

https://www.npmjs.com/package/@newrelic/publish-sourcemap
https://www.npmjs.com/package/@types/new-relic-browser

$ yarn add --dev "@newrelic/publish-sourcemap" "@types/new-relic-browser"
  • 1つめはsourcemapをNew Relicへpublishするためのパッケージです
  • 2つめはブラウザ上で利用するNew Relicの型パッケージです。これがないとVSCode上での赤い波線とTypeCheckで死ぬ

Nuxtでsourcemapを出力する設定を追記

これでよしです

nuxt.config.ts
export default defineNuxtConfig({
  // ...
  sourcemap: {
    server: true,
    client: true
  }

})

app.vueやPluginでNew Relicに連携するreleaseIdとreleaseNameを指定できるようにする

今回僕はPluginでやりました。(App.vueのほうが簡単に設定はできます)

$ mkdir -p ~/plugins/newrelic/
$ touch ~/types/newrelic.d.ts
$ touch ~/plugins/newrelic/newrelic.ts
~/types/newrelic.d.ts
/// <reference types="new-relic-browser" />


declare global {
  interface Window {
    newrelic?: typeof newrelic
  }
}

export { }
~/plugins/newrelic/newrelic.ts
import { getVersion } from './version'

export default defineNuxtPlugin(() => {
  if (!process.client) { return }
  const nuxtApp = useNuxtApp()
  nuxtApp.hook('app:mounted', () => {
    const newrelic = window.newrelic || undefined
    if (!newrelic) { return }

    // ビルド時に生成されるversion.tsからリリース情報を取得
    const version = getVersion()
    newrelic.addRelease(version.releaseName, version.releaseId)
  })
})
  • この時点では、version.tsがないため、動きません
  • newrelic変数は、レンダリング後でないと利用できないので、nuxtApp.hook('app:mounted', ...)にしています
  • newrelic.addReleaseでリリース時に生成される値をブラウザ上で実行することができます

version.tsを生成するコマンド(JSファイル)を用意する

generate-release.ts
import fs from 'fs'
import path from 'path'

// 環境変数からリリース情報を取得
const releaseId = process.env.RELEASE_ID || 'defaultReleaseId'
const releaseName = process.env.RELEASE_NAME || 'defaultReleaseName'

// TypeScriptファイルの内容
const content = `
export const getVersion = (): { releaseId: string, releaseName: string} => {
  return { releaseId: '${releaseId}', releaseName: '${releaseName}' }
}
`

console.log('RELEASE_ID: ', releaseId, ', RELEASE_NAME: ', releaseName)
// ファイルを書き出す
const filePath = path.join(__dirname, '../../plugins/newrelic/version.ts')
fs.writeFileSync(filePath, content)

というTypeScriptファイルを用意します

version.tsをビルド前に生成するhookを設定する

最初、package.jsonにコマンドを並べて追加しようと思いましたが、Nuxtのドキュメントを漁ったところ、すばらしい機能がありました

https://nuxt.com/docs/guide/going-further/hooks

https://nuxt.com/docs/api/advanced/hooks#nuxt-hooks-build-time

ビルド前にプログラムが実行できるようなので追記します

nuxt.config.ts
export default defineNuxtConfig({
  // ...
  hooks: {
    build: {
      before () {
        if (lifecycle === 'build') {
          require('./generate-release')
        }
      }
    }
  },
  // ...

これで、 $ yarn run buildで、version.tsが生成されます

GitHub ActionsのJobを用意する

これまで用意したファイルをピタゴラしていきます。今回採用した方法は

sourcemap publshに関連するGitHub ActionsのJobを4つ用意して組み合わせて、コマンド、ソースコードで指定できるようにする

なので

  1. releaseIdとreleaseNameを自動生成するコマンドを実行し、環境変数として登録?する
  2. Nuxtビルド時にversion.tsを出力するコマンドを叩く → version.tsが自動生成される
  3. 合わせて生成されたpublish-sourcemapコマンドを使って、sourcemapをNew RelicへPublish
  4. アプリケーションをデプロイ

という手はずを取りました めんどい

まず1はこうしました

~/.github/deploy.yaml
# ...
      - name: Set up newrelic release environment variables
        run: |
          RELEASE_ID=$(uuidgen)
          RELEASE_NAME=${{ needs.setup.outputs.environment }}_${RELEASE_ID}
          echo "RELEASE_ID=${RELEASE_ID}" >> $GITHUB_ENV
          echo "RELEASE_NAME=${RELEASE_NAME}" >> $GITHUB_ENV
# ...
  • Ubuntuのイメージなので、uuidgenが利用できるため、こいつを使って自動生成して環境変数にしています
  • それぞれ次のJobで利用したいので、$GITHUB_ENVに追加します

2つめはNuxtのビルドです

~/.github/deploy.yaml
# ...
      - name: Build Nuxt
        run: yarn build
        env:
          # ...
          RELEASE_ID: ${{ env.RELEASE_ID }}
          RELEASE_NAME: ${{ env.RELEASE_NAME }}
# ...
  • envに先ほど用意した環境変数を足してください
  • このタイミングで、version.tsが生成されて、newrelic.addReleaseに環境変数経由で値が渡ります

3つめはsourcemapのpublishコマンドです。すーぱーごちゃっています

~/.github/deploy.yaml
# ...
      - name: Upload sourcemap for Newrelic
        run: |
          APPLICATION_URL=$(echo ${APPLICATION_URL_}/_nuxt | sed -e "s|https://|https:\\\/\\\/|g")
          ls -l .nuxt/dist/client/_nuxt/*.js.map | \
          sed -e "s|^.*\.nuxt|.nuxt|g" \
              -e "s|\(\.nuxt/dist/client/_nuxt\)/\(.*\.js\)\.map|./node_modules/.bin/publish-sourcemap \1/\2.map ${APPLICATION_URL}/\2 --apiKey=${N_API_KEY} --applicationId=${N_APPLICATION_ID} --releaseName ${RELEASE_NAME} --releaseId ${RELEASE_ID}|g" | \
          xargs -I{} bash -c {}
        env:
          NEWRELIC_API_KEY: ${{ vars.N_API_KEY }}
          NEWRELIC_APPLICATION_ID: ${{ vars.N_APPLICATION_ID }}
          RELEASE_ID: ${{ env.RELEASE_ID }}
          RELEASE_NAME: ${{ env.RELEASE_NAME }}
          APPLICATION_URL_: ${{ vars.APPLICATION_URL_ }}
# ...
  • まず、環境変数を用意します。APPLICATION_URLでアプリケーションをデプロイするURLを用意します。これからpublishするsourcemapと公開されているどのJavaScriptが組み合わせなのかが必要なためです
    • めっちゃ置換するので、sedにてエスケープをつけていますが、これ今思いましたが、|で区切り文字しているので、いらないな多分
  • publish-sourcemapのコマンドの全体像はこんな感じです↓ これを作っているだけです
    • $ ./node_modules/.bin/publish-sourcemap ${ソースマップファイルのパス} ${公開されている監視したいWebサービスのJavaScriptファイルURL} --applicationId=${N_APPLICATION_ID} --apiKey=${N_API_KEY} --releaseId=${RELEASE_ID} --releaseName=${RELEASE_NAME}
    • releaseIdとreleaseNameは必須なはずなのに、optionalなのがハマりポイント
  • ビルド時に生成されたmapファイルをls -lhで検索して、行ごとにpublish-sourcemapコマンドに置換して、最後にxargsで行単位にbashで実行するというコマンドです

ここまで無事完了したら4番目はデプロイするだけです!

ハマったところ

releaseIdreleaseName について

先人のおかげで助かりました(というか踏み抜いた)

https://ceblog.mediba.jp/post/664462603059445760/newrelic-ブラウザモニタリングのソースマップで色々ハマった話

リリース時、releaseIdreleaseName はコマンドとソースコードでどちらでも利用できるようにする

最初、@newrelic/publish-sourcemapをコマンドでガチャガチャ検証してたので、うまくいったのち、newrelic.addReleaseが必要なのね。となり、あれ?これはビルド前に必要だけど、ビルド後にコマンド使ってpublishしないといけないよね?となり、前述に説明した手法でリリースできるようにしました

ただ、あんまりイケていないと思うので、もっと良い案があれば別案を採用したいです

publish-sourcemapはJavaScriptでの実行もできるので、生成したreleaseIdとreleaseNameは環境変数のままで、どうにかして生成されたsourcemapを。。。って思ったけど、トランスパイル後は後追いできないじゃん。うーんって感じ

https://www.npmjs.com/package/@newrelic/publish-sourcemap

環境変数で渡す分には簡単なんですが、Nuxtの場合、あらかじめ nuxt.config.tsに追加しないといけないので、アカンとなりました。。。あれ???

書いてて思ったんですが、nuxt.config.ts上では、直接.envを参照できるので、ダイナミックにruntimeConfigに渡すことはできるのでは? そしたら、2がいらなくなりますね

今日はもう遅いので明日(今日)以降頑張ります(執筆時00:24)

New Relic Browser monitoringのErrorsでリストアップされるErrorがわからなかった

いざsourcemapをpublishできたとて、本当にうまく動くのかわからなかったです。ガチャガチャ試して無理やりエラー出力さしても、ブラウザのconsole上では確認できるが、New Relicでは確認できねぇとなっていました

ChatGPTに聞きまくり&ドキュメントをあさりまくりでここにたどり着く

https://docs.newrelic.com/docs/errors-inbox/browser-tab/

また、Query Your Data画面でクエリで確認することもできます(いつも適当に書いているやつ)

SELECT * FROM JavaScriptError WHERE appName = 'アプリケーションの名前' SINCE 24 HOURS AGO

まだよくわかっていないですが、致命的なエラー(操作中にアプリケーションが停止するれべる)だと通知してくれるとのことらしいです(多分)

そうすると、無理やりエラー出したとしても意味がないらしいです

またドキュメントを漁った結果、よさそうなAPIに出会えたのでこうしました

const err = new Error('test error')
window.newrelic.noticeError(err)

これで無事意図的にエラーを追加することができ、sourcemapがpublishされていることが確認できました!

https://docs.newrelic.com/jp/docs/apm/agents/nodejs-agent/api-guides/nodejs-agent-api/#noticeError

雑感

  • ちょっとつかれた
  • これからもっと活用していきたい気持ち
CBcloud Tech Blog

Discussion