dio_curl_interceptor
A Flutter package with a Dio interceptor that logs HTTP requests as cURLβideal for debugging. Includes a modern UI to view, filter, and manage logs, plus webhook integration for team collaboration.
Features
- π Core β Convert Dio HTTP requests to executable cURL commands; detailed FormData file info
- π₯οΈ Viewer β In-app log viewer with search, status/date filtering, copy, clear, share
- πΎ Storage β Local Hive cache with filtering & search
- π Webhooks β Discord & Telegram sinks; automatic sensitive header redaction
- π― Filtering β Status filtering (forward only client/server errors or custom buckets); Path filtering (block/mock endpoints via exact/regex/glob; live editor in viewer)
- π Reliability β Per-sink circuit breaker, exponential retry with jitter, dedupe cache
- π Extensibility β Pluggable sink interfaces; send manual non-HTTP logs (app start, button taps, errors) to any sink
- π Utilities β Standalone helpers for custom interceptors or ad-hoc logging
See Screenshots for simultaneous vs. chronological logging examples.
Migration Guide
Upgrading from 3.x? See doc/breaking-changes/v4.0.0.md for the breaking-change mapping and code examples.
Usage
Option 1: Using DioCurlInterceptor (4.0+)
Add the interceptor to your Dio instance; one config object drives everything:
final interceptor = DioCurlInterceptor(
config: CurlConfig(
behavior: CurlBehavior.chronological,
onRequest: const RequestDetails(visible: true),
onResponse: const ResponseDetails(
visible: true,
requestBody: true,
responseBody: true,
limitResponseBody: 4096,
),
onError: const ErrorDetails(visible: true),
sinks: [PrinterSink(printer: print)],
),
);
final dio = Dio()..interceptors.add(interceptor);
// Send a manual message at any time (no HTTP request required):
await interceptor.sendMessage('App started');
You can customize the relay behaviour with RelayOptions inside CurlConfig and fan out to multiple sinks:
DioCurlInterceptor(
config: CurlConfig(
sinks: [
DiscordSink(webhookUrls: ['https://your-webhook']),
TelegramSink(
botToken: 'YOUR_BOT_TOKEN',
chatIds: ['-1003019608685'], // note: chatIds is List<String>
),
HiveSink(),
PrinterSink(printer: print),
],
relayOptions: const RelayOptions(
retry: true,
circuitBreaker: true,
dedupeTtl: Duration(minutes: 1),
),
onRequest: const RequestDetails(
visible: true,
ansi: Ansi.yellow, // ANSI color for request
),
onResponse: const ResponseDetails(
visible: true,
requestHeaders: true,
requestBody: true,
responseBody: true,
responseHeaders: true,
limitResponseBody: null,
ansi: Ansi.green, // ANSI color for response
),
onError: const ErrorDetails(
visible: true,
requestHeaders: true,
requestBody: true,
responseBody: true,
responseHeaders: true,
limitResponseBody: null,
ansi: Ansi.red, // ANSI color for errors
),
// Configure pretty printing options
prettyConfig: PrettyConfig(
blockEnabled: true, // Enable pretty printing
colorEnabled: true, // Force enable/disable colored
emojiEnabled: true, // Enable/disable emoji
lineLength: 100, // Set the length of separator lines
),
// Custom printer function to override default logging behavior
printer: (String text) {
// do whatever you want with the text
// ...
// Your custom logging implementation
print('Custom log: $text'); // remember to print the text
},
),
);
Option 2: Using CurlUtils directly in your own interceptor
If you prefer to use the utility methods in your own custom interceptor, you can use CurlUtils directly (sinks belong in CurlConfig.sinks; CurlUtils only handles log generation and caching):
class YourInterceptor extends Interceptor {
final StopwatchClock stopwatch = StopwatchClock();
@override
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
// your request handling logic (adding headers, modifying options, etc.)
// for measuring request time, X-Client-Time is added and consumed on response.
CurlUtils.addXClientTime(options);
CurlUtils.logCurl(options);
handler.next(options);
}
@override
void onResponse(Response response, ResponseInterceptorHandler handler) {
// your response handling logic
CurlUtils.handleOnResponse(response);
handler.next(response);
}
@override
void onError(DioException err, ErrorInterceptorHandler handler) {
// your error handling logic
CurlUtils.handleOnError(err);
handler.next(err);
}
}
Note:
CurlUtils.handleOnRequest/handleOnResponse/handleOnErrorno longer acceptwebhookInspectors. Configure webhooks viaCurlConfig(sinks: [DiscordSink(...), TelegramSink(...)])instead.
Option 3: Using path filtering
You can use path filtering to stop specific API calls and return custom responses:
final dio = Dio();
// Create filter options
final filterOptions = FilterOptions(
rules: [
// Block access to a specific endpoint
FilterRule.exact('/api/sensitive-data'),
// Mock a response for a specific endpoint
FilterRule.exact(
'/api/users/profile',
responseData: {
'id': 'mock-user-123',
'name': 'Mock User',
'email': 'mock@example.com',
},
),
// Use regex pattern to match multiple endpoints
FilterRule.regex(
r'/api/v1/.*',
responseData: {'message': 'API v1 is deprecated'},
statusCode: 410,
),
],
// Never filter these paths
exclusions: ['/api/health', '/api/version'],
);
// Add the interceptor with filtering
dio.interceptors.add(DioCurlInterceptor(
config: CurlConfig(filterOptions: filterOptions),
));
});
Option 4: Real-time filter editing with CurlViewer
You can now edit filter rules directly in the CurlViewer interface:
import 'package:dio_curl_interceptor/dio_curl_interceptor.dart';
// Show CurlViewer with filter editing capabilities
showDialog(
context: context,
builder: (context) => CurlViewer(
displayType: CurlViewerDisplayType.dialog,
enablePersistence: true, // Enable filter persistence
),
);
// Users can now:
// 1. Click the filters button (π) in the CurlViewer header
// 2. Add, edit, and delete filter rules in real-time
// 3. Test filter rules against sample requests
// 4. See immediate effects on API blocking
Option 5: Using webhook integration
You can use webhook integration to send cURL logs to Discord channels or Telegram chats for remote logging and team collaboration:
Setting up Telegram Webhooks
For Telegram integration, you need to:
-
Create a Telegram Bot:
- Message @BotFather on Telegram
- Use
/newbotcommand and follow the instructions - Save your bot token
-
Get your Chat ID:
- Start a conversation with your bot
- Send any message to the bot
- Visit
https://api.telegram.org/bot<YOUR_BOT_TOKEN>/getUpdates - Find your chat ID in the response (it's a number, can be negative for groups)
-
Configure the
TelegramSink:- Use
botTokenandchatIdsparameters directly - Example:
TelegramSink(botToken: '123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11', chatIds: ['123456789'])
- Use
final interceptor = DioCurlInterceptor(
config: CurlConfig(
sinks: [
DiscordSink(
webhookUrls: ['https://discord.com/api/webhooks/your-webhook-url'],
),
TelegramSink(
botToken: 'YOUR_BOT_TOKEN', // Get from @BotFather
chatIds: ['-1003019608685'], // List<String>; get from getUpdates API
),
],
),
);
dio.interceptors.add(interceptor);
// Manual, non-HTTP messages reach every MessageSink (Discord + Telegram).
await interceptor.sendMessage('Hello from the app!');
await interceptor.sendMessage(
'Only Discord will receive this',
targetSinks: ['Discord:https://discord.com/api/webhooks/your-webhook-url'],
);
Option 6: Using utility functions directly
If you don't want to add a full interceptor, you can use the utility functions directly in your code:
// Generate a curl command from request options
final dio = Dio();
final response = await dio.get('https://example.com');
// Generate and log a curl command
CurlUtils.logCurl(response.requestOptions);
// Log response details
CurlUtils.handleOnResponse(response);
// Cache a successful response
CurlUtils.cacheResponse(response);
// Log error details
try {
await dio.get('https://invalid-url.com');
} on DioException catch (e) {
CurlUtils.handleOnError(e);
// Cache an error response
CurlUtils.cacheError(e);
}
## Dio Cache Storage
### Public Flutter Widget: cURL Log Viewer
Show pre-built popup cURL log viewer widget with `showCurlViewer(context)`:
```dart
ElevatedButton(
onPressed: () => showCurlViewer(context),
child: const Text('View cURL Logs'),
);
The log viewer supports:
- Search and filter by status code, date range, or text
- Copy cURL command
- Clear all logs
- Enhanced sharing functionality with improved system integration
- Better error handling and UI responsiveness
Floating Bubble Overlay
For a non-intrusive debugging experience, use the floating bubble overlay that wraps your main app content:
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: CurlBubble(
// Wrap your main app content
body: YourMainContent(),
controller: BubbleOverlayController(),
style: BubbleStyle(
initialPosition: const Offset(50, 200),
snapToEdges: false, // Stays where you drag it
),
onExpanded: () => debugPrint('Bubble expanded'),
onMinimized: () => debugPrint('Bubble minimized'),
),
),
);
}
}
Bubble Features
- Draggable: Drag the bubble around the screen
- Free Positioning: Stays where you drag it (no auto-snapping by default)
- Expandable: Tap to expand and view cURL logs
- Non-intrusive: Stays on top without blocking your app
- Controller-based: Full programmatic control via
BubbleOverlayController - Resizable: Expand and resize the bubble content
- Customizable: Use custom widgets for minimized and expanded states
Note: File export functionality has been removed in v3.3.3. Use copy/share features instead.
Cache Storage Initialization
Before using caching or the log viewer, initialize storage in your main():
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await CachedCurlService.init();
runApp(const MyApp());
}
Note: In v3.3.3,
CachedCurlStoragewas renamed toCachedCurlService. See MIGRATION.md for details.
Screenshots
Simultaneous (log the curl and response (error) together)
Chronological (log the curl immediately after the request is made)
Cached Viewer
Inspect Bug Discord
Inspect cURL Discord
License
This project is licensed under the MIT License - see the LICENSE file for details.
- Repository: GitHub
- Bug Reports: Please file issues on the GitHub repository
- Feature Requests: Feel free to suggest new features through GitHub issues
Contributions are welcome! Please feel free to submit a Pull Request.
