Skip to content

Commit ec17dd1

Browse files
authored
Organize logging (#138)
1 parent a13c971 commit ec17dd1

File tree

6 files changed

+100
-12
lines changed

6 files changed

+100
-12
lines changed
+85-4
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,97 @@
11
# Logging
22

33
AGNext uses Python's built-in [`logging`](https://docs.python.org/3/library/logging.html) module.
4-
The logger names are:
54

6-
- `agnext` for the main logger.
5+
There are two kinds of logging:
76

8-
Example of how to use the logger:
7+
- **Trace logging**: This is used for debugging and is human readable messages to indicate what is going on. This is intended for a developer to understand what is happening in the code. The content and format of these logs should not be depended on by other systems.
8+
- Name: {py:attr}`~agnext.application.logging.TRACE_LOGGER_NAME`.
9+
- **Structured logging**: This logger emits structured events that can be consumed by other systems. The content and format of these logs should be can be depended on by other systems.
10+
- Name: {py:attr}`~agnext.application.logging.EVENT_LOGGER_NAME`.
11+
- See the module {py:mod}`agnext.application.logging.events` to see the available events.
12+
- {py:attr}`~agnext.application.logging.ROOT_LOGGER` can be used to enable or disable all logs at the same time.
13+
14+
## Enabling logging output
15+
16+
To enable trace logging, you can use the following code:
917

1018
```python
1119
import logging
1220

21+
from agnext.application.logging import TRACE_LOGGER_NAME
22+
1323
logging.basicConfig(level=logging.WARNING)
14-
logger = logging.getLogger('agnext')
24+
logger = logging.getLogger(TRACE_LOGGER_NAME)
1525
logger.setLevel(logging.DEBUG)
1626
```
27+
28+
### Structured logging
29+
30+
Structured logging allows you to write handling logic that deals with the actual events including all fields rather than just a formatted string.
31+
32+
For example, if you had defined this custom event and were emitting it. Then you could write the following handler to receive it.
33+
34+
```python
35+
import logging
36+
from dataclasses import dataclass
37+
38+
@dataclass
39+
class MyEvent:
40+
timestamp: str
41+
message: str
42+
43+
class MyHandler(logging.Handler):
44+
def __init__(self) -> None:
45+
super().__init__()
46+
47+
def emit(self, record: logging.LogRecord) -> None:
48+
try:
49+
# Use the StructuredMessage if the message is an instance of it
50+
if isinstance(record.msg, MyEvent):
51+
print(f"Timestamp: {record.msg.timestamp}, Message: {record.msg.message}")
52+
except Exception:
53+
self.handleError(record)
54+
```
55+
56+
And this is how you could use it:
57+
58+
```python
59+
logger = logging.getLogger(EVENT_LOGGER_NAME)
60+
logger.setLevel(logging.INFO)
61+
my_handler = MyHandler()
62+
logger.handlers = [my_handler]
63+
```
64+
65+
66+
## Emitting logs
67+
68+
These two names are the root loggers for these types. Code that emits logs should use a child logger of these loggers. For example, if you are writing a module `my_module` and you want to emit trace logs, you should use the logger named:
69+
70+
```python
71+
import logging
72+
73+
from agnext.application.logging import TRACE_LOGGER_NAME
74+
logger = logging.getLogger(f"{TRACE_LOGGER_NAME}.my_module")
75+
```
76+
77+
### Emitting structured logs
78+
79+
If your event looks like:
80+
81+
```python
82+
from dataclasses import dataclass
83+
84+
@dataclass
85+
class MyEvent:
86+
timestamp: str
87+
message: str
88+
```
89+
90+
Then it could be emitted in code like this:
91+
92+
```python
93+
from agnext.application.logging import EVENT_LOGGER_NAME
94+
95+
logger = logging.getLogger(EVENT_LOGGER_NAME + ".my_module")
96+
logger.info(MyEvent("timestamp", "message"))
97+
```
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,18 @@
1-
from ._events import DeliveryStage, LLMCallEvent, MessageEvent, MessageKind
21
from ._llm_usage import LLMUsageTracker
32

3+
ROOT_LOGGER_NAME = "agnext"
4+
"""str: Logger name used for structured event logging"""
5+
46
EVENT_LOGGER_NAME = "agnext.events"
7+
"""str: Logger name used for structured event logging"""
8+
9+
10+
TRACE_LOGGER_NAME = "agnext.trace"
11+
"""str: Logger name used for developer intended trace logging. The content and format of this log should not be depended upon."""
512

613
__all__ = [
7-
"LLMCallEvent",
14+
"ROOT_LOGGER_NAME",
815
"EVENT_LOGGER_NAME",
16+
"TRACE_LOGGER_NAME",
917
"LLMUsageTracker",
10-
"MessageEvent",
11-
"MessageKind",
12-
"DeliveryStage",
1318
]

python/src/agnext/application/logging/_llm_usage.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import logging
22

3-
from ._events import LLMCallEvent
3+
from .events import LLMCallEvent
44

55

66
class LLMUsageTracker(logging.Handler):

python/src/agnext/components/models/_openai_client.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@
3232
from openai.types.shared_params import FunctionDefinition, FunctionParameters
3333
from typing_extensions import Unpack
3434

35-
from ...application.logging import EVENT_LOGGER_NAME, LLMCallEvent
35+
from ...application.logging import EVENT_LOGGER_NAME
36+
from ...application.logging.events import LLMCallEvent
3637
from .. import (
3738
FunctionCall,
3839
Image,

python/tests/test_llm_usage.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import logging
22

3-
from agnext.application.logging import EVENT_LOGGER_NAME, LLMCallEvent, LLMUsageTracker
3+
from agnext.application.logging import EVENT_LOGGER_NAME, LLMUsageTracker
4+
from agnext.application.logging.events import LLMCallEvent
45

56

67
def test_llm_usage() -> None:

0 commit comments

Comments
 (0)