Skip to content
Merged
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
4 changes: 4 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

### Enhancements

* Simplified logging to console. You can now set configuration setting `logging`
to a log level which will implicitly enable console logging with given log
level. [#64]

* Added a section in the notebook `examples/zappend-demo.ipynb`
that demonstrates transaction rollbacks.

Expand Down
28 changes: 22 additions & 6 deletions docs/guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -691,14 +691,28 @@ the [Configuration Reference](config.md) for more details.
## Logging

The `zappend` logging output is configured using the `logging` setting.
Its configuration follows exactly the
[dictionary schema](https://docs.python.org/3/library/logging.config.html#logging-config-dictschema)
of the Python module `logging.config`. The logger used by the `zappend` tool is named
`zappend`. Note that you can also configure the logger of other Python modules, e.g.,
`xarray` or `dask` using an entry in the `loggers` setting.
In the simplest case, if you just want logging output from `zappend` to the console:

```json
{
"logging": true
}
```

The above uses log level `INFO`. If you want a different log level,
just provide its name:

```json
{
"logging": "DEBUG"
}
```

If you also want logging output in a file or using a different format or if you want to
see logging output of other Python modules, you can configure Python's logging
system following the [logging dictionary schema](https://docs.python.org/3/library/logging.config.html#logging-config-dictschema).
Given here is an example that logs `zappend`'s output to the console using
the INFO level:
the `INFO` level (same as `"logging": true`):

```json
{
Expand Down Expand Up @@ -726,3 +740,5 @@ the INFO level:
}
```

Using the `loggers` entry you can configure the logger of other Python modules, e.g.,
`xarray` or `dask`. The logger used by the `zappend` tool is named `zappend`.
53 changes: 40 additions & 13 deletions tests/test_log.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,27 @@


class LogTest(unittest.TestCase):
old_level = None
old_handlers = None

@classmethod
def setUpClass(cls):
cls.old_level = logger.level
cls.old_handlers = list(logger.handlers)
for h in cls.old_handlers:
logger.removeHandler(h)

def tearDown(self):
logger.setLevel(self.old_level)
for h in self.old_handlers:
logger.addHandler(h)

def test_logger(self):
self.assertIsInstance(logger, logging.Logger)
self.assertEqual(logging.NOTSET, logger.level)
self.assertEqual(1, len(logger.handlers))

def test_configure_logging(self):
def test_configure_logging_dict(self):
logging_config = {
"version": 1,
"formatters": {
Expand All @@ -31,19 +46,31 @@ def test_configure_logging(self):
"loggers": {"zappend": {"level": "INFO", "handlers": ["console"]}},
}

old_level = logger.level
old_handlers = list(logger.handlers)
for h in old_handlers:
logger.removeHandler(h)
configure_logging(logging_config)
self.assertEqual(logging.INFO, logger.level)
self.assertEqual(1, len(logger.handlers))
self.assertIsInstance(logger.handlers[0], logging.StreamHandler)

try:
configure_logging(logging_config)
self.assertEqual(logging.INFO, logger.level)
self.assertEqual(1, len(logger.handlers))
finally:
logger.setLevel(old_level)
for h in old_handlers:
logger.addHandler(h)
def test_configure_logging_false(self):
logging_config = False
configure_logging(logging_config)
self.assertEqual(logging.NOTSET, logger.level)
self.assertEqual(1, len(logger.handlers))
self.assertIsInstance(logger.handlers[0], logging.StreamHandler)

def test_configure_logging_true(self):
logging_config = True
configure_logging(logging_config)
self.assertEqual(logging.INFO, logger.level)
self.assertEqual(1, len(logger.handlers))
self.assertIsInstance(logger.handlers[0], logging.StreamHandler)

def test_configure_logging_level(self):
logging_config = "WARNING"
configure_logging(logging_config)
self.assertEqual(logging.WARNING, logger.level)
self.assertEqual(1, len(logger.handlers))
self.assertIsInstance(logger.handlers[0], logging.StreamHandler)

def test_get_log_level(self):
self.assertEqual(logging.DEBUG, get_log_level("DEBUG"))
Expand Down
25 changes: 23 additions & 2 deletions zappend/config/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -297,9 +297,9 @@
}


LOGGING_SCHEMA = {
DETAILED_LOGGING_SCHEMA = {
"description": (
f"Logging configuration. For details refer to the"
f"Detailed logging configuration. For details refer to the"
f" [dictionary schema]({LOG_REF_URL})"
f" of the Python module `logging.config`."
),
Expand Down Expand Up @@ -441,6 +441,27 @@
"additionalProperties": True,
}

LOGGING_SCHEMA = {
"description": "Logging configuration.",
"oneOf": [
{
"description": (
"Shortform that enables logging to the console"
' using log level `"INFO"`.'
),
"type": "boolean",
},
{
"description": (
"Shortform that enables logging to the console"
" using the specified log level."
),
"enum": LOG_LEVELS,
},
DETAILED_LOGGING_SCHEMA,
],
}

CONFIG_SCHEMA_V1 = {
"title": "Configuration Reference",
"type": "object",
Expand Down
26 changes: 23 additions & 3 deletions zappend/log.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,26 @@ def get_log_level(level_name: str) -> int:
return _nameToLevel.get(level_name, logging.NOTSET)


def configure_logging(logging_config: dict[str, Any] | None):
if logging_config:
logging.config.dictConfig(logging_config)
def configure_logging(logging_config: dict[str, Any] | str | bool | None):
if not logging_config:
return
if isinstance(logging_config, (str, bool)):
logging_config = {
"version": 1,
"formatters": {
"normal": {
"format": "%(asctime)s %(levelname)s %(message)s",
"style": "%",
}
},
"handlers": {
"console": {"class": "logging.StreamHandler", "formatter": "normal"}
},
"loggers": {
"zappend": {
"level": _nameToLevel.get(logging_config, logging.INFO),
"handlers": ["console"],
}
},
}
logging.config.dictConfig(logging_config)