Self-hosted crash logging service for Flutter and mobile apps
Features • Quick Start • Architecture • API Reference • Flutter SDK • Dashboard • Deployment
Inceptor is a lightweight, self-hosted crash logging and error tracking service designed primarily for Flutter applications but extensible to any platform. It provides crash ingestion via REST and gRPC, intelligent crash grouping, a modern web dashboard, alerting, and configurable retention policies.
| Feature | Description |
|---|---|
| Crash Collection | Receive crash reports via REST API or gRPC |
| Smart Grouping | Automatically group similar crashes using fingerprinting algorithm |
| Web Dashboard | Modern Nuxt 3 + Nuxt UI dashboard for viewing and managing crashes |
| Alerting | Get notified via webhooks, email, or Slack when crashes occur |
| Retention Policies | Configurable per-app retention with automatic cleanup |
| Flutter SDK | Easy-to-use SDK with automatic error capture |
| API Key Auth | Secure per-app API keys with admin access control |
| File + SQLite Storage | Efficient hybrid storage with SQLite index and file-based logs |
- Go 1.22+
- Node.js 18+ (for dashboard)
- Flutter 3.10+ (for SDK)
# Clone the repository
git clone https://github.com/base-go/inceptor.git
cd inceptor
# Login to your BasePod server
bp login your-server.example.com
# Deploy
bp push# Clone and build
git clone https://github.com/base-go/inceptor.git
cd inceptor
# Install dependencies
go mod download
# Create config
cp configs/config.example.yaml configs/config.yaml
# Edit configs/config.yaml with your settings
# Run
go run cmd/inceptor/main.go# Build
docker build -t inceptor:latest .
# Run
docker run -d \
-p 8080:8080 \
-p 9090:9090 \
-v inceptor-data:/app/data \
-e INCEPTOR_AUTH_ADMIN_KEY=your-secure-key \
inceptor:latest┌─────────────────────────────────────────────────────────────────┐
│ Inceptor │
├─────────────────────────────────────────────────────────────────┤
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────────────┐ │
│ │ REST API │ │ gRPC API │ │ Web Dashboard │ │
│ │ (Gin) │ │ (protobuf) │ │ (Nuxt 3) │ │
│ └──────┬───────┘ └──────┬───────┘ └──────────┬───────────┘ │
│ │ │ │ │
│ └─────────────────┼──────────────────────┘ │
│ ▼ │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ Core Service Layer │ │
│ │ • Crash Processor (parsing, normalization) │ │
│ │ • Crash Grouper (fingerprinting, deduplication) │ │
│ │ • Alert Manager (webhooks, email, Slack) │ │
│ │ • Retention Manager (scheduled cleanup) │ │
│ └────────────────────────────────────────────────────────────┘ │
│ │ │
│ ┌─────────────────┼─────────────────┐ │
│ ▼ ▼ ▼ │
│ ┌────────────┐ ┌──────────────┐ ┌────────────────┐ │
│ │ SQLite │ │ File Store │ │ Alert Queue │ │
│ │ (index) │ │ (raw logs) │ │ (in-memory) │ │
│ └────────────┘ └──────────────┘ └────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
inceptor/
├── cmd/
│ └── inceptor/
│ └── main.go # Application entry point
├── internal/
│ ├── api/
│ │ ├── rest/
│ │ │ ├── handler.go # REST API handlers
│ │ │ ├── middleware.go # Auth, CORS middleware
│ │ │ └── routes.go # Route definitions
│ │ └── grpc/
│ │ └── server.go # gRPC service implementation
│ ├── config/
│ │ └── config.go # Configuration management (Viper)
│ ├── core/
│ │ ├── crash.go # Data models (Crash, Group, App, Alert)
│ │ ├── grouper.go # Fingerprinting algorithm
│ │ ├── alerter.go # Alert system (webhook, email, Slack)
│ │ └── retention.go # Retention policy & cleanup
│ └── storage/
│ ├── repository.go # Storage interfaces
│ ├── sqlite.go # SQLite implementation
│ └── filestore.go # File-based log storage
├── api/
│ └── proto/
│ └── crash.proto # gRPC service definitions
├── web/ # Nuxt 3 Dashboard
│ ├── nuxt.config.ts
│ ├── app.vue
│ ├── layouts/
│ │ └── default.vue
│ ├── pages/
│ │ ├── index.vue # Dashboard home
│ │ ├── crashes/
│ │ │ ├── index.vue # Crash list
│ │ │ └── [id].vue # Crash details
│ │ ├── groups/
│ │ │ └── index.vue # Crash groups
│ │ ├── apps/
│ │ │ └── index.vue # App management
│ │ └── settings.vue # Settings & alerts
│ ├── composables/
│ │ └── useApi.ts # API client composable
│ └── types/
│ └── index.ts # TypeScript types
├── sdk/
│ └── flutter/ # Flutter SDK
│ ├── lib/
│ │ ├── inceptor.dart # Main export
│ │ └── src/
│ │ ├── inceptor.dart # Core SDK implementation
│ │ ├── config.dart # Configuration
│ │ └── models/
│ │ ├── crash_report.dart
│ │ ├── breadcrumb.dart
│ │ └── stack_frame.dart
│ ├── example/
│ │ └── lib/main.dart # Example Flutter app
│ └── pubspec.yaml
├── configs/
│ └── config.example.yaml # Example configuration
├── basepod.yaml # BasePod deployment config
├── Dockerfile # Multi-stage Docker build
├── Makefile # Build commands
├── go.mod
└── go.sum
All configuration can be set via environment variables with the INCEPTOR_ prefix:
| Variable | Default | Description |
|---|---|---|
INCEPTOR_SERVER_REST_PORT |
8080 |
REST API port |
INCEPTOR_SERVER_GRPC_PORT |
9090 |
gRPC port |
INCEPTOR_SERVER_HOST |
0.0.0.0 |
Bind address |
INCEPTOR_STORAGE_SQLITE_PATH |
./data/inceptor.db |
SQLite database path |
INCEPTOR_STORAGE_LOGS_PATH |
./data/crashes |
Crash log files path |
INCEPTOR_RETENTION_DEFAULT_DAYS |
30 |
Default retention period |
INCEPTOR_RETENTION_CLEANUP_INTERVAL |
24h |
Cleanup job interval |
INCEPTOR_AUTH_ENABLED |
true |
Enable authentication |
INCEPTOR_AUTH_ADMIN_KEY |
`` | Admin API key |
INCEPTOR_ALERTS_SMTP_HOST |
`` | SMTP server host |
INCEPTOR_ALERTS_SLACK_WEBHOOK_URL |
`` | Slack webhook URL |
# configs/config.yaml
server:
rest_port: 8080
grpc_port: 9090
host: "0.0.0.0"
storage:
sqlite_path: "./data/inceptor.db"
logs_path: "./data/crashes"
retention:
default_days: 30
cleanup_interval: "24h"
alerts:
smtp:
host: "smtp.example.com"
port: 587
username: ""
password: ""
from: "inceptor@example.com"
slack:
webhook_url: ""
auth:
enabled: true
admin_key: "your-secure-admin-key"All API endpoints require the X-API-Key header:
curl -H "X-API-Key: your-api-key" https://your-server.com/api/v1/crashes- App API Key: For crash submission and viewing app-specific data
- Admin API Key: For managing apps, alerts, and viewing all data
| Method | Endpoint | Description |
|---|---|---|
POST |
/api/v1/crashes |
Submit a crash report |
GET |
/api/v1/crashes |
List crashes (with filters) |
GET |
/api/v1/crashes/:id |
Get crash details |
DELETE |
/api/v1/crashes/:id |
Delete a crash |
Submit Crash Request:
{
"app_version": "1.0.0+1",
"platform": "android",
"os_version": "Android 14",
"device_model": "Pixel 8",
"error_type": "FormatException",
"error_message": "Invalid JSON format",
"stack_trace": [
{
"file_name": "package:myapp/services/api.dart",
"line_number": 42,
"method_name": "parseResponse",
"class_name": "ApiService"
}
],
"user_id": "user_123",
"environment": "production",
"metadata": {
"screen": "checkout",
"cart_items": 3
},
"breadcrumbs": [
{
"timestamp": "2024-01-15T10:30:00Z",
"type": "navigation",
"category": "navigation",
"message": "Navigated to /checkout",
"level": "info"
}
]
}Response:
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"group_id": "660e8400-e29b-41d4-a716-446655440001",
"fingerprint": "a1b2c3d4e5f6g7h8",
"is_new_group": false
}| Method | Endpoint | Description |
|---|---|---|
GET |
/api/v1/groups |
List crash groups |
GET |
/api/v1/groups/:id |
Get group details |
PATCH |
/api/v1/groups/:id |
Update group (status, notes) |
Update Group:
{
"status": "resolved",
"assigned_to": "john@example.com",
"notes": "Fixed in v1.0.1"
}| Method | Endpoint | Description |
|---|---|---|
POST |
/api/v1/apps |
Create a new app |
GET |
/api/v1/apps |
List all apps |
GET |
/api/v1/apps/:id |
Get app details |
GET |
/api/v1/apps/:id/stats |
Get app statistics |
Create App:
{
"name": "My Flutter App",
"retention_days": 30
}Response (save the API key!):
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "My Flutter App",
"api_key": "ink_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6",
"created_at": "2024-01-15T10:00:00Z",
"retention_days": 30
}| Method | Endpoint | Description |
|---|---|---|
POST |
/api/v1/alerts |
Create an alert |
GET |
/api/v1/alerts |
List alerts |
DELETE |
/api/v1/alerts/:id |
Delete an alert |
Create Webhook Alert:
{
"app_id": "550e8400-e29b-41d4-a716-446655440000",
"type": "webhook",
"config": {
"url": "https://example.com/webhook",
"conditions": {
"on_new_group": true
}
},
"enabled": true
}Add to your pubspec.yaml:
dependencies:
inceptor_flutter:
git:
url: https://github.com/base-go/inceptor.git
path: sdk/flutterimport 'dart:async';
import 'package:flutter/material.dart';
import 'package:inceptor_flutter/inceptor.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// Initialize Inceptor
await Inceptor.init(
endpoint: 'https://your-server.com',
apiKey: 'ink_your-api-key',
environment: 'production',
debug: false,
);
// Capture Flutter framework errors
FlutterError.onError = Inceptor.recordFlutterError;
// Capture async errors
runZonedGuarded(() {
runApp(MyApp());
}, Inceptor.recordError);
}try {
await riskyOperation();
} catch (e, stackTrace) {
await Inceptor.captureException(
e,
stackTrace: stackTrace,
context: {
'operation': 'checkout',
'user_action': 'submit_order',
},
);
}// Navigation tracking
Inceptor.addNavigationBreadcrumb(
from: '/home',
to: '/product/123',
);
// HTTP request tracking
Inceptor.addHttpBreadcrumb(
method: 'POST',
url: 'https://api.example.com/orders',
statusCode: 201,
);
// User action tracking
Inceptor.addUserBreadcrumb(
action: 'button_press',
target: 'checkout_button',
data: {'cart_total': 99.99},
);
// Custom breadcrumb
Inceptor.addBreadcrumb(
InceptorBreadcrumb.custom(
type: 'state',
category: 'redux',
message: 'Cart updated',
data: {'items': 5},
),
);// Set user identifier
Inceptor.setUser('user_123');
// Add metadata
Inceptor.setMetadata('subscription', 'premium');
Inceptor.setMetadata('feature_flags', {'dark_mode': true});
// Clear on logout
Inceptor.setUser(null);
Inceptor.clearMetadata();MaterialApp(
navigatorObservers: [InceptorNavigatorObserver()],
// ...
);The web dashboard is built with Nuxt 3 and Nuxt UI.
cd web
npm install
npm run devOpen http://localhost:3000 and enter your admin API key.
- Dashboard: Overview with crash statistics and trends
- Crashes: List, search, and filter crash reports
- Groups: Manage crash groups (resolve, ignore, assign)
- Apps: Register and manage applications
- Settings: Configure alerts and API access
cd web
npm run build
npm run previewThe project includes basepod.yaml for easy deployment:
# Login to your server
bp login your-server.example.com
# Deploy
bp push
# View logs
bp logs inceptor
# Manage
bp stop inceptor
bp start inceptor
bp restart inceptorversion: '3.8'
services:
inceptor:
build: .
ports:
- "8080:8080"
- "9090:9090"
volumes:
- inceptor-data:/app/data
environment:
- INCEPTOR_AUTH_ADMIN_KEY=your-secure-key
- INCEPTOR_ALERTS_SLACK_WEBHOOK_URL=https://hooks.slack.com/...
restart: unless-stopped
volumes:
inceptor-data:# /etc/systemd/system/inceptor.service
[Unit]
Description=Inceptor Crash Logging Service
After=network.target
[Service]
Type=simple
User=inceptor
WorkingDirectory=/opt/inceptor
ExecStart=/opt/inceptor/bin/inceptor
Restart=always
RestartSec=5
Environment=INCEPTOR_AUTH_ADMIN_KEY=your-secure-key
[Install]
WantedBy=multi-user.target# Install dependencies
make deps
# Build binary
make build
# Run locally
make run
# Run tests
make test
# Build for all platforms
make build-all
# Generate gRPC code (requires protoc)
make proto# Run all tests
go test ./...
# With coverage
go test -coverprofile=coverage.out ./...
go tool cover -html=coverage.out| Field | Type | Description |
|---|---|---|
id |
string | Unique crash ID |
app_id |
string | Associated app ID |
app_version |
string | App version string |
platform |
string | Platform (ios, android, web, flutter) |
os_version |
string | OS version |
device_model |
string | Device model |
error_type |
string | Exception/error type |
error_message |
string | Error message |
stack_trace |
array | Stack frames |
fingerprint |
string | Grouping fingerprint |
group_id |
string | Associated group ID |
user_id |
string | Optional user identifier |
environment |
string | Environment (production, staging, dev) |
created_at |
datetime | Timestamp |
metadata |
object | Custom metadata |
breadcrumbs |
array | Events leading to crash |
| Field | Type | Description |
|---|---|---|
id |
string | Unique group ID |
app_id |
string | Associated app ID |
fingerprint |
string | Unique fingerprint |
error_type |
string | Common error type |
error_message |
string | Representative message |
first_seen |
datetime | First occurrence |
last_seen |
datetime | Most recent occurrence |
occurrence_count |
int | Total occurrences |
status |
string | open, resolved, ignored |
assigned_to |
string | Assignee email |
notes |
string | Notes/comments |
Inceptor uses a fingerprinting algorithm to group similar crashes:
- Error Type: The exception/error class name
- Top Stack Frames: First 5 non-native frames (normalized)
- Normalization: Removes line numbers, closure IDs, and build hashes
This ensures crashes from the same code path are grouped together, even across different app versions.
MIT License - see LICENSE for details.
Contributions are welcome! Please read our contributing guidelines before submitting PRs.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request