navree
Documentation

Lighthouse

Application monitoring inside your app: exceptions, slow requests and queries, failed jobs and outgoing HTTP, grouped into issues, with alerts, a dashboard, a JSON API and MCP tools for agents.

On this page

Introduction

Featurelighthouse Lighthouse watches your app in production and tells you what went wrong. It records every exception, slow request, slow query, failed job and failing outgoing call, groups them into issues by fingerprint, and shows them on a dashboard served by your own app at /_lighthouse. When something new breaks, it sends an alert to email, Slack or a webhook. Scripts read and resolve issues through its JSON API, coding agents through its MCP tools, and your traces can also go to an OpenTelemetry collector through ObserveModule.

Lighthouse runs inside your app and stores its data in your database. There is no agent to install, no service to host and no data leaving your servers unless you configure an alert or an export.

  • Every process reports. serve, queue:work, schedule:work, Fleet workers and one-off commands all write to the same store, so a job that fails on a worker shows up next to the request that failed on the web server.
  • No code changes. Lighthouse reads what the framework already produces: the request pipeline and its exceptions and panics, query timings from navree-db, job outcomes from navree-queue, scheduled runs, mail, cache calls and the HTTP client. You add the module; the rest is configuration.
  • Off the request path. Recording copies a few fields into an in-memory buffer. A background task writes them to the database in batches, so a request never waits for Lighthouse.

Installation

The lighthouse feature of the navree facade brings the crate navree-lighthouse. It is part of full, the default, so an app made with navree new already has it, and its src/app.rs already includes the module. To add it to an existing app, put LighthouseModule after the modules it builds on:

src/app.rs

LighthouseModule is global. It needs:

ModuleWhy
DatabaseModuleLighthouse stores issues, occurrences and samples in the app's database (or in its own).
AuthModule and AccessModuleThe dashboard signs users in and checks their abilities. Turn the dashboard off with lighthouse.dashboard = false in an app without them.
CryptoModuleThe dashboard's session cookie is encrypted with the application key.
MailModuleOnly for email alerts.
QueueModuleOptional. Alerts are sent as lighthouse.alert jobs when the app has a queue, and at once from the background writer otherwise.
ScheduleModuleOptional. Lighthouse schedules its own pruning when the app has a scheduler.
McpModuleOptional, with the mcp feature. Lighthouse adds its tools for agents to your app's MCP server.

The module contributes the migration set lighthouse, which migrate runs with your app's migrations. It creates five tables, all prefixed lighthouse_: issues, occurrences, samples (request and job samples with their spans), aggregates (per-minute and per-hour counts and latency histograms) and alerts (the alerts sent). On TimescaleDB, samples, occurrences and aggregates are hypertables, a day per chunk, in the app's database or in lighthouse.database_url's.

Shell

Then give yourself access and open the dashboard. Lighthouse registers the subject Lighthouse with the permission prefix lighthouse, so the permission lighthouse.view lets a role look and lighthouse.resolve lets it change issues:

Shell

Users with the super-admin role need nothing: they pass every check. Open http://127.0.0.1:8080/_lighthouse and sign in with your app's account.

What Lighthouse records

KindRecorded whenKept as
ExceptionA request answers 500 or above, a handler panics, or your code reports an errorAn occurrence of an issue
RequestEvery request completesCounts, error rates and latency per route, every request; a sampled trace
Slow requestA request takes lighthouse.thresholds.slow_request_ms (1 s) or longerAn issue per route, with the full trace of each occurrence
Slow queryA query takes lighthouse.thresholds.slow_query_ms (200 ms) or longerAn issue per normalized statement, with the route or job that ran it
JobA queued job finishes an attemptCounts and runtimes per job type; a failed attempt with its error
Failed jobA job uses up its attempts and moves to the failed jobsAn issue per job type and error
Scheduled taskA task of the scheduler failsAn issue per task and error
Outgoing requestA call through HttpClient fails, answers 5xx or takes lighthouse.thresholds.slow_outgoing_ms (2 s) or longerCounts and latency per host; an issue per host and failure

A 4xx response is not an exception: it is the app working as intended. Requests answering 4xx count towards the route's error rate on the dashboard, and that's all.

Each record carries the context Lighthouse can find without your help:

  • the request id (the X-Request-Id your client saw), the method, the route template and the status;
  • the signed-in user's id, and with workspaces the workspace's id;
  • the release, the environment, the server's host name and the process (serve, queue:work, the command's name);
  • for a job, its id, type, queue and attempt;
  • the trace id, which is also the one exported to OpenTelemetry.

