Skip to content
Air-Cloud docs

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.