Talker

モバイルアプリデバッグ用の便利なツールキット。Webインスペクターからインスパイアされ、モバイルアプリケーション開発向けにカスタマイズ。シンプルかつ多様な機能により、包括的なデバッグ環境を提供する現代的なソリューション。

ロギングFlutterDartモバイル開発デバッグ

ライブラリ

Talker

概要

TalkerはFlutter/Dart向けの「高機能エラーハンドリング・ロギングライブラリ」として開発された、Flutter アプリケーション開発における包括的なデバッグ・モニタリングソリューションです。「人間のためのログ」をコンセプトに、UI、HTTPリクエスト、エラー、その他の情報を統合的に記録・管理し、アプリの監視、ログ履歴の保存、レポート共有、カスタムログ、色彩豊かな表示カスタマイズなど、Flutter開発者にとって必要不可欠な機能を提供しています。

詳細

Talker 2025年版はFlutter/Dartエコシステムにおけるデバッグ・モニタリングツールの決定版として確固たる地位を確立しています。シンプルで直感的なAPIを提供し、アプリケーション内での例外処理、ログ管理、エラー追跡を一元化。DioによるHTTPリクエスト/レスポンスの自動ログ、BLoCパターンとの統合、Riverpodとの連携など、Flutter開発で使用される主要なライブラリ・パターンとの豊富な統合機能を持ちます。アプリ内でのログUI表示、ログ履歴の共有・保存、UI例外のアラート表示など、開発・デバッグフローを大幅に改善する実用的な機能を包括的に提供します。

主な特徴

  • 包括的ログ管理: UI、HTTP、エラー、カスタムログの統合管理
  • リッチなUI表示: TalkerScreenによる美しいログ表示とカスタマイズ
  • 自動統合サポート: Dio HTTP、BLoC、Riverpodとの組み込み連携
  • エラー追跡とレポート: Firebase Crashlytics、Sentry等との連携
  • カラーカスタマイズ: 8種類のデフォルトログタイプと完全カスタム対応
  • ストリーミング対応: リアルタイムログ監視とアラート機能

メリット・デメリット

メリット

  • Flutter/Dartエコシステムでの豊富な統合オプションとコミュニティサポート
  • 直感的で美しいUIによる効率的なデバッグ体験とログ管理
  • DioとのネイティブHTTPログ統合による通信デバッグの簡素化
  • BLoC、Riverpodとの専用パッケージによる状態管理ログの自動化
  • Firebase Crashlytics、Sentryとの連携による本格的なエラー分析
  • 100%テストカバレッジによる高い信頼性と安定性

デメリット

  • Flutter専用ライブラリのため他プラットフォームでの利用不可
  • 大量ログ出力時のメモリ使用量増加とパフォーマンス影響
  • カスタマイズ機能が豊富な分、初期設定の複雑性
  • 本番環境での詳細ログ出力によるセキュリティリスク
  • ログ保存・共有機能の適切な管理とプライバシー配慮が必要
  • 依存ライブラリが多くアプリサイズへの影響

参考ページ

書き方の例

インストール・セットアップ(pubspec.yaml追加)

# pubspec.yamlにTalkerの依存関係を追加
dependencies:
  flutter:
    sdk: flutter
  
  # Core Talker package
  talker: ^4.0.0
  
  # Flutter UI integration
  talker_flutter: ^4.0.0
  
  # HTTP logging (optional, if using Dio)
  talker_dio_logger: ^4.0.0
  
  # BLoC integration (optional, if using BLoC)
  talker_bloc_logger: ^4.0.0
  
  # Riverpod integration (optional, if using Riverpod)
  talker_riverpod_logger: ^4.0.0

dev_dependencies:
  flutter_test:
    sdk: flutter

# パッケージのインストール
# flutter pub get
// main.dartでTalkerを初期化
import 'package:flutter/material.dart';
import 'package:talker_flutter/talker_flutter.dart';

// Talkerインスタンスをグローバルに定義
final talker = TalkerFlutter.init();

void main() {
  // Flutterエラーハンドリングの設定
  FlutterError.onError = (FlutterErrorDetails details) {
    talker.handle(details.exception, details.stack);
  };
  
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Talker Demo',
      
      // TalkerのNavigatorObserverを追加(ナビゲーションログ用)
      navigatorObservers: [
        TalkerRouteObserver(talker),
      ],
      
      home: MyHomePage(),
      
      // グローバルエラーハンドラーの設定
      builder: (context, child) {
        return TalkerWrapper(
          talker: talker,
          child: child!,
        );
      },
    );
  }
}

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  @override
  void initState() {
    super.initState();
    // アプリ起動ログ
    talker.info('アプリが起動しました');
  }

  void _incrementCounter() {
    setState(() {
      _counter++;
      talker.info('カウンターが増加: $_counter');
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Talker Demo'),
        actions: [
          // ログ画面への遷移ボタン
          IconButton(
            icon: Icon(Icons.bug_report),
            onPressed: () {
              Navigator.of(context).push(
                MaterialPageRoute(
                  builder: (context) => TalkerScreen(talker: talker),
                ),
              );
            },
          ),
        ],
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'ボタンを押した回数:',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.headlineMedium,
            ),
            ElevatedButton(
              onPressed: () {
                // 意図的にエラーを発生させる例
                try {
                  throw Exception('テスト例外です');
                } catch (e, st) {
                  talker.handle(e, st);
                }
              },
              child: Text('エラーをテスト'),
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ),
    );
  }
}

基本的なログ出力

import 'package:talker/talker.dart';

// Talkerインスタンスの作成
final talker = Talker();

void basicLoggingExample() {
  // 情報ログ
  talker.info('アプリケーションが正常に起動しました');
  
  // 警告ログ
  talker.warning('メモリ使用量が高くなっています 🚨');
  
  // デバッグログ
  talker.debug('ユーザー認証プロセスを開始します 🔍');
  
  // 成功ログ
  talker.good('データの保存が完了しました ✅');
  
  // エラーログ(簡単な文字列)
  talker.error('ネットワーク接続に失敗しました ❌');
  
  // カスタムログタイプ
  talker.log('カスタムメッセージ', title: 'CUSTOM');
  
  // ログレベル指定
  talker.logTyped(
    TalkerLog(
      '重要な処理が開始されました',
      logLevel: LogLevel.critical,
      title: 'PROCESS',
    ),
  );
  
  // 構造化ログ(マップデータ)
  talker.info({
    'event': 'user_login',
    'user_id': '12345',
    'timestamp': DateTime.now().toIso8601String(),
    'success': true,
  });
  
  // 複数行ログ
  talker.info('''
  多行にわたる詳細情報:
  - ユーザー: 田中太郎
  - 操作: データ更新
  - 結果: 成功
  ''');
}

// 例外とエラーハンドリング
void errorHandlingExample() {
  try {
    // 何らかの処理
    performRiskyOperation();
  } catch (error, stackTrace) {
    // エラーと詳細なスタックトレースを記録
    talker.handle(error, stackTrace, 'ユーザーデータ処理中のエラー');
  }
  
  // 条件付きログ
  if (someCondition) {
    talker.warning('条件に一致: 注意が必要です');
  }
  
  // ログレベルに基づく出力制御
  talker.configure(
    settings: TalkerSettings(
      logLevel: LogLevel.info, // info以上のレベルのみ出力
    ),
  );
}

void performRiskyOperation() {
  throw Exception('サンプル例外');
}

bool someCondition = true;

HTTPリクエストログ

import 'package:dio/dio.dart';
import 'package:talker_dio_logger/talker_dio_logger.dart';
import 'package:talker/talker.dart';

