Skip to content

Commit

Permalink
Add "Logs" panel for evaluation v2.
Browse files Browse the repository at this point in the history
Evaluation classes could use `self.debug`/`self.info`/`self.warning`/`self.error`/`self.fatal` to write logs to this panel.

PiperOrigin-RevId: 707742072
  • Loading branch information
daiyip authored and langfun authors committed Dec 19, 2024
1 parent 96d3c24 commit 57ecab1
Show file tree
Hide file tree
Showing 4 changed files with 117 additions and 6 deletions.
79 changes: 73 additions & 6 deletions langfun/core/eval/v2/evaluation.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@
"""Base class for Langfun evaluation tasks."""

import abc
import datetime
import functools
import threading
import time

from typing import Annotated, Any, Callable, Iterable
Expand Down Expand Up @@ -63,6 +65,8 @@ def _on_bound(self):
self.__dict__.pop('is_leaf', None)
self.__dict__.pop('children', None)
super()._on_bound()
self._log_entries = []
self._log_lock = threading.Lock()

#
# Handling evaluation hierarchy (materialized vs. hyper evaluations).
Expand Down Expand Up @@ -277,6 +281,41 @@ def _reset(self) -> None:
for metric in self.metrics:
metric.reset()

#
# Evaluation-level logging.
#

def _log(self, level: lf.logging.LogLevel, message: str, **kwargs):
with self._log_lock:
self._log_entries.append(
lf.logging.LogEntry(
level=level,
time=datetime.datetime.now(),
message=message,
metadata=kwargs,
)
)

def debug(self, message: str, **kwargs):
"""Logs a debug message to the session."""
self._log('debug', message, **kwargs)

def info(self, message: str, **kwargs):
"""Logs an info message to the session."""
self._log('info', message, **kwargs)

def warning(self, message: str, **kwargs):
"""Logs a warning message to the session."""
self._log('warning', message, **kwargs)

def error(self, message: str, **kwargs):
"""Logs an error message to the session."""
self._log('error', message, **kwargs)

def fatal(self, message: str, **kwargs):
"""Logs a fatal message to the session."""
self._log('fatal', message, **kwargs)

#
# HTML views.
#
Expand Down Expand Up @@ -465,6 +504,25 @@ def _metric_value_tab(
)
)

def _logs_tab() -> pg.views.html.controls.Tab:
"""Renders a tab for the logs of the evaluation."""
with self._log_lock:
log_history = '\n'.join(str(l) for l in self._log_entries)
return pg.views.html.controls.Tab(
label='Logs',
content=pg.Html.element(
'div',
[
pg.Html.element(
'textarea',
[pg.Html.escape(log_history)],
readonly=True,
css_classes=['logs-textarea'],
)
]
)
)

def _main_tabs() -> pg.Html:
return pg.Html.element(
'div',
Expand All @@ -474,6 +532,8 @@ def _main_tabs() -> pg.Html:
_definition_tab(),
] + [
_metric_tab(m) for m in self.metrics
] + [
_logs_tab()
],
selected=1,
)
Expand Down Expand Up @@ -593,6 +653,14 @@ def _html_tree_view_css_styles(self) -> list[str]:
width:100%;
height:100%;
}
.logs-textarea {
width: 100%;
height: 500px;
padding: 5px;
border: 1px solid #DDD;
background-color: #EEE;
resize: vertical;
}
"""
]

Expand All @@ -615,16 +683,15 @@ def load(
assert isinstance(example, example_lib.Example), example
self._evaluated_examples[example.id] = example

@property
def evaluated_examples(self) -> dict[int, example_lib.Example]:
"""Returns the examples in the state."""
return self._evaluated_examples

def get(self, example_id: int) -> example_lib.Example | None:
"""Returns the example with the given ID."""
return self._evaluated_examples.get(example_id)

def update(self, example: example_lib.Example) -> None:
"""Updates the state with the given example."""
self._evaluated_examples[example.id] = example

@property
def evaluated_examples(self) -> dict[int, example_lib.Example]:
"""Returns the examples in the state."""
return self._evaluated_examples

6 changes: 6 additions & 0 deletions langfun/core/eval/v2/evaluation_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,12 @@ def test_evaluate_with_state(self):

def test_html_view(self):
exp = test_helper.TestEvaluation()
exp.debug('debug message')
exp.info('info message')
exp.warning('warning message', x=1)
exp.error('error message', x=1)
exp.fatal('fatal message')

self.assertIn(
exp.id,
exp.to_html(extra_flags=dict(card_view=True, current_run=None)).content
Expand Down
19 changes: 19 additions & 0 deletions langfun/core/logging.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,25 @@ class LogEntry(pg.Object, pg.views.HtmlTreeView.Extension):
def should_output(self, min_log_level: LogLevel) -> bool:
return _LOG_LEVELS.index(self.level) >= _LOG_LEVELS.index(min_log_level)

def format(self,
compact: bool = False,
verbose: bool = True,
root_indent: int = 0,
*,
text_format: bool = True,
**kwargs):
if text_format:
s = f"""{self.time.strftime('%H:%M:%S')} {self.level.upper()} - {self.message}"""
if self.metadata:
s += f' (metadata: {self.metadata!r})'
return s
return super().format(
compact=compact,
verbose=verbose,
root_indent=root_indent,
**kwargs
)

def _html_tree_view_summary(
self,
view: pg.views.HtmlTreeView,
Expand Down
19 changes: 19 additions & 0 deletions langfun/core/logging_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,25 @@ def assert_html_content(self, html, expected):
print(actual)
self.assertEqual(actual, expected)

def test_format(self):
time = datetime.datetime(2024, 10, 10, 12, 30, 45)
self.assertEqual(
str(
logging.LogEntry(
level='info', message='hello\nworld',
time=time, metadata=dict(x=1),
)
),
'12:30:45 INFO - hello\nworld (metadata: {x=1})',
)
self.assertIn(
'LogEntry(',
logging.LogEntry(
level='info', message='hello\nworld',
time=time, metadata=dict(x=1),
).format(text_format=False),
)

def test_html(self):
time = datetime.datetime(2024, 10, 10, 12, 30, 45)
self.assert_html_content(
Expand Down

0 comments on commit 57ecab1

Please sign in to comment.