Dartvel, home
Docs
Features
Studio
Cloud
Compared

Search the site

GitHub
pub.dev

GETTING STARTED

Getting started
Existing Flutter apps
Existing Native apps
Run on your phone

APP

UI and styling
Routing
State
Accessibility
Keyboard shortcuts
Localization
Devices and desktop
Native device access
Media, 3D and XR

DATA

Data models
Forms
Search
Sync and offline
Import and export
Change capture
Database
Cache
File storage
Images
Privacy and erasure

BACKEND

Backend functions
Auth and sessions
Authorization
Queues and jobs
Workers and memory
Notifications and mail
Outbound HTTP
AI
Webhooks
GraphQL and OpenAPI
API keys and OAuth
Multi-tenancy
Billing and commerce
Modules

OPERATIONS

Edge security
Secrets and environments
Monitoring
Releases

SHIPPING

Build targets
Static web hosting
Servers and deploying

REFERENCE

Testing
CLI reference
Coding agents

OPERATIONS

Monitoring

Every generated backend serves metrics and a health check, and every app records its own crashes, with nothing to install.

Alerts, incidents and consent-aware analytics build on the same signals.

ON THIS PAGE

Read metrics and health from any backend

One log stream, on the device and on the server

Keep logs on the device and send them when asked

Follow one request across services

Crash reports are on from the first build

Alert on error budgets and open incidents

Product analytics that respect consent

Status

Read metrics and health from any backend

curl -H "Authorization: Bearer $DARTVEL_METRICS_TOKEN" https://your-host/metrics
curl https://your-host/health
dartvel metrics

Copy code to clipboard

GET /metrics serves Prometheus text, so any Prometheus-compatible scraper can read it.

It is private by default. It answers the same host and private networks, and anyone sending the server's DARTVEL_METRICS_TOKEN as a bearer token. Everyone else gets a 404.

GET /health runs real checks with a deadline and answers with each result.

dartvel metrics fetches the running server's /metrics and says plainly when no server answers.

// Registered on first use, and served at GET /metrics as Prometheus text.
DV.ObservabilityAndLogging.metrics
    .counter('refunds_total', <String, String>{'currency': 'usd'})
    .increment();

DV.ObservabilityAndLogging.metrics.gauge('queue_depth').set(12);

DV.ObservabilityAndLogging.metrics
    .histogram('checkout_seconds')
    .observe(0.42);

Copy code to clipboard

// Each check answers for itself when GET /health is asked, under a
// deadline. Degraded is not down: a cold cache is slow, and reporting it
// as an outage pages somebody for nothing.
DV.ObservabilityAndLogging.health.register('payments', () async {
  final bool reachable = await gatewayReachable();
  return reachable
      ? DVHealthResult.up()
      : DVHealthResult.down('the gateway did not answer');
});

Copy code to clipboard

One log stream, on the device and on the server

DV.log writes one record: a message, a tag and the fields as data. It takes the same arguments in the app and on the server, and there is a method per level, like DV.log.warn.

The framework logs through it too, so your lines and Dartvel's arrive in one stream.

Each record also goes to the platform's own log: logcat on Android, the unified log on iOS and macOS, journald or stderr on Linux, the console in a browser, and JSON lines on stdout from a server.

Sensitive data model fields, tokens, passwords in URLs and secrets your app has read are redacted before any of them sees the record.

A code is what an alert rule matches on, so it survives a rewording of the message.

// One record: a message, a tag to filter on, and the fields as data. The
// same call on a phone, in a browser and on the server; there is no second
// logger to configure per package.
void recordRefund(String orderId, int cents) {
  DV.log(
    'refunded an order',
    tag: 'billing',
    context: <String, Object?>{'order': orderId, 'cents': cents},
  );
  // A method per level, each taking the same shape.
  DV.log.warn('refund took a second attempt', tag: 'billing');
}

Copy code to clipboard

// The same two names on the server: DV.log for a line, and
// DV.ObservabilityAndLogging for everything the server serves about itself.
DV.log('Refund accepted', context: <String, Object?>{'orderId': 'o-42'});

DV.log(
  'Gateway slow',
  level: DVLogLevel.warn,
  // A code is what an alert rule matches on. Wording changes; this does not.
  code: 'PAY-SLOW',
  context: <String, Object?>{'gateway': 'stripe', 'ms': 2400},
);

Copy code to clipboard

Keep logs on the device and send them when asked

On a phone or desktop, records also go to a log file that never grows past 2 MB, by default, and drops files older than 7 days. Set the size and age under dartvel.logging.file, or turn it off with file: false.

DV.log.export returns what was kept, and DV.log.share opens the share sheet with it. DV.log.clear deletes it.

Warnings and errors also become crash report breadcrumbs, so a crash report shows the lines that came before it.

To collect device logs on your own backend, set dartvel.logging.ship.enabled to true. Devices then send warnings and errors with a random install id, the release and the platform, and nothing that names a person.

The backend writes those records into its own log stream. On a deployment Dartvel Cloud hosts, it can also pass them on to the hosting log service.

// What the device kept, oldest first, one JSON object per line -- for a
// "send us your logs" button. share() opens the platform share sheet with
// the newest records that fit.
Future<void> sendLogs() => DV.log.share();
Future<String> logsAsText() => DV.log.export();

