T-CREATOR

Turbopack のロギング・モニタリング活用法

Turbopack のロギング・モニタリング活用法

開発の現場で「なぜビルドが遅いのか」「エラーの原因がわからない」といった悩みを抱えていませんか?Turbopack のログ機能を正しく活用すれば、これらの問題を効率的に解決できるようになります。

本記事では、Turbopack のログシステムの基本から、実践的なトラブルシューティングまで、開発効率を劇的に向上させる方法を詳しく解説します。初心者の方でも理解できるよう、具体的なエラーコードや設定例を交えて説明していきます。

Turbopack のログシステムの基本

Turbopack のログ出力の仕組み

Turbopack は、ビルドプロセスの各段階で詳細なログを出力します。これらのログは、開発者がビルドの進行状況や問題を把握するための重要な情報源となります。

まず、Turbopack のログ出力を有効にする基本的な設定を見てみましょう。

javascript// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
  experimental: {
    turbo: {
      // ログレベルを設定
      logLevel: 'info',
      // 詳細なログを出力
      verbose: true,
    },
  },
};

module.exports = nextConfig;

この設定により、Turbopack は詳細なログを出力するようになります。logLevelにはerrorwarninfodebugの 4 つのレベルが利用可能です。

ログレベルの種類と使い分け

Turbopack のログレベルは、開発段階や目的に応じて適切に使い分けることが重要です。

javascript// 開発時の推奨設定
const devConfig = {
  experimental: {
    turbo: {
      logLevel: 'info', // 開発時は詳細情報を表示
      verbose: true,
    },
  },
};

// 本番ビルド時の設定
const prodConfig = {
  experimental: {
    turbo: {
      logLevel: 'error', // 本番ではエラーのみ表示
      verbose: false,
    },
  },
};

各ログレベルの特徴を理解することで、効率的なデバッグが可能になります。

ログの出力先と設定方法

Turbopack のログは、コンソール出力だけでなく、ファイルへの保存も可能です。これにより、ログの分析や共有が容易になります。

javascript// ログファイル出力の設定例
const fs = require('fs');
const path = require('path');

// ログをファイルに保存する関数
function setupLogging() {
  const logDir = path.join(process.cwd(), 'logs');

  if (!fs.existsSync(logDir)) {
    fs.mkdirSync(logDir, { recursive: true });
  }

  const logFile = path.join(
    logDir,
    `turbopack-${Date.now()}.log`
  );

  // コンソール出力をファイルにもリダイレクト
  const originalLog = console.log;
  console.log = (...args) => {
    originalLog(...args);
    fs.appendFileSync(logFile, args.join(' ') + '\n');
  };
}

module.exports = setupLogging;

この設定により、ログがファイルに保存され、後から分析することができます。

開発時のログ活用術

ビルドエラーの詳細解析

Turbopack でビルドエラーが発生した際、ログを正しく読み取ることで、問題の原因を素早く特定できます。

よくあるエラーパターンとその対処法を見てみましょう。

bash# モジュール解決エラーの例
[ERROR] Module not found: Can't resolve './components/Button' in '/app/src'
[ERROR] Error: ENOENT: no such file or directory, open '/app/src/components/Button.tsx'

このエラーは、ファイルパスが間違っているか、ファイルが存在しないことを示しています。ログから以下の情報を確認できます:

  • エラーの種類:Module not found
  • 問題のファイル:.​/​components​/​Button
  • 検索された場所:​/​app​/​src

解決方法は、ファイルの存在確認とパスの修正です。

typescript// 正しいインポート例
import Button from './components/Button'; // ファイルが存在することを確認

パフォーマンス問題の特定

Turbopack のログから、ビルドのパフォーマンス問題を特定できます。

bash# パフォーマンスログの例
[INFO] Compiling 1 module with 2 dependencies
[INFO] Module compilation took 2.3s
[WARN] Large bundle detected: 1.2MB (threshold: 500KB)
[INFO] Cache hit rate: 85%

これらのログから以下の情報が読み取れます:

  • コンパイル時間:2.3 秒
  • バンドルサイズ:1.2MB(警告レベル)
  • キャッシュ効率:85%

パフォーマンスを改善するための設定例:

