What is ISpect?
In-app debugging tool for Flutter. Inspect network requests, logs, and UI during development and testing.
Features:
- Network monitoring (Dio, http)
- Structured logging with filtering
- UI inspector
- Observer pattern for external integrations
- Removed from production builds via tree-shaking
Table of Contents
- Interface Preview
- Architecture
- Features
- Getting Started
- Logger Configuration
- Internationalization
- Production Safety
- Customization
- Contributing
- License
Interface Preview
Web Demo
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.7.0-dev04
Quick Start
ISpect uses automatic tree-shaking via kISpectEnabled. By default, ISpect is disabled and completely removed from production builds.
import 'package:flutter/material.dart';
import 'package:ispect/ispect.dart';
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(
localizationsDelegates: ISpectLocalizations.delegates(),
navigatorObservers: [observer],
builder: (context, child) => ISpectBuilder(
options: ISpectOptions(observer: observer),
child: child ?? const SizedBox.shrink(),
),
home: const HomePage(),
);
}
}
Build Commands:
# Development (ISpect enabled)
flutter run --dart-define=ISPECT_ENABLED=true
# Production (ISpect automatically removed via tree-shaking)
flutter build apk
Note: When
ISPECT_ENABLEDis not set (default),ISpect.run(),ISpectBuilder, andISpectLocalizations.delegates()automatically become no-ops, allowing Dart's tree-shaking to remove all ISpect code from your production build.
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.
ISpect uses kISpectEnabled compile-time constant for automatic tree-shaking. When disabled (default), all ISpect code is removed from production builds.
Zero-Conditional API
ISpect provides factory methods that handle the kISpectEnabled check internally, eliminating conditional logic in your code:
void main() {
ISpect.run(() => runApp(const MyApp()));
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
localizationsDelegates: ISpectLocalizations.delegates(),
navigatorObservers: ISpectNavigatorObserver.observers(),
builder: (_, child) => ISpectBuilder.wrap(child: child!),
home: const MyHomePage(),
);
}
}
Build Commands:
# Development (ISpect enabled)
flutter run --dart-define=ISPECT_ENABLED=true
# Production (ISpect removed via tree-shaking)
flutter build apk
Security recommendations
1. Maximum Security Build
flutter build apk --release \
--obfuscate \
--split-debug-info=debug-info/
This enables:
- Tree-shaking: Removes unused code (including disabled ISpect)
- Obfuscation: Mangles class/method names
- Debug symbol stripping: Removes debug information
2. Verified Tree-Shaking Results
| Build | APK Size | libapp.so | "ispect" strings |
|---|---|---|---|
| Obfuscated Production | 42.4 MB | - | 6 |
| Non-obfuscated Production | 44.5 MB | 3.8 MB | 34 |
| Development | 51.0 MB | 5.8 MB | 276 |
Benefits:
- No data exposure in production
- Zero runtime overhead
- Smaller production builds
- Easier 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(
'ISPECT_ENABLED',
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=ISPECT_ENABLED=true \
--dart-define=ENVIRONMENT=staging
Customization
Theming
final observer = ISpectNavigatorObserver();
ISpectBuilder(
options: ISpectOptions(observer: observer),
theme: ISpectTheme(
pageTitle: 'Debug Panel',
// Unified color theming with ISpectDynamicColor
background: ISpectDynamicColor(
light: Colors.white,
dark: Colors.black,
),
divider: ISpectDynamicColor(
light: Colors.grey.shade300,
dark: 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,
},
// Custom descriptions for log types
logDescriptions: {
'error': 'Critical application errors',
'info': 'Informational messages',
},
// Disable specific log categories
disabledLogTypes: {
'riverpod-add',
'riverpod-update',
'riverpod-dispose',
'riverpod-fail',
},
),
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.7.0-dev04 # Core package (required)
ispectify_dio: ^4.7.0-dev04 # Dio HTTP client
ispectify_http: ^4.7.0-dev04 # Standard http package
ispectify_db: ^4.7.0-dev04 # Database operations
ispectify_ws: ^4.7.0-dev04 # WebSocket traffic
ispectify_bloc: ^4.7.0-dev04 # 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(
disabledLogTypes: {
'bloc-event',
'bloc-transition',
'bloc-state',
},
),
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