Skip to content

Cache and expose extractors #155

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.0.1
rev: v5.0.0
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please drop these changes, we can do them in another PR

hooks:
- id: check-case-conflict
- id: check-executables-have-shebangs
- id: check-merge-conflict
- repo: https://github.com/elastic/apm-pipeline-library
rev: current
rev: v1.19.13
hooks:
- id: check-bash-syntax
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v0.910
rev: v1.15.0
hooks:
- id: mypy
args:
Expand All @@ -21,12 +21,12 @@ repos:
--implicit-reexport,
]
- repo: https://github.com/psf/black
rev: 22.12.0
rev: 25.1.0
hooks:
- id: black
language_version: python3
- repo: https://github.com/pycqa/flake8
rev: 3.9.2
rev: 7.1.1
hooks:
- id: flake8
exclude: tests|conftest.py|setup.py
48 changes: 30 additions & 18 deletions ecs_logging/_stdlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,7 @@
merge_dicts,
)

from typing import Any, Callable, Dict, Optional, Sequence, Union

try:
from typing import Literal # type: ignore
except ImportError:
from typing_extensions import Literal # type: ignore
from typing import Any, Callable, Dict, Optional, Sequence, Union, Literal


# Load the attributes of a LogRecord so if some are
Expand Down Expand Up @@ -164,21 +159,25 @@ def format(self, record: logging.LogRecord) -> str:
result = self.format_to_ecs(record)
return json_dumps(result)

def format_to_ecs(self, record: logging.LogRecord) -> Dict[str, Any]:
"""Function that can be overridden to add additional fields to
(or remove fields from) the JSON before being dumped into a string.
@property
@lru_cache
def extractors(self) -> Dict[str, Callable[[logging.LogRecord], Any]]:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not very inclined to add a new public property, no objection in creating a private _extractors though

"""Property that can be overridden to add additional field
extractors to (or remove fields from) the JSON before being
dumped into a string.

.. code-block: python

class MyFormatter(StdlibFormatter):
def format_to_ecs(self, record):
result = super().format_to_ecs(record)
del result["log"]["original"] # remove unwanted field(s)
result["my_field"] = "my_value" # add custom field
return result
@property
@lru_cache
def extractors(self):
extractors = super().extractors
del extractors["log.original"] # remove unwanted field(s)
extractors["my_field"] = self._my_extractor # add custom field
return extractors
"""

extractors: Dict[str, Callable[[logging.LogRecord], Any]] = {
return {
"@timestamp": self._record_timestamp,
"ecs.version": lambda _: ECS_VERSION,
"log.level": lambda r: (r.levelname.lower() if r.levelname else None),
Expand All @@ -196,11 +195,24 @@ def format_to_ecs(self, record):
"error.stack_trace": self._record_error_stack_trace,
}

def format_to_ecs(self, record: logging.LogRecord) -> Dict[str, Any]:
"""Function that can be overridden to add additional fields to
(or remove fields from) the JSON before being dumped into a string.

.. code-block: python

class MyFormatter(StdlibFormatter):
def format_to_ecs(self, record):
result = super().format_to_ecs(record)
del result["log"]["original"] # remove unwanted field(s)
result["my_field"] = "my_value" # add custom field
return result
"""
result: Dict[str, Any] = {}
for field in set(extractors.keys()).difference(self._exclude_fields):
for field in set(self.extractors.keys()).difference(self._exclude_fields):
if self._is_field_excluded(field):
continue
value = extractors[field](record)
value = self.extractors[field](record)
if value is not None:
# special case ecs.version that should not be de-dotted
if field == "ecs.version":
Expand Down