Repository: elastic/apm-agent-python

Issue:
AttributeError: 'Config' object has no attribute 'start_stop_order'
# Error
We started seeing this traceback via Sentry coming from the elasticapm agent with the Django integration after upgrading from 6.1.3 to 6.2.0:

```
AttributeError: 'Config' object has no attribute 'start_stop_order'
  File "elasticapm/instrumentation/packages/dbapi2.py", line 276, in call_if_sampling
    return self.call(module, method, wrapped, instance, args, kwargs)
  File "elasticapm/instrumentation/packages/psycopg2.py", line 104, in call
    return PGConnectionProxy(wrapped(*args, **kwargs), destination_info=destination_info)
  File "elasticapm/traces.py", line 709, in __exit__
    span = transaction.end_span(self.skip_frames, duration=self.duration, outcome=outcome)
  File "elasticapm/traces.py", line 293, in end_span
    span.end(skip_frames=skip_frames, duration=duration)
  File "elasticapm/traces.py", line 484, in end
    tracer.queue_func(SPAN, self.to_dict())
  File "elasticapm/base.py", line 275, in queue
    self.start_threads()
  File "elasticapm/base.py", line 220, in start_threads
    for manager_type, manager in sorted(
  File "elasticapm/base.py", line 221, in <lambda>
    self._thread_managers.items(), key=lambda item: item[1].start_stop_order
  File "elasticapm/conf/__init__.py", line 709, in __getattr__
    return getattr(self._config, item)
```

# Environment

OS: Linux
Python version: 3.8.9
Framework and version [e.g. Django 2.1]: Django 3.1.10
APM Server version: 7.6
Agent version: 6.2.0

# Additional context

I think the bug was introduced in https://github.com/elastic/apm-agent-python/pull/1139/files.

The change sorts `self._thread_managers.items()` by the `start_stop_order` of each dict item.
`self._thread_managers['config']` is an instance of [`VersionedConfig`](https://github.com/elastic/apm-agent-python/blob/master/elasticapm/conf/__init__.py#L636), which inherits [`ThreadManager`](https://github.com/elastic/apm-agent-python/blob/c5fe64662f971f26deb200b4619860c363b3e483/elasticapm/utils/threading.py#L98) where this attribute is set. But the `__getattr__` method of `VersionedConfig` [is overriden](https://github.com/elastic/apm-agent-python/blob/master/elasticapm/conf/__init__.py#L708) to instead fetch the item from `VersionedConfig._config`, which is a [`Config`](https://github.com/elastic/apm-agent-python/blob/master/elasticapm/conf/__init__.py#L508) object and doesn't have the attribute `start_stop_order`.



Discussion:
Hey @simbs, great catch and great analysis of the problem! I'll get a fix shipped ASAP

Resolving patch:
diff --git a/elasticapm/conf/__init__.py b/elasticapm/conf/__init__.py
--- a/elasticapm/conf/__init__.py
+++ b/elasticapm/conf/__init__.py
@@ -647,6 +647,7 @@ class VersionedConfig(ThreadManager):
         "transport",
         "_update_thread",
         "pid",
+        "start_stop_order",
     )
 
     def __init__(self, config_object, version, transport=None):


Test patch:
diff --git a/tests/config/tests.py b/tests/config/tests.py
--- a/tests/config/tests.py
+++ b/tests/config/tests.py
@@ -428,3 +428,13 @@ def test_valid_values_validator():
     assert v("Bar", "foo") == "Bar"
     with pytest.raises(ConfigurationError):
         v("foobar", "foo")
+
+
+def test_versioned_config_attribute_access(elasticapm_client):
+    # see https://github.com/elastic/apm-agent-python/issues/1147
+    val = elasticapm_client.config.start_stop_order
+    assert isinstance(val, int)
+    # update config to ensure start_stop_order isn't read from the proxied Config object
+    elasticapm_client.config.update("2", capture_body=True)
+    val = elasticapm_client.config.start_stop_order
+    assert isinstance(val, int)