How to log a message in a charm

See first: Juju | Log, Juju | How to manage logs > Manage the logging configuration

To log a message in a charm, import Python’s logging module, then use the getLogger() function with the desired level. For example:

import logging
# ...
logger = logging.getLogger(__name__)

class HelloOperatorCharm(ops.CharmBase):
    # ...

    def _on_config_changed(self, _):
        current = self.config["thing"]
        if current not in self._stored.things:
            # Note the use of the logger here
            logger.debug("found a new thing: %r", current)
            self._stored.things.append(current)

See more:

Juju automatically picks up logs from charm code that uses the Python logging facility, so we can use the Juju debug-log command to display logs for a model. Note that it shows logs from the charm code (charm container), but not the workload container. Read “Use juju debug-log for more information.

Besides logs, stderr is also captured by Juju. So, if a charm generates a warning, it will also end up in Juju’s debug log. This behaviour is consistent between K8s charms and machine charms.

Tips for good practice:

  • Note that some logging is performed automatically by the Juju controller, for example when an event handler is called. Try not to replicate this behaviour in your own code.

  • Keep developer specific logging to a minimum, and use logger.debug() for such output. If you are debugging a problem, ensure you comment out or remove large information dumps (such as config files, etc.) from the logging once you are finished.

  • When passing messages to the logger, do not build the strings yourself. Allow the logger to do this for you as required by the specified log level. That is:

# Do this!
logger.info("Got some information %s", info)
# Don't do this
logger.info("Got some information {}".format(info))
# Or this ...
logger.info(f"Got some more information {more_info}")

When passing messages to the logger, do not build the strings yourself. Allow the logger to do this for you as required by the specified log level. An example is shown below:

This part could do with some clarification. It says it can do this as required by the specified log level, but doesn’t clarify how the specified log level will change the outcome.

Will it sometimes include or not include the interpolated strings, or, is it just a guide to use the loggers inbuilt “printf” instead of the other functions so that it doesn’t have to spend time actually building the string if the log level isn’t active?

This :slight_smile: Thanks for the feedback :slight_smile:

To help clarify potential confusion about why printf-style formatting is preferred over f-strings, it might be helpful to also source some information from blog posts out there such as the following one: https://blog.pilosus.org/posts/2020/01/24/python-f-strings-in-logging/

I’m thinking this page could be a good place for including guidelines for log levels. I imagine it would make admin life easier knowing what kind of stuff to expect, generally, from different logging levels.

Here’s a draft. Wdyt? CC: @tmihoc @0x12b @mthaddon @przemek-lal

Intentionally suppress

  • DEBUG logs from imported libs such as httpx and httpcore.

DEBUG

  • Workload manifest changes due to pebble operations (e.g. pushed a new cert, updated pebble layer).
  • Dump charm config options on config-changed.

INFO

  • Charm-code-initiated service restart.
  • Relation created/broken and config changed. Currently ops gives us Emit logs for all hooks at DEBUG level, but relation created/broken and config-changed can be elevated to INFO for easier troubleshooting.

Warning

TODO

Error

  • Messages that accompany BlockedStatus.

I agree having some standards/recommendations on what should be logged for different levels makes sense, and I think we should add it to https://juju.is/docs/sdk/styleguide#heading--logging once we have an agreement on it.

The specifics of what to log at each level does seems quite tricky though. Perhaps we should look at how this is done in some particular charms and then we can decide on whether they seem right, need adjustment, and what patterns we can extract from them to apply to other charms.