Hacktoberfest 2026: the issues maintainers tagged for October, open and beginner-friendly. Browse Hacktoberfest issues

create_logger adds a new handler every call, so repeat callers emit each line N times

Open Beginner friendly
#232 1 comment 0 reactions 0 assignees View on GitHub

Maintainers usually reply within 1 day

@swaragvs is already working on this.

Since Oct 11, 2026.

Assessment

Difficulty
2/5
Estimated time
1-3 hours
Newbie friendliness
82/100
Issue type
Bug
Clarity
Clearly specified
Activity status
Active
Tech stack
python
Domain
tooling

Research direction

The bug is in create_logger in bioengine/utils, which calls addHandler on the process-wide logger from logging.getLogger(name) every time it runs. Start by running the reproduction in the issue (call create_logger five times with one name, then count the handlers) to confirm the count grows. Done means repeated calls leave the same handler set in place, with a second log_file destination still possible through an explicit path, and a test in the repo's existing utils tests should cover the repeated-call case.

Written by the indexing model from the issue text.

Description

What goes wrong

Any service that asks for its logger more than once ends up printing every log line many times over, and the number grows the longer the service runs. A line printed once ends up in the log hundreds of times, which does not just make the output noisy: it makes the log unusable as a record of what happened, because a message repeated 259 times looks exactly like work that was done 259 times.

We hit this on a Chiron data server. Its background scan runs every 30 seconds and asked for its logger at the top of each pass. Two hours in, a preprocessing request that had been handled exactly once appeared 259 times in docker logs, against roughly 264 scans elapsed at the moment it ran. We only established that the work had happened once by counting the status files it had written.

Cause

bioengine/utils create_logger takes a process-wide logger from logging.getLogger(name) and then calls addHandler unconditionally:

def create_logger(name, level=logging.INFO, log_file=None):
    logger = logging.getLogger(name)
    logger.setLevel(level)
    logger.propagate = False

    console_handler = logging.StreamHandler()
    console_handler.setFormatter(logging.Formatter(...))
    logger.addHandler(console_handler)          # <-- every call

    if log_file:
        file_handler = logging.FileHandler(log_file)   # <-- and another
        logger.addHandler(file_handler)

    return logger

logging.getLogger(name) returns the same object for the same name, so the handler list grows by one (or two, with log_file) per call and nothing ever removes the previous one. A caller that creates its logger inside a loop body, or in a function called repeatedly, has no way to tell from the signature that this is what happens: the name says create, and it reads like an idempotent accessor.

With log_file set this also leaks a file descriptor per call.

Reproducing

Against bioengine 0.16.1, inside the worker image:

import logging
from bioengine.utils import create_logger

for _ in range(5):
    create_logger("HandlerLeakProbe")

print(len(logging.getLogger("HandlerLeakProbe").handlers))

Prints 5. Expected 1.

Suggested fix

Make create_logger idempotent for a given name and destination. Either return early when the logger already has handlers, or clear the handler list before attaching, or tag the handlers this function attaches and replace only those. Returning early is the smallest change and matches what callers evidently assume:

logger = logging.getLogger(name)
if getattr(logger, "_bioengine_configured", False):
    logger.setLevel(level)
    return logger
...
logger._bioengine_configured = True

A caller that genuinely wants a second destination can then be given an explicit way to add one, rather than getting it as a side effect of asking for the logger again.

Workaround

We cache the logger on our side and call create_logger once per process. That keeps our data server correct on any bioengine version, but every other service calling it in a loop has the same problem, so it seems worth fixing upstream.

Environment
  • bioengine 0.16.1, running as the BioEngine Datasets proxy server inside ghcr.io/aicell-lab/chiron-tabula:0.7.8
  • Python 3.11
Dominant language
Python
Stars
9
Forks
4
Avg merge
2h 10m
Merged PRs (30d)
40

Getting set up

  • Ships a Dockerfile or Docker Compose file
  • No pull request template
  • No contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from aicell-lab/bioengine

All issues in aicell-lab/bioengine

Similar issues

More Python issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.