Logs and invoking functions
Listing and reading functions
aircloud functions list
aircloud functions list --all
aircloud functions show hello
functions list says what is live. A function is deployed in N environments (<names>) while the active release
of any environment, a preview included, holds it. With none, it is removed if a release active in production once
held it, else undeployed. Removed functions are listed only with --all.
functions show gives a function as deployed: the version each environment runs, its triggers, its versions and its
recent lifecycle.
Shared components
Code several functions use is a component, not a copy:
aircloud generate component pricing
writes lib/pricing/. A function composes it by listing it in its function.toml — components = ["pricing"] —
and a JavaScript function imports it by name (import { describe } from "pricing"). The platform's build links it
as the function's node_modules/pricing (a file: dependency is refused by the lockfile rules), and
aircloud test prepare links it the same way for the tests. A listed component with no lib/<name>/ fails the
build build.component_missing; an installed package of the same name, build.component_link_conflict.
Unit tests
npm --prefix test ci && npm --prefix test test runs the unit layer. Its pretest runs aircloud test prepare,
which compiles db/schema/ to the file the testing double reads, so a generated test declares the product's real
schema — owners, shared entities, defaults and write roles, placed at the root as the platform places them — with
world.flex.declareFromSchema() after world.reset(). Do not also create a schema entity with
world.flex.defineEntity: an entity created through the API is placed at the signed-in node, and the double refuses
a schema declaration over one placed there.
Time zones in functions
The JavaScript function runtime has no Intl: any Intl call throws ReferenceError: Intl is not defined. A
Date's local time is UTC (getTimezoneOffset() is 0), and toLocaleString, toLocaleDateString and
toLocaleTimeString ignore the locale and the timeZone option. A function takes a zone's date and time from
civilDate and instantOf in @aircloudhq/web/function, which read the rules of the zones the product declares
from its locale bundle; the zone defaults to the time_zone declared in aircloud.toml:
import { i18nFor, civilDate, instantOf } from "@aircloudhq/web/function";
const i18n = await i18nFor(request, { function: "book" });
civilDate(i18n); // "2024-06-01": today, in time_zone
civilDate(i18n, { at: "2024-06-01T02:30:00Z" }); // the date on the wall there at that instant
instantOf(i18n, { date: "2024-06-01", time: "19:00" }); // a Date: when the wall clock there reads 19:00
instantOf resolves a wall time the zone passes twice to the earlier instant and shifts one it skips forward by
the gap. Never re-implement zone math — offsets, daylight saving — in function code. The unit tests' invoke()
runs a function with the same Intl and time zone, so a test catches a call the runtime would refuse.
Invoking a function
aircloud functions invoke hello --data '{"name":"Ada"}'
aircloud functions invoke hello --env <preview> --data @payload.json
echo '[1,2]' | aircloud functions invoke sum --data -
functions invoke runs a deployed function once, in production unless --env names another environment: one of
the product's previews, or a ci-<n> while its verification run lasts (the platform destroys it when the run ends;
see Releases and environments). The
payload is a JSON value — inline, read from a file (@file), or from standard input (-) — and reaches the
function as the body of a POST / with Content-Type: application/json. Without --data the function receives
no body and no Content-Type; --data 'null' sends the JSON null.
--method, --path and --header name=value (repeatable) name the request the function receives instead, so a
function that routes by method and path is exercised as its clients call it:
aircloud functions invoke items --method GET --path /items --header accept=application/json
The platform admits a closed set of methods and of headers (the content-negotiation ones: content-type, accept,
accept-language) and bounds the length of the path and of each header; a request outside them is refused, naming
the rule, before the function is called. A GET
or HEAD has no body, so --data with one is refused; so is a content-type header without --data, or one that is
not a JSON media type (application/json, application/<x>+json): the payload is JSON. Each is refused before
anything is sent, with exit status 2. A function needs no route to be invoked.
The function's response body is printed on standard output, and the verdict on standard error:
aircloud: ok · response 200 · billed · correlation 0198a3c4-…
The verdict is the platform's: the invocation's status, the function's response status, whether it was billed, and
the correlation id its invocation record carries. The exit status says what the function answered: 0 for a 2xx or
3xx response, 3 for a 4xx, 4 for a 5xx or a refusal by the platform before the function ran, 1 when the CLI or
the API failed and no verdict exists, 2 for a usage error. An invocation is
metered and billed like any other. Payload and response are each capped at 1 MiB; a longer response is truncated.
The platform screens every request to your product for known attacks before it reaches your function. A JSON or
form body is screened up to a fixed number of values (the platform's published inspection bound). A larger body is
still delivered whole, and the values past that point reach your function unscreened, so validate what your function
reads from a payload rather than trusting its shape. A request whose query string carries an unusually large number
of parameters is refused.
The CLI never retries an invocation, except once with a renewed credential when an expired token was refused without
billing. When the outcome cannot be read, the CLI says so and names the correlation id to settle it with:
aircloud logs --correlation-id 0198a3c4-…
Reading the logs
aircloud logs --function hello --since 15m
aircloud logs --status error --follow
aircloud logs --env production --source flex
aircloud logs lists the product's function invocations, oldest first. Each record is one line — time, function,
environment, status, duration, trigger (http, api, event or cron) and correlation id — followed by the
response status and the lines the function logged. The filters combine:
| option | keeps |
|---|---|
--function <name> |
one function's invocations |
--env <environment> |
one environment's: the product's own, or a platform ci/ci-<n> one |
--status <status> |
those whose status starts with this: ok, error, error:functions.guest_server_error |
--response-status <class or code> |
those the function answered with this HTTP status: a class (4xx) or a code (404) |
--source <domain> |
event invocations from one trigger-source domain (aircloud help logs lists them) |
--correlation-id <id> |
the one invocation with this id |
--since <time or duration> |
those at or after an RFC 3339 time, or this long ago (15m, 2h) |
--limit <n> |
at most this many of the newest records per read |
--status is the platform's verdict, where a function's own 4xx answer is ok; --response-status is what the
function answered, so --response-status 4xx finds the requests it refused. An environment the product has no
invocations for, or a source outside the trigger-source domains, is refused by the platform with
functions.invocation_filter_unknown, naming the values it can be; the same refusal answers the API and the dev MCP
server.
--follow keeps reading new invocations, every few seconds, until you interrupt it.
The codes a function's refusal can carry are on Service error codes.