로그 기록과 오류 처리
워크플로 코드를 설정해 로그를 기록하고, 로컬 노트북에 오류를 보고하고, 작업이 취소될 때 부분 작업을 저장하세요. 로컬 노트북에서 이러한 출력을 읽는 방법은 관리형 워크플로 실행하기를 참고하세요.
| 워크플로에서 | 로컬 노트북에서 |
|---|---|
logging 모듈의 로거로 기록하거나 print()를 호출합니다. | job.logs()가 해당 줄을 반환합니다. |
| 예외를 발생시키거나 0이 아닌 코드로 종료합니다. | job.status()가 ERROR를 반환하고, job.logs()가 트레이스백을 보여 줍니다. |
send_error()를 호출합니다. | job.error_message()와 job.events(type="ERROR")가 코드, 메시지, 세부 정보를 반환합니다. |
SIGTERM 핸들러에서 save_result()를 호출합니다. | job.result()가 취소된 작업의 부분 결과를 반환합니다. |
로그 기록
워크플로가 표준 출력이나 표준 오류에 쓰는 모든 내용은 작업의 로그로 들어갑니다. 작업이 실행되는 동안이나 끝난 후에 job.logs()로 로그를 읽으세요.
로거 설정
Python 표준 라이브러리의 logging 모듈을 사용하세요. logging.basicConfig()는 표준 오류에 기록하며, 이는 지연 없이 로그에 도달합니다. 다음 예제의 형식은 각 줄에 타임스탬프를 추가합니다.
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은 다른 패키지의 INFO 메시지를 숨기고, logger.setLevel(logging.INFO)는 직접 만든 로거의 INFO 메시지를 유지합니다. qiskit_ibm_runtime은 QISKIT_IBM_RUNTIME_LOG_LEVEL 환경 변수로 자체 수준을 설정합니다.
print()도 로그에 도달하지만 표준 출력은 버퍼링되므로, 출력된 줄이 작업이 끝날 때까지 나타나지 않을 수 있습니다. print()를 사용한다면 flush=True를 전달하세요.
실행마다 로그 수준 설정
코드를 변경하지 않고 한 번의 실행을 디버그하려면 로그 수준을 인수로 전달하세요.
run_workflow_remotely.ipynb
job = my_workflow.run(circuits=circuits, log_level="DEBUG")
이를 활성화하려면 인수로 로거의 수준을 설정하세요.
source_files/my_workflow.py
from qiskit_serverless import get_arguments
arguments = get_arguments()
logger.setLevel(arguments.get("log_level", "INFO"))
워크플로에 인수 스키마가 있다면 log_level을 선언하세요. 예를 들어 {"enum": ["DEBUG", "INFO", "WARNING"]}로 선언할 수 있습니다.
모범 사례: 기록할 내용 정하기
로그만으로 실행을 재구성할 수 있을 만큼 충분히 기록하세요.
-
시작할 때 실행이 받은 내용을 기록하세요. 백엔드 이름, 회로 수, 실행마다 달라지는 다른 설정을 기록합니다.
-
각 단계의 시작과 끝을 기록하세요. 검색할 수 있는 고정된 메시지를 사용하세요.
-
반복은 일정한 간격으로 기록하세요. 예를 들어 매 반복마다 기록하는 대신, 10번 반복할 때마다
iteration 10/200, energy=-1.1000같은 줄을 기록합니다. -
except블록 안에서logger.exception()으로 예외를 기록하세요. 워크플로가 오류에서 복구되더라도 해당 줄에는 전체 트레이스백이 포함됩니다. -
진행률 표시줄을 끄세요. 로그는
tqdm같은 진행률 표시줄의 갱신마다 별도의 줄로 저장합니다.tqdm에disable=True를 전달하거나 일정한 간격으로 기록하세요. -
로그를 작게 유지하세요. 모든 샷과 같은 큰 데이터는 기록하지 마세요. 관리형 워크플로는 작업마다 최신 로그 50 MB만 보관합니다.
-
"error"와 "exception"이라는 단어는 실패에 관한 메시지에만 사용하세요. 실패한 작업이 오류를 보고하지 않았다면,
job.result()가 발생시키는 예외에는 이 단어가 포함된 로그 줄만 들어 있습니다.
모범 사례: send_error()로 오류 보고하기
워크플로가 예외를 발생시키면 작업은 ERROR 상태로 끝나고 트레이스백은 로그로 들어갑니다. 관리형 워크플로는 ServerlessError인 경우에도 오류를 저장하지 않습니다. job.error_message()는 {}를 반환하며, job.result()가 발생시키는 예외에는 error 또는 exception이 포함된 로그 줄만 들어 있습니다. 인수 타입과 범위의 검사는 작업이 시작되기 전에 잘못된 실행을 거부하는 인수 스키마에 두어야 합니다.
워크플로를 중단시키는 모든 오류를 send_error()로 보고하면 로컬 노트북이 코드와 세부 정보가 포함된 짧은 오류를 받습니다.
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()는 같은 텍스트로 QiskitServerlessException을 발생시킵니다.
이를 활성화하려면 오류가 발생하는 곳에서 ServerlessError를 발생시키고, 엔트리포인트의 코드를 try 블록으로 감싸 모든 오류를 send_error()로 전송하세요. 워크플로는 Catalog Functions에는 이 핸들러를 추가하지만 관리형 워크플로에는 추가하지 않습니다.
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는 로그에 트레이스백을 남기고 작업을ERROR상태로 끝냅니다. -
ServerlessError의 하위 클래스로 오류 그룹에 이름을 붙이세요. 핸들러는InputError같은 클래스 이름을exception필드로 전송합니다. -
다른 예외도 핸들러에 도달합니다. 이 예외는 코드
1을 받고,ValueError('bad shots')처럼repr()이 메시지가 됩니다.
문제를 보고하고 계속하기
send_error()는 워크플로를 중단하거나 상태를 바꾸지 않으므로, 건너뛴 회로처럼 워크플로가 복구하는 문제도 보고할 수 있습니다.
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")는 DONE 상태로 끝난 작업의 오류를 포함해 워크플로가 보고한 모든 오류를 반환합니다. 오류의 원인 찾기를 참고하세요.
-
args에는 숫자와 문자열을 전달하세요.error_message()는 리스트의 첫 번째 항목만 보여 줍니다. -
반환값을 확인하세요. 코드가 작업 밖에서 실행되는 등의 이유로 워크플로가 오류를 저장할 수 없으면,
send_error()는 오류를 로그에 쓰고False를 반환합니다.
작업이 취소될 때 정리하기
작업을 취소하면 워크플로는 엔트리포인트에 SIGTERM을 보내고, 최종 로그를 업로드하고, 환경을 종료합니다. 핸들러가 없으면 Python이 즉시 중단되고 finally 블록은 실행되지 않습니다. 워크플로는 get_runtime_service()로 시작한 QPU 작업과 세션도 취소하려고 시도합니다. 연결된 QPU 작업 취소를 참고하세요.
부분 작업을 보존하려면 SIGTERM에 대한 핸들러를 등록하세요. 그러면 로컬 노트북이 취소된 작업의 부분 결과를 받습니다.
run_workflow_remotely.ipynb
job.cancel()
print(job.result()) # {'status': 'canceled', 'partial_results': [...]}
이를 활성화하려면 엔트리포인트 시작 부분에 핸들러를 추가하세요.
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()는 실행 중인 코드에서 SystemExit를 발생시키므로, 스크립트가 끝나기 전에 finally 블록이 실행됩니다. 작업은 여전히 CANCELED 상태로 끝납니다.
워크플로는 SIGTERM 직후 곧 환경을 종료하므로 핸들러는 짧게 유지하세요. 오래 실행되는 작업에서는 체크포인트도 저장해, 작업이 실패하거나 24시간 제한에 도달한 뒤 이후 실행이 재개할 수 있게 하세요. 모범 사례: 장시간 작업에 체크포인트 사용하기를 참고하세요.
다음 단계
- 관리형 워크플로 실행하기에서 작업을 시작하고 상태, 로그, 결과를 읽어 보세요.
- 실행 간 파일 관리하기에서 이후 실행이 재개할 수 있는 체크포인트를 저장해 보세요.
- 컴퓨트 프로필 선택하기에서 더 많은 CPU, 메모리 또는 GPU 리소스로 워크플로를 실행해 보세요.