0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Cloud Runのエラーを検知してJIRAチケットを起票する(アラートポリシー&通知チャンネルを使う方法)

0
Last updated at Posted at 2026-08-04

エラー発生時に自動的にJIRAチケットを起票したい

エラーが発生した際、気づける仕組みや対応の未済管理ができる仕組みは必要です。
これらがないとエラーが発生していることに気づかず、長期にわたってユーザーに迷惑をかけてしまう恐れがあります。

業務でシステム運用している際は、自動的にエラーを検知し、PagerDutyをはじめとしたインシデント管理製品に連携するのが一般的かと思います。
個人開発でもライトにエラー検知と未済管理をしたいと思い、エラー発生時にJIRAチケットを自動起票する仕組みを試してみました。

システム構成

以下のような流れです。

エラーログのJIRAチケット起票_アラートポリシーバージョン.png

省略していますが、JIRAチケットを起票するために必要なメールアドレスやアクセストークンはSecretManagerで管理しています。

やってみた

検証用のアプリケーション

SpringBoot + Spring Security + Thymeleafでログインするだけのアプリを用意しました。
Spring Initializrで作ったひな型プロジェクトをベースにAntigravity CLIで作ってもらいました。

ログイン画面はこんな感じ
image.png

ログインするとこんな画面
image.png

このアプリですが、なんと 「ユーザー名に数字以外が含まれていたらログイン時にシステムエラーになる」 という致命的なバグがあります。
(検証のためにわざと埋め込みました)

埋め込んだバグは以下の個所です。

@Controller
public class LoginController {

    // 割愛

    @GetMapping("/")
    public String home(Model model, Authentication authentication) {
        if (authentication != null) {
            model.addAttribute(
                "username", 
                Integer.parseInt(authentication.getName()) // ★検証用に仕込んだバグ!ユーザー名に数字以外が含まれていたらエラー
            );
        }
        return "index";
    }
}

アラートポリシーと通知チャンネル

Terraformで作りました。

# Pub/Sub 通知チャンネル
resource "google_monitoring_notification_channel" "pubsub" {
  display_name = "Cloud Run Error Log Pub/Sub Notification Channel"
  project      = var.project_id
  type         = "pubsub"

  labels = {
    topic = google_pubsub_topic.cloud_run_error_logs.id
  }

  depends_on = [google_project_service.required]
}

# Cloud Runのエラーログを検知するアラートポリシー
resource "google_monitoring_alert_policy" "cloud_run_error_alert" {
  display_name = "Cloud Run Error Logs Alert Policy"
  project      = var.project_id
  combiner     = "OR"

  conditions {
    display_name = "Cloud Run Error Log Detected"

    condition_matched_log {
      filter = <<-EOT
        resource.type="cloud_run_revision"
        severity>=${var.error_log_severity}
        resource.labels.service_name!="${var.jira_ticket_creator_service_name}"
        logName="projects/${var.project_id}/logs/run.googleapis.com%2Fstdout"
      EOT

      label_extractors = {
        "service_name"  = "EXTRACT(resource.labels.service_name)"
        "revision_name" = "EXTRACT(resource.labels.revision_name)"
        "log_message"   = "EXTRACT(jsonPayload.message)"
        "stack_trace"   = "EXTRACT(jsonPayload.stack_trace)"
      }
    }
  }

  notification_channels = [
    google_monitoring_notification_channel.pubsub.name
  ]

  alert_strategy {
    notification_rate_limit {
      period = "300s"
    }
    auto_close = "1800s"
  }

  # Markdown形式でドキュメントを記述(log.extracted_label.[KEY]変数を使用)
  # ドキュメントをJIRAチケットのsummaryとdescriptionとして利用する
  documentation {
    mime_type = "text/markdown"
    content   = <<-EOT
      # [${var.error_log_severity}] Cloud Run: $${log.extracted_label.service_name} で $${log.extracted_label.log_message} が発生しました

      Cloud Runサービス「$${log.extracted_label.service_name}」でエラーログを検知しました。

      ### ログ詳細メッセージ
      ```
      $${log.extracted_label.stack_trace}
      ```

      ### インシデント詳細
      - **サービス名**: $${log.extracted_label.service_name}
      - **リビジョン名**: $${log.extracted_label.revision_name}
      - **ポリシー名**: $${policy.display_name}
    EOT
  }

  depends_on = [google_project_service.required]
}

