-
Notifications
You must be signed in to change notification settings - Fork 5.8k
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
[Core][2/n] Core structured logging: add Text formatter and pass log config to worker process #45344
[Core][2/n] Core structured logging: add Text formatter and pass log config to worker process #45344
Changes from 13 commits
99024ee
b380e9a
6e78f48
9113d25
9218481
def7635
a16f762
571e2bc
8ec3107
1b37390
dc62d5c
69f8b57
d9e63aa
2ec1b4f
9ef1eee
f5f5dec
d811347
229f9d4
c8621a0
2893b84
5083efb
36dd127
83cf819
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,24 +1,55 @@ | ||
import logging | ||
import json | ||
from ray._private.structured_logging.constants import LogKey, LOGRECORD_STANDARD_ATTRS | ||
from ray._private.ray_constants import LOGGER_FORMAT | ||
|
||
|
||
class JSONFormatter(logging.Formatter): | ||
def format(self, record): | ||
record_format = { | ||
LogKey.ASCTIME: self.formatTime(record), | ||
def generate_record_format_attrs( | ||
formatter: logging.Formatter, | ||
record: logging.LogRecord, | ||
exclude_standard_attrs, | ||
) -> dict: | ||
record_format_attrs = {} | ||
# If `exclude_standard_attrs` is False, include the standard attributes. | ||
# Otherwise, include only Ray and user-provided context. | ||
if not exclude_standard_attrs: | ||
record_format_attrs = { | ||
LogKey.ASCTIME: formatter.formatTime(record), | ||
LogKey.LEVELNAME: record.levelname, | ||
LogKey.MESSAGE: record.getMessage(), | ||
LogKey.FILENAME: record.filename, | ||
LogKey.LINENO: record.lineno, | ||
} | ||
if record.exc_info: | ||
if not record.exc_text: | ||
record.exc_text = self.formatException(record.exc_info) | ||
record_format[LogKey.EXC_TEXT] = record.exc_text | ||
|
||
for key, value in record.__dict__.items(): | ||
# Both Ray and user-provided context are stored in `record_format`. | ||
if key not in LOGRECORD_STANDARD_ATTRS: | ||
record_format[key] = value | ||
return json.dumps(record_format) | ||
record.exc_text = formatter.formatException(record.exc_info) | ||
record_format_attrs[LogKey.EXC_TEXT] = record.exc_text | ||
|
||
for key, value in record.__dict__.items(): | ||
# Both Ray and user-provided context are stored in `record_format`. | ||
if key not in LOGRECORD_STANDARD_ATTRS: | ||
record_format_attrs[key] = value | ||
return record_format_attrs | ||
|
||
|
||
class JSONFormatter(logging.Formatter): | ||
def format(self, record): | ||
record_format_attrs = generate_record_format_attrs( | ||
self, record, exclude_standard_attrs=False | ||
) | ||
return json.dumps(record_format_attrs) | ||
|
||
|
||
class TextFormatter(logging.Formatter): | ||
def __init__(self) -> None: | ||
self._inner_formatter = logging.Formatter(LOGGER_FORMAT) | ||
|
||
def format(self, record: logging.LogRecord) -> str: | ||
s = self._inner_formatter.format(record) | ||
record_format_attrs = generate_record_format_attrs( | ||
self, record, exclude_standard_attrs=True | ||
) | ||
additional_attrs = " ".join( | ||
[f"{key}={value}" for key, value in record_format_attrs.items()] | ||
) | ||
return f"{s} {additional_attrs}" |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
from ray._private.structured_logging.formatters import TextFormatter | ||
from ray._private.structured_logging.filters import CoreContextFilter | ||
|
||
LOG_MODE_DICT = { | ||
"TEXT": lambda log_level: { | ||
"version": 1, | ||
kevin85421 marked this conversation as resolved.
Show resolved
Hide resolved
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
"disable_existing_loggers": False, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
"formatters": { | ||
"text": { | ||
"()": TextFormatter, | ||
}, | ||
}, | ||
"filters": { | ||
"core_context": { | ||
"()": CoreContextFilter, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. "The key '()' has been used as the special key because it is not a valid keyword parameter name, and so will not clash with the names of the keyword arguments used in the call. The '()' also serves as a mnemonic that the corresponding value is a callable." Ref: https://docs.python.org/3/library/logging.config.html#logging-config-dictschema |
||
}, | ||
}, | ||
"handlers": { | ||
"console": { | ||
"level": log_level, | ||
"class": "logging.StreamHandler", | ||
"formatter": "text", | ||
"filters": ["core_context"], | ||
}, | ||
}, | ||
"root": { | ||
"level": log_level, | ||
"handlers": ["console"], | ||
}, | ||
}, | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2583,6 +2583,21 @@ def maybe_initialize_job_config(): | |
print(job_id_magic_token, end="") | ||
print(job_id_magic_token, file=sys.stderr, end="") | ||
|
||
# Configure worker process's Python logging. | ||
log_config_dict = {} | ||
serialized_py_logging_config = \ | ||
core_worker.get_job_config().serialized_py_logging_config | ||
if serialized_py_logging_config: | ||
py_logging_config = pickle.loads(serialized_py_logging_config) | ||
log_config_dict = py_logging_config.get_dict_config() | ||
if log_config_dict: | ||
try: | ||
logging.config.dictConfig(log_config_dict) | ||
except Exception as e: | ||
backtrace = \ | ||
"".join(traceback.format_exception(type(e), e, e.__traceback__)) | ||
print(backtrace) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we don't add import ray
import logging
from ray.job_config import LoggingConfig
ray.init(
job_config=ray.job_config.JobConfig(py_logging_config=LoggingConfig({"abc": "123"}))
)
def init_logger():
return logging.getLogger()
logger = logging.getLogger("ray")
logger.info("Driver process", extra={"username": "johndoe"})
@ray.remote
def f():
logger = init_logger()
logger.info("A Ray task")
task_obj_ref = f.remote()
ray.get(task_obj_ref) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's remove this There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I don't think any user can troubleshoot this kind of issue on their own before we configure the root logger of the driver process. It's OK to remove
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Updated 36dd127 |
||
core_worker.drain_and_exit_worker("user", backtrace) | ||
job_config_initialized = True | ||
|
||
|
||
|
@@ -3389,7 +3404,7 @@ cdef class CoreWorker: | |
|
||
if exit_type == "user": | ||
c_exit_type = WORKER_EXIT_TYPE_USER_ERROR | ||
if exit_type == "system": | ||
elif exit_type == "system": | ||
c_exit_type = WORKER_EXIT_TYPE_SYSTEM_ERROR | ||
elif exit_type == "intentional_system_exit": | ||
c_exit_type = WORKER_EXIT_TYPE_INTENTIONAL_SYSTEM_ERROR | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: I think we can just define this in the constants.py?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Moving to
constants.py
will cause a circular import.LOG_MODE_DICT
requiresTextFormatter
andCoreContextFilter
fromformatters.py
andfilters.py
.filters.py
andformatters.py
also importconstants.py
.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated 2ec1b4f