pd_log 0.7.3 copy "pd_log: ^0.7.3" to clipboard
pd_log: ^0.7.3 copied to clipboard

Cross-platform Flutter logging with pure Dart; buffered file writing; no platform channels.

example/lib/main.dart

import 'package:flutter/material.dart';
import 'dart:async';
// 删除示例内文件读取工具,改用插件 API。

import 'package:flutter/services.dart';
import 'package:pd_log/pd_log.dart';
// import removed: platform interface no longer used

/// 应用入口:启动示例应用。
void main() {
  runApp(const MyApp());
}

class MyApp extends StatefulWidget {
  /// 构造函数:创建应用根部件。
  const MyApp({super.key});

  @override

  /// 创建应用的状态对象。
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  String _platformVersion = 'Unknown';
  bool _useConsole = false;
  LogLevel _minLevel = LogLevel.verbose;
  bool _showCaller = false;
  bool _showTimestamp = false;
  // 文件日志相关状态
  bool _nativeFileEnabled = true;
  final _flushIntervalCtrl = TextEditingController(text: '2000');
  final _maxEntriesCtrl = TextEditingController(text: '100');
  final _maxBytesCtrl = TextEditingController(text: '65536');
  String? _logRootPath;
  List<PDLogFile> _files = [];
  int _totalSize = 0;
  // 预览内容改为新页面展示
  // 元数据与历史(快照)相关状态
  List<LogRecord> _summary = [];
  UploadState? _filterState;
  String? _selectedPath;

  // 文件事件订阅相关状态
  final List<Map<String, dynamic>> _events = <Map<String, dynamic>>[];
  StreamSubscription<Map<String, dynamic>>? _eventsSub;
  bool _listening = false;

  // 查询与筛选(ListOptions)相关状态
  SortBy _sortBy = SortBy.time;
  SortDirection _sortDirection = SortDirection.desc;
  final _pageSizeCtrl = TextEditingController(text: '20');
  final _pageCtrl = TextEditingController(text: '1');
  final _yearCtrl = TextEditingController(text: '2025');
  final _monthCtrl = TextEditingController(text: '10');

  @override

  /// 初始化:配置日志并拉取平台信息与初始文件列表。
  void initState() {
    super.initState();
    // Configure logging defaults.
    PDLog.configure(PDLogConfig(
      defaultTag: 'Example 测试项目',
      minLevel: _minLevel,
      useConsole: _useConsole,
      showCaller: _showCaller,
      showTimestamp: _showTimestamp,
      fileLoggingMinLevel: LogLevel.info,
      nativeFileLoggingEnabled: _nativeFileEnabled,
      nativeFileLoggingFlushIntervalMs:
          int.tryParse(_flushIntervalCtrl.text) ?? 2000,
      nativeFileLoggingMaxBufferEntries:
          int.tryParse(_maxEntriesCtrl.text) ?? 100,
      nativeFileLoggingMaxBufferBytes:
          int.tryParse(_maxBytesCtrl.text) ?? 65536,
    ));
    PDLog.i('App starting...');
    initPlatformState();
    _refreshRootPath();
    _refreshFiles();
    // 初始化年份与月份默认值
    final now = DateTime.now();
    _yearCtrl.text = now.year.toString();
    _monthCtrl.text = now.month.toString();
  }

  // Platform messages are asynchronous, so we initialize in an async method.
  /// 异步获取平台版本信息并更新界面状态。
  Future<void> initPlatformState() async {
    String platformVersion;
    try {
      platformVersion =
          await PDLog.getPlatformVersion() ?? 'Unknown platform version';
    } on PlatformException {
      platformVersion = 'Failed to get platform version.';
    }
    if (!mounted) return;
    setState(() {
      _platformVersion = platformVersion;
    });
  }

  @override

