dart:developer log

DartとFlutterに組み込まれた標準ロギング機能。package:loggingと密接に連携するよう設計され、Flutter DevToolsとの統合を提供。外部依存関係なしで基本的なロギング要件を満たし、開発ツールでの直接表示が可能。

ロギングライブラリDartFlutterDevToolsデバッグ標準

ライブラリ

dart:developer log

概要

dart:developer logは、DartとFlutterに組み込まれた標準ロギング機能で、開発時のデバッグとパフォーマンス測定に特化した機能を提供します。package:loggingと密接に連携するよう設計され、Flutter DevToolsとの統合により開発者ツールでの直接表示が可能。外部依存関係なしで基本的なロギング要件を満たし、log関数による詳細なメッセージ出力、Timeline APIによるパフォーマンストレース、postEventによるカスタムイベント送信をサポート。軽量なログ要件や依存関係を最小化したいプロジェクトで重宝される標準搭載のデバッグソリューションです。

詳細

dart:developer libraryは、Dart VM サービスプロトコルを介してFlutter DevToolsと通信し、ログ、パフォーマンスデータ、メモリ使用量などの診断情報を収集する仕組みを提供します。2025年Flutter開発での基本的なデバッグ手段として広く使用され、DevToolsとの統合によりデバッグ体験が向上。log関数はprint()やdebugPrint()と比較して、より詳細な粒度と豊富な情報をロギング出力に含めることができ、name、time、sequenceNumber、level、error、stackTrace等のパラメータにより構造化された情報を提供。Timeline.startSync/finishSyncによるカスタムパフォーマンストレース、postEventによる任意のイベントデータ送信、inspectによるオブジェクト検査機能など、開発・デバッグに特化した多様な機能を統合しています。

主な特徴

  • Dart/Flutter標準搭載: 外部パッケージ不要の即座利用
  • DevTools統合: Flutter DevToolsでのログ表示と検査機能
  • 構造化ログ: name、level、時刻等の豊富なメタデータ
  • パフォーマンストレース: Timeline APIによる実行時間測定
  • イベント送信: postEventによるカスタムイベントデータ
  • オブジェクト検査: DevToolsでのオブジェクト詳細表示

メリット・デメリット

メリット

  • Dart/Flutterエコシステムでの標準的地位と学習コストの低さ
  • 外部依存関係なしによる軽量性とプロジェクトの複雑性削減
  • Flutter DevToolsとの優れた統合性と直感的なデバッグ体験
  • 構造化ログによる詳細な情報提供とフィルタリング機能
  • Timeline APIによる正確なパフォーマンス測定とボトルネック特定
  • 学習段階から本格開発まで一貫して使用可能な継続性

デメリット

  • 本番環境でのログ管理機能とファイル出力機能の限界
  • 大規模アプリケーションでの高度なフィルタリング・分析機能不足
  • 外部ログ収集サービスやクラウド連携機能の欠如
  • print()と比較したprint length制限の存在(一部環境)
  • エンタープライズレベルのログ分析・監視要件に対する機能不足
  • DevTools依存によるCI/CD環境での制約

参考ページ

書き方の例

基本的なログ出力

import 'dart:developer' as developer;

void main() {
  // 基本的なログ出力
  developer.log('アプリケーションが開始されました');
  
  // カテゴリ付きログ
  developer.log('ユーザー認証開始', name: 'auth');
  developer.log('データベース接続確立', name: 'database');
  developer.log('API呼び出し完了', name: 'api');
  
  // レベル付きログ
  developer.log('デバッグ情報', name: 'debug', level: 500);
  developer.log('情報メッセージ', name: 'info', level: 800);
  developer.log('警告メッセージ', name: 'warning', level: 900);
  developer.log('エラーメッセージ', name: 'error', level: 1000);
  
  // 詳細な変数情報
  final user = User(id: 123, name: '田中太郎', email: '[email protected]');
  developer.log('ユーザー情報: $user', name: 'user_data');
  
  // JSON形式のログ
  final userData = {
    'id': 123,
    'name': '田中太郎',
    'loginTime': DateTime.now().toIso8601String(),
    'permissions': ['read', 'write']
  };
  developer.log('ユーザーデータ', name: 'json_data', error: userData);
}

