Fragments of verbose memory

冗長な記憶の断片 - Web技術のメモをほぼ毎日更新(準備中)

Mar 13, 2025 - 日記

Cursorと Grafana LokiをMCPで繋いで開発体験を向上させる

ソフトウェア開発において、効率的なデバッグとトラブルシューティングは生産性に大きく影響します。特に複雑なアプリケーションを開発する際、ログの管理と分析は重要な要素となります。

この記事では、以下のツールを組み合わせて、ローカル開発環境でのログ管理と問題解決を効率化する方法を紹介します:

over all of grafana loki mcp cursor

  1. local-logs - ローカル環境に簡単にGrafana Lokiスタックを構築するツール
  2. grafana-loki-mcp - CursorのMCP(Model Control Protocol)にGrafana Lokiを統合するプラグイン
  3. Cursor - AIを活用したコーディング支援IDE

これらを組み合わせることで、開発中のアプリケーションのログをリアルタイムで収集・分析し、CursorのAIエージェントがログデータを基に問題解決を支援する環境を構築できます。

local-logsでGrafana Lokiスタックをローカルに構築する

Grafana Lokiとは

Grafana Loki は、Grafanaが開発したログ集約システムです。Prometheusのラベルベースのアプローチを採用しており、ログデータの効率的な保存と検索を可能にします。

local-logsの導入

local-logs は、Docker Composeを使用してGrafana Lokiスタックをローカル環境に簡単にデプロイするためのツールです。以下の手順で導入できます:

# リポジトリのクローン
git clone https://github.com/tumf/local-logs.git
cd local-logs

# Docker Composeでサービスを起動
docker-compose up -d

これにより、以下のコンポーネントがローカル環境で起動します:

  • Grafana: http://localhost:9020 (デフォルトユーザー名/パスワード: admin/admin)
  • Loki: http://localhost:9020/loki

設定の確認

Grafanaにログインすると、Lokiデータソースが既に設定されており、ログの閲覧が可能な状態になっています。

開発中のアプリケーションからログをLokiに送信する

アプリケーションからLokiにログを送信する方法はいくつかありますが、ここでは一般的な方法を紹介します。

Node.jsアプリケーションの場合

Node.jsアプリケーションでは、winston-loki などのライブラリを使用できます:

const winston = require('winston');
const LokiTransport = require('winston-loki');

const logger = winston.createLogger({
    transports: [
        new LokiTransport({
            host: "http://localhost:9020/loki",
            labels: {
                app: "my-app"
            },
            json: true,
            batching: true,
            interval: 5
        })
    ]
});

// ログの出力
logger.info('Hello, Loki!');

Pythonアプリケーションの場合

Pythonでは、python-logging-loki を使用できます:

import logging
import logging_loki

handler = logging_loki.LokiHandler(
    url="http://localhost:9020/loki/loki/api/v1/push",
    tags={"application": "my-python-app"},
    version="1",
)

logger = logging.getLogger("my-python-app")
logger.addHandler(handler)
logger.setLevel(logging.INFO)

# ログの出力
logger.info("Hello, Loki!")

ブラウザから (next.js)の場合

WebSocketを使って接続すると、DeveloperConsoleに接続ログが残らないのでおすすめです。

// Lokiサーバーの設定
const LOKI_URL = process.env.NEXT_PUBLIC_LOKI_URL || '';
const isWebSocket = LOKI_URL && (LOKI_URL.startsWith('ws://') || LOKI_URL.startsWith('wss://'));
const isHttpApi = LOKI_URL && (LOKI_URL.startsWith('http://') || LOKI_URL.startsWith('https://'));
const isLoggingEnabled = !!LOKI_URL && (isWebSocket || isHttpApi);

// Lokiのペイロード型定義
interface LokiPayload {
  streams: Array<{
    stream: Record<string, string>;
    values: Array<[string, string]>;
  }>;
}