// TalkerとDioの統合設定
void setupHttpLogging() {
  final talker = Talker();
  final dio = Dio();
  
  // 基本的なDio Talkerログ設定
  dio.interceptors.add(
    TalkerDioLogger(
      talker: talker,
      settings: const TalkerDioLoggerSettings(
        printRequestHeaders: true,
        printResponseHeaders: true,
        printResponseMessage: true,
      ),
    ),
  );
  
  // 高度なHTTPログ設定
  dio.interceptors.add(
    TalkerDioLogger(
      talker: talker,
      settings: TalkerDioLoggerSettings(
        // リクエスト詳細設定
        printRequestHeaders: true,
        printRequestData: true,
        printResponseHeaders: true,
        printResponseData: true,
        printResponseMessage: true,
        
        // ログ色設定
        requestPen: AnsiPen()..blue(),
        responsePen: AnsiPen()..green(), 
        errorPen: AnsiPen()..red(),
        
        // フィルタリング設定
        requestFilter: (RequestOptions options) {
          // '/secure'パスを含むリクエストを除外
          return !options.path.contains('/secure');
        },
        responseFilter: (Response response) {
          // 301リダイレクトレスポンスを除外
          return response.statusCode != 301;
        },
        
        // エラーフィルタ
        errorFilter: (DioException error) {
          // 特定のエラータイプのみログ
          return error.type != DioExceptionType.cancel;
        },
      ),
    ),
  );
}

// 実際のHTTPリクエスト例
void httpRequestExample() async {
  final dio = Dio();
  
  try {
    // GETリクエスト
    talker.info('ユーザーデータを取得開始');
    final response = await dio.get(
      'https://jsonplaceholder.typicode.com/users',
      queryParameters: {'_limit': 10},
    );
    talker.good('ユーザーデータ取得完了: ${response.data.length}件');
    
    // POSTリクエスト
    talker.info('新規投稿を作成開始');
    final postResponse = await dio.post(
      'https://jsonplaceholder.typicode.com/posts',
      data: {
        'title': 'Talkerテスト投稿',
        'body': 'これはTalkerのHTTPログテストです',
        'userId': 1,
      },
    );
    
    if (postResponse.statusCode == 201) {
      talker.good('投稿作成成功: ID=${postResponse.data['id']}');
    }
    
  } catch (e) {
    talker.error('HTTP リクエストエラー: $e');
  }
}

// カスタムHTTPログクラス
class CustomHttpLogger {
  final Talker talker;
  
  CustomHttpLogger(this.talker);
  
  void logRequest(String method, String url, Map<String, dynamic>? data) {
    talker.log(
      'HTTP $method $url${data != null ? '\nBody: $data' : ''}',
      title: 'HTTP_REQ',
    );
  }
  
  void logResponse(int statusCode, String url, dynamic data) {
    final isSuccess = statusCode >= 200 && statusCode < 300;
    
    if (isSuccess) {
      talker.good('HTTP $statusCode $url');
    } else {
      talker.error('HTTP $statusCode $url\nResponse: $data');
    }
  }
  
  void logError(String method, String url, String error) {
    talker.handle(
      Exception('HTTP $method $url failed: $error'),
      null,
      'HTTPリクエストエラー',
    );
  }
}

// カスタムHTTPログの使用例
void customHttpLoggingExample() async {
  final logger = CustomHttpLogger(talker);
  
  logger.logRequest('GET', '/api/users', null);
  
  try {
    // HTTPリクエストの実行
    // ...
    logger.logResponse(200, '/api/users', {'count': 10});
  } catch (e) {
    logger.logError('GET', '/api/users', e.toString());
  }
}

エラーハンドリング

import 'package:flutter/material.dart';
import 'package:talker_flutter/talker_flutter.dart';

// 包括的なエラーハンドリング設定
class ErrorHandlingService {
  static final Talker talker = TalkerFlutter.init(
    settings: TalkerSettings(
      // 本番環境では詳細ログを無効化
      enabled: true,
      useConsoleLogs: true,
      maxHistoryItems: 1000,
    ),
  );
  