class User {
  final int id;
  final String name;
  final String email;
  
  User({required this.id, required this.name, required this.email});
  
  @override
  String toString() => 'User(id: $id, name: $name, email: $email)';
}

エラーハンドリングとスタックトレース

import 'dart:developer' as developer;

void main() {
  // 例外処理とスタックトレース
  try {
    riskyOperation();
  } catch (error, stackTrace) {
    developer.log(
      'エラーが発生しました',
      name: 'error_handler',
      error: error,
      stackTrace: stackTrace,
      level: 1000,
    );
  }
  
  // カスタム例外の詳細ログ
  try {
    validateUserInput('');
  } catch (error, stackTrace) {
    developer.log(
      'バリデーションエラー',
      name: 'validation',
      error: error,
      stackTrace: stackTrace,
      level: 900,
      time: DateTime.now(),
    );
  }
}

void riskyOperation() {
  throw Exception('何かが間違っています');
}

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

// カスタム例外クラス
class CustomException implements Exception {
  final String message;
  final String code;
  final Map<String, dynamic> details;
  
  CustomException(this.message, this.code, {this.details = const {}});
  
  @override
  String toString() => 'CustomException($code): $message';
}

// 例外をログに記録する関数
void logException(Exception exception, StackTrace stackTrace, {String? context}) {
  developer.log(
    'Exception in ${context ?? "unknown context"}',
    name: 'exception',
    error: exception,
    stackTrace: stackTrace,
    level: 1000,
    time: DateTime.now(),
  );
}

パフォーマンス測定とタイムライン

import 'dart:developer' as developer;

void main() async {
  // 基本的なタイムライン測定
  developer.Timeline.startSync('データ読み込み');
  await loadData();
  developer.Timeline.finishSync();
  
  // ネストしたタイムライン測定
  developer.Timeline.startSync('ユーザー処理');
  
  developer.Timeline.startSync('認証');
  await authenticateUser();
  developer.Timeline.finishSync();
  
  developer.Timeline.startSync('権限チェック');
  await checkPermissions();
  developer.Timeline.finishSync();
  
  developer.Timeline.finishSync();
  
  // 関数の実行時間測定
  await measureFunction('API呼び出し', () async {
    await callApi();
  });
  
  // 複数の処理の比較測定
  await measureFunction('データベース読み込み', () async {
    await loadFromDatabase();
  });
  
  await measureFunction('キャッシュ読み込み', () async {
    await loadFromCache();
  });
}

Future<void> loadData() async {
  await Future.delayed(Duration(milliseconds: 100));
}

Future<void> authenticateUser() async {
  await Future.delayed(Duration(milliseconds: 50));
}

Future<void> checkPermissions() async {
  await Future.delayed(Duration(milliseconds: 30));
}

Future<void> callApi() async {
  await Future.delayed(Duration(milliseconds: 200));
}

Future<void> loadFromDatabase() async {
  await Future.delayed(Duration(milliseconds: 150));
}

Future<void> loadFromCache() async {
  await Future.delayed(Duration(milliseconds: 20));
}

// 汎用的な実行時間測定関数
Future<T> measureFunction<T>(String name, Future<T> Function() function) async {
  final stopwatch = Stopwatch()..start();
  developer.Timeline.startSync(name);
  
  try {
    final result = await function();
    stopwatch.stop();
    
    developer.log(
      '$name 完了: ${stopwatch.elapsedMilliseconds}ms',
      name: 'performance',
      level: 800,
    );
    
    return result;
  } finally {
    developer.Timeline.finishSync();
  }
}

// 同期処理の測定
void measureSyncOperation() {
  developer.Timeline.startSync('数値計算');
  
  var result = 0;
  for (int i = 0; i < 1000000; i++) {
    result += i;
  }
  
  developer.Timeline.finishSync();
  
  developer.log('計算結果: $result', name: 'calculation');
}

DevToolsとの高度な統合

import 'dart:developer' as developer;
import 'dart:convert';

