Python
Logging
Debugging
Source Code
Programming Tips

How to log source file name and line number in Python

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

Introduction

Python's built-in logging module can include the source file name and line number automatically. In most cases, you do not need stack inspection or custom logger classes. You only need the right format string, and in wrapper-heavy code you may also need stacklevel so the log points to the caller instead of the helper function.

The Basic Formatter Fields

The two most common fields are:

  • '%(filename)s for the file name'
  • '%(lineno)d for the source line number'

A minimal working example:

python
1import logging
2
3logging.basicConfig(
4    level=logging.INFO,
5    format="%(levelname)s %(filename)s:%(lineno)d %(message)s",
6)
7
8logging.info("Server started")

Typical output looks like:

text
INFO app.py:8 Server started

This is the simplest and usually the correct solution.

Useful Variants of the File Information

Depending on how much detail you want, you can also use:

  • '%(pathname)s for the full file path'
  • '%(module)s for the module name'
  • '%(funcName)s for the function name'

Example:

python
1import logging
2
3logging.basicConfig(
4    level=logging.DEBUG,
5    format="%(levelname)s %(pathname)s:%(lineno)d %(funcName)s %(message)s",
6)
7
8
9def run_job():
10    logging.debug("job started")
11
12
13run_job()

This is helpful when short file names are not unique across a larger codebase.

Logger Objects Work the Same Way

You do not have to use the root logger.

python
1import logging
2
3logger = logging.getLogger(__name__)
4handler = logging.StreamHandler()
5handler.setFormatter(logging.Formatter("%(name)s %(filename)s:%(lineno)d %(message)s"))
6logger.addHandler(handler)
7logger.setLevel(logging.INFO)
8logger.propagate = False
9
10logger.info("custom logger ready")

The key idea is the same: source metadata comes from the log record, and the formatter decides whether to display it.

The Wrapper Problem and stacklevel

If you wrap logging calls in helper functions, the logged file name and line number will point at the helper, not at the original caller.

python
1import logging
2
3logging.basicConfig(
4    level=logging.INFO,
5    format="%(filename)s:%(lineno)d %(message)s",
6)
7
8
9def log_info(message):
10    logging.info(message)
11
12
13log_info("hello")

That reports the line inside log_info, which is often not what you want.

Use stacklevel to shift the reported source location outward:

python
1import logging
2
3logging.basicConfig(
4    level=logging.INFO,
5    format="%(filename)s:%(lineno)d %(message)s",
6)
7
8
9def log_info(message):
10    logging.info(message, stacklevel=2)
11
12
13log_info("hello")

Now the log points to the caller of log_info, which is usually the real source location you care about.

When Full Paths Are Better Than File Names

%(filename)s is compact, but it can be ambiguous if multiple modules share the same file name, such as utils.py. In those cases, %(pathname)s or %(name)s may be more useful.

A common production compromise is:

python
format="%(asctime)s %(levelname)s %(name)s %(filename)s:%(lineno)d %(message)s"

That keeps logs readable while still giving enough context.

Avoid Manual Frame Inspection Unless You Really Need It

The logging module already captures filename and line information for you. Manual inspection through inspect or sys._getframe is usually unnecessary unless you are building a custom logging framework.

The built-in logging machinery is simpler and less fragile.

Common Pitfalls

  • Using a formatter that omits %(filename)s and %(lineno)d, then trying to recover the data manually.
  • Forgetting that wrapper functions change the apparent call site.
  • Using %(filename)s when identical file names exist in multiple packages.
  • Adding custom stack inspection even though the standard logging record already has the data.
  • Expecting line numbers to point at the original business call when a helper wrapper did not pass stacklevel.

Summary

  • Use %(filename)s and %(lineno)d in the logging format string to log source location.
  • '%(pathname)s and %(funcName)s are useful when you need more context.'
  • Custom logger instances work the same way as the root logger.
  • When logging through helper functions, use stacklevel so the location points at the true caller.
  • The built-in logging module already provides this metadata without custom frame inspection.

Related reading
Course
Beginner
27 lessons
10 hours
System Design Fundamentals

Build a strong foundation in designing scalable, reliable distributed systems.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

All Rights Reserved.