Skip to content

Support @classmethod in Tracer.capture_method() #2011

@tibbe

Description

@tibbe

Use case

Sometimes you want to trace a @classmethod. In my case I have an ORM-like (https://en.wikipedia.org/wiki/Object%E2%80%93relational_mapping) library that provides some get, put, query, etc. class methods in a base class. I want tracing for all subclasses. Something like:

class Table:
    @classmethod
    @tracer.capture_method  # Unclear which should nest innermost.
    def get(cls, id: str):
        db.get(table_name=cls.table_name, id=id)

    table_name: str

class Users:
    table_name = "users"

user = Users.get(123)

This doesn't work at the moment (fails with an error about get not having __module__ defined IIRC, at least if you nest @classmethod innermost).

Solution/User Experience

Either extend capture_method to cover @classmethods or add another decorator.

Ideally, as far as I'm concerned, we actually want to use the actual class (cls) in the sub-segment name. So in the example above "## user_module.Users.get". This provides more information in the span name than using Table. You could extend the same argument to the existing behavior of capture_method for instance methods. Although changing it there would be a breaking change (although probably benign).

Alternative solutions

Currently I've written my own, limited workaround:

_P = ParamSpec('_P')
_R_co = TypeVar('_R_co', covariant=True)


_Class = TypeVar('_Class', bound=type)

# Tracer.capture_method does not work for classmethods, so we need our own
# version.
def decorate_sync_classmethod(
    func: Callable[Concatenate[_Class, _P], _R_co],
) -> Callable[Concatenate[_Class, _P], _R_co]:
    '''Equivalent to `@Tracer.capture_method`, but for classmethods.

    Only works on sync (i.e. not async) classmethods.
    '''
    @functools.wraps(func)
    def wrapper(cls: _Class, *args: _P.args, **kwargs: _P.kwargs) -> _R_co:
        method_name = f'{cls.__module__}.{cls.__qualname__}.{func.__name__}'
        with _tracer.provider.in_subsegment(name=f"## {method_name}"):
            return func(cls, *args, **kwargs)
    return wrapper

Acknowledgment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    Status

    Closed

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions