ispectify_db 4.4.8-dev03
ispectify_db: ^4.4.8-dev03 copied to clipboard
Database logging utilities for ISpect toolkit (passive DB observability)
π± What is ISpect? #
In-app debugging tool for Flutter. Monitor network requests, database queries, logs, performance, and UIβall from within your running app.
Core capabilities:
- π Network monitoring β HTTP/HTTPS requests and responses (Dio, http package)
- ποΈ Database logging β Query execution time and results
- π Advanced logging β Categorized logs with export and filtering
- π¨ UI inspector β Widget tree and layout analysis
- π Observers β Integrate with Sentry, Crashlytics, or custom analytics
- π Production-safe β Tree-shaken when disabled (zero overhead)
π Table of Contents #
- Interface Preview
- Try It Out
- Architecture
- Features
- Getting Started
- Logger Configuration
- Internationalization
- Production Safety
- Customization
- Integrations
- Examples
- Contributing
- License
Interface Preview #
π Try It Out #
Live Web Demo: https://yelmuratoff.github.io/ispect/
Drag and drop exported log files to explore them in the browser.
ποΈ Architecture #
Modular designβadd only what you need:
| Package | Role | Version |
|---|---|---|
| ispect | Core panel + inspectors | |
| ispectify | Logging backbone | |
| ispectify_dio | Dio HTTP capture | |
| ispectify_http | http package capture | |
| ispectify_ws | WebSocket traffic | |
| ispectify_db | Database operations | |
| ispectify_bloc | BLoC events/states |
β¨ Features #
π Network Monitoring #
Inspect HTTP requests and responses with headers, bodies, timing, and errors.
ποΈ Database Operations #
Track queries with execution time, result counts, and errors.
π Logging System #
- Log levels (info, warning, error, debug)
- Category-based filtering
- Configurable history retention
- Export and sharing
- Observer pattern for third-party integrations
π¨ UI Inspector #
Widget tree inspection, layout measurements, color picker.
β‘ Performance Tracking #
Frame rates, memory usage, performance metrics.
π± Device Information #
Device details, app version, platform info.
π¬ Feedback Collection #
In-app feedback with screenshot capture and log attachment.
π Observer Pattern #
Observers receive log events in real-time for integration with error tracking services.
import 'dart:developer';
import 'package:ispect/ispect.dart';
// Observer that sends errors to Sentry
class SentryISpectObserver implements ISpectObserver {
@override
void onError(ISpectLogData err) {
// Send to Sentry
log('SentryISpectObserver - onError: ${err.message}');
// Sentry.captureException(err.exception, stackTrace: err.stackTrace);
}
@override
void onException(ISpectLogData err) {
log('SentryISpectObserver - onException: ${err.message}');
// Sentry.captureException(err.exception, stackTrace: err.stackTrace);
}
@override
void onLog(ISpectLogData data) {
// Optionally send high-priority logs to Sentry as breadcrumbs
log('SentryISpectObserver - onLog: ${data.message}');
}
}
// Observer that sends data to your backend
class BackendISpectObserver implements ISpectObserver {
@override
void onError(ISpectLogData err) {
log('BackendISpectObserver - onError: ${err.message}');
// Send error to your analytics/logging backend
}
@override
void onException(ISpectLogData err) {
log('BackendISpectObserver - onException: ${err.message}');
}
@override
void onLog(ISpectLogData data) {
log('BackendISpectObserver - onLog: ${data.message}');
}
}
void main() {
final logger = ISpectFlutter.init();
// Add multiple observers
logger.addObserver(SentryISpectObserver());
logger.addObserver(BackendISpectObserver());
ISpect.run(logger: logger, () => runApp(const MyApp()));
}
Observers receive all logs, errors, and exceptions. Use them to forward events to Sentry, Crashlytics, or custom analytics endpoints.
π Getting Started #
Installation #
dependencies:
ispect: ^4.4.8-dev03
Quick Start #
import 'package:flutter/material.dart';
import 'package:ispect/ispect.dart';
// Create observer instance
final observer = ISpectNavigatorObserver();
void main() {
ISpect.run(() => runApp(const MyApp()));
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
// Add observer to track navigation
navigatorObservers: [observer],
builder: (context, child) => ISpectBuilder(
options: ISpectOptions(observer: observer),
child: child ?? const SizedBox.shrink(),
),
home: Scaffold(
appBar: AppBar(title: const Text('My App')),
body: Center(
child: ElevatedButton(
onPressed: () {
ISpect.logger.info('Button pressed!');
},
child: const Text('Press me'),
),
),
),
);
}
}
A draggable button appears on screen. Tap it to open the ISpect panel.
Important: ISpect requires
ISpectNavigatorObserverto work correctly. Always add it to bothnavigatorObserversandISpectOptions. You can passnulltoobserverparameter, but this will disable some features like navigation tracking and proper context handling.
βοΈ Logger Configuration #
Default Setup #
void main() {
ISpect.run(() => runApp(const MyApp()));
}
Custom Logger Options #
Configure the logger during initialization:
void main() {
final logger = ISpectFlutter.init(
options: ISpectLoggerOptions(
enabled: true,
useHistory: true, // Store logs in memory
useConsoleLogs: kDebugMode, // Print to console in debug mode
maxHistoryItems: 5000, // Keep last 5000 log entries
logTruncateLength: 4000, // Truncate long messages
),
);
ISpect.run(logger: logger, () => runApp(const MyApp()));
}
Quiet Mode (Disable Console Output) #
If console logs are too noisy, disable them:
final logger = ISpectFlutter.init(
options: const ISpectLoggerOptions(useConsoleLogs: false),
);
ISpect.run(logger: logger, () => runApp(const MyApp()));
You can also change this at runtime:
ISpect.logger.configure(
options: ISpect.logger.options.copyWith(useConsoleLogs: false),
);
Stateless Mode (No History) #
If you don't need log history (e.g., for real-time streaming only):
final logger = ISpectFlutter.init(
options: const ISpectLoggerOptions(useHistory: false),
);
ISpect.run(logger: logger, () => runApp(const MyApp()));
Logs will still be sent to observers and console, but won't be stored in memory.
Filtering #
Filter logs by priority or custom criteria:
// Only capture warnings and errors
class WarningsAndAbove implements ISpectFilter {
@override
bool apply(ISpectLogData data) {
return (data.logLevel?.priority ?? 0) >= LogLevel.warning.priority;
}
}
void main() {
final logger = ISpectFlutter.init(filter: WarningsAndAbove());
ISpect.run(logger: logger, () => runApp(const MyApp()));
}
For advanced configuration options (redaction, dynamic reconfiguration, performance tuning), see the ISpectLogger documentation.
π Internationalization #
Supported languages: en, ru, kk, zh, es, fr, de, pt, ar, ko, ja, hi
MaterialApp(
localizationsDelegates: ISpectLocalizations.delegates(
delegates: [
// Add your own localization delegates here
],
),
// ...
)
You can extend or override translations using the ISpectLocalizations delegate.
π Production Safety #
Security Best Practice: Debug and logging tools should not be included in production builds. They can expose sensitive data (API keys, tokens, user data, network traffic) and increase app size. This applies to all debugging tools, not just ISpect.
ISpect supports conditional compilation via --dart-define flags. When the flag is not set, all ISpect code is automatically tree-shaken from your production buildβzero impact on size, performance, or security.
Setup with Build Flags #
Step 1: Use a build flag to control initialization
// Define a constant based on a build-time flag
const bool kEnableISpect = bool.fromEnvironment(
'ENABLE_ISPECT',
defaultValue: false,
);
void main() {
if (kEnableISpect) {
// ISpect is only initialized when the flag is true
ISpect.run(() => runApp(const MyApp()));
} else {
// Normal app startup without ISpect
runApp(const MyApp());
}
}
Step 2: Conditionally include ISpect UI
final observer = ISpectNavigatorObserver();
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
localizationsDelegates: kEnableISpect
? ISpectLocalizations.delegates(delegates: [
// Your localization delegates
])
: [
// Your localization delegates without ISpect
],
navigatorObservers: kEnableISpect ? [observer] : [],
builder: (context, child) {
if (kEnableISpect) {
return ISpectBuilder(
options: ISpectOptions(observer: observer),
child: child ?? const SizedBox.shrink(),
);
}
return child ?? const SizedBox.shrink();
},
home: const MyHomePage(),
);
}
}
Step 3: Build with or without ISpect
# Development build with ISpect
flutter run --dart-define=ENABLE_ISPECT=true
# QA/Staging build with ISpect
flutter build apk --dart-define=ENABLE_ISPECT=true --dart-define=ENVIRONMENT=staging
# Production build WITHOUT ISpect (default)
flutter build apk
# Or explicitly disable it
flutter build apk --dart-define=ENABLE_ISPECT=false
Step 4: Verify it's excluded
Build your app twiceβonce with the flag enabled and once withoutβthen compare the APK/IPA sizes. The difference should reflect the ISpect code being tree-shaken away in the production build.
Why use build flags?
- π Security β Prevents accidental data exposure in production
- β‘ Performance β Zero overhead (code is completely removed)
- π¦ App Size β Production builds don't include debug assets
- β Compliance β Easier to pass security audits
Environment-Based Configuration #
For more complex setups (dev/staging/prod environments), you can create a configuration file:
// lib/config/ispect_config.dart
import 'package:flutter/foundation.dart';
class ISpectConfig {
static const bool isEnabled = bool.fromEnvironment(
'ENABLE_ISPECT',
defaultValue: kDebugMode, // Enable in debug mode by default
);
static const String environment = String.fromEnvironment(
'ENVIRONMENT',
defaultValue: 'development',
);
// Only enable in non-production environments
static bool get shouldInitialize => isEnabled && environment != 'production';
}
Then use it in your main.dart:
void main() {
if (ISpectConfig.shouldInitialize) {
ISpect.run(() => runApp(const MyApp()));
} else {
runApp(const MyApp());
}
}
Build with environment flags:
flutter build apk \
--dart-define=ENABLE_ISPECT=true \
--dart-define=ENVIRONMENT=staging
π¨ Customization #
Theming #
final observer = ISpectNavigatorObserver();
ISpectBuilder(
options: ISpectOptions(observer: observer),
theme: ISpectTheme(
pageTitle: 'Debug Panel',
lightBackgroundColor: Colors.white,
darkBackgroundColor: Colors.black,
lightDividerColor: Colors.grey.shade300,
darkDividerColor: Colors.grey.shade800,
// Custom colors for log types
logColors: {
'error': Colors.red,
'warning': Colors.orange,
'info': Colors.blue,
'debug': Colors.grey,
},
// Custom icons for log types
logIcons: {
'error': Icons.error,
'warning': Icons.warning,
'info': Icons.info,
'debug': Icons.bug_report,
},
// Disable specific log categories
logDescriptions: [
LogDescription(key: 'riverpod-add', isDisabled: true),
LogDescription(key: 'riverpod-update', isDisabled: true),
LogDescription(key: 'riverpod-dispose', isDisabled: true),
LogDescription(key: 'riverpod-fail', isDisabled: true),
],
),
child: child ?? const SizedBox.shrink(),
)
Panel Actions #
final observer = ISpectNavigatorObserver();
ISpectBuilder(
options: ISpectOptions(
observer: observer,
locale: const Locale('en'),
// Custom action items in the menu
actionItems: [
ISpectActionItem(
onTap: (context) {
// Clear cache, reset state, etc.
},
title: 'Clear All Data',
icon: Icons.delete_sweep,
),
ISpectActionItem(
onTap: (context) {
// Switch to a test environment
},
title: 'Switch Environment',
icon: Icons.swap_horiz,
),
],
// Custom panel items (icons)
panelItems: [
DraggablePanelItem(
enableBadge: false,
icon: Icons.settings,
onTap: (context) {
// Open settings
},
),
],
// Custom panel buttons (labeled)
panelButtons: [
DraggablePanelButtonItem(
icon: Icons.info,
label: 'App Info',
onTap: (context) {
// Show app version, build number, etc.
},
),
],
),
child: child ?? const SizedBox.shrink(),
)
Settings Persistence #
import 'dart:convert';
import 'package:shared_preferences/shared_preferences.dart';
final observer = ISpectNavigatorObserver();
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// Load persisted settings
final prefs = await SharedPreferences.getInstance();
final settingsJson = prefs.getString('ispect_settings');
final initialSettings = settingsJson != null
? ISpectSettingsState.fromJson(jsonDecode(settingsJson))
: null;
final logger = ISpectFlutter.init();
ISpect.run(logger: logger, () => runApp(MyApp(initialSettings: initialSettings)));
}
class MyApp extends StatelessWidget {
final ISpectSettingsState? initialSettings;
const MyApp({super.key, this.initialSettings});
@override
Widget build(BuildContext context) {
return MaterialApp(
navigatorObservers: [observer],
builder: (context, child) => ISpectBuilder(
options: ISpectOptions(
observer: observer,
initialSettings: initialSettings ?? const ISpectSettingsState(
disabledLogTypes: {'warning'},
enabled: true,
useConsoleLogs: true,
useHistory: true,
),
onSettingsChanged: (settings) async {
// Save settings when they change
final prefs = await SharedPreferences.getInstance();
await prefs.setString('ispect_settings', jsonEncode(settings.toJson()));
},
),
child: child ?? const SizedBox.shrink(),
),
home: const MyHomePage(),
);
}
}
Custom Callbacks #
import 'package:open_filex/open_filex.dart';
import 'package:share_plus/share_plus.dart';
final observer = ISpectNavigatorObserver();
ISpectBuilder(
options: ISpectOptions(
observer: observer,
// Load log content from external source
onLoadLogContent: (context) async {
// Use file_picker to let users select a log file
// final result = await FilePicker.platform.pickFiles();
// if (result != null) {
// return File(result.files.single.path!).readAsStringSync();
// }
return 'Loaded log content from file';
},
// Handle file opening
onOpenFile: (path) async {
await OpenFilex.open(path);
},
// Handle sharing
onShare: (ISpectShareRequest request) async {
final files = request.filePaths.map((path) => XFile(path)).toList();
await Share.shareXFiles(
files,
text: request.text,
subject: request.subject,
);
},
),
child: child ?? const SizedBox.shrink(),
)
Available callbacks:
onLoadLogContentβ Load log files from storageonOpenFileβ Open exported files with system viewersonShareβ Share logs via system share sheet
Useful packages: file_picker, open_filex, share_plus
π Integrations #
ISpect provides companion packages for common Flutter libraries.
Available Packages #
dependencies:
ispect: ^4.4.8-dev03 # Core package (required)
ispectify_dio: ^4.4.8-dev03 # Dio HTTP client
ispectify_http: ^4.4.8-dev03 # Standard http package
ispectify_db: ^4.4.8-dev03 # Database operations
ispectify_ws: ^4.4.8-dev03 # WebSocket traffic
ispectify_bloc: ^4.4.8-dev03 # BLoC/Cubit integration
π HTTP Monitoring #
Dio
import 'package:dio/dio.dart';
import 'package:ispectify_dio/ispectify_dio.dart';
final dio = Dio(BaseOptions(baseUrl: 'https://api.example.com'));
ISpect.run(
() => runApp(MyApp()),
logger: logger,
onInit: () {
dio.interceptors.add(
ISpectDioInterceptor(
logger: logger,
settings: const ISpectDioInterceptorSettings(
printRequestHeaders: true,
printResponseHeaders: true,
printRequestData: true,
printResponseData: true,
),
),
);
},
);
HTTP Package
import 'package:http_interceptor/http_interceptor.dart' as http_interceptor;
import 'package:ispectify_http/ispectify_http.dart';
final http_interceptor.InterceptedClient client =
http_interceptor.InterceptedClient.build(interceptors: []);
ISpect.run(
() => runApp(MyApp()),
logger: logger,
onInit: () {
client.interceptors.add(
ISpectHttpInterceptor(
logger: logger,
settings: const ISpectHttpInterceptorSettings(
printRequestHeaders: true,
printResponseHeaders: true,
),
),
);
},
);
Multiple Clients
final Dio mainDio = Dio(BaseOptions(baseUrl: 'https://api.example.com'));
final Dio uploadDio = Dio(BaseOptions(baseUrl: 'https://upload.example.com'));
ISpect.run(
() => runApp(MyApp()),
logger: logger,
onInit: () {
mainDio.interceptors.add(ISpectDioInterceptor(logger: logger));
uploadDio.interceptors.add(ISpectDioInterceptor(logger: logger));
},
);
ποΈ Database Integration #
import 'package:sqflite/sqflite.dart';
import 'package:ispectify_db/ispectify_db.dart';
// Configure database logging
ISpectDbCore.config = const ISpectDbConfig(
sampleRate: 1.0,
redact: true,
attachStackOnError: true,
enableTransactionMarkers: false,
slowQueryThreshold: Duration(milliseconds: 400),
);
// Log database operations
final rows = await ISpect.logger.dbTrace<List<Map<String, Object?>>>(
source: 'sqflite',
operation: 'query',
statement: 'SELECT * FROM users WHERE id = ?',
args: [userId],
table: 'users',
run: () => db.rawQuery('SELECT * FROM users WHERE id = ?', [userId]),
projectResult: (rows) => {'rows': rows.length},
);
π WebSocket Integration #
import 'package:ws/ws.dart';
import 'package:ispectify_ws/ispectify_ws.dart';
final interceptor = ISpectWSInterceptor(
logger: logger,
settings: const ISpectWSInterceptorSettings(
enabled: true,
printSentData: true,
printReceivedData: true,
printReceivedMessage: true,
printErrorData: true,
printErrorMessage: true,
),
);
final client = WebSocketClient(
WebSocketOptions.common(
interceptors: [interceptor],
),
);
interceptor.setClient(client);
π― BLoC Integration #
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:ispectify_bloc/ispectify_bloc.dart';
ISpect.run(
() => runApp(MyApp()),
logger: logger,
onInit: () {
Bloc.observer = ISpectBlocObserver(
logger: logger,
);
},
);
Filter specific BLoC logs:
final observer = ISpectNavigatorObserver();
ISpectBuilder(
options: ISpectOptions(observer: observer),
theme: const ISpectTheme(
logDescriptions: [
LogDescription(key: 'bloc-event', isDisabled: true),
LogDescription(key: 'bloc-transition', isDisabled: true),
LogDescription(key: 'bloc-state', isDisabled: true),
],
),
child: child,
)
π§ Navigation Tracking #
import 'package:flutter/material.dart';
import 'package:ispect/ispect.dart';
class MyApp extends StatefulWidget {
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
final _observer = ISpectNavigatorObserver(
isLogModals: true,
isLogPages: true,
isLogGestures: false,
isLogOtherTypes: true,
);
@override
Widget build(BuildContext context) {
return MaterialApp(
navigatorObservers: [_observer],
builder: (context, child) {
return ISpectBuilder(
observer: _observer,
child: child ?? const SizedBox(),
);
},
);
}
}
Navigation events are logged with the route key.
π Data Redaction #
Sensitive data (tokens, passwords, API keys) is automatically redacted by default.
Custom redaction:
// HTTP / WebSocket
final redactor = RedactionService();
redactor.ignoreKeys(['authorization', 'x-api-key']);
redactor.ignoreValues(['<test-token>']);
// Database
ISpectDbCore.config = const ISpectDbConfig(
redact: true,
redactKeys: ['password', 'token', 'secret'],
);
// Disable redaction (only for non-sensitive test data)
ISpectDioInterceptor(
settings: const ISpectDioInterceptorSettings(
enableRedaction: false,
),
);
π Examples #
Check out the example/ directory for a complete working app with all integrations.
π€ Contributing #
Contributions welcome! See CONTRIBUTING.md for guidelines.
π License #
MIT License - see LICENSE for details.
π¦ Related Packages #
- ispectify β Core logging system
- ispectify_dio β Dio integration
- ispectify_http β HTTP package integration
- ispectify_ws β WebSocket monitoring
- ispectify_db β Database logging
- ispectify_bloc β BLoC integration