Skip to main content
Logging is essential for debugging your fal App during development and monitoring it after deployment. Every message your app writes to stdout or stderr is captured by fal and made available through the dashboard, CLI, and queue status API. This means print() statements, Python logging output, and even error tracebacks are all collected automatically. The most important thing to understand about logging on fal is that logs exist at different scopes, and each scope determines who can see them and where they appear. Runner logs capture everything a runner does across its lifetime. Request logs are the subset visible to callers. Playground logs appear in real-time during testing. Knowing which scope you are working with helps you decide what information is safe to log and where to look when debugging.

Runner Logs

Runner logs capture the full output of a runner process from the moment it starts until it shuts down. This includes output from setup() (model loading, initialization), every request the runner handles, and any background output between requests. Runner logs are visible only to the app owner through the dashboard and CLI. Use runner logs for internal diagnostics: model loading times, memory usage, cache behavior, and anything that spans the runner’s lifetime rather than a single request.
class MyApp(fal.App):
    def setup(self):
        print("Loading model weights...")  # This appears in runner logs
        self.model = load_model()
        print(f"Model loaded, VRAM: {torch.cuda.memory_allocated() / 1e9:.1f}GB")
You can view runner logs in the CLI with fal runners logs <runner-id>, or in the dashboard filtered by runner ID.

Request Logs

Request logs are the portion of runner logs emitted while a specific request is being processed. They are time-scoped: fal captures the start and end of each request and extracts the log messages in between. These are the logs that callers see when they poll for queue status with with_logs=True (Python) or logs: true (JavaScript).
class MyApp(fal.App):
    @fal.endpoint("/")
    def run(self, prompt: str) -> dict:
        print(f"Generating for prompt: {prompt}")  # Visible to callers
        result = self.model(prompt)
        print(f"Generation complete, inference time: {result.time:.2f}s")
        return {"image": result.image}
Because request logs are visible to anyone calling your app, be careful about what you log during request handling. Avoid logging secrets, user data, or internal model details that should not be exposed.

Private Logs

If your request logs contain sensitive information, you can make them private so only you (the app owner) can see them. Callers polling the queue will see no log output.
class MyApp(fal.App):
    host_kwargs = {"private_logs": True}

    @fal.endpoint("/")
    def run(self, prompt: str) -> dict:
        print("This log is only visible to the app owner")
        return {"result": "..."}
When private_logs is enabled, logs are still captured and visible in the dashboard and CLI, but callers receive empty log arrays in their status responses.

Playground Logs

When you test your app in the Playground, logs appear in real-time in the output panel as your endpoint processes the request. The Playground shows request logs (the same logs callers would see via the queue API), so you can verify exactly what output your users will receive. During development with fal run, logs also stream directly to your terminal, giving you immediate feedback without needing the dashboard.

Writing Logs

For quick debugging, print() is the simplest approach. Both stdout and stderr are captured.
import fal
import sys

class MyApp(fal.App):
    @fal.endpoint("/")
    def run(self) -> dict:
        print("Processing request...")
        print("Warning: cache miss", file=sys.stderr)
        return {"message": "Hello, World!"}
For production apps, use Python’s standard logging module to control log levels and message format. A good pattern is to route INFO and WARNING to stdout and ERROR and above to stderr, so you can filter by severity when reviewing logs.
import logging
import sys
import fal


def get_logger():
    stdout_handler = logging.StreamHandler(sys.stdout)
    stdout_handler.setLevel(logging.INFO)
    stdout_handler.addFilter(lambda record: record.levelno < logging.ERROR)

    stderr_handler = logging.StreamHandler(sys.stderr)
    stderr_handler.setLevel(logging.ERROR)

    logging.basicConfig(
        level=logging.INFO,
        format="%(asctime)s %(levelname)s %(name)s: %(message)s",
        handlers=[stdout_handler, stderr_handler],
    )
    return logging.getLogger("my_app")


class MyApp(fal.App):
    def setup(self):
        self.logger = get_logger()

    @fal.endpoint("/")
    def run(self) -> dict:
        self.logger.info("Started request processing")
        try:
            result = {"message": "Hello, World!"}
            self.logger.info("Request completed successfully")
            return result
        except Exception:
            self.logger.exception("Request failed")
            raise
Include the request ID in your log output for easier debugging. You can access it via self.current_request.request_id in your endpoint handler. See Request Headers for all available per-request context including caller identity and custom headers.
Avoid logging secrets, API keys, or large raw payloads. Log IDs, sizes, and high-level status instead.

Log Sources

When filtering logs in the dashboard or CLI, the source field tells you where the logs came from:
  • run - logs from runners created by fal run during development
  • gateway - logs from runners serving deployed apps in production
  • deploy - logs emitted by the deployment process itself when using fal deploy

Where to View Logs

What you needWhere to look
All logs for a specific runnerDashboard filtered by runner ID, or fal runners logs <runner-id> in the CLI
Logs for a specific requestDashboard filtered by request ID, or queue status API with logs enabled
Real-time logs during developmentTerminal output from fal run
Real-time logs in the browserDashboard logs page streams all runner logs in real-time. Playground shows request-filtered logs only.
Logs filtered by deployment versionDashboard filtered by version ID