Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Serve] add RAY_SERVE_LOG_ENCODING env to set the global logging behavior for Serve #42781

Merged
Merged
7 changes: 6 additions & 1 deletion python/ray/serve/_private/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,8 +163,13 @@
"instead (see https://docs.ray.io/en/latest/serve/model_composition.html)."
)

# Jsonify the log messages
# Environment variable name for to specify the encoding of the log messages
RAY_SERVE_LOG_ENCODING = os.environ.get("RAY_SERVE_LOG_ENCODING", "TEXT")

# Jsonify the log messages. This constant is deprecated and will be removed in the
# future. Use RAY_SERVE_LOG_ENCODING or 'LoggingConfig' to enable json format.
RAY_SERVE_ENABLE_JSON_LOGGING = os.environ.get("RAY_SERVE_ENABLE_JSON_LOGGING") == "1"

# Logging format attributes
SERVE_LOG_REQUEST_ID = "request_id"
SERVE_LOG_ROUTE = "route"
Expand Down
8 changes: 6 additions & 2 deletions python/ray/serve/_private/logging_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,8 @@ def configure_component_logger(
component_id: str,
logging_config: LoggingConfig,
component_type: Optional[ServeComponentType] = None,
max_bytes: Optional[int] = None,
backup_count: Optional[int] = None,
):
"""Configure a logger to be used by a Serve component.

Expand Down Expand Up @@ -228,8 +230,10 @@ def record_factory(*args, **kwargs):
logs_dir = get_serve_logs_dir()
os.makedirs(logs_dir, exist_ok=True)

max_bytes = ray._private.worker._global_node.max_bytes
backup_count = ray._private.worker._global_node.backup_count
if max_bytes is None:
max_bytes = ray._private.worker._global_node.max_bytes
if backup_count is None:
backup_count = ray._private.worker._global_node.backup_count

log_file_name = get_component_log_file_name(
component_name=component_name,
Expand Down
8 changes: 5 additions & 3 deletions python/ray/serve/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
from ray.serve._private.constants import (
DEFAULT_GRPC_PORT,
DEFAULT_UVICORN_KEEP_ALIVE_TIMEOUT_S,
RAY_SERVE_LOG_ENCODING,
SERVE_DEFAULT_APP_NAME,
)
from ray.serve._private.deployment_info import DeploymentInfo
Expand Down Expand Up @@ -130,10 +131,11 @@ class Config:
extra = Extra.forbid

encoding: Union[str, EncodingType] = Field(
default="TEXT",
default_factory=lambda: RAY_SERVE_LOG_ENCODING,
description=(
"Encoding type for the serve logs. Default to 'TEXT'. 'JSON' is also "
"supported to format all serve logs into json structure."
"Encoding type for the serve logs. Defaults to 'TEXT'. The default can be "
"overwritten using the `RAY_SERVE_LOG_ENCODING` environment variable. "
"'JSON' is also supported for structured logging."
),
)
log_level: Union[int, str] = Field(
Expand Down
68 changes: 67 additions & 1 deletion python/ray/serve/tests/test_logging.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import sys
import time
from contextlib import redirect_stderr
from unittest.mock import patch

import pytest
import requests
Expand All @@ -17,12 +18,15 @@
from ray import serve
from ray._private.test_utils import wait_for_condition
from ray.serve._private.common import ServeComponentType
from ray.serve._private.constants import SERVE_LOG_EXTRA_FIELDS
from ray.serve._private.constants import SERVE_LOG_EXTRA_FIELDS, SERVE_LOGGER_NAME
from ray.serve._private.logging_utils import (
ServeFormatter,
ServeJSONFormatter,
configure_component_logger,
get_component_log_file_name,
get_serve_logs_dir,
)
from ray.serve.schema import EncodingType, LoggingConfig


@pytest.fixture
Expand Down Expand Up @@ -578,5 +582,67 @@ def format_and_verify_json_output(record, expected_record: dict):
format_and_verify_json_output(record, expected_json)


@pytest.mark.parametrize(
"log_encoding",
[
[None, None, "TEXT"],
[None, "TEXT", "TEXT"],
[None, "JSON", "JSON"],
["TEXT", None, "TEXT"],
["TEXT", "TEXT", "TEXT"],
["TEXT", "JSON", "JSON"],
["JSON", None, "JSON"],
["JSON", "TEXT", "TEXT"],
["JSON", "JSON", "JSON"],
["FOOBAR", None, "TEXT"],
["FOOBAR", "TEXT", "TEXT"],
["FOOBAR", "JSON", "JSON"],
],
)
def test_configure_component_logger_with_log_encoding_env_text(log_encoding):
"""Test the configure_component_logger function with different log encoding env.

When the log encoding env is not set, set to "TEXT" or set to unknon values,
the ServeFormatter should be used. When the log encoding env is set to "JSON",
the ServeJSONFormatter should be used. Also, the log config should take the
precedence it's set.
"""
env_encoding, log_config_encoding, expected_encoding = log_encoding

with patch("ray.serve.schema.RAY_SERVE_LOG_ENCODING", env_encoding):

# Clean up logger handlers
logger = logging.getLogger(SERVE_LOGGER_NAME)
logger.handlers.clear()

# Ensure there is no logger handlers before calling configure_component_logger
assert logger.handlers == []

if log_config_encoding is None:
logging_config = LoggingConfig(logs_dir="/tmp/fake_logs_dir")
else:
logging_config = LoggingConfig(
encoding=log_config_encoding, logs_dir="/tmp/fake_logs_dir"
)
configure_component_logger(
component_name="fake_component_name",
component_id="fake_component_id",
logging_config=logging_config,
component_type=ServeComponentType.REPLICA,
max_bytes=100,
backup_count=3,
)

for handler in logger.handlers:
if isinstance(handler, logging.handlers.RotatingFileHandler):
if expected_encoding == EncodingType.JSON:
assert isinstance(handler.formatter, ServeJSONFormatter)
else:
assert isinstance(handler.formatter, ServeFormatter)

# Clean up logger handlers
logger.handlers.clear()


if __name__ == "__main__":
sys.exit(pytest.main(["-v", "-s", __file__]))
Loading