  // アプリ全体のエラーハンドリングを初期化
  static void initializeErrorHandling() {
    // Flutterフレームワークエラー
    FlutterError.onError = (FlutterErrorDetails details) {
      talker.handle(
        details.exception,
        details.stack,
        'Flutter フレームワークエラー',
      );
      
      // 本番環境では外部サービス(Crashlytics等)にも送信
      if (isProduction) {
        sendToCrashlytics(details);
      }
    };
    
    // Dart非同期エラー
    PlatformDispatcher.instance.onError = (error, stack) {
      talker.handle(error, stack, 'Dart 非同期エラー');
      return true;
    };
    
    // ゾーンエラー(オプション)
    runZonedGuarded(
      () {
        runApp(MyApp());
      },
      (error, stack) {
        talker.handle(error, stack, 'ゾーンキャッチエラー');
      },
    );
  }
  
  // カスタム例外クラス
  static void handleBusinessError(String operation, Exception error) {
    talker.error('業務エラー[$operation]: ${error.toString()}');
    
    // ユーザー向けメッセージの表示
    showErrorDialog(operation, error);
  }
  
  // ネットワークエラー専用処理
  static void handleNetworkError(String endpoint, dynamic error) {
    String message = 'ネットワークエラー[$endpoint]';
    
    if (error is DioException) {
      switch (error.type) {
        case DioExceptionType.connectionTimeout:
          message += ': 接続タイムアウト';
          break;
        case DioExceptionType.receiveTimeout:
          message += ': 受信タイムアウト';
          break;
        case DioExceptionType.badResponse:
          message += ': サーバーエラー(${error.response?.statusCode})';
          break;
        default:
          message += ': ${error.message}';
      }
    }
    
    talker.error(message);
  }
  
  // 回復可能エラーの処理
  static Future<T?> handleRecoverableError<T>(
    String operation,
    Future<T> Function() action,
    {int maxRetries = 3}
  ) async {
    for (int attempt = 0; attempt < maxRetries; attempt++) {
      try {
        talker.debug('$operation を実行中... (試行 ${attempt + 1}/$maxRetries)');
        final result = await action();
        
        if (attempt > 0) {
          talker.good('$operation が再試行で成功 (試行 ${attempt + 1})');
        }
        
        return result;
        
      } catch (error, stackTrace) {
        talker.warning('$operation 失敗 (試行 ${attempt + 1}/$maxRetries): $error');
        
        if (attempt == maxRetries - 1) {
          talker.handle(error, stackTrace, '$operation の最終試行で失敗');
          rethrow;
        }
        
        // 指数バックオフで待機
        await Future.delayed(Duration(seconds: 2 << attempt));
      }
    }
    
    return null;
  }
}

// エラー専用ダイアログ
void showErrorDialog(String operation, Exception error) {
  // 実装は環境に応じて
  talker.info('エラーダイアログを表示: $operation');
}

// 外部サービス送信(例:Firebase Crashlytics)
void sendToCrashlytics(FlutterErrorDetails details) {
  // 実際の実装ではFirebase Crashlyticsに送信
  talker.info('Crashlyticsにエラーを送信: ${details.exception}');
}

bool get isProduction => const bool.fromEnvironment('dart.vm.product');

// 使用例
void errorHandlingUsageExample() async {
  // 基本的なエラーハンドリング
  try {
    await riskyOperation();
  } catch (e, st) {
    ErrorHandlingService.talker.handle(e, st, 'リスク操作でエラー');
  }
  
  // 業務エラーの処理
  try {
    validateUserInput('');
  } on ValidationException catch (e) {
    ErrorHandlingService.handleBusinessError('ユーザー入力検証', e);
  }
  
  // 回復可能エラーの処理
  final result = await ErrorHandlingService.handleRecoverableError(
    'データ同期',
    () => syncDataFromServer(),
    maxRetries: 5,
  );
  
  if (result != null) {
    ErrorHandlingService.talker.good('データ同期完了');
  }
}

Future<void> riskyOperation() async {
  throw Exception('何かが問題です');
}

void validateUserInput(String input) {
  if (input.isEmpty) {
    throw ValidationException('入力が空です');
  }
}

Future<String> syncDataFromServer() async {
  // サーバーからのデータ同期(失敗する可能性あり)
  if (DateTime.now().millisecond % 3 == 0) {
    throw Exception('サーバー接続エラー');
  }
  return 'データ同期完了';
}

class ValidationException implements Exception {
  final String message;
  ValidationException(this.message);
  
