Skip to content

Commit

Permalink
[Enhancement] Allow full logging customization through YAML configura… (
Browse files Browse the repository at this point in the history
bentoml#1365)

* [Enhancement] Allow full logging customization through YAML configuration (bentoml#1009)

* Add yaml to install_requires in setup.py

* Use ruamel.yaml instead of yaml for parsing logging configuration and remove local file handlers for prediction and feedback loggers

* Address linter errors and update documentation.

* Addressed pull request comments.
  - Added configuration for disabling console and file logging
  - Added unit test for log.py
  - Added documentation for configuration in general
  - Updated documentation for logging

* Added debug logging to specify where logging configuration was loaded from.

* Fix unit test file permission issue on Windows.

* Update documentation log file examples.
  • Loading branch information
ssheng authored Jan 6, 2021
1 parent b8a5e83 commit 7d37a68
Show file tree
Hide file tree
Showing 6 changed files with 450 additions and 56 deletions.
7 changes: 4 additions & 3 deletions bentoml/configuration/default_bentoml.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,17 @@ default_namespace = BENTOML
prometheus_multiproc_dir = {BENTOML_HOME}/prometheus_multiproc_dir

[logging]
logging_config = {BENTOML_HOME}/logging.yml
console_logging_enabled = true
file_logging_enabled = true

level = INFO
log_format = [%%(asctime)s] %%(levelname)s - %%(message)s
dev_log_format = [%%(asctime)s] {{%%(filename)s:%%(lineno)d}} %%(levelname)s - %%(message)s

# the base file directory where bentoml store all its log files
base_log_dir = {BENTOML_HOME}/logs/

log_request_image_files = True

prediction_log_filename = prediction.log
prediction_log_json_format = "%%(service_name)s %%(service_version)s %%(api)s %%(request_id)s %%(task)s %%(result)s %%(asctime)s"

Expand All @@ -44,7 +46,6 @@ feedback_log_json_format = "%%(service_name)s %%(service_version)s %%(request_id

yatai_web_server_log_filename = yatai_web_server.log


[tracing]
# example: http://127.0.0.1:9411/api/v1/spans
zipkin_api_url =
Expand Down
130 changes: 84 additions & 46 deletions bentoml/utils/log.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

from bentoml import config
from bentoml.configuration import get_debug_mode
from bentoml.utils.ruamel_yaml import YAML


def get_logging_config_dict(logging_level, base_log_directory):
Expand All @@ -33,6 +34,61 @@ def get_logging_config_dict(logging_level, base_log_directory):
FEEDBACK_LOG_FILENAME = conf.get("feedback_log_filename")
FEEDBACK_LOG_JSON_FORMAT = conf.get("feedback_log_json_format")

MEGABYTES = 1024 * 1024

handlers = {}
bentoml_logger_handlers = []
prediction_logger_handlers = []
feedback_logger_handlers = []
if conf.getboolean("console_logging_enabled"):
handlers.update(
{
"console": {
"level": logging_level,
"formatter": "console",
"class": "logging.StreamHandler",
"stream": sys.stdout,
}
}
)
bentoml_logger_handlers.append("console")
prediction_logger_handlers.append("console")
feedback_logger_handlers.append("console")
if conf.getboolean("file_logging_enabled"):
handlers.update(
{
"local": {
"level": logging_level,
"formatter": "dev",
"class": "logging.handlers.RotatingFileHandler",
"filename": os.path.join(base_log_directory, "active.log"),
"maxBytes": 100 * MEGABYTES,
"backupCount": 2,
},
"prediction": {
"class": "logging.handlers.RotatingFileHandler",
"formatter": "prediction",
"level": "INFO",
"filename": os.path.join(
base_log_directory, PREDICTION_LOG_FILENAME
),
"maxBytes": 100 * MEGABYTES,
"backupCount": 10,
},
"feedback": {
"class": "logging.handlers.RotatingFileHandler",
"formatter": "feedback",
"level": "INFO",
"filename": os.path.join(base_log_directory, FEEDBACK_LOG_FILENAME),
"maxBytes": 100 * MEGABYTES,
"backupCount": 10,
},
}
)
bentoml_logger_handlers.append("local")
prediction_logger_handlers.append("prediction")
feedback_logger_handlers.append("feedback")

return {
"version": 1,
"disable_existing_loggers": False,
Expand All @@ -48,51 +104,20 @@ def get_logging_config_dict(logging_level, base_log_directory):
"fmt": FEEDBACK_LOG_JSON_FORMAT,
},
},
"handlers": {
"console": {
"level": logging_level,
"formatter": "console",
"class": "logging.StreamHandler",
"stream": sys.stdout,
},
"local": {
"level": logging_level,
"formatter": "dev",
"class": "logging.handlers.RotatingFileHandler",
"filename": os.path.join(base_log_directory, "active.log"),
"maxBytes": 100 * 1000 * 1000,
"backupCount": 2,
},
"prediction": {
"class": "logging.handlers.RotatingFileHandler",
"formatter": "prediction",
"level": "INFO",
"filename": os.path.join(base_log_directory, PREDICTION_LOG_FILENAME),
"maxBytes": 100 * 1000 * 1000,
"backupCount": 10,
},
"feedback": {
"class": "logging.handlers.RotatingFileHandler",
"formatter": "feedback",
"level": "INFO",
"filename": os.path.join(base_log_directory, FEEDBACK_LOG_FILENAME),
"maxBytes": 100 * 1000 * 1000,
"backupCount": 10,
},
},
"handlers": handlers,
"loggers": {
"bentoml": {
"handlers": ["console", "local"],
"handlers": bentoml_logger_handlers,
"level": logging_level,
"propagate": False,
},
"bentoml.prediction": {
"handlers": ["prediction", "console"],
"handlers": prediction_logger_handlers,
"level": "INFO",
"propagate": False,
},
"bentoml.feedback": {
"handlers": ["feedback", "console"],
"handlers": feedback_logger_handlers,
"level": "INFO",
"propagate": False,
},
Expand All @@ -101,16 +126,29 @@ def get_logging_config_dict(logging_level, base_log_directory):


def configure_logging(logging_level=None):
if logging_level is None:
logging_level = config("logging").get("LEVEL").upper()
if "LOGGING_LEVEL" in config("logging"):
# Support legacy config name e.g. BENTOML__LOGGING__LOGGING_LEVEL=debug
logging_level = config("logging").get("LOGGING_LEVEL").upper()

if get_debug_mode():
logging_level = logging.getLevelName(logging.DEBUG)

base_log_dir = os.path.expanduser(config("logging").get("BASE_LOG_DIR"))
Path(base_log_dir).mkdir(parents=True, exist_ok=True)
logging_config = get_logging_config_dict(logging_level, base_log_dir)
logging.config.dictConfig(logging_config)
if os.path.exists(config("logging").get("logging_config")):
logging_config_path = config("logging").get("logging_config")
with open(logging_config_path, "rb") as f:
logging_config = YAML().load(f.read())
logging.config.dictConfig(logging_config)
logging.getLogger(__name__).debug(
"Loaded logging configuration from %s." % logging_config_path
)
else:
if logging_level is None:
logging_level = config("logging").get("LEVEL").upper()
if "LOGGING_LEVEL" in config("logging"):
# Support legacy config name e.g. BENTOML__LOGGING__LOGGING_LEVEL=debug
logging_level = config("logging").get("LOGGING_LEVEL").upper()

if get_debug_mode():
logging_level = logging.getLevelName(logging.DEBUG)

logging_config = get_logging_config_dict(logging_level, base_log_dir)
logging.config.dictConfig(logging_config)
logging.getLogger(__name__).debug(
"Loaded logging configuration from default configuration "
+ "and environment variables."
)
63 changes: 63 additions & 0 deletions docs/source/guides/configuration.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
.. _configuration-page:

Configuration
=============

BentoML can be configured through configuration properties defined in the `default_bentoml.cfg <https://github.com/bentoml/BentoML/blob/master/bentoml/configuration/default_bentoml.cfg>`_.
The values of configuration properties are applied in the following precedence order.

- Environment Variables
- User Defined Configuration File
- BentoML Defaults

Environment Variables
^^^^^^^^^^^^^^^^^^^^^

To override a configuration property, environment variables should be named in the following convention,
`BENTOML__<SECTION>__<KEY>`, in upper case letters.

For example, to override the `level` property to `ERROR` in the `logging` section of the configuration, user
should define an environment variable named `BENTOML__LOGGING__LEVEL` with value `ERROR`.


.. code-block:: cfg
:caption: default_bentoml.cfg
[logging]
level = INFO
See Docker example below for setting the environment variable of logging level.

.. code-block:: shell
$ docker run -e BENTOML__LOGGING__LEVEL=ERROR
User Defined Configuration File
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

A user defined configuration file, in the same format as the
`default_bentoml.cfg <https://github.com/bentoml/BentoML/blob/master/bentoml/configuration/default_bentoml.cfg>`_
can be placed under the BentoML home directory with the file name `bentoml.cfg`, to override existing configuration
properties.

The example below, overrides both `level` and `file_logging_enabled` properties in the `logging` section, to change
logging level to `WARN` and disable file based logging.

.. code-block:: cfg
:caption: {BENTOML_HOME}/bentoml.cfg
[logging]
level = WARN
file_logging_enabled = false
See Docker example below for injecting the BentoML configuration file into the container.

.. code-block:: shell
$ docker run -v /local/path/to/bentoml.cfg:{BENTOML_HOME}/bentoml.cfg
BentoML Defaults
^^^^^^^^^^^^^^^^

Any non-overridden properties will fallback to the default values defined in
`default_bentoml.cfg <https://github.com/bentoml/BentoML/blob/master/bentoml/configuration/default_bentoml.cfg>`_.
3 changes: 2 additions & 1 deletion docs/source/guides/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@ and ask in the bentoml-users channel.
.. toctree::
:glob:

configuration
logging
batch_serving
monitoring
logging
micro_batching
custom_artifact
custom_input_adapter
Expand Down
Loading

0 comments on commit 7d37a68

Please sign in to comment.