3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

クライアントサイドからGoogle Cloud Loggingにログを書き出す

Last updated at Posted at 2023-06-07

概要

クライアントサイド (Webブラウザ) から、Google Cloudのログ基盤であるCloud Loggingにログを書き出す方法について記載します。

昨今のWebシステムではクライアントサイドで複雑な処理を実装することが多く、クライアントサイドで発生した障害の把握や調査が難しくなっています。サーバ側にログを送ることによってこれらが可能になります。

Webブラウザ上で実行されるJavaScriptコードから直接Cloud Loggingに書き出すことはできないため、この記事ではFirebase Functionsを仲介として使用しています。

Firebase Functions

※ Firebaseプロジェクトの作成方法やFunctionsのデプロイ方法についての説明は割愛します。

ログをCloud Loggingに書き出す関数を作成します。
言語は任意ですが、以下の例ではnode (TypeScript) を使用しています。

Cloud LoggingのクライアントSDKをインストール

npm install @google-cloud/logging

関数定義

index.ts
import * as admin from 'firebase-admin'
import * as functions from 'firebase-functions'
import { Logging } from '@google-cloud/logging'

admin.initializeApp()

export const writeLog = functions
  .region('asia-northeast1')
  .runWith({
    timeoutSeconds: 10,
    memory: '128MB',
    maxInstances: 3,
  })
  .https.onCall(async ({ name, severity, payload }) => {
    const logging = new Logging({
      projectId: process.env.GCP_PROJECT || process.env.GCLOUD_PROJECT,
    })
    const log = logging.log(name)

    const entry = log.entry(
      {
        severity,
      },
      payload
    )

    await log.write(entry)
  })

ここでは、writeLogという関数を定義しています。

maxInstancesの指定によって、意図しない量の書き込みによってサーバコストが増大してしまうことを防止します。

パラメータとして、以下の3つが渡されてきます。

  • name: 任意のログ名 (ログ閲覧時の絞り込みに使う)
  • severity: ログレベル (重大度)
  • payload: ログの内容 (任意のオブジェクト)

クライアントサイド

Firebase SDKのインストール

npm install firebase

Firebaseクライアント初期化処理

import { initializeApp } from 'firebase/app'

initializeApp({
  // FirebaseのプロジェクトID
  projectId: YOUR_PROJECT_ID,
  // FirebaseのウェブAPIキー
  apiKey: YOUR_API_KEY,
})

ロガーモジュール

※ 以下はFirebase SDK V9の例です。(V8以前とは若干異なります)

logger.ts
import { getFunctions, httpsCallable } from 'firebase/functions'

const functions = getFunctions()
functions.region = 'asia-northeast1'
const sendLog = httpsCallable(functions, 'writeLog')

const putLog = async (
  position: string,
  severity: 'DEBUG' | 'INFO' | 'WARNING' | 'ERROR',
  message: unknown,
  error?: unknown,
) => {
  const payload: { [index: string]: unknown } = {
    message,
    severity,
    url: location.href,
    position,
    userId: localStorage.getItem('userId'),
    userAgent: navigator.userAgent,
  }

  if (error) {
    if (error instanceof Error) {
      payload.error = error.stack ?? `${error.name}: ${error.message}`
    } else {
      payload.error = String(error)
    }
  }

  sendLog({
    name: 'web_browser',
    severity,
    payload,
  })
}

export default {
  // debugレベルのログを出力する
  debug: (
    position: string,
    message: unknown,
    error?: unknown,
  ) => {
    return putLog(position, 'DEBUG', message, error)
  },
  // infoレベルのログを出力する
  info: (
    position: string,
    message:unknown,
    error?: unknown,
  ) => {
    return putLog(position, 'INFO', message, error)
  },
  // warnレベルのログを出力する
  warn: (
    position: string,
    message: unknown,
    error?: unknown,
  ) => {
    return putLog(position, 'WARNING', message, error)
  },
  // errorレベルのログを出力する
  error: (
    position: string,
    message: unknown,
    error?: unknown,
  ) => {
    return putLog(position, 'ERROR', message, error)
  },
}

ログ出力内容は任意ですが、ここでは次の情報をpayloadに渡しています。

  • message: ログメッセージ (文字列またはオブジェクト)
  • url: ブラウザが現在表示しているURL
  • position: ログ出力箇所を出力している情報 (ソースファイル名、メソッド名など)
  • error: エラー情報 (Errorオブジェクトなど)
  • userId: ユーザID
  • userAgent: ブラウザのユーザエージェント

使用例

import logger from './logger'

localStorage.setItem('userId', 'sample')
test()

function test() {
  try {
    // messageに文字列
    logger.debug('app.ts|test', 'ああああ')
  
    // messageにオブジェクト
    logger.info('app.ts|test', {
      foo: 'あいうえお',
      bar: 12345,
    })
  
    throw Error('xxxx')
  } catch (err) {
    // errorにErrorオブジェクト
    logger.error('app.ts|test', '○○○失敗', err)
  }
}

ログの閲覧

GCP管理コンソールの「ログ エクスプローラ」で、書き出されたログを確認することができます。(反映されるまで、数十秒程度のタイムラグがあります)

2023-06-07_18h41_32.png

Cloud Loggingでは、GCP全体のログが集められています。ログ名 (この記事の例ではweb_browserとしている) や、重大度 (severity) などによって絞り込むことができます。

注意

コスト

FunctionsやCloud Loggingには無料枠がありますが、一定量を超えると使用量が課せられます。特にBtoC系のシステムでは想定外の量が書き込まれる可能性があるため注意が必要です。

Cloud LoggingよりBigQueryの方が安価であるため、BigQueryを使う方がコストが抑えられます。(Functionsのコードの書き方はほぼ同じですが、BigQueryにテーブル定義などが必要になります)

また、状況によってはFunctionsの代わりにCloud RunでRESTful APIを実装した方が安くなる場合もあります。

プライバシー

不用意にユーザの個人情報やクライアント環境の情報を書き出さないようにしましょう。特に外国向けにサービスを提供する場合には、EU一般データ保護規則(GDPR) などに違反しないように注意してください。

3
1
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
3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?