From 926f72a9c4aa96c85345100a37f8418695cca596 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Ma=C5=9Blanka?= Date: Fri, 13 Dec 2024 19:24:44 +0100 Subject: [PATCH 01/11] fixes #76 --- HISTORY.rst | 11 +++++- docs/usage-gunicorn.rst | 4 +- docs/usage.rst | 34 +++++++++++++++- seqlog/__init__.py | 71 ++++++--------------------------- seqlog/feature_flags.py | 14 ++++--- seqlog/structured_logging.py | 76 +++++++++++++++++++++++++++--------- tests/stubs.py | 8 ++-- tests/test_configuration.py | 28 +++++++------ tests/test_dict_config.py | 68 -------------------------------- 9 files changed, 144 insertions(+), 170 deletions(-) delete mode 100644 tests/test_dict_config.py diff --git a/HISTORY.rst b/HISTORY.rst index d01524e..372db78 100755 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -2,8 +2,15 @@ History ======= -Current (0.4.0) - (unreleased yet) ------------------------------------ + +0.5.0 - (2024-12-13) +-------------------- + +* made the configuration a little bit more Pythonic +* deprecated `configure_from_dict` and `configure_from_file` + +0.4.0 - (2024-12-8) +-------------------- * You can enable and disable all of the feature flags at runtime * Added support for the `CLEF submission format `_. diff --git a/docs/usage-gunicorn.rst b/docs/usage-gunicorn.rst index 33e9f1b..21a6878 100644 --- a/docs/usage-gunicorn.rst +++ b/docs/usage-gunicorn.rst @@ -2,6 +2,7 @@ Usage (Gunicorn) ================ + Using seqlog with `Gunicorn ` involves some additional configuration because of the way Gunicorn uses ``fork`` to create new worker processes. A custom ``JSONEncoder`` is also used to handle objects that are not `JSON serializable`. @@ -118,6 +119,7 @@ A custom ``JSONEncoder`` is also used to handle objects that are not `JSON seria console: class: seqlog.structured_logging.ConsoleStructuredLogHandler formatter: standard + use_stdout: False seq: class: seqlog.structured_logging.SeqLogHandler @@ -148,4 +150,4 @@ A custom ``JSONEncoder`` is also used to handle objects that are not `JSON seria try: return json.JSONEncoder.default(self, obj) except: - return str(obj) \ No newline at end of file + return str(obj) diff --git a/docs/usage.rst b/docs/usage.rst index e602ceb..40219c9 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -2,6 +2,8 @@ Usage ===== +Recommended way is to use logging.config.dictConfig(). + Configure logging programmatically ---------------------------------- @@ -45,6 +47,9 @@ The formal definition of the configure function is as follows: Configure logging from a file ----------------------------- +.. deprecated:: 0.5.0 + Use logging.config.dictConfig() directly + Seqlog can also use a YAML-format file to describe the desired logging configuration. This file has the schema specified in Python's `logging.config `_ module. First, create your configuration file (e.g. ``/foo/bar/my_config.yml``): @@ -78,6 +83,7 @@ First, create your configuration file (e.g. ``/foo/bar/my_config.yml``): console: class: seqlog.structured_logging.ConsoleStructuredLogHandler formatter: seq + override_existing_logger: True # Log to Seq seq: @@ -112,6 +118,9 @@ Then, call ``seqlog.configure_from_file()``: Configuring logging from a dictionary ------------------------------------- +.. deprecated:: 0.5.0 + Use logging.config.dictConfig() directly + Seqlog can also use a dictionary to describe the desired logging configuration. This dictionary has the schema specified in Python's `logging.config `_ module. @@ -131,6 +140,29 @@ This dictionary has the schema specified in Python's `logging.config `_ format: +attached according to the `CLEF `_ format: * ``span_id`` - this will get removed and be replaced with ``@sp`` * ``trace_id`` - this will get removed and be replaced with ``@tr`` diff --git a/seqlog/__init__.py b/seqlog/__init__.py index 325f74a..ef9b7b4 100644 --- a/seqlog/__init__.py +++ b/seqlog/__init__.py @@ -6,7 +6,7 @@ import yaml from seqlog.feature_flags import FeatureFlag, configure_feature -from seqlog.structured_logging import StructuredLogger, StructuredRootLogger +from seqlog.structured_logging import StructuredLogger, StructuredRootLogger, _override_root_logger from seqlog.structured_logging import SeqLogHandler, ConsoleStructuredLogHandler from seqlog.structured_logging import get_global_log_properties as _get_global_log_properties from seqlog.structured_logging import set_global_log_properties as _set_global_log_properties @@ -16,79 +16,39 @@ __author__ = 'Adam Friedman' __email__ = 'tintoy@tintoy.io' -__version__ = '0.4.0a1' +__version__ = '0.5.0a1' -def configure_from_file(file_name, override_root_logger=True, support_extra_properties=False, support_stack_info=False, ignore_seq_submission_errors=False, - use_clef=False): +def configure_from_file(file_name): """ Configure Seq logging using YAML-format configuration file. - Uses `logging.config.dictConfig()`. + .. deprecated: 0.5.0 + Use logging.config.fileConfig(). Also, let it be known that Python uses different format natively (your file cannot be YAML) - :param file_name: The name of the configuration file to use. - :type file_name: str - :param override_root_logger: Override the root logger to use a Seq-specific implementation? (default: True) - :type override_root_logger: bool - :param support_extra_properties: Support passing of additional properties to log via the `extra` argument? - :type support_extra_properties: bool - :param support_stack_info: Support attaching of stack-trace information (if available) to log records? - :type support_stack_info: bool - :param ignore_seq_submission_errors: Ignore errors encountered while sending log records to Seq? - :type ignore_seq_submission_errors: bool - :param use_clef: use the newer submission format CLEF - :type use_clef: bool + Uses `logging.config.dictConfig()`. """ - configure_feature(FeatureFlag.EXTRA_PROPERTIES, support_extra_properties) - configure_feature(FeatureFlag.STACK_INFO, support_stack_info) - configure_feature(FeatureFlag.IGNORE_SEQ_SUBMISSION_ERRORS, ignore_seq_submission_errors) - configure_feature(FeatureFlag.USE_CLEF, use_clef) - with open(file_name) as config_file: config = yaml.load(config_file, Loader=yaml.SafeLoader) - configure_from_dict(config, override_root_logger, True) + configure_from_dict(config) -def configure_from_dict(config, override_root_logger=True, use_structured_logger=True, support_extra_properties=None, - support_stack_info=None, ignore_seq_submission_errors=None, - use_clef=None): +def configure_from_dict(config): """ Configure Seq logging using a dictionary. Uses `logging.config.dictConfig()`. + .. deprecated: 0.5.0 + Use logging.config.dictConfig() directly. + Note that if you provide None to any of the default arguments, it just won't get changed (ie. it will stay the same). :param config: A dict containing the configuration. :type config: dict - :param override_root_logger: Override the root logger to use a Seq-specific implementation? (default: True) - :type override_root_logger: bool - :param use_structured_logger: Configure the default logger class to be StructuredLogger, which support named format arguments? (default: True) - :type use_structured_logger: bool - :param support_extra_properties: Support passing of additional properties to log via the `extra` argument? - :type support_extra_properties: bool - :param support_stack_info: Support attaching of stack-trace information (if available) to log records? - :type support_stack_info: bool - :param ignore_seq_submission_errors: Ignore errors encountered while sending log records to Seq? - :type ignore_seq_submission_errors: bool - :param use_clef: use the newer submission format CLEF - :type use_clef: bool """ - - configure_feature(FeatureFlag.EXTRA_PROPERTIES, support_extra_properties) - configure_feature(FeatureFlag.STACK_INFO, support_stack_info) - configure_feature(FeatureFlag.IGNORE_SEQ_SUBMISSION_ERRORS, ignore_seq_submission_errors) - configure_feature(FeatureFlag.USE_CLEF, use_clef) - - if override_root_logger: - _override_root_logger() - - # Must use StructuredLogger to support named format argments. - if use_structured_logger: - logging.setLoggerClass(StructuredLogger) - logging.config.dictConfig(config) @@ -237,12 +197,3 @@ def clear_global_log_properties(): _clear_global_log_properties() - -def _override_root_logger(): - """ - Override the root logger with a `StructuredRootLogger`. - """ - - logging.root = StructuredRootLogger(logging.WARNING) - logging.Logger.root = logging.root - logging.Logger.manager = logging.Manager(logging.Logger.root) diff --git a/seqlog/feature_flags.py b/seqlog/feature_flags.py index e07837f..aa8526f 100644 --- a/seqlog/feature_flags.py +++ b/seqlog/feature_flags.py @@ -17,11 +17,12 @@ class FeatureFlag(Enum): USE_CLEF = 4 #: Use more modern API to submit log entries +# Here None (despite for if purposes being False) carries additional meaning - that this entry was not yet configured _features = { - FeatureFlag.EXTRA_PROPERTIES: False, - FeatureFlag.STACK_INFO: False, - FeatureFlag.IGNORE_SEQ_SUBMISSION_ERRORS: False, - FeatureFlag.USE_CLEF: False + FeatureFlag.EXTRA_PROPERTIES: None, + FeatureFlag.STACK_INFO: None, + FeatureFlag.IGNORE_SEQ_SUBMISSION_ERRORS: None, + FeatureFlag.USE_CLEF: None } @@ -60,14 +61,17 @@ def disable_feature(feature: FeatureFlag): configure_feature(feature, False) -def configure_feature(feature: FeatureFlag, enable: tp.Optional[bool]): +def configure_feature(feature: FeatureFlag, enable: tp.Optional[bool], if_not_yet_configured: bool = False): """ Enable or disable a feature. :param feature: A `FeatureFlag` value representing the feature to configure. If you pass None, it won't get changed. :type feature: FeatureFlag :param enable: `True`, to enable the feature; `False` to disable it. + :param if_not_yet_configured: configure only if this has not yet been configured """ if enable is None: return + if not if_not_yet_configured and _features[feature] is None: + return _features[feature] = enable diff --git a/seqlog/structured_logging.py b/seqlog/structured_logging.py index c6596ac..4a2e779 100644 --- a/seqlog/structured_logging.py +++ b/seqlog/structured_logging.py @@ -17,7 +17,7 @@ import requests from seqlog.consumer import QueueConsumer -from seqlog.feature_flags import FeatureFlag, is_feature_enabled +from seqlog.feature_flags import FeatureFlag, is_feature_enabled, configure_feature # Well-known keyword arguments used by the logging system. _well_known_logger_kwargs = {"extra", "exc_info", "func", "sinfo"} @@ -165,19 +165,55 @@ def getMessage(self): return self.msg -class StructuredLogger(logging.Logger): +_root_logger_overrided = False + + +def _override_root_logger(): """ - Custom (dummy) logger that understands named log arguments. + Override the root logger with a `StructuredRootLogger`. """ + global _root_logger_overrided + if _root_logger_overrided: + return + logging.root = StructuredRootLogger(logging.WARNING) + logging.Logger.root = logging.root + logging.Logger.manager = logging.Manager(logging.Logger.root) + _root_logger_overrided = True - def __init__(self, name, level=logging.NOTSET): - """ - Create a new StructuredLogger - :param name: The logger name. - :param level: The logger minimum level (severity). - """ - super().__init__(name, level) +class BaseStructuredLogHandler(logging.Handler): + """ + Base structured logger to set up all of the arguments previously required. + + :param override_root_logger: whether to override the root logger, default is True + :param support_extra_properties: logger will support named arguments instead of passing them via extra + :param stack_info: attach stack info + :param ignore_seq_submission_errors: whether to ignore submission errors + :param use_clef: whether to use CLEF + """ + def __init__(self, *args, level=logging.NOTSET, override_root_logger=True, **kwargs): + logging.Handler.__init__(self, level) + + if override_root_logger: + _override_root_logger() + + if kwargs.pop('use_structured_logger', None): + logging.setLoggerClass(StructuredLogger) + + if kwargs.pop('support_extra_properties', None): + configure_feature(FeatureFlag.EXTRA_PROPERTIES, True, if_not_yet_configured=True) + if kwargs.pop('stack_info', None): + configure_feature(FeatureFlag.STACK_INFO, True, if_not_yet_configured=True) + if kwargs.pop('ignore_seq_submission_errors', None): + configure_feature(FeatureFlag.IGNORE_SEQ_SUBMISSION_ERRORS, True, if_not_yet_configured=True) + if kwargs.pop('use_clef', None): + configure_feature(FeatureFlag.USE_CLEF, True, if_not_yet_configured=True) + + +class StructuredLogger(logging.Logger): + """ + Custom (dummy) logger that understands named log arguments. + """ @property def _support_extra_properties(self): @@ -310,16 +346,19 @@ def makeRecord(self, name, level, fn, lno, msg, args, exc_info, func=None, extra return super().makeRecord(name, level, fn, lno, msg, args, exc_info, func, extra, sinfo) -class ConsoleStructuredLogHandler(logging.Handler): - def __init__(self): - super().__init__() +class ConsoleStructuredLogHandler(BaseStructuredLogHandler): + + def __init__(self, *args, use_stdout=True, **kwargs): + super().__init__(*args, **kwargs) + self.use_stdout = use_stdout def emit(self, record): msg = self.format(record) - print(msg) + out = sys.stdout if self.use_stdout else sys.stderr + out.write(msg) if hasattr(record, 'kwargs'): - print("\tLog entry properties: {}".format(repr(record.kwargs))) + out.write("\tLog entry properties: {}\n".format(repr(record.kwargs))) def best_effort_json_encode(arg): @@ -343,12 +382,13 @@ def best_effort_json_encode(arg): return arg -class SeqLogHandler(logging.Handler): +class SeqLogHandler(BaseStructuredLogHandler): """ Log handler that posts to Seq. """ - def __init__(self, server_url, api_key=None, batch_size=10, auto_flush_timeout=None, json_encoder_class=None): + def __init__(self, server_url, api_key=None, batch_size=10, auto_flush_timeout=None, json_encoder_class=None, + **kwargs): """ Create a new `SeqLogHandler`. @@ -360,7 +400,7 @@ def __init__(self, server_url, api_key=None, batch_size=10, auto_flush_timeout=N :param json_encoder_class: The custom JSON encoder class (or fully-qualified class name), if any, to use. """ - super().__init__() + super().__init__(**kwargs) self.base_server_url = server_url if not self.base_server_url.endswith("/"): diff --git a/tests/stubs.py b/tests/stubs.py index a223958..28681b4 100644 --- a/tests/stubs.py +++ b/tests/stubs.py @@ -1,9 +1,11 @@ import logging +from seqlog.structured_logging import BaseStructuredLogHandler -class StubStructuredLogHandler(logging.Handler): - def __init__(self): - super().__init__() + +class StubStructuredLogHandler(BaseStructuredLogHandler): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) self.records = [] self.messages = [] diff --git a/tests/test_configuration.py b/tests/test_configuration.py index 7200ead..8dcb8e7 100644 --- a/tests/test_configuration.py +++ b/tests/test_configuration.py @@ -7,13 +7,16 @@ Tests for `seqlog.config_from_*` module. """ +import logging.config import os import yaml -from seqlog import configure_from_file, configure_from_dict +from tests.test_structured_logger import create_logger -CFG_CONTENT = """# This is the Python logging schema version (currently, only the value 1 is supported here). +CFG_CONTENT = """ +# This is the Python logging schema version (currently, only the value 1 is supported here). +--- version: 1 # Configure logging from scratch. @@ -24,29 +27,30 @@ level: 'DEBUG' handlers: - console - - seq handlers: console: class: seqlog.structured_logging.ConsoleStructuredLogHandler formatter: standard - seq: - class: seqlog.structured_logging.SeqLogHandler - formatter: standard - server_url: 'http://localhost' + override_root_logger: True + use_structured_logging: True formatters: standard: format: '[%(levelname)s] %(asctime)s %(name)s: %(message)s' """ + class TestConfiguration(object): def test_valid_config(self): + try: - with open('test', 'w', encoding='utf-8') as w_out: + with open('test.yaml', 'w', encoding='utf-8') as w_out: w_out.write(CFG_CONTENT) - configure_from_file('test') - with open('test', 'r', encoding='utf-8') as r_in: + with open('test.yaml', 'r', encoding='utf-8') as r_in: dct = yaml.load(r_in, Loader=yaml.Loader) - configure_from_dict(dct) + logging.config.dictConfig(dct) finally: - os.unlink('test') + os.unlink('test.yaml') + logger, handler = create_logger() + logger.warning('This is a {message}', message='message') + assert handler.records[0].getMessage() == 'This is a message' diff --git a/tests/test_dict_config.py b/tests/test_dict_config.py deleted file mode 100644 index 1455172..0000000 --- a/tests/test_dict_config.py +++ /dev/null @@ -1,68 +0,0 @@ -from logging import LogRecord -import seqlog -import tests.stubs -from seqlog.structured_logging import StructuredLogger - - -class DictConfigLogger(StructuredLogger): - def __init__(self, name, level=seqlog.logging.NOTSET): - super().__init__(name, level) - - def callHandlers(self, record: LogRecord) -> None: - super().callHandlers(record) - popped = False - for handler in self.handlers: - if isinstance(handler, tests.stubs.StubStructuredLogHandler): - handler.pop_record() - popped = True - if not popped: - raise Exception("Could not pop record from handler! No handler instance found for class 'StubStructuredLogHandler'.!") - - -TEST_CONFIG = { - "version": 1, - "loggers": { - "test_logger": { - "level": seqlog.logging.INFO, - "handlers": ['test_handler'], - "propagate": False, - # "parent": "root", - # "class": TestDictConfigLogger - } - }, - "handlers": { - "test_handler": { - "class": 'tests.stubs.StubStructuredLogHandler', - "formatter": "test_formatter" - } - }, - "formatters": { - "test_formatter": { - "style": '{', - "validate": True, - "format": "[{levelname}] {asctime}: {name} (<{module}:{funcName}> {filename}:{lineno}) {msg}" - } - } -} - - -class TestDictConfig(): - def test_logger_with_dict_config(self): - global TEST_CONFIG - test_levels = { - "critical": seqlog.logging.CRITICAL, - "error": seqlog.logging.ERROR, - "warning": seqlog.logging.WARNING, - "info": seqlog.logging.INFO, - "debug": seqlog.logging.DEBUG, - "notset": seqlog.logging.NOTSET, - } - seqlog.logging.setLoggerClass(DictConfigLogger) - - for level in test_levels.keys(): - TEST_CONFIG['loggers']['test_logger']['level'] = test_levels.get(level) - seqlog.configure_from_dict(TEST_CONFIG) - - for log_level in test_levels: - test_logger = seqlog.logging.getLogger('test_logger') - test_logger.log(msg=f"This is a {log_level} log sent with {level} set.", level=test_levels.get(level)) From 34cef6c8a89536dd736e0761319ab4b137f6b685 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Ma=C5=9Blanka?= Date: Fri, 13 Dec 2024 19:30:40 +0100 Subject: [PATCH 02/11] a doc update --- HISTORY.rst | 1 + setup.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/HISTORY.rst b/HISTORY.rst index 372db78..757beec 100755 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -8,6 +8,7 @@ History * made the configuration a little bit more Pythonic * deprecated `configure_from_dict` and `configure_from_file` +* you can choose whether ConsoleStructuredLogHandler will log to stdout or to stderr. 0.4.0 - (2024-12-8) -------------------- diff --git a/setup.py b/setup.py index b29006a..cca080a 100644 --- a/setup.py +++ b/setup.py @@ -32,7 +32,7 @@ setup( name='seqlog', - version='0.4.0', + version='0.5.0', description="SeqLog enables logging from Python to Seq.", long_description=readme + '\n\n' + history, author="Adam Friedman", From 5f3496f439399c3937aac9187bbc2f062458bdeb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Ma=C5=9Blanka?= Date: Fri, 13 Dec 2024 19:50:00 +0100 Subject: [PATCH 03/11] undeprecate configure_from_file - YAML is useful --- HISTORY.rst | 2 +- docs/usage.rst | 4 +--- seqlog/__init__.py | 10 +++++----- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/HISTORY.rst b/HISTORY.rst index 757beec..02bdb7d 100755 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -7,7 +7,7 @@ History -------------------- * made the configuration a little bit more Pythonic -* deprecated `configure_from_dict` and `configure_from_file` +* deprecated `configure_from_dict` * you can choose whether ConsoleStructuredLogHandler will log to stdout or to stderr. 0.4.0 - (2024-12-8) diff --git a/docs/usage.rst b/docs/usage.rst index 40219c9..b709a42 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -47,9 +47,6 @@ The formal definition of the configure function is as follows: Configure logging from a file ----------------------------- -.. deprecated:: 0.5.0 - Use logging.config.dictConfig() directly - Seqlog can also use a YAML-format file to describe the desired logging configuration. This file has the schema specified in Python's `logging.config `_ module. First, create your configuration file (e.g. ``/foo/bar/my_config.yml``): @@ -142,6 +139,7 @@ This dictionary has the schema specified in Python's `logging.config Date: Fri, 13 Dec 2024 20:03:44 +0100 Subject: [PATCH 04/11] test configure_from_file - it is still useful --- tests/test_configuration.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/tests/test_configuration.py b/tests/test_configuration.py index 8dcb8e7..3291182 100644 --- a/tests/test_configuration.py +++ b/tests/test_configuration.py @@ -12,6 +12,7 @@ import yaml +from seqlog import configure_from_file from tests.test_structured_logger import create_logger CFG_CONTENT = """ @@ -40,12 +41,27 @@ class TestConfiguration(object): - def test_valid_config(self): + def test_valid_config_file(self): try: with open('test.yaml', 'w', encoding='utf-8') as w_out: w_out.write(CFG_CONTENT) + configure_from_file('test.yaml') + + with open('test.yaml', 'r', encoding='utf-8') as r_in: + dct = yaml.load(r_in, Loader=yaml.Loader) + logging.config.dictConfig(dct) + finally: + os.unlink('test.yaml') + logger, handler = create_logger() + logger.warning('This is a {message}', message='message') + assert handler.records[0].getMessage() == 'This is a message' + + def test_valid_config_dict(self): + try: + with open('test.yaml', 'w', encoding='utf-8') as w_out: + w_out.write(CFG_CONTENT) with open('test.yaml', 'r', encoding='utf-8') as r_in: dct = yaml.load(r_in, Loader=yaml.Loader) logging.config.dictConfig(dct) From 67e96cde7f0a7f80a1d962ccd5ef356630a8f2eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Ma=C5=9Blanka?= Date: Fri, 20 Dec 2024 15:32:46 +0100 Subject: [PATCH 05/11] fixes #76 --- docs/conf.py | 10 +++ docs/index.rst | 1 + docs/migration.rst | 22 +++++++ docs/usage.rst | 31 ++-------- seqlog/__init__.py | 35 +++++------ seqlog/feature_flags.py | 77 ----------------------- seqlog/structured_logging.py | 104 ++++++++++---------------------- tests/test_configuration.py | 4 +- tests/test_structured_logger.py | 3 +- 9 files changed, 88 insertions(+), 199 deletions(-) create mode 100644 docs/migration.rst delete mode 100644 seqlog/feature_flags.py diff --git a/docs/conf.py b/docs/conf.py index 6473847..d203694 100755 --- a/docs/conf.py +++ b/docs/conf.py @@ -54,6 +54,16 @@ # The master toctree document. master_doc = 'index' +autodoc_default_options = { + 'members': True, +} +autodoc_default_flags = [ + 'show-inheritance' +] +autodoc_typehints = "description" +autoclass_content = 'both' + + # General information about the project. project = u'SeqLog' copyright = u"2016, Adam Friedman" diff --git a/docs/index.rst b/docs/index.rst index 08a06b9..ca420ae 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -17,6 +17,7 @@ Contents: readme installation + migration usage usage-gunicorn Modules diff --git a/docs/migration.rst b/docs/migration.rst new file mode 100644 index 0000000..a0af02e --- /dev/null +++ b/docs/migration.rst @@ -0,0 +1,22 @@ +=============================== +Migration guide from 0.4 to 0.5 +=============================== + +First of all, the official way to configure Seq is via + +.. autofunction:: seqlog.configure_from_dict + +Alternatively you can call + +.. autofunction:: seqlog.configure_from_file + + +.. warning:: DO NOT call :code:`logging.config.fromDict` + +Then, FeatureFlags were completely obliterated and moved to SeqLogHandler's constructor. + +Then, the SeqLogHandler accepts way more arguments that you can define in this dict: + +.. autoclass:: seqlog.structured_logging.SeqLogHandler + :members: + diff --git a/docs/usage.rst b/docs/usage.rst index b709a42..c1c73ba 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -58,6 +58,8 @@ First, create your configuration file (e.g. ``/foo/bar/my_config.yml``): # Configure logging from scratch. disable_existing_loggers: True + override_root_logger: True + use_structured_logger: True # Configure the root logger to use Seq root: @@ -98,7 +100,7 @@ First, create your configuration file (e.g. ``/foo/bar/my_config.yml``): seq: style: '{' -Then, call ``seqlog.configure_from_file()``: +Then, call :func:`seqlog.configure_from_file`: .. code-block:: python @@ -115,11 +117,9 @@ Then, call ``seqlog.configure_from_file()``: Configuring logging from a dictionary ------------------------------------- -.. deprecated:: 0.5.0 - Use logging.config.dictConfig() directly - Seqlog can also use a dictionary to describe the desired logging configuration. This dictionary has the schema specified in Python's `logging.config `_ module. +With some extra options described in :func:`seqlog.configure_from_dict`. .. code-block:: python @@ -137,29 +137,6 @@ This dictionary has the schema specified in Python's `logging.config Date: Fri, 20 Dec 2024 15:36:46 +0100 Subject: [PATCH 06/11] minor fixes --- HISTORY.rst | 6 ++++-- seqlog/__init__.py | 9 ++------- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/HISTORY.rst b/HISTORY.rst index 02bdb7d..60b665b 100755 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -3,11 +3,13 @@ History ======= -0.5.0 - (2024-12-13) +0.5.0 - (2024-12-20) -------------------- * made the configuration a little bit more Pythonic -* deprecated `configure_from_dict` +* FeatureFlags were removed +* `configure_from_dict` is the official way to configure Seq +* `support_stack_info` was removed from `log_to_console` * you can choose whether ConsoleStructuredLogHandler will log to stdout or to stderr. 0.4.0 - (2024-12-8) diff --git a/seqlog/__init__.py b/seqlog/__init__.py index a938ccf..d606eab 100644 --- a/seqlog/__init__.py +++ b/seqlog/__init__.py @@ -112,7 +112,7 @@ def log_to_seq(server_url, api_key=None, level=logging.WARNING, return log_handlers[0] -def log_to_console(level=logging.WARNING, override_root_logger=False, support_extra_properties=False, support_stack_info=False, **kwargs): +def log_to_console(level=logging.WARNING, override_root_logger=False, support_extra_properties=False, **kwargs): """ Configure the logging system to send log entries to the console. @@ -124,13 +124,8 @@ def log_to_console(level=logging.WARNING, override_root_logger=False, support_ex when using the logging.XXX functions. :param support_extra_properties: Support passing of additional properties to log via the `extra` argument? :type support_extra_properties: bool - :param support_stack_info: Support attaching of stack-trace information (if available) to log records? - :type support_stack_info: bool """ - configure_feature(FeatureFlag.EXTRA_PROPERTIES, support_extra_properties) - configure_feature(FeatureFlag.STACK_INFO, support_stack_info) - logging.setLoggerClass(StructuredLogger) if override_root_logger: @@ -139,7 +134,7 @@ def log_to_console(level=logging.WARNING, override_root_logger=False, support_ex logging.basicConfig( style='{', handlers=[ - ConsoleStructuredLogHandler() + ConsoleStructuredLogHandler(support_extra_properties=support_extra_properties) ], level=level, **kwargs From f4ed444af18ea8e690aab37c8e41426e5fc75d1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Ma=C5=9Blanka?= Date: Fri, 20 Dec 2024 15:36:46 +0100 Subject: [PATCH 07/11] minor fixes --- seqlog/__init__.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/seqlog/__init__.py b/seqlog/__init__.py index d606eab..e4213b6 100644 --- a/seqlog/__init__.py +++ b/seqlog/__init__.py @@ -24,8 +24,6 @@ def configure_from_file(file_name): """ Configure Seq logging using YAML-format configuration file. Essentially loads the YAML, and invokes :func:`configure_from_dict`. - - Uses `logging.config.dictConfig()`. """ with open(file_name) as config_file: From 44af1dd3134be0241313d3ae27e7a2dd7c9ee134 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Ma=C5=9Blanka?= Date: Fri, 20 Dec 2024 16:24:08 +0100 Subject: [PATCH 08/11] removed feature flags --- docs/feature_flags.rst | 22 ---------------------- docs/index.rst | 1 - docs/migration.rst | 7 +++---- 3 files changed, 3 insertions(+), 27 deletions(-) delete mode 100644 docs/feature_flags.rst diff --git a/docs/feature_flags.rst b/docs/feature_flags.rst deleted file mode 100644 index 6969e5b..0000000 --- a/docs/feature_flags.rst +++ /dev/null @@ -1,22 +0,0 @@ -Feature flags -------------- - -You can change certain behaviour of the software at runtime, even after you configure Seq. You'll have to use: - -.. autofunction:: seqlog.feature_flags.enable_feature - -.. autofunction:: seqlog.feature_flags.disable_feature - -.. autofunction:: seqlog.feature_flags.configure_feature - -.. autoclass:: seqlog.feature_flags.FeatureFlag - :members: - -An example to disable ignoring of submission errors for Seq failures would look like this: - -.. code-block:: python - - from seqlog.feature_flags import disable_feature, FeatureFlag - - disable_feature(FeatureFlag.IGNORE_SEQ_SUBMISSION_ERRORS) - diff --git a/docs/index.rst b/docs/index.rst index ca420ae..5146620 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -21,7 +21,6 @@ Contents: usage usage-gunicorn Modules - feature_flags contributing authors history diff --git a/docs/migration.rst b/docs/migration.rst index a0af02e..419e0e8 100644 --- a/docs/migration.rst +++ b/docs/migration.rst @@ -10,13 +10,12 @@ Alternatively you can call .. autofunction:: seqlog.configure_from_file - -.. warning:: DO NOT call :code:`logging.config.fromDict` +.. warning:: DO NOT call :code:`logging.config.fromDict` directly. Then, FeatureFlags were completely obliterated and moved to SeqLogHandler's constructor. -Then, the SeqLogHandler accepts way more arguments that you can define in this dict: +Then, the SeqLogHandler accepts way more arguments, that you can define in the dictionary supporting the configuration: .. autoclass:: seqlog.structured_logging.SeqLogHandler - :members: + From 1cceecf93e8d967beb0587caf19ae69dedf949ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Ma=C5=9Blanka?= Date: Fri, 20 Dec 2024 16:28:27 +0100 Subject: [PATCH 09/11] some extra notes --- docs/migration.rst | 8 ++------ docs/usage.rst | 10 +++++++++- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/docs/migration.rst b/docs/migration.rst index 419e0e8..31579a5 100644 --- a/docs/migration.rst +++ b/docs/migration.rst @@ -2,13 +2,9 @@ Migration guide from 0.4 to 0.5 =============================== -First of all, the official way to configure Seq is via +First of all, the official way to configure Seq is via :func:`seqlog.configure_from_dict` -.. autofunction:: seqlog.configure_from_dict - -Alternatively you can call - -.. autofunction:: seqlog.configure_from_file +Alternatively you can call :func:`seqlog.configure_from_file`. .. warning:: DO NOT call :code:`logging.config.fromDict` directly. diff --git a/docs/usage.rst b/docs/usage.rst index c1c73ba..03c8117 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -2,7 +2,15 @@ Usage ===== -Recommended way is to use logging.config.dictConfig(). +Recommended way is to use + +.. autofunction:: seqlog.configure_from_dict + +or + +.. autofunction:: seqlog.configure_from_file + +This way you can leverage the Python logging configuration syntax. Configure logging programmatically ---------------------------------- From 6fe02b57d71b95b629b4bf9c9385653781dc5b19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Ma=C5=9Blanka?= Date: Fri, 20 Dec 2024 16:33:40 +0100 Subject: [PATCH 10/11] no auto members in docs --- docs/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/conf.py b/docs/conf.py index d203694..f5e0d21 100755 --- a/docs/conf.py +++ b/docs/conf.py @@ -55,7 +55,7 @@ master_doc = 'index' autodoc_default_options = { - 'members': True, + } autodoc_default_flags = [ 'show-inheritance' From 63a54dc3ba2ddf292acd64232827ac14db73f5e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Ma=C5=9Blanka?= Date: Fri, 20 Dec 2024 16:34:41 +0100 Subject: [PATCH 11/11] spelling --- seqlog/structured_logging.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/seqlog/structured_logging.py b/seqlog/structured_logging.py index 23e3663..e532b98 100644 --- a/seqlog/structured_logging.py +++ b/seqlog/structured_logging.py @@ -367,7 +367,7 @@ def __init__(self, server_url, api_key=None, batch_size=10, auto_flush_timeout=N :param use_clef: whether to use the CLEF format :param ignore_seq_submission_errors: whether to ignore failures to send logs to Seq :param support_stack_info: whether to recognize stack_info as an alternative to exc_info (with some limitations) - :poram support_extra_properties: whether to natively recognize kwargs (if True) or supply them as extra + :param support_extra_properties: whether to natively recognize kwargs (if True) or supply them as extra (if False). """