Copy code to clipboard

Follow one request across services

Each request joins the W3C traceparent it arrives with, or starts one, and sends it back on the response.

Sampling is decided from the trace id, so every service in a trace makes the same choice without talking to the others.

Recent spans are kept in memory and served at /_dartvel/traces when diagnostics endpoints are on.

// Timed as one named step, so a slow checkout points at the work inside
// it instead of at the request as a whole.
Future<T> priced<T>(Future<T> Function() work) =>
    DV.ObservabilityAndLogging.trace<T>('pricing', work);

Copy code to clipboard

// The request already has a span, joined to the traceparent it arrived
// with. This is for the work inside it worth seeing on its own.
final DVSpan span =
    DV.ObservabilityAndLogging.tracer.startSpan('reprice-basket');
span.setAttribute('basket.id', 'b-9');
try {
  await Future<void>.delayed(const Duration(milliseconds: 5));
} finally {
  // In a finally: a thrown error would otherwise leave the span open, and
  // the trace then shows a request that never ended.
  span.end();
}

Copy code to clipboard

Partial

Spec section: Distributed Tracing

Planned work and implementation limits

No OTLP exporter, so spans do not reach a collector yet.

Only the request itself gets a span. Database queries, outbound HTTP, jobs and AI calls do not.

A job is not linked back to the request that queued it.

Crash reports are on from the first build

The generated client installs crash reporting at startup. A crash is written as it happens and sent on the next launch, once.

DV.Crashes.record reports an error you caught. On the web, uncaught errors and rejected promises are recorded too.

With sink: dartvel under dartvel.crashes, your own backend receives reports. Server processes record a 500, a failing schedule and a dead-lettered job.

On Android a Java or Kotlin crash is recorded too, by a handler installed before the Flutter engine starts. It writes into the store Dart sends from on the next launch, then lets Android end the process as it would have.

A release build is obfuscated. dartvel build android collects that build's R8 mapping and Flutter debug symbols into build/symbols, and dartvel symbols upload (or dartvel.crashes.symbols.upload) sends them to your backend with the DARTVEL_SYMBOLS_TOKEN secret. Each report is then read with its own release's symbols and no other's, and grouped by the original names so a bug keeps its group across releases.

A report whose release has no symbols says it is not symbolicated instead of showing the compiler's names.

Partial

Spec section: Crash Reporting and Release Health

Planned work and implementation limits

No native signal handler on Android, and no handlers at all on iOS or Windows, so a crash below Java and Dart is not caught.

Symbols are collected and read for Android only: no iOS dSYMs and no web source maps. Native frames are stored but not read.

Nothing reads stored reports back yet: no grouping view, no Studio view and no Sentry or Crashlytics sink.

Alert on error budgets and open incidents

DVServiceLevel sets a success-rate objective and reports how fast the error budget is burning.

DVAlerting fires a rule after it has held for a set time and delivers through DV.Notifications or PagerDuty, once per episode.

An alert opens an incident with a timeline, and a status snapshot shows each component's health without internal detail.

// A promise with a budget attached, and a rule that fires on the budget
// instead of on a single bad minute.
const DVServiceLevel checkout = DVServiceLevel(
  name: 'checkout',
  objective: DVObjective.successRate(0.995, over: Duration(days: 30)),
  applies: DVAppliesTo.backendFunction('placeOrder'),
);

final DVAlertRule backlog = DVAlertRule(
  name: 'mail-backlog',
  signal: const DVSignalRef.queueDepth('mail'),
  condition: const DVAlertWhen.above(100),
  // A rule that fires on the first sample pages somebody for a spike that
  // cleared itself.
  forDuration: const Duration(minutes: 5),
  notify: const <DVAlertTarget>[DVAlertTarget.team('ops')],
);

Copy code to clipboard

Partial

Spec section: Alerting, SLOs and Status Pages

Planned work and implementation limits

No hosted status page yet, and no incidents view in Studio.

Rule state and incident history live in memory and are lost on restart.

PagerDuty is the only pager, and there is no latency objective.

Product analytics that respect consent

Declare consent categories under dartvel.analytics. Events in a denied category are dropped at the call, never sent later.

Events are typed classes. One that names a sensitive model field is refused.

The generated app shows a consent banner and a settings page, and asks again when your policy version changes.

Partial

Spec section: Product Analytics and Consent

Planned work and implementation limits

On the web, consent is held in memory, so the banner asks again on each visit.

Events from the app stay on the device. No backend endpoint receives them yet.

No PostHog or Mixpanel adapter, and the banner is English only.

Status

Partial

Spec section: Monitoring and Observability

Planned work and implementation limits

Worker and cron processes have no sink yet, so what they log stays in a buffer in the process.

The Rust server core still prints its own messages as plain text, outside the JSON stream.

There is no logs view in Studio, and dartvel logs does not read a device's log file.

PREVIOUS Secrets and environments Keys that stay on the server, checked at build and deploy
NEXT Releases Flags, branch previews, rollouts and old clients
GitHub
pub.dev
npm
Acknowledgements
Privacy
Terms

FSL-1.1-MIT licensed. Built with Dartvel.

Dartvel is made by

SigmaDev Digital

To the bottom