Traces

A trace is the timeline of one request or job: every query, outgoing call, cache call and mail it made, as spans with their durations. Lighthouse keeps the trace of every slow request, slow job, failed request or job, request that reported an error, and of a sample of the others. A trace shows you where the time went: in the slow request below, eleven identical queries for contacts take most of the 1.4 seconds, and the waterfall shows them as one row.

GET /api/v1/deals/board · 1,412 ms · trace 4bf92f3577b34da6

A trace keeps at most 200 spans; the rest are counted.

Reporting errors yourself

Some errors never reach a 500: you catch them, log them and carry on. Report them to Lighthouse so they still become issues. Inject Arc<Lighthouse> and call report:

src/modules/billing/webhooks.rs

report takes any error, a concrete type (its type name joins the fingerprint) or a dyn std::error::Error, and returns at once: the occurrence goes into the buffer with the context of the current request or job and the location of the call. For more control, build a Report:

Rust
MethodWhat it does
Report::error(&e), Report::message(text)Starts a report from an error (its whole source chain is kept) or from a message.
.level(Level::Error)Error (the default), Warning or Info. The dashboard filters by level, and alerts ignore Info.
.context(key, value)Adds a value shown on the occurrence. Any Serialize value.
.tag(key, value)Adds a tag: a short string you can filter issues by.
.fingerprint([..])Replaces the fingerprint, so these reports group together however their messages differ.
.user(id)Sets the user when there is no request, for example in a job working for someone.
.backtrace()Captures a backtrace where the report is built.

To tag everything the current request or job reports, call self.lighthouse.tag_current("plan", "pro") once early on, for example in a guard. The tag also goes on the request's sampled trace.

Issues

An issue is every occurrence of one problem. Lighthouse computes a fingerprint for each record and adds it to the issue with that fingerprint, opening a new issue the first time it sees one. The dashboard then shows one line per problem with its count, the users and workspaces it affected, and when it was first and last seen, instead of a thousand identical errors.

Fingerprints

Rust errors don't carry a stack trace, so Lighthouse fingerprints what they do carry: the error's type, where it happened (the route or the job), and the message chain with its variable parts taken out. Numbers, UUIDs, quoted strings, email addresses and hexadecimal ids in a message are replaced before hashing, so deal 0199c1d2-… not found and deal 0199c1e7-… not found are one issue.

KindGrouped by
ExceptionError type (when report was given a concrete type) + route or job + normalized message chain
PanicFile and line of the panic + normalized message
Slow requestMethod + route template
Slow queryThe normalized statement: literals become ?, IN lists and multi-row VALUES collapse to one item
Failed jobJob type + normalized error message
Scheduled taskTask name + normalized message
Outgoing requestHost + 5xx, slow, or the error kind (timeout, connect, error)

Panics carry their location. For a report you build yourself, .backtrace() adds a backtrace, and .fingerprint([..]) decides the grouping outright.

Statuses

StatusMeaning
OpenNew, or seen again after it was resolved. Open issues send alerts.
ResolvedYou fixed it. A new occurrence reopens it as a regression.
Resolved in next releaseReopens only when it happens again in a release other than the one current when you resolved it, so stragglers from the old release don't reopen it.
IgnoredKept and counted, but never alerts. Ignore forever, until a date, or until it occurs N more times.

The release comes from lighthouse.release. Harbor sets it to the release id on every deploy (APP_LIGHTHOUSE__RELEASE); without it, Lighthouse uses the app.version setting, else unknown.

The dashboard

The dashboard is served by your app at lighthouse.path (/_lighthouse), outside the API prefix: server-rendered pages from templates compiled into the crate, with a small stylesheet and script of its own. No Node.js, no CDN and no separate process. The overview refreshes its figures and its feed every ten seconds while you watch.

  • Overview. A row of figures for the selected period (the last hour, 24 hours or 7 days): requests per minute, the error rate, the 95th percentile latency, failed jobs and open issues. Below them, a timeline of requests and exceptions, and a feed of the latest occurrences, new ones sliding in at the top.
  • Issues. One line per issue (open ones by default): its title (the first line of the message), the route or job it comes from, a sparkline of the last 24 hours, the number of occurrences, users and workspaces affected, and first and last seen. Filter by status, kind, level, release, environment, route, job, tag (key:value), user or workspace, search titles, and sort by last seen, first seen or count.
  • An issue. The message and the error's source chain, the backtrace when there is one, and the latest occurrence: the request (method, route, headers with secrets masked), the user, the workspace, the release, the server, tags and your report's context values. A chart shows occurrences per hour over seven days, and a list shows recent occurrences with a link to each trace. Buttons resolve, resolve in the next release, ignore (forever, for an hour, a day or 100 more events) or reopen it, and a table shows the alerts sent about it.
  • Requests. Every route with its throughput, error rate and p50, p95 and p99 latency, slowest first. A route's page lists its slowest traces, each drawn as a waterfall of spans.
  • Queries. Normalized statements with how often they ran, their total, average and p95 time, and the routes and jobs that ran them.
  • Jobs. Every job type with throughput, runtime, failed attempts and failed jobs. For queue workers and wait times in depth, see Fleet.
  • Outgoing. Every host your app calls through HttpClient, with latency, error rate and the slowest calls.

