Skip to main content
IBM Quantum Platform
Información sobre la traducción

Próximamente habrá traducciones a otros idiomas de esta página.

Write logs and handle errors

Set up your workflow code to write logs, report errors to your local notebook, and save partial work when a job is canceled. To read these outputs from your local notebook, see Run a managed workflow.

In the workflow
In your local notebook
Write with a logger from the logging module, or call print().job.logs() returns the lines.
Raise an exception, or exit with a nonzero code.job.status() returns ERROR, and job.logs() shows the traceback.
Call send_error().job.error_message() and job.events(type="ERROR") return the code, message, and details.
Call save_result() in a SIGTERM handler.job.result() returns the partial result of a canceled job.

Write logs

Anything that the workflow writes to standard output or standard error goes to the job's logs. Read the logs with job.logs() while the job runs or after it ends.

Set up a logger

Use the logging module from the Python standard library. logging.basicConfig() writes to standard error, which reaches the logs without delay. The format in the following example adds a timestamp to each line:

source_files/my_workflow.py
import logging

logging.basicConfig(
    level=logging.WARNING,
    format="%(asctime)s %(levelname)s %(name)s: %(message)s",
)
logger = logging.getLogger("my_workflow")
logger.setLevel(logging.INFO)

logger.info("Transpiled %d circuits: depth %d -> %d", len(circuits), 1250, 730)
2026-11-01 21:57:35,195 INFO my_workflow: Transpiled 12 circuits: depth 1250 -> 730

level=logging.WARNING hides INFO messages from other packages, and logger.setLevel(logging.INFO) keeps INFO messages for your own logger. qiskit_ibm_runtime sets its own level with the QISKIT_IBM_RUNTIME_LOG_LEVEL environment variable.

print() also reaches the logs, but standard output is buffered, so printed lines might not appear until the job ends. If you use print(), pass flush=True.

Set the log level for each run

To debug one run without changing the code, pass the log level as an argument:

run_workflow_remotely.ipynb
job = my_workflow.run(circuits=circuits, log_level="DEBUG")

To enable this, set the level of your logger from the argument:

source_files/my_workflow.py
from qiskit_serverless import get_arguments

arguments = get_arguments()
logger.setLevel(arguments.get("log_level", "INFO"))

If the workflow has an argument schema, declare log_level in it, for example as {"enum": ["DEBUG", "INFO", "WARNING"]}.

Best practice: decide what to log

Log enough to reconstruct a run from its logs:

  • At the start, log what the run received. Log the backend name, the number of circuits, and the other settings that change between runs.
  • Log the start and end of each stage. Use fixed messages that you can search for.
  • Log iterations at a fixed interval. For example, log a line such as iteration 10/200, energy=-1.1000 every 10 iterations, instead of every iteration.
  • Log exceptions with logger.exception() inside an except block. The line includes the full traceback, even if the workflow recovers from the error.
  • Turn off progress bars. The logs store each refresh of a progress bar, such as one from tqdm, as a separate line. Pass disable=True to tqdm, or log at an interval instead.
  • Keep the logs small. Do not log large data, such as every shot. A managed workflow keeps only the newest 50 MB of logs for each job.
  • Use the words "error" and "exception" only in messages about failures. If a failed job did not report its error, the exception that job.result() raises contains only the log lines that include these words.

Best practice: report errors with send_error()

When the workflow raises an exception, the job ends with the ERROR status, and the traceback goes to the logs. A managed workflow does not store the error, even for a ServerlessError. job.error_message() returns {}, and the exception that job.result() raises contains only the log lines that include error or exception. Checks of argument types and ranges belong in an argument schema, which rejects a bad run before the job starts.

Report every error that stops the workflow with send_error(), so that your local notebook gets a short error with a code and details:

run_workflow_remotely.ipynb
print(job.error_message())

| Message: The circuit has more qubits than the backend.
| Code: 4500
| Exception: InputError
| Details:
|   - circuit_qubits: 10
|   - backend_qubits: 5

job.result() raises a QiskitServerlessException with the same text.

To enable this, raise ServerlessError where the error happens, and wrap the code of your entrypoint in a try block that sends any error with send_error(). A workflow adds this handler to Catalog Functions, but not to managed workflows:

source_files/my_workflow.py
from qiskit_serverless import ServerlessError, send_error


class InputError(ServerlessError):
    """The arguments are valid, but the workflow cannot use them."""


def main():
    ...
    if num_qubits > backend.num_qubits:
        raise InputError(
            code="4500",
            message="The circuit has more qubits than the backend.",
            details={"circuit_qubits": num_qubits, "backend_qubits": backend.num_qubits},
        )
    ...


try:
    main()
except Exception as error:
    send_error(
        code=getattr(error, "code", "1"),
        message=getattr(error, "message", repr(error)),
        exception=type(error).__name__,
        args=getattr(error, "details", None),
    )
    raise
  • Raise the error again. The final raise keeps the traceback in the logs and ends the job with the ERROR status.
  • Subclass ServerlessError to name a group of errors. The handler sends the name of the class, such as InputError, as the exception field.
  • Other exceptions also reach the handler. They get the code 1, and their repr() becomes the message, such as ValueError('bad shots').

Report a problem and continue

send_error() does not stop the workflow or change its status, so you can also report problems that the workflow recovers from, such as each circuit that it skips:

source_files/my_workflow.py
valid_circuits = []
for index, circuit in enumerate(circuits):
    if circuit.num_qubits > backend.num_qubits:
        send_error(
            code="4501",
            message="Skipped a circuit that is wider than the backend.",
            exception="InputError",
            args={"circuit_index": index},
        )
        continue
    valid_circuits.append(circuit)

job.events(type="ERROR") returns every error that the workflow reported, including the errors of a job that ended with the DONE status. See Find the cause of an error.

  • Pass numbers and strings in args. error_message() shows only the first item of a list.
  • Check the return value. If a workflow cannot store the error, for example because the code runs outside a job, send_error() writes the error to the logs and returns False.

Clean up when a job is canceled

When you cancel a job, the workflow sends SIGTERM to your entrypoint, uploads the final logs, and shuts down the environment. Without a handler, Python stops immediately, and finally blocks do not run. The workflow also tries to cancel the QPU jobs and sessions that the workflow started with get_runtime_service(). See Cancel linked QPU jobs.

To keep partial work, register a handler for SIGTERM. Your local notebook then gets the partial result from a canceled job:

run_workflow_remotely.ipynb
job.cancel()
print(job.result())  # {'status': 'canceled', 'partial_results': [...]}

To enable this, add the handler at the start of your entrypoint:

source_files/my_workflow.py
import signal
import sys

from qiskit_serverless import save_result

partial_results = []


def handle_sigterm(signum, frame):
    save_result({"status": "canceled", "partial_results": partial_results})
    sys.exit(0)


signal.signal(signal.SIGTERM, handle_sigterm)

sys.exit() in the handler raises SystemExit in the code that is running, so finally blocks run before the script ends. The job still ends with the CANCELED status.

Keep the handler short, because the workflow shuts down the environment soon after SIGTERM. For long runs, also save checkpoints, so that a later run can resume after a job fails or reaches the 24-hour limit. See Best practice: checkpoint long jobs.


Next steps

Recommendations
¿Le ha resultado útil esta página?
Informe de un error, de una errata o solicite contenido en GitHub.