javascript// パフォーマンス最適化の設定
const nextConfig = {
  experimental: {
    turbo: {
      // 並列処理を有効化
      parallel: true,
      // キャッシュサイズを調整
      cacheSize: '2GB',
      // メモリ使用量を制限
      memoryLimit: '4GB',
    },
  },
};

依存関係の解決状況の確認

Turbopack は、依存関係の解決過程を詳細にログ出力します。

bash# 依存関係解決のログ例
[INFO] Resolving dependencies for /app/src/index.tsx
[INFO] Found 15 direct dependencies
[INFO] Resolving transitive dependencies...
[INFO] Total dependencies: 127
[WARN] Circular dependency detected: package-a -> package-b -> package-a

循環依存の警告が表示された場合の対処法:

javascript// 循環依存を解決する例
// 問題のある構造
import { ComponentA } from './ComponentA'; // ComponentAがComponentBをインポート
import { ComponentB } from './ComponentB'; // ComponentBがComponentAをインポート

// 解決方法:共通のインターフェースを作成
interface SharedProps {
  data: any;
  onAction: () => void;
}

// 各コンポーネントで共通インターフェースを使用

本格的なモニタリング環境の構築

ログの集約と分析ツールの導入

本格的な開発環境では、ログの集約と分析が重要になります。ELK スタック(Elasticsearch、Logstash、Kibana)の導入例を見てみましょう。

yaml# docker-compose.yml for ELK Stack
version: '3.8'
services:
  elasticsearch:
    image: docker.elastic.co/elasticsearch/elasticsearch:8.11.0
    environment:
      - discovery.type=single-node
      - xpack.security.enabled=false
    ports:
      - '9200:9200'
    volumes:
      - elasticsearch_data:/usr/share/elasticsearch/data

  logstash:
    image: docker.elastic.co/logstash/logstash:8.11.0
    ports:
      - '5044:5044'
    volumes:
      - ./logstash/pipeline:/usr/share/logstash/pipeline
    depends_on:
      - elasticsearch

  kibana:
    image: docker.elastic.co/kibana/kibana:8.11.0
    ports:
      - '5601:5601'
    environment:
      - ELASTICSEARCH_HOSTS=http://elasticsearch:9200
    depends_on:
      - elasticsearch

volumes:
  elasticsearch_data:

Logstash の設定ファイルで、Turbopack のログを解析します:

ruby# logstash/pipeline/turbopack.conf
input {
  file {
    path => "/app/logs/turbopack-*.log"
    start_position => "beginning"
    codec => multiline {
      pattern => "^\["
      negate => true
      what => "previous"
    }
  }
}

filter {
  grok {
    match => { "message" => "\[%{LOGLEVEL:level}\] %{GREEDYDATA:content}" }
  }

  if [level] == "ERROR" {
    mutate {
      add_tag => [ "error" ]
    }
  }

  if [level] == "WARN" {
    mutate {
      add_tag => [ "warning" ]
    }
  }
}

output {
  elasticsearch {
    hosts => ["elasticsearch:9200"]
    index => "turbopack-logs-%{+YYYY.MM.dd}"
  }
}

アラート設定と通知の仕組み

重要なエラーが発生した際に、自動的に通知を受け取る仕組みを構築します。

javascript// アラート通知システムの例
const nodemailer = require('nodemailer');
const Slack = require('@slack/webhook');

class AlertManager {
  constructor() {
    this.emailTransporter = nodemailer.createTransporter({
      host: 'smtp.gmail.com',
      port: 587,
      secure: false,
      auth: {
        user: process.env.EMAIL_USER,
        pass: process.env.EMAIL_PASS,
      },
    });

    this.slackWebhook = new Slack.IncomingWebhook(
      process.env.SLACK_WEBHOOK_URL
    );
  }

  async sendAlert(error, context) {
    const message = this.formatAlertMessage(error, context);

    // メール通知
    await this.sendEmailAlert(message);

    // Slack通知
    await this.sendSlackAlert(message);
  }

  formatAlertMessage(error, context) {
    return {
      subject: `[Turbopack Alert] ${error.type}`,
      body: `
        Error: ${error.message}
        Context: ${context}
        Time: ${new Date().toISOString()}
        Build ID: ${process.env.BUILD_ID || 'unknown'}
      `,
    };
  }