When observe.tracing.trace_url is set, each trace id links to the trace in your tracing backend.

Signing in and access

The dashboard's sign-in screen uses your app's own users: an email and password through AuthService, then a two-factor code (or a recovery code) when the account has two-factor authentication on. It is subject to the same login throttle as POST /api/auth/login. Signing in starts no API session (the refresh token the sign-in creates is revoked at once): the dashboard session lives in an encrypted, httpOnly, SameSite=Strict cookie scoped to /_lighthouse for twelve hours, so the rest of your API never sees it, and every change the dashboard makes carries a CSRF token.

Its JSON API, under /_lighthouse/api, also accepts an access token in Authorization: Bearer, for scripts, agents and tests.

Every route of the dashboard and its API checks an ability on the subject Lighthouse:

ActionAllowsPermission
viewOpening the dashboard and reading everything on itlighthouse.view
resolveResolving, ignoring and reopening issueslighthouse.resolve

Grant the permissions to a role, as in installation, or write the rules in your abilities function:

src/abilities.rs

A signed-in user without view gets a 403 page; a guest goes to the sign-in screen. The API answers 401 and 403 in the usual JSON error shape.

The JSON API for scripts and agents

Everything on the dashboard is also JSON under /_lighthouse/api, for scripts, CI jobs and coding agents. Send an access token of a user with the right ability:

RouteWhat it returns or doesAbility
GET /api/overview?period=24hThe overview's figures, timeline and latest occurrences (period is 1h, 24h or 7d)view
GET /api/issuesA page of issues, {"data": [..], "meta": {..}}, with the filters of the Issues page as query parameters: status, kind, level, release, environment, route, job, tag, user, workspace, search, sort (last_seen, first_seen, count), page, per_pageview
GET /api/issues/{id}One issue: the message, the source chain, the latest occurrence with its context and backtrace, the timeline and the alertsview
GET /api/issues/{id}/occurrencesIts stored occurrences, newest first (page, per_page)view
POST /api/issues/{id}/resolveResolves it: {"in_next_release": true, "comment": "fixed in #812"}, both optionalresolve
POST /api/issues/{id}/ignoreIgnores it: forever ({}), {"until": "2026-11-01T00:00:00Z"} or {"occurrences": 100}, with an optional commentresolve
POST /api/issues/{id}/reopenReopens itresolve
GET /api/routesEvery route with throughput, error rate and p50, p95 and p99 latency, slowest firstview
GET /api/routes/detail?endpoint=GET /api/v1/deals/{id}One route's figures, timeline and slowest tracesview
GET /api/queries, /api/jobs, /api/outgoingThe Queries, Jobs and Outgoing pagesview
GET /api/traces/{id}A trace with its spansview
GET /api/alertsThe latest alerts and whether they were deliveredview

A session with a coding agent then looks like this: you ask it to fix the newest Lighthouse issue; it fetches /api/issues?sort=first_seen, reads the source chain and the failing request from /api/issues/{id}, opens the code, writes the fix and a test, and resolves the issue in the next release with a comment linking the change. Give it a token for a user with lighthouse.view to let it read, and lighthouse.resolve to let it close issues. On your own machine, Navigator (navree mcp) gives the agent the same issues and figures without a token, read-only, straight from your local database.

MCP tools for agents

With the facade's mcp feature on next to lighthouse (mcp is opt-in, so name it next to the defaults) and McpModule in the app, Lighthouse adds tools, a resource and a prompt to your app's MCP server at /mcp. There is nothing else to register: the facade turns on navree-lighthouse's own mcp feature, and LighthouseModule registers its server, LighthouseMcp.

