Skip to content

Commit 6c4dbe1

Browse files
committed
feat: add custom request log
1 parent a175bf1 commit 6c4dbe1

File tree

4 files changed

+73
-4
lines changed

4 files changed

+73
-4
lines changed

src/memos/api/context/dependencies.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import logging
2+
import os
23

34
from fastapi import Depends, Header, Request
45

@@ -24,6 +25,13 @@ def get_trace_id_from_header(
2425
return g_trace_id or x_trace_id or trace_id
2526

2627

28+
def generate_trace_id() -> str:
29+
"""
30+
Get a random trace_id.
31+
"""
32+
return os.urandom(16).hex()
33+
34+
2735
def get_request_context(
2836
request: Request, trace_id: str | None = Depends(get_trace_id_from_header)
2937
) -> RequestContext:
@@ -57,6 +65,9 @@ def get_g_object(trace_id: str | None = Depends(get_trace_id_from_header)) -> G:
5765
This creates a RequestContext and sets it globally for access
5866
throughout the request lifecycle.
5967
"""
68+
if trace_id is None:
69+
trace_id = generate_trace_id()
70+
6071
g = RequestContext(trace_id=trace_id)
6172
set_request_context(g)
6273
logger.info(f"Request g object created with trace_id: {g.trace_id}")

src/memos/api/product_api.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import logging
22

3-
from fastapi import FastAPI
3+
from fastapi import Depends, FastAPI
44

5+
from memos.api.context.dependencies import get_g_object
56
from memos.api.exceptions import APIExceptionHandler
67
from memos.api.routers.product_router import router as product_router
78

@@ -14,6 +15,7 @@
1415
title="MemOS Product REST APIs",
1516
description="A REST API for managing multiple users with MemOS Product.",
1617
version="1.0.0",
18+
dependencies=[Depends(get_g_object)],
1719
)
1820

1921
# Include routers

src/memos/api/start_api.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,12 @@
44
from typing import Any, Generic, TypeVar
55

66
from dotenv import load_dotenv
7-
from fastapi import FastAPI
7+
from fastapi import Depends, FastAPI
88
from fastapi.requests import Request
99
from fastapi.responses import JSONResponse, RedirectResponse
1010
from pydantic import BaseModel, Field
1111

12+
from memos.api.context.dependencies import get_g_object
1213
from memos.configs.mem_os import MOSConfig
1314
from memos.mem_os.main import MOS
1415
from memos.mem_user.user_manager import UserManager, UserRole
@@ -76,6 +77,7 @@ def get_mos_instance():
7677
title="MemOS REST APIs",
7778
description="A REST API for managing and searching memories using MemOS.",
7879
version="1.0.0",
80+
dependencies=[Depends(get_g_object)],
7981
)
8082

8183

src/memos/log.py

Lines changed: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,22 @@
11
import logging
2+
import os
23

4+
from contextlib import suppress
35
from logging.config import dictConfig
46
from pathlib import Path
57
from sys import stdout
68

9+
import requests
10+
11+
from dotenv import load_dotenv
12+
713
from memos import settings
14+
from memos.api.context.context import get_current_trace_id
815

916

17+
# Load environment variables
18+
load_dotenv()
19+
1020
selected_log_level = logging.DEBUG if settings.DEBUG else logging.WARNING
1121

1222

@@ -21,6 +31,16 @@ def _setup_logfile() -> Path:
2131
return logfile
2232

2333

34+
class CustomLoggerRequestHandler(logging.Handler):
35+
def emit(self, record):
36+
if os.getenv("CUSTOM_LOGGER_URL") is None:
37+
return
38+
39+
if record.levelno == logging.INFO or record.levelno == logging.ERROR:
40+
with suppress(Exception):
41+
log_custom_request(record.getMessage())
42+
43+
2444
LOGGING_CONFIG = {
2545
"version": 1,
2646
"disable_existing_loggers": False,
@@ -48,13 +68,17 @@ def _setup_logfile() -> Path:
4868
"class": "logging.handlers.RotatingFileHandler",
4969
"filename": _setup_logfile(),
5070
"maxBytes": 1024**2 * 10,
51-
"backupCount": 10,
71+
"backupCount": 3,
5272
"formatter": "standard",
5373
},
74+
"customRequest": {
75+
"level": "INFO",
76+
"class": "memos.log.CustomLoggerRequestHandler",
77+
},
5478
},
5579
"root": { # Root logger handles all logs
5680
"level": logging.DEBUG if settings.DEBUG else logging.INFO,
57-
"handlers": ["console", "file"],
81+
"handlers": ["console", "file", "customRequest"],
5882
},
5983
"loggers": {
6084
"memos": {
@@ -76,3 +100,33 @@ def get_logger(name: str | None = None) -> logging.Logger:
76100
if name:
77101
return parent_logger.getChild(name)
78102
return parent_logger
103+
104+
105+
def log_custom_request(message: str):
106+
logger_url = os.getenv("CUSTOM_LOGGER_URL")
107+
token = os.getenv("CUSTOM_LOGGER_TOKEN")
108+
109+
trace_id = get_current_trace_id()
110+
111+
print(f"trace_id: {trace_id}")
112+
113+
headers = {
114+
"Content-Type": "application/json",
115+
}
116+
post_content = {
117+
"message": message,
118+
}
119+
120+
for key, value in os.environ.items():
121+
if key.startswith("CUSTOM_LOGGER_ATTRIBUTE_"):
122+
attribute_key = key[len("CUSTOM_LOGGER_ATTRIBUTE_") :].lower()
123+
post_content[attribute_key] = value
124+
125+
if token is not None:
126+
headers["Authorization"] = token
127+
128+
if trace_id is not None:
129+
headers["traceId"] = trace_id
130+
post_content["trace_id"] = trace_id
131+
132+
requests.post(url=logger_url, headers=headers, json=post_content, timeout=5)

0 commit comments

Comments
 (0)