  @override
  String toString() => 'ValidationException: $message';
}

UI統合(TalkerScreen)

import 'package:flutter/material.dart';
import 'package:talker_flutter/talker_flutter.dart';

// カスタマイズされたTalkerScreen実装
class CustomTalkerScreen extends StatefulWidget {
  final Talker talker;
  
  const CustomTalkerScreen({Key? key, required this.talker}) : super(key: key);
  
  @override
  State<CustomTalkerScreen> createState() => _CustomTalkerScreenState();
}

class _CustomTalkerScreenState extends State<CustomTalkerScreen> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('アプリログ'),
        backgroundColor: Colors.blueGrey[800],
        foregroundColor: Colors.white,
        actions: [
          // ログクリアボタン
          IconButton(
            icon: const Icon(Icons.clear_all),
            onPressed: () {
              showDialog(
                context: context,
                builder: (context) => AlertDialog(
                  title: const Text('ログクリア'),
                  content: const Text('すべてのログを削除しますか?'),
                  actions: [
                    TextButton(
                      onPressed: () => Navigator.pop(context),
                      child: const Text('キャンセル'),
                    ),
                    TextButton(
                      onPressed: () {
                        widget.talker.cleanHistory();
                        Navigator.pop(context);
                        widget.talker.info('ログ履歴をクリアしました');
                      },
                      child: const Text('削除'),
                    ),
                  ],
                ),
              );
            },
          ),
          // ログ共有ボタン
          IconButton(
            icon: const Icon(Icons.share),
            onPressed: () => _shareLogHistory(),
          ),
          // 設定ボタン
          IconButton(
            icon: const Icon(Icons.settings),
            onPressed: () => _showLogSettings(),
          ),
        ],
      ),
      body: TalkerScreen(
        talker: widget.talker,
        // カスタムテーマ適用
        theme: TalkerScreenTheme(
          backgroundColor: Colors.grey[900]!,
          cardColor: Colors.grey[800]!,
          textColor: Colors.white,
          logColors: {
            // HTTPリクエスト・レスポンスの色
            TalkerLogType.httpRequest.key: Colors.blue[300]!,
            TalkerLogType.httpResponse.key: Colors.green[300]!,
            
            // エラーレベル別の色設定
            TalkerLogType.error.key: Colors.red[400]!,
            TalkerLogType.critical.key: Colors.red[600]!,
            TalkerLogType.warning.key: Colors.orange[400]!,
            TalkerLogType.info.key: Colors.blue[200]!,
            TalkerLogType.debug.key: Colors.grey[400]!,
            TalkerLogType.verbose.key: Colors.grey[600]!,
            
            // カスタムログタイプの色
            'CUSTOM': Colors.purple[300]!,
            'BUSINESS': Colors.teal[300]!,
            'PERFORMANCE': Colors.yellow[300]!,
          },
        ),
        // ログアイテムのカスタムビルダー
        itemsBuilder: (context, data) {
          return TalkerDataCards(
            data: data,
            onCopyTap: (text) {
              _copyToClipboard(text);
              ScaffoldMessenger.of(context).showSnackBar(
                const SnackBar(content: Text('ログをクリップボードにコピーしました')),
              );
            },
          );
        },
      ),
      
      // フローティングアクションボタンでテストログ追加
      floatingActionButton: FloatingActionButton.extended(
        onPressed: _addTestLogs,
        icon: const Icon(Icons.add),
        label: const Text('テストログ'),
      ),
    );
  }
  
  void _shareLogHistory() {
    final logs = widget.talker.history.formatted;
    
    // 実際の実装ではshare_plusパッケージ等を使用
    widget.talker.info('ログ履歴を共有します(${logs.length}行)');
    
    // シミュレーション: 実際はファイル共有やメール送信
    showDialog(
      context: context,
      builder: (context) => AlertDialog(
        title: const Text('ログ共有'),
        content: Text('${logs.length}行のログが共有用に準備されました'),
        actions: [
          TextButton(
            onPressed: () => Navigator.pop(context),
            child: const Text('OK'),
          ),
        ],
      ),
    );
  }
  
  void _showLogSettings() {
    showModalBottomSheet(
      context: context,
      builder: (context) => Container(
        padding: const EdgeInsets.all(16),
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            const Text(
              'ログ設定',
              style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
            ),
            const SizedBox(height: 16),
            ListTile(
              title: const Text('ログレベル'),
              subtitle: const Text('表示するログの最小レベル'),
              trailing: DropdownButton<LogLevel>(
                value: LogLevel.verbose,
                items: LogLevel.values.map((level) {
                  return DropdownMenuItem(
                    value: level,
                    child: Text(level.name),
                  );
                }).toList(),
                onChanged: (level) {
                  if (level != null) {
                    widget.talker.configure(
                      settings: TalkerSettings(logLevel: level),
                    );
                    Navigator.pop(context);
                  }
                },
              ),
            ),
            ListTile(
              title: const Text('履歴サイズ'),
              subtitle: const Text('保持するログの最大件数'),
              trailing: const Text('1000件'),
              onTap: () {
                // 履歴サイズ設定ダイアログ
                Navigator.pop(context);
              },
            ),
          ],
        ),
      ),
    );
  }
  
  void _copyToClipboard(String text) {
    // 実際の実装ではClipboard.setData()を使用
    widget.talker.debug('クリップボードにコピー: ${text.substring(0, 50)}...');
  }
  
  void _addTestLogs() {
    widget.talker.info('テストログを追加します');
    widget.talker.warning('これは警告メッセージです');
    widget.talker.error('これはエラーメッセージです');
    widget.talker.good('これは成功メッセージです');
    widget.talker.debug('これはデバッグメッセージです');
    
    // カスタムログ
    widget.talker.log(
      'カスタムビジネスロジックのログです',
      title: 'BUSINESS',
    );
    
    // 構造化データ
    widget.talker.info({
      'event': 'test_log_added',
      'timestamp': DateTime.now().toIso8601String(),
      'data': {'count': 5, 'type': 'sample'}
    });
  }
}