condition_matched_logで指定しているログのフィルタ条件は、下記2つが重要です。

resource.labels.service_name!="${var.jira_ticket_creator_service_name}"が前述の無限エラー地獄を避ける除外条件です。

logName="projects/${var.project_id}/logs/run.googleapis.com%2Fstdout"は、SpringBootアプリケーションが出力したエラーログだけを検知するための条件です。
500応答を返すとCloud Runのリクエストログもseverity = ERRORでログ出力するため、1つの事象で複数チケット起票を避けるために入れています。

1つの事象で二つのエラーが出ている様子
image.png

label_extractorsはアラートポリシーによってインシデントが作成されたとき、説明文にログの内容を埋め込むための仕組みです。
ログ中の項目を変数に割り当て、documentationブロックで定義したMarkdown形式の説明文中で利用します。
ログから抽出した項目は$${log.extracted_label.KEY名}という形式で変数展開できます。

その他、documentationで利用できる変数は以下のドキュメントを参照ください。

JIRAチケット起票をするアプリケーション

express(TypeScript)で開発しました。
監視対象アプリはSpringBoot(Java)で用意しましたが、勉強のためにフレームワークと言語を変えてみました。

Pub/SubのメッセージをCloud Runで読み取る方法は以下のドキュメントを参照ください。

Pub/Subのメッセージを元にJIRAチケットを起票する処理は以下の通りです。

function buildDescription(descriptionText: string) {
  // Atlassian Document Format (ADF)
  const lines = descriptionText.split('\n');
  const paragraphs = lines.map((line) => ({
    type: 'paragraph',
    content: line ? [{ type: 'text', text: line }] : [],
  }));

  return {
    type: 'doc',
    version: 1,
    content: paragraphs.length > 0 ? paragraphs : [{ type: 'paragraph', content: [] }],
  };
}

export async function createIssue(config: Config, elements: JiraIssueElements): Promise<string> {
  const url = `${config.jiraBaseUrl}/rest/api/3/issue`;

  const payload = {
    fields: {
      project: { key: config.jiraProjectKey },
      issuetype: { name: config.jiraIssueType },
      summary: truncate(elements.summary, 250),
      labels: [`incident-${elements.incidentId}`, 'auto-created', 'cloud-run-error'],
      description: buildDescription(elements.description),
    },
  };

  const res = await fetch(url, {
    method: 'POST',
    headers: {
      Authorization: authHeader(config),
      'Content-Type': 'application/json',
      Accept: 'application/json',
    },
    body: JSON.stringify(payload),
  });

  if (!res.ok) {
    throw new Error(`JIRAチケット作成に失敗しました: ${res.status} ${await res.text()}`);
  }

  const body = (await res.json()) as JiraCreateIssueResponse;
  return body.key;
}

elements: JiraIssueElementsはpub/subメッセージから必要な要素を抽出したオブジェクトです。
アラートポリシーで作成したドキュメントはincident.documentation.contentでアクセスできます。
今回は、ドキュメント先頭の1文をsummaryとし、残りをdescriptionとして利用しました。

エラーを発生させてみた結果

Google Cloudコンソール上の表示

アラートポリシーに合致するログが検出されるとインシデントが作成されます。
お試しアプリでシステムエラーを発生させたところ以下のように表示されました。

image.png

image.png

ドキュメントの表示範囲が狭い点は難点ですね…

Cloud Monitoring上でインシデントのステータス管理ができるので、単純な未済管理だけで良ければJIRA等のチケット管理システムを使わない選択肢も取れます。

インシデントを確認するをクリックした結果
image.png

インシデントを閉じるをクリックした結果
image.png

JIRA

無事チケット起票されていました!

image.png

~中略~

image.png

まとめ

アラートポリシーはインシデントがオープンな間は複数回のエラーログをオープン中のインシデントに集約してくれる点が便利ですね。
実運用を考えた時、大量のアラートが発生すると重要なアラートを見逃しかねないので、不要にアラートが増えることを避ける仕組みは重要だと思います。

一方、アラートポリシーではnotification_rate_limitで指定した時間が経過しないとアラート作成されないので、アラート検出までのリードタイムが長くなる点はデメリットです。
このデメリットを避ける場合はアラートポリシーを使わない選択肢もありかもしれません。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?