Cargo.toml
src/app.rs

Each item checks the same abilities as the JSON API, so a client sees only the tools its user may call:

ToolWhat it returns or doesAbility
lighthouse_list_issuesA page of issues, {"data": [..], "meta": {..}}, filtered by status (open by default, or resolved, resolved_in_next_release, ignored, any), kind, level, release, environment, route, job, tag, user, workspace and search, sorted by sort (last_seen, first_seen, count), with page and per_page (at most 100)view
lighthouse_get_issueOne issue (issue_id): the message, the source chain, the latest occurrence with its request, context and backtrace, the timeline and the alertsview
lighthouse_list_occurrencesAn issue's stored occurrences, newest first (issue_id, page, per_page)view
lighthouse_get_traceA stored trace with its spans (trace_id: an occurrence's sample_id or one of a route's slowest traces)view
lighthouse_get_overviewThe overview's figures, timeline and latest occurrences (period: 1h, 24h or 7d; 24h by default)view
lighthouse_list_routes, lighthouse_list_queries, lighthouse_list_jobs, lighthouse_list_outgoingThe Requests, Queries, Jobs and Outgoing pages (period)view
lighthouse_get_routeOne route's figures, timeline and slowest traces (endpoint, such as GET /api/v1/deals/{id}, and period)view
lighthouse_list_alertsThe latest alerts and whether they were deliveredview
lighthouse_resolve_issueResolves an issue (issue_id, in_next_release, comment)resolve
lighthouse_ignore_issueIgnores an issue forever, until a time or for occurrences more occurrences (issue_id, comment)resolve
lighthouse_reopen_issueReopens an issue (issue_id)resolve

The read tools carry readOnlyHint and the status changes idempotentHint. A result is the JSON of the matching API route (a list also comes as {"result": [..]} in the structured content); an unknown id is a tool error with a 404 body, and invalid arguments a 400 with field messages.

Each issue is also the resource lighthouse://issues/{id} (the JSON of lighthouse_get_issue). The prompt Fix a Lighthouse issue (fix-lighthouse-issue, with the argument issue_id, ability view) attaches that resource with instructions: find the cause, write a failing test, fix it, run the tests, then call lighthouse_resolve_issue with in_next_release and a comment naming the change.

Connect your coding agent to the app the way any MCP client connects, as a user with lighthouse.view to read and lighthouse.resolve to resolve, and ask it to fix the newest issue. Set lighthouse.mcp = false to keep Lighthouse off your MCP server; with lighthouse.store = "off" it offers nothing either.

Alerts

Lighthouse sends an alert when something needs a person. Each channel says which kinds of alert it wants:

KindSent when
new_issueAn issue is opened for the first time.
regressionA resolved issue occurs again.
spikeAn open issue occurs alerts.spike.occurrences times (50) or more within alerts.spike.minutes (5).
failed_jobA job moves to the failed jobs.
long_waitA queue's oldest job has waited longer than its threshold. Fleet raises it through Lighthouse::raise_alert.

Configure the channels in the lighthouse.alerts section:

config/app.toml

A channel without on gets new_issue, regression and spike. Issues at level Info and ignored issues never alert. Every alert links to the issue on the dashboard at lighthouse.url, which defaults to app.url followed by the path; set it when your API has its own host, as in https://api.crm.example.com/_lighthouse.

Email

Email alerts go through your app's Mailer, from the configured sender. The message names the issue, its kind, count, release and environment, and links to it. to takes one address or a list; from the environment, a comma-separated string ([email protected],[email protected]).

Slack

Create an incoming webhook in your Slack workspace and set its URL. Keep it out of committed files: it is a secret.

.env

Each alert is one message with the issue's title, the route or job, the count and an Open in Lighthouse button.

Webhooks

A webhook alert is a POST of JSON to your URL, signed following the Standard Webhooks specification: webhook-id, webhook-timestamp and webhook-signature headers, an HMAC-SHA256 over the id, the timestamp and the body with your secret (a whsec_ secret is base64-decoded first, as the specification says). Verify the signature before you trust the body; navree::lighthouse::webhook_signature computes it.

POST https://ops.example.com/hooks/lighthouse

With a queue, each alert is a lighthouse.alert job per channel, and a failed delivery is retried with backoff, like any job: five attempts, from ten seconds up to ten minutes apart. Without a queue it is sent once, at once. The lighthouse_alerts table and the issue's page show each alert and whether it was delivered.

Your own channels

Implement AlertChannel to send alerts anywhere else, and register it with m.alert_channel::<C>():