// ログをLokiサーバーに送信する関数
const sendLogToLoki = async (
  level: string,
  message: string,
  meta: Record<string, unknown> = {}
) => {
  if (!isLoggingEnabled) return;
  
  try {
    // Lokiに送信するためのペイロードを作成
    const timestamp = Date.now() * 1000000; // ナノ秒単位のタイムスタンプ
    const payload: LokiPayload = {
      streams: [
        {
          stream: {
            environment: process.env.NODE_ENV || 'development',
            application: process.env.NEXT_PUBLIC_LOKI_APPLICATION || 'unknown',
            level,
            source: 'browser',
            category: (meta.category as string) || 'client',
          },
          values: [
            [
              `${timestamp}`,
              JSON.stringify({
                message,
                ...meta,
                client: true,
                userAgent: navigator.userAgent,
                url: window.location.href,
                timestamp: new Date().toISOString(),
              }),
            ],
          ],
        },
      ],
    };

    // WebSocketが有効な場合はWebSocketで送信
    if (isWebSocket) {
      sendLogViaWebSocket(payload);
      return;
    }

    // HTTP APIが有効な場合はHTTP APIで送信
    if (isHttpApi) {
      fetch(LOKI_URL, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify(payload),
        keepalive: true,
      }).catch(() => {
        // サーバーへのログ送信に失敗した場合は無視
      });
    }
  } catch {
    // エラーが発生した場合は無視
  }
};

Dockerコンテナのログ

Dockerコンテナのログを直接Lokiに送信するには、Docker Composeファイルに以下の設定を追加します:

services:
  my-app:
    image: my-app-image
    logging:
      driver: loki
      options:
        loki-url: "http://localhost:9020/loki/loki/api/v1/push"
        loki-batch-size: "400"
        loki-retries: "5"
        loki-timeout: "1s"
        labels: "job=my-app"

grafana-loki-mcpでCursorとLokiを連携させる

Model Control Protocol (MCP)とは

CursorのMCP(Model Control Protocol)は、AIモデルに外部データソースへのアクセスを提供するためのプロトコルです。これにより、AIエージェントは開発環境の様々なコンテキスト情報を活用できるようになります。

CursorでのMCP設定

grafana-loki-mcp は、CursorのMCPにGrafana Lokiを統合するプラグインです。以下の手順で設定します:

Cursorより Cursor Settings > MCP より

Cursor MCP setting

  • Name: Grafana
  • Type: Command
  • Command: には以下の文字列を入力してください
uvx grafana-loki-mcp -u http://localhost:9020 -k {Grafana API Key}

http://localhost:9020 は GrafanaのURLです。

Grafana APIキーは、Grafana UIの「Configuration > API Keys」から生成できます。

Cursorを起動し、設定画面からMCPの設定を行います:

  1. 設定(Settings)を開く
  2. 「Model Control Protocol」セクションを探す
  3. 「Enable MCP」をオンにする
  4. 「MCP URL」に http://localhost:3200 を入力
  5. 保存して再起動

開発プロセスの改善

これらのツールを組み合わせることで、以下のような開発プロセスの改善が期待できます:

AIによる問題解決支援

CursorのAIエージェントは、grafana-loki-mcpを通じてログデータにアクセスできるようになります。これにより:

  1. エラーの原因特定が迅速になる
  2. 類似の問題に対する解決策を提案してくれる
  3. ログパターンから潜在的な問題を予測できる

実際の使用例

例えば、アプリケーションで例外が発生した場合:

  1. 例外のスタックトレースがLokiに記録される
  2. Cursorでコードを編集中に、AIエージェントに問題について質問する
  3. AIエージェントはMCPを通じてLokiからログを取得し、問題の文脈を理解する
  4. 具体的な解決策を提案してくれる

Agent sample

ユーザー: このAPIエンドポイントが500エラーを返す原因は何ですか?

AI: Lokiのログを確認したところ、データベース接続のタイムアウトが発生しているようです。
以下のログエントリが見つかりました:

[ERROR] Connection to database timed out after 30000ms

接続プールの設定を見直すか、クエリの最適化が必要かもしれません。

まとめ

local-logs、grafana-loki-mcp、Cursorを組み合わせることで、ローカル開発環境でのログ管理と問題解決プロセスを大幅に改善できます。特に:

  1. 可視性の向上: アプリケーションの動作をリアルタイムで把握
  2. コンテキスト理解の深化: AIエージェントがログデータを基に問題の文脈を理解
  3. 効率的なデバッグ: 問題の特定と解決が迅速に

これらのツールは、個々に使用しても価値がありますが、組み合わせることでその効果を最大化できます。特にマイクロサービスアーキテクチャなど、複雑なシステムの開発において、その恩恵は大きいでしょう。

参考リンク

Tags: Cursor Grafana Loki MCP AI

MacOSのHostName3つある

comments powered by Disqus