// TalkerScreenの埋め込み例(他画面への統合)
class DebugDrawer extends StatelessWidget {
  final Talker talker;
  
  const DebugDrawer({Key? key, required this.talker}) : super(key: key);
  
  @override
  Widget build(BuildContext context) {
    return Drawer(
      child: Column(
        children: [
          DrawerHeader(
            decoration: BoxDecoration(
              color: Colors.blue[600],
            ),
            child: const Text(
              'デバッグメニュー',
              style: TextStyle(color: Colors.white, fontSize: 20),
            ),
          ),
          Expanded(
            child: TalkerScreen(
              talker: talker,
              theme: const TalkerScreenTheme(
                backgroundColor: Colors.white,
                cardColor: Color(0xFFF5F5F5),
                textColor: Colors.black87,
              ),
            ),
          ),
          Container(
            padding: const EdgeInsets.all(8),
            child: Row(
              children: [
                Expanded(
                  child: ElevatedButton(
                    onPressed: () => talker.cleanHistory(),
                    child: const Text('ログクリア'),
                  ),
                ),
                const SizedBox(width: 8),
                Expanded(
                  child: ElevatedButton(
                    onPressed: () {
                      Navigator.push(
                        context,
                        MaterialPageRoute(
                          builder: (context) => CustomTalkerScreen(talker: talker),
                        ),
                      );
                    },
                    child: const Text('詳細画面'),
                  ),
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }
}

// メイン画面でのTalkerScreen統合例
class MainScreenWithTalker extends StatelessWidget {
  final Talker talker;
  
  const MainScreenWithTalker({Key? key, required this.talker}) : super(key: key);
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('メインアプリ'),
        actions: [
          // デバッグモードでのみログボタンを表示
          if (!const bool.fromEnvironment('dart.vm.product'))
            IconButton(
              icon: Stack(
                children: [
                  const Icon(Icons.bug_report),
                  // エラー件数バッジ
                  TalkerBuilder(
                    talker: talker,
                    builder: (context, data) {
                      final errorCount = data
                          .where((log) => log.logLevel.index >= LogLevel.error.index)
                          .length;
                      
                      if (errorCount > 0) {
                        return Positioned(
                          right: 0,
                          top: 0,
                          child: Container(
                            padding: const EdgeInsets.all(2),
                            decoration: const BoxDecoration(
                              color: Colors.red,
                              shape: BoxShape.circle,
                            ),
                            child: Text(
                              '$errorCount',
                              style: const TextStyle(
                                color: Colors.white,
                                fontSize: 10,
                              ),
                            ),
                          ),
                        );
                      }
                      return const SizedBox.shrink();
                    },
                  ),
                ],
              ),
              onPressed: () {
                Navigator.push(
                  context,
                  MaterialPageRoute(
                    builder: (context) => CustomTalkerScreen(talker: talker),
                  ),
                );
              },
            ),
        ],
      ),
      
      // デバッグドロワー
      endDrawer: !const bool.fromEnvironment('dart.vm.product')
          ? DebugDrawer(talker: talker)
          : null,
      
      body: const Center(
        child: Text('メインコンテンツ'),
      ),
    );
  }
}

カスタムログ設定

import 'package:talker/talker.dart';
import 'package:talker_flutter/talker_flutter.dart';

// カスタムログタイプの定義
class BusinessLog extends TalkerLog {
  BusinessLog(String message) : super(message);
  
  @override
  String get key => 'BUSINESS';
  
  @override
  String get title => 'ビジネスロジック';
  
  @override
  AnsiPen get pen => AnsiPen()..magenta();
}

class PerformanceLog extends TalkerLog {
  final int duration;
  final String operation;
  
  PerformanceLog(this.operation, this.duration) 
      : super('$operation 実行時間: ${duration}ms');
  
  @override
  String get key => 'PERFORMANCE';
  
  @override
  String get title => 'パフォーマンス';
  
  @override
  AnsiPen get pen => AnsiPen()..yellow();
}

class SecurityLog extends TalkerLog {
  final String userId;
  final String action;
  
  SecurityLog(this.action, {required this.userId}) 
      : super('セキュリティイベント: $action [User: $userId]');
  
  @override
  String get key => 'SECURITY';
  
  @override
  String get title => 'セキュリティ';
  
  @override
  AnsiPen get pen => AnsiPen()..red();
}

// カスタムTalker設定クラス
class AppTalkerConfig {
  static Talker createTalker() {
    return TalkerFlutter.init(
      settings: TalkerSettings(
        // 基本設定
        enabled: true,
        useConsoleLogs: true,
        useHistory: true,
        maxHistoryItems: 1000,
        
        // ログレベル設定(本番環境では調整)
        logLevel: _getLogLevel(),
        
        // ログフォーマッター
        loggerFormatter: CustomLogFormatter(),
        
        // ログフィルター
        loggerFilter: CustomLogFilter(),
      ),
      
      // カスタムログタイプ登録
      logger: TalkerLogger(
        settings: TalkerLoggerSettings(
          enableColors: true,
          lineLength: 120,
        ),
      ),
    );
  }
  
  static LogLevel _getLogLevel() {
    // 環境に応じたログレベル設定
    if (const bool.fromEnvironment('dart.vm.product')) {
      return LogLevel.warning; // 本番環境: 警告以上のみ
    } else if (const bool.fromEnvironment('dart.vm.profile')) {
      return LogLevel.info; // プロファイル: 情報以上
    } else {
      return LogLevel.verbose; // デバッグ: すべて
    }
  }
}

// カスタムログフォーマッター
class CustomLogFormatter implements LoggerFormatter {
  @override
  String fmt(LogDetails details, TalkerLoggerSettings settings) {
    final time = DateTime.now().toString().substring(11, 23);
    final level = details.logLevel.name.toUpperCase().padRight(7);
    final title = details.title?.padRight(10) ?? ''.padRight(10);
    
    return '[$time] [$level] [$title] ${details.message}';
  }
}

// カスタムログフィルター
class CustomLogFilter implements LoggerFilter {
  // フィルタリング対象キーワード
  static const List<String> _sensitiveKeywords = [
    'password',
    'token',
    'secret',
    'key',
    'credential',
  ];
  
  @override
  bool shouldLog(LogDetails details) {
    final message = details.message.toString().toLowerCase();
    
    // 機密情報を含むログを除外
    if (_sensitiveKeywords.any((keyword) => message.contains(keyword))) {
      return false;
    }
    
    // 本番環境では特定のログタイプを除外
    if (const bool.fromEnvironment('dart.vm.product')) {
      if (details.title == 'DEBUG' || details.title == 'VERBOSE') {
        return false;
      }
    }
    
    return true;
  }
}

// パフォーマンス測定付きログ
class PerformanceLogger {
  final Talker talker;
  final Map<String, DateTime> _startTimes = {};
  
  PerformanceLogger(this.talker);
  
  void startOperation(String operationName) {
    _startTimes[operationName] = DateTime.now();
    talker.debug('パフォーマンス測定開始: $operationName');
  }
  
  void endOperation(String operationName) {
    final startTime = _startTimes.remove(operationName);
    if (startTime != null) {
      final duration = DateTime.now().difference(startTime).inMilliseconds;
      talker.logTyped(PerformanceLog(operationName, duration));
      
      // 遅い処理の警告
      if (duration > 1000) {
        talker.warning('遅い処理検出: $operationName (${duration}ms)');
      }
    }
  }
  
  Future<T> measureAsync<T>(
    String operationName,
    Future<T> Function() operation,
  ) async {
    startOperation(operationName);
    try {
      final result = await operation();
      endOperation(operationName);
      return result;
    } catch (e) {
      endOperation(operationName);
      rethrow;
    }
  }
}

// 使用例とセットアップ
void customLoggingExample() {
  // カスタムTalkerの作成
  final talker = AppTalkerConfig.createTalker();
  
  // パフォーマンスロガーの作成
  final perfLogger = PerformanceLogger(talker);
  
  // カスタムログの使用例
  talker.logTyped(BusinessLog('ユーザー認証処理を開始'));
  talker.logTyped(SecurityLog('ログイン試行', userId: 'user123'));
  
  // パフォーマンス測定例
  perfLogger.startOperation('データベースクエリ');
  
  // 何らかの処理...
  Future.delayed(Duration(milliseconds: 500), () {
    perfLogger.endOperation('データベースクエリ');
  });
  
  // 非同期処理の測定
  perfLogger.measureAsync('ファイル読み込み', () async {
    await Future.delayed(Duration(milliseconds: 200));
    return 'ファイルデータ';
  });
  
  // 構造化ログの例
  talker.info({
    'event': 'user_action',
    'user_id': 'user123',
    'action': 'purchase',
    'item_id': 'item456',
    'amount': 1200,
    'timestamp': DateTime.now().toIso8601String(),
    'metadata': {
      'platform': 'mobile',
      'version': '1.2.3',
    },
  });
  
  // ログレベル別の使用例
  talker.verbose('詳細なデバッグ情報');
  talker.debug('デバッグ情報');
  talker.info('一般的な情報');
  talker.warning('注意が必要な状況');
  talker.error('エラーが発生');
  
  // カスタムタイトル付きログ
  talker.log('カスタムメッセージ', title: 'CUSTOM');
  
  // 条件付きログ
  if (shouldLogDetailedInfo()) {
    talker.debug('詳細情報: データベース接続プール使用率 85%');
  }
  
  // リアルタイムログ監視の例
  talker.stream.listen((log) {
    if (log.logLevel.index >= LogLevel.error.index) {
      // エラーレベル以上のログを外部サービスに送信
      sendToExternalService(log);
    }
  });
}

bool shouldLogDetailedInfo() {
  // デバッグモードまたは特定条件での詳細ログ有効化
  return !const bool.fromEnvironment('dart.vm.product');
}

void sendToExternalService(TalkerDataInterface log) {
  // 外部ログ集約サービス(Splunk、ELK、Datadog等)への送信
  print('外部サービスにログ送信: ${log.message}');
}