# Logs and invoking functions ## Listing and reading functions ```sh aircloud functions list aircloud functions list --all aircloud functions show hello ``` `functions list` says what is live. A function is `deployed in N environments ()` 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: ```sh 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//` 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`: ```js 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 ```sh aircloud functions invoke hello --data '{"name":"Ada"}' aircloud functions invoke hello --env --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-` while its verification run lasts (the platform destroys it when the run ends; see [Releases and environments](releases.md#other-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: ```sh 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/+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: ```text 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: ```sh aircloud logs --correlation-id 0198a3c4-… ``` ## Reading the logs ```sh 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 ` | one function's invocations | | `--env ` | one environment's: the product's own, or a platform `ci`/`ci-` one | | `--status ` | those whose status starts with this: `ok`, `error`, `error:functions.guest_server_error` | | `--response-status ` | those the function answered with this HTTP status: a class (`4xx`) or a code (`404`) | | `--source ` | event invocations from one trigger-source domain (`aircloud help logs` lists them) | | `--correlation-id ` | the one invocation with this id | | `--since