base44 logs
Fetch function logs for this app.Syntax
--app-id <id> or set BASE44_APP_ID.
Options
Examples
Notes
- Authentication required. You must be logged in before fetching logs.
-
App context required. Run from a linked project, or pass
--app-id/ setBASE44_APP_ID. - When multiple functions are specified, logs are merged and sorted by timestamp.
-
If
--functionis omitted, logs are fetched for all functions in the current app. -
The
--limitapplies after merging logs from all specified functions. -
There is no default page size. The CLI sends a limit only when you pass one, and a
--limitabove 500 is clamped down to 500 — so do not plan on paging further back by raising the number. What you get when you omit it is not a fixed number: the runtime decides, and it may cap you at 500 anyway. Pass--limitwhen the count matters to you. -
--orderis only honored for the client-side merge of several functions. The server does not read it, so it is inert on a single-function fetch — the entries come back newest-first regardless. -
The
--sinceand--untilvalues accept an ISO datetime, or a relative shorthand (e.g.1h,30m,2d) measured back from now. ISO values without a timezone are normalized to UTC (appendsZ). -
--envdefaults topreview. Ifprodreturns no logs, the app may not have been published yet — try--env previewto see draft logs. -
No logs found matching the filters.is ambiguous. It means one of: the run has not been ingested yet (~20-30s; wait and re-run — do not change flags), there is no function by that name, or a--functionfilter dropped unstamped rows from a legacy per-function deployment. It never means “the app is healthy”. -
--followstreams logs indefinitely (oldest to newest) instead of a single fetch; it’s incompatible with--since,--untiland--order. A stream that is lost and cannot be re-established ends the command with an error rather than dropping to polling. See Following logs live. -
Pass the global
--jsonflag to emit each log entry as JSON instead of the human-readable format. -
With
--follow,--jsonoutput is one JSON object per line, not one JSON document. Parse it line by line as it arrives; there is no closing bracket, because a live tail never ends normally. Nothing goes to stderr. If the stream is lost, the error arrives as one more object on its own line and the process exits 1:Notehintsholds objects, each withmessageandcommand— not strings. A consumer that parses per line sees the failure as data rather than as a broken document.
Following logs live
--follow is the tool to reach for when you can reproduce the problem, because it
does not wait on log ingestion.
Streaming or polling is decided once, at startup
--follow opens a realtime stream before it prints anything, and that first attempt
decides the mode for the whole run.
The stream opens — lines arrive in under a second of the invocation ending, and
you stay in realtime for the rest of the run.
The stream is refused — the app is still on a legacy per-function deployment, or
the feature is not enabled for it. The CLI says so and polls instead, for the life of
the process:
--follow --function <name> is refused (404) and polls — that
self-heals on the app’s next deploy, there is nothing to fix.
Both warnings go to stderr, never into the log output on stdout. They are shown
above as they appear when output is piped or otherwise not a terminal — which is
how an agent or a script sees them. In an interactive terminal the same text is
rendered by the fancy logger with a coloured glyph instead of the literal
Warning: prefix, so match on the sentence, not on the prefix. Under --json the
warning still prints, on stderr — --json routes logs to stderr rather than silencing
them, so a --json run gives you both: this warning on stderr, and log lines on
stdout. (Only the lost-stream failure below is stdout-only.)
A stream that dies mid-run ends the command — it does not quietly start polling
Once the stream has opened there is no polling fallback left. If it is lost and cannot be re-established — repeated dead connections, or a typed end frame saying the tail is gone —--follow exits with an error rather than degrading:
base44 logs --follow) or reading
recent logs without streaming (base44 logs). As with the warnings, the literal
Error: prefix is what a piped or non-terminal run prints; an interactive terminal
renders the same message with a glyph. Under --json this failure arrives only as
one more object on stdout (see the --json note above) — there is no Error: line to
match at all.
This matters if you are driving the CLI from a script or an agent loop. Exit 1 from
--follow partway through is the stream giving up, not proof that logging is
broken and not a reason to change flags. Re-run the same command. Ordinary reconnects
are invisible: the CLI reconnects on its own and only errors once it has run out of
attempts.
Reading the stream without fooling yourself
- Silence is not a verdict. Never open the stream, read for a fixed window, and conclude the pipeline is broken — a stream with nothing invoked against it is correctly silent. Trigger the function, then read until the lines arrive.
- Delivery is per-invocation. A function’s lines are delivered as a batch when the invocation ends, so a long-running call is silent while it runs.
- A deploy does not break an open stream. The script rotates in seconds and the same stream carries the new code’s lines on the next invoke. Restarting the stream after each deploy adds noise and loses nothing.
--functionfilters on stamped rows. Failure records are function-stamped, so filtered streams keep them; rows from a legacy per-function script carry no stamp and are dropped by the filter until the app’s next deploy. When a filtered stream looks empty, drop the filter before concluding anything.
Driving the SSE endpoint directly
The CLI handles all of this for you — this section matters only if you are consuming/api/apps/<id>/functions-mgmt/logs/stream yourself.
- Log frames are unnamed
data:events ({time, level, function, message}). - Comment lines (
: ping) are keepalives. They carry no logs but they do prove the connection is alive — treat a ping as liveness, not as an empty read. - The typed
event: endframe is what disambiguates silence, and itsretriableflag is the whole contract:true— every reason the backend currently sends, including a tail that went unavailable — means reconnect immediately;falsemeans stop streaming. Do not branch on the reason string. What you do after afalseis your choice as a raw consumer — the bounded polling route is the obvious one — but note the CLI itself does not do that: it ends the run with an error (see above). (And a fallback to polling in the CLI is driven by the first connection failing — either refused outright, or still unreachable after its retries — never by an end frame.) - Reconnect on a drop rather than giving up on the first one. Carry the last seen timestamp across the reconnect — but for dedupe, and to resume a polling fallback where the stream left off. It does not make the handover gapless: a tail has no replay, so lines emitted during the gap are simply gone.