Skip to content

Commit 285ad01

Browse files
committed
[Logging] put the actor name in the python logger
Differential Revision: [D85994688](https://our.internmc.facebook.com/intern/diff/D85994688/) **NOTE FOR REVIEWERS**: This PR has internal Meta-specific changes or comments, please review them on [Phabricator](https://our.internmc.facebook.com/intern/diff/D85994688/)! ghstack-source-id: 320202679 Pull Request resolved: #1732
1 parent 0f8d1a9 commit 285ad01

File tree

2 files changed

+72
-5
lines changed

2 files changed

+72
-5
lines changed

python/monarch/_src/actor/actor_mesh.py

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
from abc import abstractproperty
1818

1919
from dataclasses import dataclass
20+
21+
from functools import cache
2022
from pprint import pformat
2123
from textwrap import indent
2224
from traceback import TracebackException
@@ -257,6 +259,41 @@ def _root_client_context() -> "Context": ...
257259
"monarch.actor_mesh._context"
258260
)
259261

262+
263+
class _ActorFilter(logging.Filter):
264+
def __init__(self) -> None:
265+
super().__init__()
266+
267+
def filter(self, record: Any) -> bool:
268+
ctx = _context.get(None)
269+
if ctx is not None:
270+
record.msg = f"[actor={ctx.actor_instance}] {record.msg}"
271+
return True
272+
273+
274+
@cache
275+
def _init_context_log_handler() -> None:
276+
af: _ActorFilter = _ActorFilter()
277+
logger = logging.getLogger()
278+
for handler in logger.handlers:
279+
handler.addFilter(af)
280+
281+
_original_addHandler: Any = logging.Logger.addHandler
282+
283+
def _patched_addHandler(self: Any, handler: Any) -> None:
284+
_original_addHandler(self, handler)
285+
if af not in handler.filters:
286+
handler.addFilter(af)
287+
288+
# typing: ignore
289+
logging.Logger.addHandler = _patched_addHandler
290+
291+
292+
def _set_context(c: Context) -> None:
293+
_init_context_log_handler()
294+
_context.set(c)
295+
296+
260297
T = TypeVar("T")
261298

262299

@@ -305,7 +342,7 @@ def context() -> Context:
305342
c = _context.get(None)
306343
if c is None:
307344
c = Context._root_client_context()
308-
_context.set(c)
345+
_set_context(c)
309346

310347
from monarch._src.actor.host_mesh import create_local_host_mesh
311348
from monarch._src.actor.proc_mesh import _get_controller_controller
@@ -919,7 +956,7 @@ async def handle(
919956
# response_port can be None. If so, then sending to port will drop the response,
920957
# and raise any exceptions to the caller.
921958
try:
922-
_context.set(ctx)
959+
_set_context(ctx)
923960

924961
DebugContext.set(DebugContext())
925962

@@ -1053,7 +1090,7 @@ def _post_mortem_debug(self, exc_tb: Any) -> None:
10531090
def _handle_undeliverable_message(
10541091
self, cx: Context, message: UndeliverableMessageEnvelope
10551092
) -> bool:
1056-
_context.set(cx)
1093+
_set_context(cx)
10571094
handle_undeliverable = getattr(
10581095
self.instance, "_handle_undeliverable_message", None
10591096
)
@@ -1063,7 +1100,7 @@ def _handle_undeliverable_message(
10631100
return False
10641101

10651102
def __supervise__(self, cx: Context, *args: Any, **kwargs: Any) -> object:
1066-
_context.set(cx)
1103+
_set_context(cx)
10671104
instance = self.instance
10681105
if instance is None:
10691106
# This could happen because of the following reasons. Both

python/tests/test_python_actors.py

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import asyncio
1010
import ctypes
1111
import importlib.resources
12+
import io
1213
import logging
1314
import operator
1415
import os
@@ -20,6 +21,7 @@
2021
import time
2122
import unittest
2223
import unittest.mock
24+
from contextlib import contextmanager
2325
from tempfile import TemporaryDirectory
2426
from types import ModuleType
2527
from typing import cast, Tuple
@@ -68,7 +70,6 @@
6870
from monarch.tools.config import defaults
6971
from typing_extensions import assert_type
7072

71-
7273
needs_cuda = pytest.mark.skipif(
7374
not torch.cuda.is_available(),
7475
reason="CUDA not available",
@@ -1688,9 +1689,34 @@ def test_login_job():
16881689
j.kill()
16891690

16901691

1692+
class CaptureLogs:
1693+
def __init__(self):
1694+
log_stream = io.StringIO()
1695+
handler = logging.StreamHandler(log_stream)
1696+
handler.setFormatter(logging.Formatter("%(message)s"))
1697+
1698+
logger = logging.getLogger("capture")
1699+
logger.setLevel(logging.INFO)
1700+
logger.addHandler(handler)
1701+
1702+
self.log_stream = log_stream
1703+
self.logger = logger
1704+
1705+
@property
1706+
def contents(self) -> str:
1707+
return self.log_stream.getvalue()
1708+
1709+
16911710
class Named(Actor):
16921711
@endpoint
16931712
def report(self):
1713+
logs = CaptureLogs()
1714+
logs.logger.error("HUH")
1715+
assert (
1716+
"actor=<root>.<tests.test_python_actors.Named the_name{'f': 0/2}>"
1717+
in logs.contents
1718+
)
1719+
16941720
return context().actor_instance.creator, str(context().actor_instance)
16951721

16961722

@@ -1706,3 +1732,7 @@ def test_instance_name():
17061732
assert result == "<root>.<tests.test_python_actors.Named the_name{'f': 0/2}>"
17071733
assert cr.name == "root"
17081734
assert str(context().actor_instance) == "<root>"
1735+
1736+
logs = CaptureLogs()
1737+
logs.logger.error("HUH")
1738+
assert "actor=<root>" in logs.contents

0 commit comments

Comments
 (0)