  async sendEmailAlert(message) {
    await this.emailTransporter.sendMail({
      from: process.env.EMAIL_USER,
      to: process.env.ALERT_EMAIL,
      subject: message.subject,
      text: message.body,
    });
  }

  async sendSlackAlert(message) {
    await this.slackWebhook.send({
      text: `🚨 *Turbopack Alert*\n${message.body}`,
      color: 'danger',
    });
  }
}

module.exports = AlertManager;

パフォーマンスメトリクスの可視化

Turbopack のパフォーマンスデータを可視化するダッシュボードを作成します。

javascript// パフォーマンスメトリクス収集
const { performance } = require('perf_hooks');

class PerformanceMonitor {
  constructor() {
    this.metrics = {
      buildTime: [],
      memoryUsage: [],
      cacheHitRate: [],
      bundleSize: [],
    };
  }

  startBuild() {
    this.buildStartTime = performance.now();
    this.initialMemory = process.memoryUsage();
  }

  endBuild() {
    const buildTime =
      performance.now() - this.buildStartTime;
    const finalMemory = process.memoryUsage();

    this.metrics.buildTime.push(buildTime);
    this.metrics.memoryUsage.push(finalMemory.heapUsed);

    this.logMetrics({
      buildTime,
      memoryUsage: finalMemory.heapUsed,
      timestamp: new Date().toISOString(),
    });
  }

  logMetrics(data) {
    console.log(`[METRICS] ${JSON.stringify(data)}`);

    // メトリクスをファイルに保存
    const fs = require('fs');
    const metricsFile = './logs/metrics.json';

    let existingMetrics = [];
    if (fs.existsSync(metricsFile)) {
      existingMetrics = JSON.parse(
        fs.readFileSync(metricsFile, 'utf8')
      );
    }

    existingMetrics.push(data);
    fs.writeFileSync(
      metricsFile,
      JSON.stringify(existingMetrics, null, 2)
    );
  }
}

module.exports = PerformanceMonitor;

トラブルシューティング実践例

よくある問題とその解決方法

実際の開発現場でよく遭遇する問題と、その解決方法を具体的に紹介します。

問題 1:メモリ不足エラー

bash# エラーログ例
[ERROR] JavaScript heap out of memory
[ERROR] FATAL ERROR: Ineffective mark-compacts near heap limit
[ERROR] Allocation failed - JavaScript heap out of memory

このエラーは、Node.js のメモリ制限に達したことを示しています。

解決方法:

bash# package.jsonのスクリプトでメモリ制限を増やす
{
  "scripts": {
    "dev": "NODE_OPTIONS='--max-old-space-size=4096' next dev",
    "build": "NODE_OPTIONS='--max-old-space-size=4096' next build"
  }
}

問題 2:キャッシュ破損エラー

bash# エラーログ例
[ERROR] Cache corruption detected
[ERROR] Failed to read cache file: /app/.next/cache/turbo/cache.json
[WARN] Clearing cache and rebuilding...

キャッシュが破損した場合の対処法:

javascript// キャッシュクリアの自動化
const fs = require('fs');
const path = require('path');

function clearTurbopackCache() {
  const cacheDir = path.join(
    process.cwd(),
    '.next',
    'cache',
    'turbo'
  );

  if (fs.existsSync(cacheDir)) {
    fs.rmSync(cacheDir, { recursive: true, force: true });
    console.log('Turbopack cache cleared successfully');
  }
}

// エラー発生時に自動的にキャッシュをクリア
process.on('uncaughtException', (error) => {
  if (error.message.includes('Cache corruption')) {
    clearTurbopackCache();
    console.log(
      'Cache cleared due to corruption, please retry build'
    );
  }
});

ログから読み取るべき重要な情報

Turbopack のログから、以下の重要な情報を読み取ることができます:

ビルド時間の分析

bash# ビルド時間のログ例
[INFO] Build completed in 45.2s
[INFO] Initial compilation: 12.3s
[INFO] Incremental compilation: 2.1s
[INFO] Asset optimization: 8.7s

