fix(multiprocess): avoid double-building child metric names (#1035) by hazel-shen · Pull Request #1146 · prometheus/client_python

Description

Fixes #1035

Child metrics created via labels() could rebuild their full names when using metric subclasses with default namespace/subsystem/unit, especially in multiprocess mode, producing double-prefixed names.

Problem

With a subclass like:

class CustomCounter(Counter):
    def __init__(self, name, documentation, labelnames=(),
                 namespace='mydefaultnamespace',
                 subsystem='mydefaultsubsystem',
                 unit='defaultunit',
                 **kwargs):
        super().__init__(name, documentation, labelnames=labelnames,
                         namespace=namespace, subsystem=subsystem,
                         unit=unit, **kwargs)

and an instance:

c = CustomCounter(
    name='m',
    documentation='help',
    labelnames=('status', 'method'),
    namespace='mynamespace',
    subsystem='mysubsystem',
    unit='seconds',
)

multiprocess mode could export child metrics with double-prefixed names like:

mydefaultnamespace_mydefaultsubsystem_mynamespace_mysubsystem_m_seconds_total

instead of the correct:

mynamespace_mysubsystem_m_seconds_total

This happened because the already-built full name was passed to the child metric constructor, which then applied namespace/subsystem/unit transformations a second time.

Solution

  • In MetricWrapperBase.__init__, store original constructor arguments:

    self._original_name = name
    self._namespace = namespace
    self._subsystem = subsystem
    self._unit = unit
  • In MetricWrapperBase.labels(), use stored values when creating child metrics:

    original_name = getattr(self, '_original_name', self._name)
    namespace = getattr(self, '_namespace', '')
    subsystem = getattr(self, '_subsystem', '')
    unit = getattr(self, '_unit', '')
    
    self._metrics[labelvalues] = self.__class__(
        original_name,
        namespace=namespace,
        subsystem=subsystem,
        unit=unit,
        ...
    )

This ensures the full name is built exactly once, making single-process and multiprocess exports consistent.

Testing

  • All existing tests pass
  • Verified correct name construction in multiprocess mode
  • Verified subclass compatibility (child metrics preserve parent context)
  • Backward compatible via getattr() fallbacks

Breaking Changes

None (internal-only change to MetricWrapperBase)