Skip to content

Thread name CallsiteParameterAdder parameter doesn't work in async methods #710

@hynek

Description

@hynek

This is based on @PrieJos's helpful analysis in #693 (comment)

In async log methods, callsite collection happens in the async logger thread which makes its thread-related information worthless:

# /// script
# dependencies = [
#   "structlog",
# ]
# ///

import asyncio
import logging
import sys

import structlog

sl = structlog.get_logger()
logger = logging.getLogger()

logging.basicConfig(
    stream=sys.stdout,
    format=(
        "%(process)d %(processName)s %(module)s"
        " %(funcName)s:%(lineno)d %(threadName)s %(taskName)s"
        " %(message)s"
    ),
    datefmt=r"%Y-%m-%dT%H:%M:%S",
    level=logging.INFO,
    encoding="utf-8",
)

structlog.configure(
    processors=[
        structlog.processors.CallsiteParameterAdder(
            [
                structlog.processors.CallsiteParameter.PROCESS,
                structlog.processors.CallsiteParameter.PROCESS_NAME,
                structlog.processors.CallsiteParameter.MODULE,
                structlog.processors.CallsiteParameter.FILENAME,
                structlog.processors.CallsiteParameter.FUNC_NAME,
                structlog.processors.CallsiteParameter.LINENO,
                structlog.processors.CallsiteParameter.THREAD_NAME,
            ]
        ),
        structlog.processors.LogfmtRenderer(),
    ],
)


async def afunc():
    await sl.ainfo("async")
    logger.info("stdlib async")

def func():
    sl.info("sync")
    logger.info("stdlib sync")


asyncio.run(afunc())
func()

Notably, thread name is wrong and so would be a task name as shown in #698:

event=async process=55347 process_name=n/a module=t filename=t.py func_name=afunc lineno=47 thread_name=asyncio_0
55347 MainProcess t afunc:48 MainThread Task-1 stdlib async
event=sync process=55347 process_name=n/a module=t filename=t.py func_name=func lineno=51 thread_name=MainThread
55347 MainProcess t func:52 MainThread None stdlib sync

Not sure how to deal with this most elegantly – I feel like we'll have to pass thread/task objects into the event_dict or something?

I think the process_name is in fact correct, since logging just sets it unconditionally and we get n/a in sync code too. Happy to be corrected, though.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions