ecs-logging-python
ecs-logging-python copied to clipboard
Cache and expose extractors
This PR caches and exposes the extractors dict for two purposes:
- Loads the dict only during initialization, instead of on every logger invocation.
- Provides a clearer and more scalable way to subclass
StdlibFormatter.
This is an adapted use case from the library we use for several services in our company that adds information about errors in HTTP requests :
from typing import TYPE_CHECKING
from functools import lru_cache
from ecs_logging import StdlibFormatter
import httpx
if TYPE_CHECKING:
from logging import LogRecord
httpx._utils.SENSITIVE_HEADERS.update({
'apikey', 'api-key', 'x-api-key', 'x-auth-token',
})
class EcsFormatter(StdlibFormatter):
@property
@lru_cache
def extractors(self):
extractors = super().extractors
extractors['http'] = self._extract_httpx_response
return extractors
def _extract_httpx_response(self, record: 'LogRecord') -> dict | None:
if not record.exc_info or not isinstance(record.exc_info[1], httpx.HTTPStatusError):
return None
resp = record.exc_info[1].response
redact = lambda x: dict(httpx._utils.obfuscate_sensitive_headers(x.multi_items()))
if (url := resp.request.url).password:
url = str(url).replace(f':{url.password}@', ':[secure]@')
return {
'version': resp.http_version,
'request': {
'method': resp.request.method,
'url': str(url),
'headers': redact(resp.request.headers),
'body.content': resp.request.content,
},
'response': {
'status_code': resp.status_code,
'headers': redact(resp.headers),
'body.content': resp.content,
}
}
Additionally, this PR also:
- Removes unnecessary try/catch on
Literalimport.Literalwas added in Python 3.8. - Updates
pre-commit's config avoiding two warnings.