void main() {
  // オブジェクトの詳細検査
  final user = User(
    id: 123,
    name: '田中太郎',
    profile: UserProfile(
      age: 30,
      department: '開発部',
      skills: ['Flutter', 'Dart', 'Firebase'],
    ),
  );
  
  // DevToolsでオブジェクトを検査可能にする
  developer.inspect(user);
  developer.log('ユーザーオブジェクトを検査', name: 'object_inspection');
  
  // カスタムイベントの送信
  sendCustomEvent('user_login', {
    'userId': user.id,
    'timestamp': DateTime.now().toIso8601String(),
    'source': 'mobile_app',
  });
  
  // リストデータの詳細ログ
  final items = List.generate(10, (index) => Item(id: index, name: 'Item $index'));
  developer.log('生成されたアイテム数: ${items.length}', name: 'data_generation');
  
  for (final item in items) {
    developer.inspect(item);
  }
  
  // 状態変化のログ
  logStateChange('loading', 'loaded', {'itemCount': items.length});
  
  // パフォーマンスメトリクスの送信
  sendPerformanceMetrics();
}

class User {
  final int id;
  final String name;
  final UserProfile profile;
  
  User({required this.id, required this.name, required this.profile});
  
  @override
  String toString() => 'User(id: $id, name: $name)';
}

class UserProfile {
  final int age;
  final String department;
  final List<String> skills;
  
  UserProfile({required this.age, required this.department, required this.skills});
  
  @override
  String toString() => 'UserProfile(age: $age, department: $department)';
}

class Item {
  final int id;
  final String name;
  
  Item({required this.id, required this.name});
  
  @override
  String toString() => 'Item(id: $id, name: $name)';
}

// カスタムイベントの送信
void sendCustomEvent(String eventName, Map<String, dynamic> data) {
  developer.postEvent(eventName, data);
  developer.log(
    'イベント送信: $eventName',
    name: 'events',
    error: data,
    level: 700,
  );
}

// 状態変化のログ
void logStateChange(String from, String to, Map<String, dynamic> context) {
  final changeData = {
    'from': from,
    'to': to,
    'timestamp': DateTime.now().toIso8601String(),
    'context': context,
  };
  
  developer.postEvent('state_change', changeData);
  developer.log(
    '状態変化: $from -> $to',
    name: 'state',
    error: changeData,
    level: 600,
  );
}

// パフォーマンスメトリクスの送信
void sendPerformanceMetrics() {
  final metrics = {
    'memory_usage': 'unknown', // 実際の実装では適切なメトリクスを取得
    'frame_rate': 60,
    'build_time': DateTime.now().millisecondsSinceEpoch,
  };
  
  developer.postEvent('performance_metrics', metrics);
  developer.log(
    'パフォーマンスメトリクス送信',
    name: 'performance',
    error: metrics,
    level: 500,
  );
}

Flutter アプリケーションでの実用例

import 'dart:developer' as developer;
import 'package:flutter/material.dart';

