Skip to content

Commit 45e6e1c

Browse files
authored
Add auto_ecs_logging config (#1058)
* Implement auto_ecs_logging config and docs * Add a test for auto_ecs_logging * Changelog
1 parent 74fe1e8 commit 45e6e1c

File tree

6 files changed

+87
-0
lines changed

6 files changed

+87
-0
lines changed

CHANGELOG.asciidoc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ endif::[]
3838
===== Features
3939
4040
* Add global access to Client singleton object at `elasticapm.get_client()` {pull}1043[#1043]
41+
* Add `auto_ecs_logging` config option {pull}1058[#1058]
4142
4243
4344
[float]

docs/configuration.asciidoc

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,9 @@ file will automatically rotate.
200200
Note that setting <<config-log_level>> is required for this setting to do
201201
anything.
202202

203+
If https://github.com/elastic/ecs-logging-python[`ecs_logging`] is installed,
204+
the logs will automatically be formatted as ecs-compatible json.
205+
203206
[float]
204207
[[config-log_file_size]]
205208
==== `log_file_size`
@@ -215,6 +218,36 @@ The size of the log file, if <<config-log_file>> is set.
215218
The agent always keeps one backup file when rotating, so the max space that
216219
the log files will consume is twice the value of this setting.
217220

221+
[float]
222+
[[config-auto_ecs_logging]]
223+
==== `auto_ecs_logging`
224+
225+
[options="header"]
226+
|============
227+
| Environment | Django/Flask | Default
228+
| `ELASTIC_APM_AUTO_ECS_LOGGING` | `AUTO_ECS_LOGGING` | `False`
229+
|============
230+
231+
If https://github.com/elastic/ecs-logging-python[`ecs_logging`] is installed,
232+
setting this to `True` will cause the agent to automatically attempt to enable
233+
ecs-formatted logging.
234+
235+
For base `logging` from the standard library, the agent will get the root
236+
logger, find any attached handlers, and for each, set the formatter to
237+
`ecs_logging.StdlibFormatter()`.
238+
239+
If `structlog` is installed, the agent will override any configured processors
240+
with `ecs_logging.StructlogFormatter()`.
241+
242+
Note that this is a very blunt instrument that could have unintended side effects.
243+
If problems arise, please apply these formatters manually and leave this setting
244+
as `False`. See the
245+
https://www.elastic.co/guide/en/ecs-logging/python/current/installation.html[`ecs_logging` docs]
246+
for more information about using these formatters.
247+
248+
Also note that this setting does not facilitate shipping logs to Elasticsearch.
249+
We recommend https://www.elastic.co/beats/filebeat[Filebeat] for that purpose.
250+
218251
[float]
219252
[[other-options]]
220253
=== Other options

elasticapm/conf/__init__.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -367,9 +367,43 @@ def _log_level_callback(dict_key, old_value, new_value, config_instance):
367367
filehandler = logging.handlers.RotatingFileHandler(
368368
config_instance.log_file, maxBytes=config_instance.log_file_size, backupCount=1
369369
)
370+
try:
371+
import ecs_logging
372+
373+
filehandler.setFormatter(ecs_logging.StdlibFormatter())
374+
except ImportError:
375+
pass
370376
elasticapm_logger.addHandler(filehandler)
371377

372378

379+
def _auto_ecs_logging_callback(dict_key, old_value, new_value, config_instance):
380+
"""
381+
If ecs_logging is installed and auto_ecs_logging is set to True, we should
382+
set the ecs_logging.StdlibFormatter as the formatted for every handler in
383+
the root logger, and set the default processor for structlog to the
384+
ecs_logging.StructlogFormatter.
385+
"""
386+
if new_value:
387+
try:
388+
import ecs_logging
389+
except ImportError:
390+
return
391+
392+
# Stdlib
393+
root_logger = logging.getLogger()
394+
formatter = ecs_logging.StdlibFormatter()
395+
for handler in root_logger.handlers:
396+
handler.setFormatter(formatter)
397+
398+
# Structlog
399+
try:
400+
import structlog
401+
402+
structlog.configure(processors=[ecs_logging.StructlogFormatter()])
403+
except ImportError:
404+
pass
405+
406+
373407
class _ConfigBase(object):
374408
_NO_VALUE = object() # sentinel object
375409

@@ -582,6 +616,7 @@ class Config(_ConfigBase):
582616
)
583617
log_file = _ConfigValue("LOG_FILE", default="")
584618
log_file_size = _ConfigValue("LOG_FILE_SIZE", validators=[size_validator], type=int, default=50 * 1024 * 1024)
619+
auto_ecs_logging = _BoolConfigValue("AUTO_ECS_LOGGING", callbacks=[_auto_ecs_logging_callback], default=False)
585620

586621
@property
587622
def is_recording(self):

tests/fixtures.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,10 @@ def elasticapm_client_log_file(request):
202202
client_config.setdefault("cloud_provider", False)
203203
client_config.setdefault("log_level", "warning")
204204

205+
root_logger = logging.getLogger()
206+
handler = logging.StreamHandler()
207+
root_logger.addHandler(handler)
208+
205209
tmp = tempfile.NamedTemporaryFile(delete=False)
206210
tmp.close()
207211
client_config["log_file"] = tmp.name
@@ -217,6 +221,9 @@ def elasticapm_client_log_file(request):
217221
handler.close()
218222
os.unlink(tmp.name)
219223

224+
# Remove our streamhandler
225+
root_logger.removeHandler(handler)
226+
220227
# clear any execution context that might linger around
221228
sys.excepthook = original_exceptionhook
222229
execution_context.set_transaction(None)

tests/handlers/logging/logging_tests.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,9 @@
3434
import warnings
3535
from logging import LogRecord
3636

37+
import ecs_logging
3738
import pytest
39+
import structlog
3840

3941
from elasticapm.conf import Config
4042
from elasticapm.conf.constants import ERROR
@@ -394,3 +396,10 @@ def test_log_file(elasticapm_client_log_file):
394396
if isinstance(handler, logging.handlers.RotatingFileHandler):
395397
found = True
396398
assert found
399+
400+
401+
@pytest.mark.parametrize("elasticapm_client_log_file", [{"auto_ecs_logging": True}], indirect=True)
402+
def test_auto_ecs_logging(elasticapm_client_log_file):
403+
logger = logging.getLogger()
404+
assert isinstance(logger.handlers[0].formatter, ecs_logging.StdlibFormatter)
405+
assert isinstance(structlog.get_config()["processors"][-1], ecs_logging.StructlogFormatter)

tests/requirements/reqs-base.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ mock
4646
msgpack-python
4747
pep8
4848
pytz
49+
ecs_logging
50+
structlog
4951

5052

5153
pytest-asyncio==0.14.0 ; python_version >= '3.7'

0 commit comments

Comments
 (0)