exception handling
Python traceback
error logging
debugging
programming tips

Catch and print full Python exception traceback without halting/exiting the program

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Sometimes you want the full Python traceback for debugging, but you do not want one bad item to terminate the entire program. The safe pattern is to catch the exception at a task boundary, record the traceback, and continue with the next independent unit of work. The important part is not just how to print the traceback, but where it is correct to recover and keep going.

Inside an except block, traceback.print_exc() prints the full stack trace for the current exception:

python
1import traceback
2
3items = ["10", "0", "hello", "5"]
4
5for raw in items:
6    try:
7        result = 100 / int(raw)
8        print(f"{raw} -> {result}")
9    except Exception:
10        traceback.print_exc()
11        print(f"Skipping {raw!r} and continuing")
12
13print("Program still running")

This is a good fit for batch-style loops where each item is independent. One failed item is reported clearly, and the rest of the work still proceeds.

Capture the Traceback as a String

If you need to store, email, or log the traceback elsewhere, use traceback.format_exc():

python
1import traceback
2
3
4def divide(a, b):
5    try:
6        return a / b
7    except Exception:
8        details = traceback.format_exc()
9        print("Full traceback follows:")
10        print(details)
11        return None
12
13
14print(divide(10, 0))
15print("Still alive")

This returns the same traceback content as a string, which is often more flexible than printing directly.

Prefer Logging in Real Applications

For scripts, printing may be enough. For applications, workers, and services, use the logging module so the traceback goes through the normal log pipeline.

python
1import logging
2
3logging.basicConfig(
4    filename="app.log",
5    level=logging.ERROR,
6    format="%(levelname)s:%(message)s",
7)
8
9
10def process_record(record):
11    try:
12        return 100 / int(record)
13    except Exception:
14        logging.exception("Failed to process record %r", record)
15        return None
16
17
18for value in ["25", "bad", "0"]:
19    process_record(value)

logging.exception() is especially convenient because it automatically includes the traceback of the current exception when called inside except.

Continue Only at a Safe Boundary

Printing a traceback is easy. Continuing safely is the harder design question.

It is usually reasonable to continue after failures in cases such as:

  • one file per iteration in a batch job
  • one message per iteration in a queue worker
  • one input row per iteration in a data import

It is usually dangerous to continue when the failed code may have left shared state half-updated. If a function partially modifies a transaction, a cache, or a file on disk, blindly continuing can create a harder-to-debug corruption problem.

A safer shape is to isolate one job at a time:

python
1import traceback
2
3
4def handle_job(job):
5    if job == "boom":
6        raise RuntimeError("unexpected job failure")
7    return job.upper()
8
9
10jobs = ["a", "boom", "b"]
11results = []
12
13for job in jobs:
14    try:
15        results.append(handle_job(job))
16    except Exception:
17        traceback.print_exc()
18
19print(results)

Each job is separate, so recovery is sensible.

What Not to Catch

Avoid catching BaseException just to keep the process alive. That also catches signals such as KeyboardInterrupt and termination paths such as SystemExit, which many programs should allow to behave normally.

In most ordinary application code, except Exception: is the broadest catch you should consider, and even then it belongs at a clear recovery boundary rather than around the whole program.

Re-Raise When Recovery Is Not Real

If the program cannot continue safely, print or log the traceback and then re-raise:

python
1import logging
2
3
4def save_critical_data(payload):
5    try:
6        raise RuntimeError("database write failed")
7    except Exception:
8        logging.exception("Critical write failed")
9        raise

This preserves observability without pretending the failure was harmless.

Common Pitfalls

One common mistake is printing only str(error). That gives you the message but loses the call stack, which is usually the most useful part of the failure report.

Another mistake is catching too much at too low a level. A broad catch buried deep in the call stack can hide programming errors and make the system limp forward in a broken state.

Developers also sometimes catch BaseException to keep the program alive. That interferes with normal interruption and shutdown behavior.

Finally, "continue after error" is not automatically safe. Make sure the failed operation did not leave transactions, files, or shared objects in an inconsistent state before deciding to continue.

Summary

  • Use traceback.print_exc() to print the current exception with its full traceback.
  • Use traceback.format_exc() when you need the traceback as a string.
  • Prefer logging.exception() in real applications so the traceback is recorded through your logging pipeline.
  • Catch exceptions around independent tasks, not around the entire program without a recovery plan.
  • Continue only when the failed operation cannot corrupt the rest of the run.

Course illustration
Course illustration

All Rights Reserved.