void main() {
  developer.log('Flutter アプリケーション開始', name: 'app_lifecycle');
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    developer.log('MyApp build メソッド呼び出し', name: 'widget_lifecycle');
    return MaterialApp(
      title: 'Flutter Demo',
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() {
    developer.log('MyHomePage State 作成', name: 'widget_lifecycle');
    return _MyHomePageState();
  }
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;
  
  @override
  void initState() {
    super.initState();
    developer.log('MyHomePage initState', name: 'widget_lifecycle');
  }
  
  @override
  void dispose() {
    developer.log('MyHomePage dispose', name: 'widget_lifecycle');
    super.dispose();
  }
  
  void _incrementCounter() {
    developer.Timeline.startSync('カウンター更新');
    
    setState(() {
      _counter++;
    });
    
    developer.Timeline.finishSync();
    
    developer.log(
      'カウンター更新: $_counter',
      name: 'user_interaction',
      level: 600,
      time: DateTime.now(),
    );
    
    // カスタムイベントの送信
    developer.postEvent('counter_increment', {
      'newValue': _counter,
      'timestamp': DateTime.now().toIso8601String(),
    });
  }
  
  @override
  Widget build(BuildContext context) {
    developer.Timeline.startSync('MyHomePage build');
    
    final widget = Scaffold(
      appBar: AppBar(
        title: Text('Flutter Demo Home Page'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text('ボタンが押された回数:'),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.headlineMedium,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ),
    );
    
    developer.Timeline.finishSync();
    return widget;
  }
}

// ネットワーク要求のログ
class ApiService {
  static Future<Map<String, dynamic>> fetchUserData(int userId) async {
    developer.log('API呼び出し開始: ユーザーデータ取得', name: 'api');
    developer.Timeline.startSync('ユーザーデータ取得 API');
    
    try {
      // 模擬的なAPI呼び出し
      await Future.delayed(Duration(milliseconds: 500));
      
      final userData = {
        'id': userId,
        'name': '田中太郎',
        'email': '[email protected]',
      };
      
      developer.log(
        'API呼び出し成功: ユーザーデータ取得',
        name: 'api',
        error: userData,
        level: 800,
      );
      
      return userData;
      
    } catch (error, stackTrace) {
      developer.log(
        'API呼び出しエラー: ユーザーデータ取得',
        name: 'api',
        error: error,
        stackTrace: stackTrace,
        level: 1000,
      );
      rethrow;
    } finally {
      developer.Timeline.finishSync();
    }
  }
}

// ログレベルに基づくフィルタリング
class Logger {
  static const int DEBUG = 500;
  static const int INFO = 800;
  static const int WARNING = 900;
  static const int ERROR = 1000;
  
  static void debug(String message, {String name = 'debug'}) {
    developer.log(message, name: name, level: DEBUG);
  }
  
  static void info(String message, {String name = 'info'}) {
    developer.log(message, name: name, level: INFO);
  }
  
  static void warning(String message, {String name = 'warning'}) {
    developer.log(message, name: name, level: WARNING);
  }
  
  static void error(String message, {Object? error, StackTrace? stackTrace, String name = 'error'}) {
    developer.log(
      message,
      name: name,
      level: ERROR,
      error: error,
      stackTrace: stackTrace,
    );
  }
}

本番環境での条件付きログ

import 'dart:developer' as developer;
import 'package:flutter/foundation.dart';

void main() {
  // デバッグモードでのみログ出力
  if (kDebugMode) {
    developer.log('デバッグモードで実行中', name: 'app_mode');
  }
  
  // プロファイルモードでのパフォーマンスログ
  if (kProfileMode) {
    developer.log('プロファイルモードで実行中', name: 'app_mode');
  }
  
  // リリースモードでのエラーログのみ
  if (kReleaseMode) {
    developer.log('リリースモードで実行中', name: 'app_mode');
  }
  
  runApp(MyApp());
}

// 条件付きログ出力クラス
class ConditionalLogger {
  static void log(String message, {String name = 'app', int level = 800}) {
    // デバッグモードでのみ詳細ログ
    if (kDebugMode) {
      developer.log(message, name: name, level: level);
    }
  }
  
  static void errorLog(String message, {Object? error, StackTrace? stackTrace}) {
    // すべてのモードでエラーログ
    developer.log(
      message,
      name: 'error',
      level: 1000,
      error: error,
      stackTrace: stackTrace,
    );
  }
  
  static void performanceLog(String operation, int durationMs) {
    // プロファイルモードとデバッグモードでパフォーマンスログ
    if (kDebugMode || kProfileMode) {
      developer.log(
        '$operation 完了: ${durationMs}ms',
        name: 'performance',
        level: 600,
      );
    }
  }
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    ConditionalLogger.log('MyApp ビルド開始');
    
    return MaterialApp(
      title: 'Flutter Demo',
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    ConditionalLogger.log('MyHomePage ビルド開始');
    
    return Scaffold(
      appBar: AppBar(title: Text('Conditional Logging Demo')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            ElevatedButton(
              onPressed: () {
                ConditionalLogger.log('ボタンが押されました');
                // 意図的にエラーを発生
                try {
                  throw Exception('テストエラー');
                } catch (error, stackTrace) {
                  ConditionalLogger.errorLog(
                    'ボタン処理中にエラーが発生',
                    error: error,
                    stackTrace: stackTrace,
                  );
                }
              },
              child: Text('ログテスト'),
            ),
          ],
        ),
      ),
    );
  }
}