  /// 构建应用界面,包括日志配置与文件列表展示。
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Plugin example app'),
        ),
        body: SingleChildScrollView(
          child: Padding(
            padding: const EdgeInsets.all(16.0),
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Text('Running on: $_platformVersion'),
                const SizedBox(height: 12),
                Row(
                  children: [
                    const Text('Use console logging'),
                    Switch(
                      value: _useConsole,
                      onChanged: (v) {
                        setState(() {
                          _useConsole = v;
                          PDLog.updateConfigure(
                            useConsole: _useConsole,
                          );
                          PDLog.d(
                            'Console logging: ${_useConsole ? 'ON' : 'OFF'}',
                          );
                        });
                      },
                    ),
                  ],
                ),
                const SizedBox(height: 12),

                const SizedBox(height: 12),
                Row(
                  children: [
                    const Text('Show caller info'),
                    Switch(
                      value: _showCaller,
                      onChanged: (v) {
                        setState(() {
                          _showCaller = v;
                          PDLog.updateConfigure(
                            showCaller: _showCaller,
                          );
                          PDLog.d(
                            'Show caller: ${_showCaller ? 'ON' : 'OFF'}',
                          );
                        });
                      },
                    ),
                  ],
                ),
                const SizedBox(height: 12),
                Row(
                  children: [
                    const Text('Show Timestamp'),
                    Switch(
                      value: _showTimestamp,
                      onChanged: (v) {
                        setState(() {
                          _showTimestamp = v;
                          PDLog.updateConfigure(
                            showTimestamp: _showTimestamp,
                          );
                          PDLog.d(
                            'Show Timestamp: ${_showTimestamp ? 'ON' : 'OFF'}',
                          );
                        });
                      },
                    ),
                  ],
                ),
                const SizedBox(height: 12),
                Row(
                  children: [
                    const Text('Min level:'),
                    const SizedBox(width: 8),
                    DropdownButton<LogLevel>(
                      value: _minLevel,
                      onChanged: (level) {
                        if (level == null) return;
                        setState(() {
                          _minLevel = level;
                          PDLog.updateConfigure(
                            minLevel: _minLevel,
                          );
                          PDLog.d('Min level set to $level');
                        });
                      },
                      items: const [
                        DropdownMenuItem(
                          value: LogLevel.verbose,
                          child: Text('Verbose'),
                        ),
                        DropdownMenuItem(
                          value: LogLevel.debug,
                          child: Text('Debug'),
                        ),
                        DropdownMenuItem(
                          value: LogLevel.info,
                          child: Text('Info'),
                        ),
                        DropdownMenuItem(
                          value: LogLevel.warn,
                          child: Text('Warn'),
                        ),
                        DropdownMenuItem(
                          value: LogLevel.error,
                          child: Text('Error'),
                        ),
                      ],
                    ),
                  ],
                ),
                const SizedBox(height: 16),
                Wrap(
                  spacing: 8,
                  runSpacing: 8,
                  children: [
                    ElevatedButton(
                      onPressed: () => PDLog.v('Verbose pressed'),
                      child: const Text('Verbose'),
                    ),
                    ElevatedButton(
                      onPressed: () => PDLog.d('Debug pressed'),
                      child: const Text('Debug'),
                    ),
                    ElevatedButton(
                      onPressed: () => PDLog.i('Info pressed'),
                      child: const Text('Info'),
                    ),
                    ElevatedButton(
                      onPressed: () => PDLog.w('Warn pressed'),
                      child: const Text('Warn'),
                    ),
                    ElevatedButton(
                      onPressed: () => PDLog.e('Error pressed'),
                      child: const Text('Error'),
                    ),
                    ElevatedButton(
                      onPressed: () {
                        PDLog.out(
                          '自定义的错误警告输出: Customize pressed \n 测试换行后的文本样式',
                          tag: '自定义输出',
                          useConsole: false,
                          toFile: true,
                          showTimestamp: false,
                          style: const LogStyleConfig(
                            foreground: 37, // 白色文本
                            background: 41, // 红色背景
                            styles: [1, 4, 5], // 粗体, 下划线, 闪烁
                          ),
                        );
                      },
                      child: const Text('Customize'),
                    ),
                    ElevatedButton(
                      onPressed: _logFromHelper,
                      child: const Text('Log from helper method'),
                    ),
                    ElevatedButton(
                      onPressed: _runStressTest,
                      child: const Text('压力测试(1000 条)'),
                    ),
                  ],
                ),
                const Divider(height: 32),
                const Text(
                  '文件事件订阅(创建/删除)',
                  style: TextStyle(
                    fontSize: 16,
                    fontWeight: FontWeight.bold,
                  ),
                ),
                const SizedBox(height: 8),
                Row(
                  children: [
                    Expanded(
                      child: Text(
                        _listening ? '事件监听中(跨平台桌面,仅 Web 不支持)' : '未监听(点击开始订阅)',
                      ),
                    ),
                    ElevatedButton(
                      onPressed: _listening ? _stopEvents : _startEvents,
                      child: Text(_listening ? '停止监听' : '开始监听'),
                    ),
                    const SizedBox(width: 8),
                    ElevatedButton(
                      onPressed: () async {
                        PDLog.i('触发事件:写一条日志以创建/滚动文件', tag: 'Events');
                        // 写入一条到原生日志(可能创建新文件或增加大小)
                        PDLog.out(
                          '事件演示:${DateTime.now().toIso8601String()} 来自示例',
                          tag: 'Events',
                          useConsole: false,
                          toFile: true,
                          showTimestamp: true,
                        );
                        await PDLog.flushNativeLogs();
                        _refreshFiles();
                      },
                      child: const Text('写一条日志'),
                    ),
                  ],
                ),
                const SizedBox(height: 8),
                SizedBox(
                  height: 180,
                  child: Card(
                    child: ListView.builder(
                      itemCount: _events.length,
                      itemBuilder: (context, index) {
                        final e = _events[index];
                        final type = e['type'];
                        final path = e['path'];
                        final size = e['sizeBytes'];
                        final modMs = e['modifiedMs'];
                        final tsMs = e['tsMs'];
                        return ListTile(
                          dense: true,
                          title: Text('$type  ${path ?? ''}'),
                          subtitle: Text([
                            if (size != null) 'size=$size',
                            if (modMs != null)
                              'modified=${DateTime.fromMillisecondsSinceEpoch(modMs is int ? modMs : int.tryParse('$modMs') ?? 0)}',
                            if (tsMs != null)
                              'ts=${DateTime.fromMillisecondsSinceEpoch(tsMs is int ? tsMs : int.tryParse('$tsMs') ?? 0)}',
                          ].join('  ')),
                        );
                      },
                    ),
                  ),
                ),
                const Divider(height: 32),
                const Text(
                  'Native File Logging',
                  style: TextStyle(
                    fontSize: 16,
                    fontWeight: FontWeight.bold,
                  ),
                ),
                const SizedBox(height: 8),
                Row(
                  children: [
                    const Text('Enable native file logging'),
                    Switch(
                      value: _nativeFileEnabled,
                      onChanged: (v) {
                        setState(() {
                          _nativeFileEnabled = v;
                          _applyConfig();
                        });
                      },
                    ),
                  ],
                ),
                const SizedBox(height: 8),
                Row(children: [
                  const SizedBox(
                    width: 140,
                    child: Text('Flush interval (ms)'),
                  ),
                  Expanded(
                    child: TextField(
                      controller: _flushIntervalCtrl,
                      keyboardType: TextInputType.number,
                    ),
                  ),
                ]),
                const SizedBox(height: 8),
                Row(children: [
                  const SizedBox(
                    width: 140,
                    child: Text('Max buffer entries'),
                  ),
                  Expanded(
                    child: TextField(
                      controller: _maxEntriesCtrl,
                      keyboardType: TextInputType.number,
                    ),
                  ),
                ]),
                const SizedBox(height: 8),
                Row(children: [
                  const SizedBox(
                    width: 140,
                    child: Text('Max buffer bytes'),
                  ),
                  Expanded(
                    child: TextField(
                      controller: _maxBytesCtrl,
                      keyboardType: TextInputType.number,
                    ),
                  ),
                ]),
                const SizedBox(height: 8),
                Wrap(spacing: 8, children: [
                  ElevatedButton(
                    onPressed: _applyConfig,
                    child: const Text('应用配置'),
                  ),
                  ElevatedButton(
                    onPressed: _flushNow,
                    child: const Text('立即刷新写盘'),
                  ),
                  ElevatedButton(
                    onPressed: _refreshRootPath,
                    child: const Text('获取日志根路径'),
                  ),
                  ElevatedButton(
                    onPressed: _printFileStructure,
                    child: const Text('打印文件结构'),
                  ),
                  ElevatedButton(
                    onPressed: _printMetaFiles,
                    child: const Text('打印元数据文件'),
                  ),
                  ElevatedButton(
                    onPressed: _refreshFiles,
                    child: const Text('列出日志文件'),
                  ),
                  ElevatedButton(
                    onPressed: _deleteAllLogs,
                    child: const Text('删除全部日志'),
                  ),
                ]),
                const Divider(height: 32),
                const Text(
                  '查询与筛选(ListOptions)',
                  style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
                ),
                const SizedBox(height: 8),
                Card(
                  elevation: 0,
                  shape: RoundedRectangleBorder(
                    borderRadius: BorderRadius.circular(8),
                    side: BorderSide(color: Colors.grey.shade300),
                  ),
                  child: Padding(
                    padding: const EdgeInsets.all(12),
                    child: Wrap(
                      spacing: 12,
                      runSpacing: 12,
                      crossAxisAlignment: WrapCrossAlignment.center,
                      children: [
                        SizedBox(
                          width: 150,
                          child: DropdownButtonFormField<SortBy>(
                            value: _sortBy,
                            decoration: const InputDecoration(
                              labelText: '排序字段',
                              border: OutlineInputBorder(),
                            ),
                            onChanged: (v) =>
                                setState(() => _sortBy = v ?? SortBy.time),
                            items: const [
                              DropdownMenuItem(
                                value: SortBy.time,
                                child: Text('按时间'),
                              ),
                              DropdownMenuItem(
                                value: SortBy.name,
                                child: Text('按名称'),
                              ),
                            ],
                          ),
                        ),
                        SizedBox(
                          width: 150,
                          child: DropdownButtonFormField<SortDirection>(
                            value: _sortDirection,
                            decoration: const InputDecoration(
                              labelText: '排序方向',
                              border: OutlineInputBorder(),
                            ),
                            onChanged: (v) => setState(
                                () => _sortDirection = v ?? SortDirection.desc),
                            items: const [
                              DropdownMenuItem(
                                value: SortDirection.asc,
                                child: Text('升序'),
                              ),
                              DropdownMenuItem(
                                value: SortDirection.desc,
                                child: Text('降序'),
                              ),
                            ],
                          ),
                        ),
                        SizedBox(
                          width: 150,
                          child: TextField(
                            controller: _pageSizeCtrl,
                            keyboardType: TextInputType.number,
                            decoration: const InputDecoration(
                              labelText: '分页大小(可选)',
                              hintText: '例如 20,留空或 0 表示不分页',
                              border: OutlineInputBorder(),
                            ),
                          ),
                        ),
                        SizedBox(
                          width: 150,
                          child: TextField(
                            controller: _pageCtrl,
                            keyboardType: TextInputType.number,
                            decoration: const InputDecoration(
                              labelText: '页码',
                              hintText: '默认为 1',
                              border: OutlineInputBorder(),
                            ),
                          ),
                        ),
                        SizedBox(
                          width: 150,
                          child: TextField(
                            controller: _yearCtrl,
                            keyboardType: TextInputType.number,
                            decoration: const InputDecoration(
                              labelText: '年份',
                              border: OutlineInputBorder(),
                            ),
                          ),
                        ),
                        SizedBox(
                          width: 150,
                          child: TextField(
                            controller: _monthCtrl,
                            keyboardType: TextInputType.number,
                            decoration: const InputDecoration(
                              labelText: '月份',
                              border: OutlineInputBorder(),
                            ),
                          ),
                        ),
                      ],
                    ),
                  ),
                ),
                const SizedBox(height: 8),
                Wrap(spacing: 8, children: [
                  ElevatedButton(
                    onPressed: _refreshFilesWithOptions,
                    child: const Text('全部日志(排序/分页)'),
                  ),
                  ElevatedButton(
                    onPressed: _queryByYear,
                    child: const Text('按年份查询'),
                  ),
                  ElevatedButton(
                    onPressed: _queryByYearMonth,
                    child: const Text('按年月查询'),
                  ),
                ]),
                const SizedBox(height: 8),
                Text('日志根路径: ${_logRootPath ?? '(当前平台不支持或目录未创建)'}'),
                const SizedBox(height: 8),
                Text('当前总日志大小: $_totalSize 字节'),
                const SizedBox(height: 8),
                SizedBox(
                  height: 240,
                  child: Card(
                    child: ListView.builder(
                      itemCount: _files.length,
                      itemBuilder: (context, index) {
                        final f = _files[index];
                        final y = f.year, m = f.month, d = f.day;
                        return ListTile(
                          title: Text(f.fileName.isNotEmpty
                              ? '${f.fileName}  (${f.path})'
                              : f.path),
                          subtitle: Text(
                            'size=${f.sizeBytes}B  modified=${DateTime.fromMillisecondsSinceEpoch(f.modifiedMs)}\n'
                            'derived=${y ?? '-'}-${m ?? '-'}-${d ?? '-'}',
                          ),
                          trailing: IconButton(
                            icon: const Icon(Icons.delete_outline),
                            onPressed: () => _deleteFile(f),
                          ),
                          onTap: () => _readFileContent(f),
                        );
                      },
                    ),
                  ),
                ),
                // 预览使用新页面
                const Divider(height: 32),
                const Text(
                  '元数据与历史(快照与上传状态)',
                  style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
                ),
                const SizedBox(height: 8),
                Card(
                  elevation: 0,
                  shape: RoundedRectangleBorder(
                    borderRadius: BorderRadius.circular(8),
                    side: BorderSide(color: Colors.grey.shade300),
                  ),
                  child: Padding(
                    padding: const EdgeInsets.all(12),
                    child: Wrap(
                      spacing: 12,
                      runSpacing: 12,
                      crossAxisAlignment: WrapCrossAlignment.center,
                      children: [
                        SizedBox(
                          width: 200,
                          child: DropdownButtonFormField<UploadState?>(
                            value: _filterState,
                            decoration: const InputDecoration(
                              labelText: '上传状态筛选',
                              border: OutlineInputBorder(),
                            ),
                            onChanged: (v) => setState(() => _filterState = v),
                            items: const [
                              DropdownMenuItem(
                                value: null,
                                child: Text('全部'),
                              ),
                              DropdownMenuItem(
                                value: UploadState.unknown,
                                child: Text('未知'),
                              ),
                              DropdownMenuItem(
                                value: UploadState.pending,
                                child: Text('上传中'),
                              ),
                              DropdownMenuItem(
                                value: UploadState.success,
                                child: Text('成功'),
                              ),
                              DropdownMenuItem(
                                value: UploadState.failed,
                                child: Text('失败'),
                              ),
                            ],
                          ),
                        ),
                        ConstrainedBox(
                          constraints: const BoxConstraints(maxWidth: 360),
                          child: DropdownButtonFormField<String>(
                            value: _files.any((f) => f.path == _selectedPath)
                                ? _selectedPath
                                : null,
                            decoration: const InputDecoration(
                              labelText: '选择一个日志文件路径(用于上传标记)',
                              border: OutlineInputBorder(),
                            ),
                            isExpanded: true,
                            onChanged: (v) => setState(() => _selectedPath = v),
                            selectedItemBuilder: (context) => _files
                                .map((f) => Align(
                                      alignment: Alignment.centerLeft,
                                      child: Text(
                                        f.fileName.isNotEmpty
                                            ? '${f.fileName} (${f.path})'
                                            : f.path,
                                        overflow: TextOverflow.ellipsis,
                                        maxLines: 1,
                                      ),
                                    ))
                                .toList(),
                            items: _files
                                .map((f) => DropdownMenuItem(
                                      value: f.path,
                                      child: Text(
                                        f.fileName.isNotEmpty
                                            ? '${f.fileName} (${f.path})'
                                            : f.path,
                                        overflow: TextOverflow.ellipsis,
                                        maxLines: 1,
                                      ),
                                    ))
                                .toList(),
                          ),
                        ),
                        Wrap(
                          spacing: 8,
                          children: [
                            ElevatedButton(
                              onPressed: _refreshSummary,
                              child: const Text('刷新快照列表'),
                            ),
                            ElevatedButton(
                              onPressed: _markStarted,
                              child: const Text('标记上传开始'),
                            ),
                            ElevatedButton(
                              onPressed: _markSuccess,
                              child: const Text('标记上传成功'),
                            ),
                            ElevatedButton(
                              onPressed: _markFailed,
                              child: const Text('标记上传失败'),
                            ),
                          ],
                        ),
                      ],
                    ),
                  ),
                ),
                const SizedBox(height: 8),
                SizedBox(
                  height: 260,
                  child: Card(
                    child: ListView.builder(
                      itemCount: _summary.length,
                      itemBuilder: (context, index) {
                        final s = _summary[index];
                        final created =
                            DateTime.fromMillisecondsSinceEpoch(s.createdMs);
                        final modified =
                            DateTime.fromMillisecondsSinceEpoch(s.modifiedMs);
                        final deleted = s.deletedMs != null
                            ? DateTime.fromMillisecondsSinceEpoch(s.deletedMs!)
                            : null;
                        return ListTile(
                          title: Text(s.path),
                          subtitle: Text(
                            'exists=${s.exists} size=${s.sizeBytes}B upload=${s.uploadState}\n'
                            'created=$created modified=$modified deleted=${deleted ?? '-'} attempts=${s.uploadAttempts} error=${s.lastError ?? '-'}',
                          ),
                        );
                      },
                    ),
                  ),
                ),
              ],
            ),
          ),
        ),
      ),
    );
  }

  /// 示例:从辅助方法写入一条日志。
  void _logFromHelper() async {
    /// json美化打印, 测试数据
    final Map arg = {
      'name': 'pedro',
      'age': 30,
      'height': 183,
      'no.': '9527',
      'nickName': ['大头', '狗蛋'],
      1: '下标',
      'active': true,
      'balance': 1234.56,
      'createdAt': DateTime.now(),
      'tags': {'flutter', 'dart', '日志'},
      'addresses': [
        {'type': 'home', 'city': '上海', 'zip': 200000},
        {'type': 'work', 'city': '北京', 'zip': 100000},
      ],
      'preferences': {
        'notifications': {'email': true, 'sms': false},
        'theme': {'mode': 'dark', 'accentColor': '#00FFFF'},
      },
      'metrics': {
        'cpu': [0.12, 0.34, 0.56],
        'mem': {'used': 2048, 'total': 8192},
        'latencyMs': [12, 25, 8, 16],
      },
      'mixedList': [
        null,
        '文本',
        42,
        3.1415,
        {
          'nested': [
            'a',
            'b',
            {'deep': 1}
          ]
        },
      ],
      'mapWithNonStringKeys': {1: '一', true: '真', 3.14: 'π'},
      'emoji': '🚀🔥',
      'longText': '这是一个多行文本\n用于测试换行\n以及格式化效果',
      'url': Uri.parse('https://example.com/api?v=1'),
      'bigInt': BigInt.from(9007199254740991),
      'mapDepthTest': {
        'level1': {
          'level2': {
            'level3': {
              'list': [
                1,
                2,
                {'level4': 'ok'}
              ],
            },
          },
        },
      },
    };
    final list = [arg, arg];
    PDLog.formated(list, level: LogLevel.info, toFile: true);

    /// 检查指定日期的日志文件
    final date = DateTime.now();
    final dayPath = await PDLog.logFilePathIfExists(date);

    if (dayPath.isNotEmpty) {
      // 文件存在
      _readFileContent(PDLogFile(
        path: dayPath,
        sizeBytes: 1024,
        modifiedMs: 1760172725000,
      ));
    } else {
      PDLog.e('${date.toIso8601String()}日期没有日志文件.');
    }

    final logFiles = await PDLog.listLogFilesByYear(date.year);
    PDLog.v(logFiles);
  }

  /// 压力测试:批量写入大量日志以验证缓冲与刷新表现。
  ///
  /// 注意:为避免控制台性能影响,此处禁用控制台输出,仅写入原生缓冲与文件。
  Future<void> _runStressTest() async {
    const total = 1000;
    final started = DateTime.now();
    PDLog.v('压力测试开始: $total 条, started=$started', tag: 'Stress');

    for (var i = 0; i < total; i++) {
      final msg = '压力测试第 $i 条消息\n多行样式验证:第 ${(i % 3) + 1} 行';
      // 使用自定义输出以覆盖控制台/文件开关,避免刷屏影响性能
      PDLog.out(
        msg,
        tag: 'Stress',
        useConsole: false,
        toFile: false,
        showTimestamp: true,
        style: const LogStyleConfig(
          foreground: 36, // 青色
          styles: [1], // 粗体
        ),
      );
    }

    await PDLog.flushNativeLogs();
    final elapsed = DateTime.now().difference(started);
    PDLog.v('压力测试完成,用时 ${elapsed.inMilliseconds} ms', tag: 'Stress');
    _refreshFiles();
  }

  /// 应用当前配置到原生日志系统(刷新间隔、缓冲阈值等)。
  void _applyConfig() {
    final flushMs = int.tryParse(_flushIntervalCtrl.text) ?? 2000;
    final maxEntries = int.tryParse(_maxEntriesCtrl.text) ?? 100;
    final maxBytes = int.tryParse(_maxBytesCtrl.text) ?? 65536;
    PDLog.updateConfigure(
      nativeFileLoggingEnabled: _nativeFileEnabled,
      nativeFileLoggingFlushIntervalMs: flushMs,
      nativeFileLoggingMaxBufferEntries: maxEntries,
      nativeFileLoggingMaxBufferBytes: maxBytes,
    );
    PDLog.i(
      'Applied file logging config: enabled=$_nativeFileEnabled, interval=${flushMs}ms, entries=$maxEntries, bytes=$maxBytes',
    );
  }

  /// 立即触发原生侧刷新,将缓冲区写入磁盘并刷新文件列表。
  Future<void> _flushNow() async {
    await PDLog.flushNativeLogs();
    _refreshFiles();
  }

  /// 获取日志根路径并打印年份目录(仅一级目录)。
  Future<void> _refreshRootPath() async {
    final path = await PDLog.logRootPath();
    setState(() => _logRootPath = path);

    final years = await PDLog.listYearFolders();
    for (String e in years) {
      PDLog.d(e);
    }
  }

  /// 打印插件在本地的文件结构(树状)。
  Future<void> _printFileStructure() async {
    final text = await PDLog.fileTreeString(maxDepth: 6);
    PDLog.v(text);
  }

  /// 打印元数据文件(pd_log_ledger.jsonl 与 pd_log_summary.json)内容。
  Future<void> _printMetaFiles() async {
    PDLog.v('=== 元数据: pd_log_ledger.jsonl 开始 ===');
    PDLog.v(await PDLog.metaLedgerContent());
    PDLog.v('=== 元数据: pd_log_ledger.jsonl 结束 ===');

    PDLog.v('=== 元数据: pd_log_summary.json 开始 ===');
    PDLog.v(await PDLog.metaSummaryContent());
    PDLog.v('=== 元数据: pd_log_summary.json 结束 ===');
  }

  /// 列出所有日志文件,计算总大小并更新界面。
  Future<void> _refreshFiles() async {
    final files = await PDLog.listLogFiles();
    for (PDLogFile e in files) {
      PDLog.d(e.toJson());
    }
    final total = files.fold<int>(0, (sum, f) => sum + f.sizeBytes);
    setState(() {
      _files = files;
      _totalSize = total;
      _reconcileSelectedPath();
    });
  }

  /// 根据当前文件列表修正下拉选择的路径,避免 value 不在 items 中导致断言失败。
  void _reconcileSelectedPath() {
    final exists = _files.any((f) => f.path == _selectedPath);
    if (!exists) {
      _selectedPath = _files.isNotEmpty ? _files.first.path : null;
    }
  }

  ListOptions _buildOptions() {
    final by = _sortBy;
    final dir = _sortDirection;
    final ps = int.tryParse(_pageSizeCtrl.text);
    final page = int.tryParse(_pageCtrl.text) ?? 1;
    return ListOptions(
      by: by,
      direction: dir,
      pageSize: (ps == null || ps <= 0) ? null : ps,
      page: page <= 0 ? 1 : page,
    );
  }

  Future<void> _refreshFilesWithOptions() async {
    final options = _buildOptions();
    final files = await PDLog.listLogFiles(options: options);
    final total = files.fold<int>(0, (sum, f) => sum + f.sizeBytes);
    setState(() {
      _files = files;
      _totalSize = total;
      _reconcileSelectedPath();
    });
  }

  Future<void> _queryByYear() async {
    final y = int.tryParse(_yearCtrl.text);
    if (y == null) {
      PDLog.w('请输入有效的年份');
      return;
    }
    final options = _buildOptions();
    final files = await PDLog.listLogFilesByYear(y, options: options);
    final total = files.fold<int>(0, (sum, f) => sum + f.sizeBytes);
    setState(() {
      _files = files;
      _totalSize = total;
      _reconcileSelectedPath();
    });
  }

  Future<void> _queryByYearMonth() async {
    final y = int.tryParse(_yearCtrl.text);
    final m = int.tryParse(_monthCtrl.text);
    if (y == null || m == null) {
      PDLog.w('请输入有效的年份与月份');
      return;
    }
    final options = _buildOptions();
    final files = await PDLog.listLogFilesByYearMonth(y, m, options: options);
    final total = files.fold<int>(0, (sum, f) => sum + f.sizeBytes);
    setState(() {
      _files = files;
      _totalSize = total;
      _reconcileSelectedPath();
    });
  }

  /// 读取指定日志文件内容并打印到控制台。
  Future<void> _readFileContent(PDLogFile f) async {
    try {
      final content = await PDLog.readFileContent(f.path);
      PDLog.v('=== 文件: ${f.path} 内容开始 ===');
      PDLog.v(content);
      PDLog.v('=== 文件: ${f.path} 内容结束 ===');
    } catch (e) {
      PDLog.v('读取文件失败: $e');
    }
  }

  /// 删除指定日志文件并刷新列表。
  Future<void> _deleteFile(PDLogFile f) async {
    final ok = await PDLog.deleteLogFile(f.path);
    if (ok) {
      _refreshFiles();
    }
  }

  /// 删除所有日志文件并刷新列表。
  Future<void> _deleteAllLogs() async {
    final n = await PDLog.deleteAllLogFiles();
    PDLog.w('Deleted $n log files');
    _refreshFiles();
  }

  /// 刷新快照列表(可选按上传状态筛选)。
  Future<void> _refreshSummary() async {
    final options = _buildOptions();
    final list =
        await PDLog.listSummary(options: options, uploadState: _filterState);
    setState(() => _summary = list);
  }

  Future<void> _markStarted() async {
    final p = _selectedPath;
    if (p == null || p.isEmpty) {
      PDLog.w('请选择一个日志文件路径用于上传标记');
      return;
    }
    await PDLog.markUploadStarted(p);
    await _refreshSummary();
  }

  Future<void> _markSuccess() async {
    final p = _selectedPath;
    if (p == null || p.isEmpty) {
      PDLog.w('请选择一个日志文件路径用于上传标记');
      return;
    }
    await PDLog.markUploadSuccess(p);
    await _refreshSummary();
  }

  Future<void> _markFailed() async {
    final p = _selectedPath;
    if (p == null || p.isEmpty) {
      PDLog.w('请选择一个日志文件路径用于上传标记');
      return;
    }
    await PDLog.markUploadFailed(p, 'network error');
    await _refreshSummary();
  }

  // --- 文件事件订阅 ---
  void _startEvents() {
    try {
      _events.clear();
      _eventsSub?.cancel();
      _eventsSub = PDLog.fileEvents().listen((e) {
        setState(() {
          PDLog.v('事件: $e');
          _events.insert(0, e);
          if (_events.length > 100) _events.removeLast();
        });
      }, onError: (err) {
        PDLog.w('事件流错误: $err');
      });
      setState(() => _listening = true);
      PDLog.v('开始订阅文件事件(created/deleted)');
    } catch (e) {
      PDLog.w('无法订阅事件: $e');
    }
  }

  void _stopEvents() {
    _eventsSub?.cancel();
    _eventsSub = null;
    setState(() => _listening = false);
    PDLog.i('已停止事件订阅');
  }

  @override
  void dispose() {
    _eventsSub?.cancel();
    super.dispose();
  }
}
2
likes
140
points
297
downloads

Publisher

unverified uploader

Weekly Downloads

Cross-platform Flutter logging with pure Dart; buffered file writing; no platform channels.

Repository

Documentation

API reference

License

MIT (license)

Dependencies

flutter, path_provider, stack_trace

More

Packages that depend on pd_log