背景と課題感
やっと重い腰を上げて記事化した(作ったのは1年前の話)
SlackにGitHub ActionsのFailed時のログを表示したい
公式のGitHub AppはWorkflowの結果をSlackと連携して流すことはできるが、すべての実行で通知が行ってしまう(失敗時のみ通知が欲しい)
失敗時ログの内容を知りたい(ログをさっと見れたら当てをつけやすいがログの内容は見えない)
Issueも発行されてはいるが+1ばかりで特に進捗がない
ちょうど何かしらGitHubActionsのカスタムアクションを作ってみたかったところなので良い題材かもということで、自分ならこれが欲しいという掲題のアクションを作ってみた
使い方
通知部分はworkflow_runイベントで行う
workflow_runについては下記
ワークフローをトリガーするイベント - GitHub Docs
次のようにSlack通知する部分(カスタムアクションの利用部分)のみ記述したファイルを用意
- .github/workflows/slack.yml
name: slack notification on: workflow_run: workflows: - test - e2e types: - completed jobs: main: name: main runs-on: ubuntu-latest steps: - uses: swfz/failed-log-to-slack-action@v1.1.0 with: github-token: ${{ secrets.GITHUB_TOKEN }} slack-webhook-url: ${{ secrets.SLACK_WEBHOOK_URL }}
例として、テスト用のファイルを用意(上記サンプルだとe2eのWorkflowファイルも対象なので用意する)
- .github/workflows/test.yml
name: test on: [push] jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4.2.2 - uses: actions/setup-node@v4.1.0 with: node-version-file: .node-version cache: yarn - name: test run: | yarn yarn test
testのWorkflow終了後slack notificationワークフローが発火し、Slack通知される
- 通知イメージ

失敗したときの実際のログの最後の方数行が表示される
作ったときの話
※使う側からしたらこの先は特に読まなくて良さそうです
せっかくなのでカスタムアクションの開発で学んだこととか使ったものの所感などをつらつら書いていく
TypeScriptのテンプレートリポジトリから作成した
今回はTypeScriptのテンプレートリポジトリから作成した
Linterとかもろもろ設定済みなので最初からある程度開発に集中できる
actions/toolkitの使い方とか開発しながらドキュメント読みつつって感じだろうけど、一通り実装されているサンプルがある状態なので「こういうときはこう書くのね」みたいなのがある程度分かるので参考にしやすかった
特にcoreやcontextあたりの違いや使い方などはサンプルコードがあったのでやりやすかった
テストもある状態から始められるので意識しながら開発できるのと、例を参考にしながら実際に作成したコードに対してテストコードを書けたので良かった
SuperLinter
Super-Linter · Actions · GitHub Marketplace
テンプレートリポジトリに含まれていたので使ってみた
GitHubが作っていて、初期の開発スタート時にLinterの設定をしたり試したりがたいへんだよねっていうところから来ているらしい
たしかに、Linterの選定、フォーマッタの選定…考え出すと際限なくいろいろできるのでこの辺に労力使いたくないよねっていうのはある
設定で変えたい部分などは基本的に環境変数経由で差し込む
使って開発していたが、MarkdownのMarkdownLintで何度もCI段階で落とされたのでローカルでも実行できるようにした…
- linter.yml(一部抜粋)
- name: Lint Codebase id: super-linter uses: super-linter/super-linter/slim@v5 env: DEFAULT_BRANCH: main FILTER_REGEX_EXCLUDE: dist/**/* GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} TYPESCRIPT_DEFAULT_STYLE: prettier VALIDATE_ALL_CODEBASE: true VALIDATE_JAVASCRIPT_STANDARD: false VALIDATE_JSCPD: false
- 結果のスクショ