src/modules/ops/sms.rs

To check every channel, navree lighthouse:test-alerts sends a test alert ("test": true in a webhook's body) through each one, or navree lighthouse:test-alerts slack through one, and prints whether it was delivered. Test alerts are not stored.

Sampling

Counts are always exact: every request, job and exception is counted towards its route's throughput and error rate and its issue's occurrences. Sampling decides how many details are stored: traces of ordinary requests and jobs, and occurrences of busy issues.

config/app.toml
  • Slow requests, slow jobs, failed requests and jobs, and the first 100 occurrences of each issue per hour are always stored, whatever the rates.
  • The latency percentiles on the Requests page come from per-minute histograms of every request, not from samples.
  • Sampling is per request: a sampled request keeps its whole trace, with every query in it. Whether a request is sampled depends only on its request id, so every process agrees.

On a busy app, start with requests = 0.1 and raise it if you want more traces to look at.

Retention and pruning

lighthouse:prune deletes samples, occurrences, aggregates and sent alerts older than lighthouse.retention_days (14), per-minute aggregates older than two days (hourly ones stay for the 24-hour and 7-day views), and resolved or ignored issues with no occurrence left. Open issues are kept with their counts. On TimescaleDB, the days entirely before the cutoff go as whole chunks.

Shell
Text

When the app includes ScheduleModule, Lighthouse adds a lighthouse:prune task to the schedule itself, run every day at lighthouse.prune_at ("03:10", UTC), without overlapping; a schedule:work process runs it. Set prune_at = "" to schedule it yourself, or run it from cron in an app without a scheduler.

Storage

By default Lighthouse writes to the app's database. To keep monitoring writes away from your data, give it its own:

.env

Lighthouse connects to it when the app boots, creates its five tables there if they are missing, and reads and writes there; the dashboard reads from it too. The URL takes the same forms as database.url. (The lighthouse migration still creates the tables in the app's database, where they stay empty, so removing the setting later works.)

The background writer flushes the buffer every second, or as soon as it holds 500 records. If the database can't keep up and the buffer reaches lighthouse.buffer records (10,000), Lighthouse drops samples first and occurrences last, never counts, and says so: the dashboard shows a banner with the number this process dropped, and /metrics has lighthouse_dropped_total. On shutdown the writer flushes what it holds before the app exits.

Privacy and security

Lighthouse stores what your app handles, so it treats it with care:

  • Secrets are masked. Header values, report context values and JSON body fields whose names contain a fragment of log.redact (password, token, authorization, cookie, secret by default) are stored as [redacted], plus any fragment you add to lighthouse.redact. Authorization, Proxy-Authorization, Cookie and Set-Cookie headers are always masked. Queries are stored as SQL with placeholders, never their bound values.
  • No bodies by default. Request bodies are not recorded unless you set lighthouse.capture.request_body = true, and then only JSON bodies up to 16 KiB, with the same masking. Response bodies are never recorded.
  • Client addresses. The client IP is stored with each request sample. Set lighthouse.capture.ip = false to leave it out.
  • The dashboard denies by default. Nobody sees it without the view ability, sign-in attempts are throttled, the cookie is encrypted and scoped to the dashboard's path, and its pages are served with a strict Content Security Policy (only the dashboard's own stylesheet, script and API).
  • Data stays with you. Nothing leaves your servers except the alerts and exports you configure.

Lighthouse's own routes are left out of its recordings, as are the paths in lighthouse.ignore.routes (/health/* and /metrics by default) and the lighthouse.alert jobs. Ignore noisy jobs with lighthouse.ignore.jobs.

OpenTelemetry export

Lighthouse keeps what it records in your database. To send traces to an OpenTelemetry collector as well, turn on the export of ObserveModule (the facade's otel feature and the observe.tracing section): it turns the same telemetry events into spans, named and attributed following the OpenTelemetry semantic conventions, and Lighthouse's trace ids are the ones it exports. With observe.tracing.trace_url set, the dashboard links each trace id to your tracing backend. Metrics are on /metrics.

To keep only the export, set lighthouse.store = "off": nothing is recorded, nothing is written to the database and the dashboard isn't mounted.

Configuration

Every key of the lighthouse section is optional. As environment variables: APP_LIGHTHOUSE__RETENTION_DAYS=30, APP_LIGHTHOUSE__SAMPLE__REQUESTS=0.1, and lists as comma-separated strings (APP_LIGHTHOUSE__IGNORE__ROUTES=/health/*,/metrics,/internal/*). An invalid value, such as a sample rate above 1 or an unknown alert kind, fails the boot with a message naming the key.

KeyDefaultWhat it does
lighthouse.enabledtrueRecord anything at all. When off, the dashboard still shows what was recorded.
lighthouse.storedatabasedatabase, or off to record nothing and mount no dashboard.
lighthouse.database_urlnoneA separate database for Lighthouse's tables.
lighthouse.dashboardtrueMount the dashboard and its API.
lighthouse.mcptrueOffer the MCP tools, resource and prompt on the app's MCP server (with the mcp feature and McpModule).
lighthouse.path/_lighthouseWhere the dashboard is served.
lighthouse.urlapp.url + pathThe dashboard's public URL, for links in alerts.
lighthouse.environmentproduction in release builds, development in debug buildsThe environment label on every record.
lighthouse.releaseapp.version, else unknownThe release label. Harbor sets it on deploy.
lighthouse.serverthe host nameThe server label.
lighthouse.thresholds.slow_request_ms1000When a request is slow.
lighthouse.thresholds.slow_query_ms200When a query is slow.
lighthouse.thresholds.slow_job_ms30000When a job attempt is slow (its trace is kept).
lighthouse.thresholds.slow_outgoing_ms2000When an outgoing request is slow.
lighthouse.sample.requests1.0Fraction of request traces stored.
lighthouse.sample.jobs1.0Fraction of job traces stored.
lighthouse.sample.exceptions1.0Fraction of occurrences stored beyond the first 100 per issue per hour.
lighthouse.retention_days14What lighthouse:prune keeps.
lighthouse.prune_at"03:10"When the scheduler prunes (UTC); empty to not schedule it.
lighthouse.buffer10000Records held in memory before dropping (at least 100).
lighthouse.capture.request_bodyfalseStore JSON request bodies (up to 16 KiB) with stored requests.
lighthouse.capture.request_headerstrueStore request headers, masked.
lighthouse.capture.iptrueStore the client address.
lighthouse.redact[]Name fragments masked in addition to log.redact.
lighthouse.ignore.routes["/health/*", "/metrics"]Paths never recorded (* matches one or more segments at the end).
lighthouse.ignore.jobs[]Job types never recorded.
lighthouse.alerts.cooldown_minutes30Minimum time between two alerts for one issue and kind.
lighthouse.alerts.spike.occurrences50Occurrences that make a spike.
lighthouse.alerts.spike.minutes5The spike window (1 to 60).
lighthouse.alerts.email.tononeRecipients of email alerts.
lighthouse.alerts.email.onnew_issue, regression, spikeKinds sent by email.
lighthouse.alerts.slack.webhook_urlnoneThe Slack incoming webhook.
lighthouse.alerts.slack.onnew_issue, regression, spikeKinds sent to Slack.
lighthouse.alerts.webhook.urlnoneWhere webhook alerts are posted.
lighthouse.alerts.webhook.secretnoneThe signing secret (at least 32 bytes). Required with url.
lighthouse.alerts.webhook.onnew_issue, regression, spikeKinds sent to the webhook.

To configure the module in code instead:

Rust

Commands

CommandWhat it does
lighthouse:prune [--days N]Deletes what is older than the retention period (or N days).
lighthouse:test-alerts [channel]Sends a test alert through every channel, or one, and reports the result.

Both run in your app's binary, so navree lighthouse:prune works in development and ./crm-api lighthouse:prune on a server.

Testing

LighthouseFake records reports instead of storing them, so a test can check that your code reports what it should. Install its Lighthouse in place of the app's, the way you install a queue fake:

tests/webhooks.rs
MethodWhat it does
assert_reported::<E>(predicate)Asserts a report of an error of type E (the type given to report) for which predicate(&report) holds.
assert_reported_message(text)Asserts a report whose message contains text.
assert_reported_times(n)Asserts exactly n reports.
assert_nothing_reported()Asserts there was no report.
reports()Every recorded report: message, source chain, error type, the error's Debug form, level, fingerprint, tags, context, user, route, request id.
alerts()The alerts raised through raise_alert.
clear()Forgets what was recorded.

The fake also records the exceptions the framework captures for you. A handler that answers 500 in a test shows up in reports() with its route, so you can assert that an error you expect is reported once and an error you handle isn't reported at all. In tests that don't install the fake, set lighthouse.enabled = false to keep Lighthouse out of the way, and call flush() on the injected Lighthouse before reading its store.