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