イベントの情報とか
カスタムアクションは、Workflow実行時の情報をつかって何かするみたいなことが主な要件だと思うが、実際に実行してどのようなデータ構造でどんな情報が参照できるのかわからなかった
これもソースコード読んだりして把握した
actions/toolkit:github/src/context.ts
下記のように基本的には環境変数にセットしてツール側がそれを参照する形でデータのやりとりを行っている
import {WebhookPayload} from './interfaces' import {readFileSync, existsSync} from 'fs' import {EOL} from 'os' export class Context { constructor() { this.payload = {} if (process.env.GITHUB_EVENT_PATH) { if (existsSync(process.env.GITHUB_EVENT_PATH)) { this.payload = JSON.parse( readFileSync(process.env.GITHUB_EVENT_PATH, {encoding: 'utf8'}) ) } else { const path = process.env.GITHUB_EVENT_PATH process.stdout.write(`GITHUB_EVENT_PATH ${path} does not exist${EOL}`) } } this.eventName = process.env.GITHUB_EVENT_NAME as string this.sha = process.env.GITHUB_SHA as string this.ref = process.env.GITHUB_REF as string this.workflow = process.env.GITHUB_WORKFLOW as string this.action = process.env.GITHUB_ACTION as string this.actor = process.env.GITHUB_ACTOR as string this.job = process.env.GITHUB_JOB as string this.runAttempt = parseInt(process.env.GITHUB_RUN_ATTEMPT as string, 10) this.runNumber = parseInt(process.env.GITHUB_RUN_NUMBER as string, 10) this.runId = parseInt(process.env.GITHUB_RUN_ID as string, 10) this.apiUrl = process.env.GITHUB_API_URL ?? `https://api.github.com` this.serverUrl = process.env.GITHUB_SERVER_URL ?? `https://github.com` this.graphqlUrl = process.env.GITHUB_GRAPHQL_URL ?? `https://api.github.com/graphql` } get repo(): {owner: string; repo: string} { if (process.env.GITHUB_REPOSITORY) { const [owner, repo] = process.env.GITHUB_REPOSITORY.split('/') return {owner, repo} } if (this.payload.repository) { return { owner: this.payload.repository.owner.login, repo: this.payload.repository.name } } throw new Error( "context.repo requires a GITHUB_REPOSITORY environment variable like 'owner/repo'" ) } }
GITHUB_EVENT_PATHに基本的なイベントデータが入っているのでそこに実際のデータを用意して差し込むのが開発時は良さそう
この方法だと、テスト時にJestで環境変数を切り替えてテストしなくてはならなくなったのでたいへんだったがこの辺はまた後日記事にしたい
context? github.event?
開発をしていくなかでcontext、とgithub.eventの違いがよくわからなくなってしまったのでそれぞれ出力して比べてみた
- イベントデータを出力するサンプル
jobs: main: name: main runs-on: ubuntu-20.04 steps: - run: | echo '${{ toJson(github) }}' - run: | cat $GITHUB_EVENT_PATH - uses: actions/github-script@v6 with: script: | core.info(JSON.stringify(context))
結論は
context.payload = github.event = GITHUB_EVENT_PATHの中身
で、納得した
ログの扱い
失敗時のJobを特定してログの中身を取得したかったがサクッとはいかなかった
最初、octokitを使ってログを出力させたが、すべてのJobのログが一緒になった状態で返ってきてしまったのでこれだと使えないな…という感じだった
調べたらgh run view --failed-log ${run_id}でやりたいことができているのでもはやこの出力をそのまま流したいw
ということでGitHub CLIの下記コードを参考にするため読んだ
https://github.com/cli/cli/blob/trunk/pkg/cmd/run/view/view.go#L534
これ見ると、ログファイル郡はzipになっていてそれをステップごとにみているっぽい
展開後のツリーはこんな感じ
tree tmp
tmp
├── 0_test.txt
├── -2147483648_test.txt
└── test
├── 16_Post Run actionscheckout@v4.1.1.txt
├── 17_Complete job.txt
├── 1_Set up job.txt
├── 2_Run actionscheckout@v4.1.1.txt
├── 3_Run actionssetup-node@v4.0.0.txt
├── 4_Install Dependencies.txt
├── 5_Install Playwright Browsers.txt
├── 6_Build.txt
├── 7_Run Playwright tests.txt
└── 8_Run actionsupload-artifact@v3.1.3.txt
1 directory, 12 files
通し番号+Step名というファイル名になっている
APIで取れるJobデータの中にステップ数と名前、成否のデータはあるので、ほしいログファイルの中身を取得できるようになった
ログファイル(zip)の出力タイミング
これでログも見れるようになった!と喜んでいたらworkflow_runイベント以外の呼び出しで失敗するようになってしまった…
JobのログをDLするところで失敗していた、よく考えたら現在実行中のWorkflow以下のJobたちのログをWorkflowの最後に取得するってできないよね(zipだし…)
対象WorkflowのJobログのDLができるようにworkflow_runでWorkflowを分割し、対象Workflowが終了するのを待ってログをDL可能にした
という経緯があり本カスタムアクションはworkflow_runイベントを前提とした処理になっている
一応workflow_run以外でも実行できるが出力される情報が少なくなり、ログについては出力されない
Slack通知 Block KitとAttachments
公式的にはBlock kitを使ってねって流れだったがBlock Kitと併用してAttachmentsを使うようにした
Block Kitは、Slackのよう々な場所で利用できる、インタラクティブなUIを構築するためのフレームワーク
AttachmentsはSlack通知ではお馴染みの緑や赤などメッセージの横に色をつけられるやつ
Attachmentsについてはドキュメント見に行ったら以前はなくなるよって書かれてた気がするがそうは書かれていなかった
この機能は、Slackアプリのメッセージング機能のレガシーな部分です。レイアウトブロックにこだわることをお勧めしますが、それでも添付ファイルを使いたい場合は、注意事項をお読みください。
やはり色をつけられるのは視認性という意味で外せないんだよな…
セカンダリーコンテンツは、優先順位の低いコンテンツ(メッセージの意図を理解するために必ずしも見る必要はないが、おそらくさらなる文脈や追加情報を追加するコンテンツ)をメッセージに添付できる。
Reference: Secondary message attachments | Slack
- Block Kitとattachmentsの併用サンプルコード
return await webhook.send({ blocks: [block], attachments: [ { color: '#FF6600', blocks: blocks } ] });
Slack通知、メッセージの折りたたみ
エラー文言出すならできれば折り畳めたほうがよい
5行以上連続した文かつblockkitのトップレベル以外
とのことだったので折り畳めるようなメッセージの組み立て方にした
まとめ
- GitHub Actionsのカスタムアクションを作った
- ワークフローの失敗時のみSlack通知を送れる機能
- 失敗したワークフローのログの最後の方数行のログを表示させる機能
- GitHub Actionsがどう動いているかの解像度が上がった
- actions/toolkitの使い方
- 開発時のデータの作り方
- ログファイルの置かれ方
- テンプレートリポジトリ学びが多い
- 普段使っていないツールが入っているのでInputになる
よかったら使ってみてください