Operate an App in production

What to watch after install day — the routine that tells you an App is healthy, the order to investigate in when it is not, which failures the tenant sees, and what to hand over when you escalate.

The tutorials before this one end when your App answers a real request. This one covers the months after that: what a healthy App looks like on the screens you already have, what you open when something is wrong, and in what order.

This is for whoever carries the pager for an App installed on tenants you do not control. It assumes the App is deployed and verified (see Deploy with the CLI and Verifying live). Operating describes each App Studio screen. This page is the order you use them in.

Log one line per invocation

One habit is worth adopting now rather than during an incident, because it only helps from the moment you start.

Log one line per invocation with the request id, the tenant and the outcome. That turns "a customer says it was slow at 14:20" into a lookup instead of a hunt. But never log a sensitive setting or personal data: execution logs are readable by whoever operates the tenant, which is not you.

What healthy looks like

Four things, all observable without instrumenting anything.

  • The active version is the one you shipped. A build that failed leaves the previous version serving, and a tenant can sit on an older version than the one you deployed last. Deployments answers this.
  • Invocations are not zero. An App with no invocations after a launch has a routing or install problem, not a code problem. Nothing is reaching it.
  • The error rate is flat, and the p99 is a population rather than one request. Usage shows both over time. A single pathological request moving the p99 is a different problem from a tail that is getting worse.
  • Every schedule you declared appears. Ticks land in Executions like any other invocation, with the schedule name. A schedule that stopped firing is silent by construction: nobody gets an error, the work just does not happen.

Nothing on the platform pages you for any of this. There is no alerting to configure, so "watch" means you open the screen, on a rhythm you decide. After every release, at minimum.

After each release

Three screens, in this order, and it takes a minute.

  1. Deployments. The version you just shipped is the active one, and the build succeeded. If you deploy from CI onto a git-wired App, a deployment with no commit association is the fingerprint of a CLI deploy onto a git-wired app.
  2. Anatomy. The routes, grants, events and schedules you think you declared are the ones that got applied. It is generated from the applied version rather than your working copy, so it is the authoritative answer when a route is missing.
  3. Usage. The error rate after the release looks like the error rate before it.

Then re-run the live checks, including the POST /defaults call. It is idempotent, so it belongs in your deploy script rather than in a runbook nobody reads, and app.installed does not reliably fire on a Marketplace install.

Watching the work nobody calls

Scheduled jobs and outbound calls fail quietly, because no caller is waiting on them.

Schedules. A tick is one tenant, and the job's cost is your slowest tenant times the number of tenants. A nightly job that took two seconds at launch takes forty a year later, and eventually it times out on the tenant with the most data, at 3 a.m. Usage is where you see that curve before it becomes an incident. Make the job return what it did. The return value lands in the execution record either way, so { scanned: 4210, expired: 17 } costs you nothing and {} tells you nothing. See Scheduled work.

Outbound HTTP. A call to an external host fails as your own 502 or as a timeout, so it looks like your App failing. There is no separate egress screen to watch: you find these in Executions. What keeps them rare is on your side, a timeout on every external call and no third party on a path the storefront awaits. Your outbound declarations are a separate matter. Enforcement is still being built, so an undeclared call currently succeeds, and an App whose register is wrong will break when enforcement lands. Keep it honest now.

When something is wrong

In this order. Each step is cheap and rules out a whole class of cause.

1. What is actually running?

Deployments, first, every time. It is the question people assume they know the answer to, and a tenant on an older version explains behaviour that makes no sense against your working copy.

2. Did the request reach you?

Find the invocation in Executions. A call that is not there was rejected by the gateway before your function ran, and that changes what you investigate: the contract, the credential or the install, not your code. The common one is a POST sent with no body against a capability that declares a request schema. Contract validation fails in front of you, your handler is never invoked, and nothing appears in your log. Send {}.

3. Whose failure is it?

What the caller gotWhere it happenedWhat to look at
400 on a POST you are sure aboutGateway, before youMissing body, or a request schema stricter than your handler
401GatewayThe credential, not your App
404 on your pathGatewayThe install did not run for this version, or the App is not installed on this tenant
429GatewayA rate limit. The caller handles it; there is no setting to change
403 on a writeYour handlerThe entity grant does not cover that operation. The invocation ran, so Executions has it
400, 403, 404, 409 you raisedYour handlerYour own guard or constraint. Executions has the log line
502Your handlerYour function threw, or something it called did. Read the execution

The four gateway rows are the ones worth internalising: the tenant sees a failure you have no record of. If a partner reports errors you cannot find in Executions, stop looking at your code and look at the contract and the install. Anatomy and the published OpenAPI document are the two places that answer it.

4. Where did the time go?

Executions gives you the duration of one invocation, not its breakdown. Narrowing it further is on you: the line you log per invocation is what turns a slow invocation into a slow step, so log the boundaries of anything you call. Forwarding X-Request-ID when you call another app or an external host is what lets you line those logs up across the boundary.

If the slow part is a cold start on a latency-sensitive path, a capability the storefront or checkout awaits, warmup is the switch. It is not free, so it is a decision per App rather than a default.

5. Still wrong?

Check status.revenexx.com before you write anything. An incident there is already being worked and does not need a support request.

What to hand over

A support request that gets answered in one round trip carries these. Gather them before you open it, not after someone asks.

  • The X-Request-ID of a failing request. Every response has one, and it is what turns "it failed at 14:20" into something traceable.
  • The tenant slug you sent in X-Revenexx-Tenant.
  • The endpoint, the method, and the timestamp in UTC, not "this morning".
  • What you expected and what you got: the real status and body, not a paraphrase.
  • The active version from Deployments, and whether the failure started with a release.

Requests go to support.revenexx.com. There is no separate developer queue. A page that is wrong or unclear goes somewhere else, the "Was this page helpful?" control at the bottom of it, because nobody triages an outage from a docs issue. See Get help.

What's next

  • Operating — every App Studio screen and the question it answers.
  • Verifying live — the calls to make after each deploy.
  • Versioning — shipping the next version without breaking installed tenants.
Was this page helpful?