このログから、どの段階で時間がかかっているかを特定できます。

依存関係の分析

bash# 依存関係のログ例
[INFO] Resolving 1,247 dependencies
[WARN] Large dependency tree detected
[INFO] Circular dependencies: 3 found
[INFO] Unused dependencies: 15 found

未使用の依存関係や循環依存を特定できます。

デバッグの効率化テクニック

効率的なデバッグのために、以下のテクニックを活用しましょう。

ログフィルタリングの活用

javascript// ログフィルタリングツール
class LogFilter {
  constructor() {
    this.filters = {
      error: true,
      warn: true,
      info: false,
      debug: false,
    };
  }

  setFilter(level, enabled) {
    this.filters[level] = enabled;
  }

  filterLog(logLine) {
    const match = logLine.match(/\[(\w+)\]/);
    if (match) {
      const level = match[1].toLowerCase();
      return this.filters[level];
    }
    return true;
  }

  processLogFile(filePath) {
    const fs = require('fs');
    const lines = fs
      .readFileSync(filePath, 'utf8')
      .split('\n');

    return lines.filter((line) => this.filterLog(line));
  }
}

// 使用例
const filter = new LogFilter();
filter.setFilter('info', false); // INFOレベルのログを除外
const filteredLogs = filter.processLogFile(
  './logs/turbopack.log'
);

自動化されたデバッグスクリプト

javascript// 自動デバッグスクリプト
const { spawn } = require('child_process');

class AutoDebugger {
  constructor() {
    this.errorPatterns = [
      /Module not found/,
      /Cannot resolve/,
      /Unexpected token/,
      /Memory heap out of memory/,
    ];
  }

  async runBuildWithDebug() {
    return new Promise((resolve, reject) => {
      const build = spawn('yarn', ['build'], {
        stdio: ['pipe', 'pipe', 'pipe'],
        env: { ...process.env, TURBO_LOG: 'verbose' },
      });

      let output = '';

      build.stdout.on('data', (data) => {
        output += data.toString();
        this.analyzeOutput(data.toString());
      });

      build.stderr.on('data', (data) => {
        output += data.toString();
        this.analyzeError(data.toString());
      });

      build.on('close', (code) => {
        if (code === 0) {
          resolve(output);
        } else {
          reject(
            new Error(
              `Build failed with code ${code}\n${output}`
            )
          );
        }
      });
    });
  }

  analyzeOutput(output) {
    // 出力をリアルタイムで分析
    if (output.includes('WARN')) {
      console.log('⚠️  Warning detected:', output);
    }
  }

  analyzeError(error) {
    // エラーパターンを分析して自動対処
    for (const pattern of this.errorPatterns) {
      if (pattern.test(error)) {
        this.suggestSolution(error, pattern);
      }
    }
  }

  suggestSolution(error, pattern) {
    const solutions = {
      'Module not found': 'ファイルパスを確認してください',
      'Cannot resolve':
        '依存関係をインストールしてください',
      'Unexpected token':
        'シンタックスエラーを確認してください',
      'Memory heap out of memory':
        'メモリ制限を増やしてください',
    };

    console.log(
      `🔧 Suggested solution: ${solutions[pattern.source]}`
    );
  }
}

module.exports = AutoDebugger;

まとめ

Turbopack のログ機能を正しく活用することで、開発効率を劇的に向上させることができます。本記事で紹介した手法を実践することで、以下のような効果が期待できます:

  • 問題の早期発見: ログを活用することで、ビルドエラーやパフォーマンス問題を素早く特定できます
  • デバッグ時間の短縮: 構造化されたログ分析により、問題の原因を効率的に特定できます
  • チーム開発の効率化: 統一されたログ管理により、チーム全体での問題解決がスムーズになります
  • 継続的な改善: パフォーマンスメトリクスの可視化により、継続的な最適化が可能になります

特に重要なのは、ログを「見る」だけでなく「活用する」ことです。本記事で紹介したツールやスクリプトを活用して、Turbopack のログから最大限の価値を引き出してください。

開発の現場で「なぜ?」という疑問が生まれたとき、ログがその答えを教えてくれるでしょう。正しいログ活用により、より良い開発体験を実現できます。

関連リンク