Skip to content

Commit b5fe787

Browse files
authored
feat: Labeling Frontend adaptations + Backend build and deploy + Logging improvement (#55)
* feat: Front-end data annotation page adaptation to the backend API. * feat: Implement labeling configuration editor and enhance annotation task creation form * feat: add python backend build and deployment; add backend configuration for Label Studio integration and improve logging setup * refactor: remove duplicate log configuration
1 parent f3958f0 commit b5fe787

File tree

13 files changed

+190
-210
lines changed

13 files changed

+190
-210
lines changed

deployment/docker/datamate/backend.conf

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,20 @@ server {
77

88
client_max_body_size 1024M;
99

10+
location /api/synthesis/ {
11+
proxy_pass http://datamate-backend-python:18000/api/synthesis/;
12+
proxy_set_header Host $host;
13+
proxy_set_header X-Real-IP $remote_addr;
14+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
15+
}
16+
17+
location /api/annotation/ {
18+
proxy_pass http://datamate-backend-python:18000/api/annotation/;
19+
proxy_set_header Host $host;
20+
proxy_set_header X-Real-IP $remote_addr;
21+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
22+
}
23+
1024
location /api/ {
1125
proxy_pass http://datamate-backend:8080/api/;
1226
proxy_set_header Host $host;

deployment/docker/datamate/docker-compose.yml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,22 @@ services:
1515
depends_on:
1616
- datamate-database
1717

18+
# 1) backend (Python)
19+
datamate-backend-python:
20+
container_name: datamate-backend-python
21+
image: datamate-backend-python
22+
restart: on-failure
23+
privileged: true
24+
environment:
25+
- log_level=DEBUG
26+
volumes:
27+
- dataset_volume:/dataset
28+
- flow_volume:/flow
29+
- log_volume:/var/log/datamate
30+
networks: [ datamate ]
31+
depends_on:
32+
- datamate-database
33+
1834
# 2) frontend(NodePort 30000)
1935
datamate-frontend:
2036
container_name: datamate-frontend
@@ -28,6 +44,7 @@ services:
2844
networks: [ datamate ]
2945
depends_on:
3046
- datamate-backend
47+
- datamate-backend-python
3148

3249
# 3) database
3350
datamate-database:
@@ -47,6 +64,8 @@ services:
4764
- ../../../scripts/db:/docker-entrypoint-initdb.d
4865
- ./utf8.cnf:/etc/mysql/conf.d/utf8.cnf:ro
4966
- database_log_volume:/var/log/datamate/database
67+
ports:
68+
- "3306:3306"
5069
networks: [ datamate ]
5170

5271
# 3) runtime

deployment/docker/label-studio/docker-compose.yml

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
services:
22

3-
app:
3+
label-studio:
44
stdin_open: true
55
tty: true
66
image: heartexlabs/label-studio:latest
@@ -11,7 +11,7 @@ services:
1111
ports:
1212
- "8000:8000"
1313
depends_on:
14-
- db
14+
- pg-db
1515
environment:
1616
- DJANGO_DB=default
1717
- POSTGRE_NAME=postgres
@@ -23,17 +23,19 @@ services:
2323
- LOCAL_FILES_SERVING_ENABLED=true
2424
- LOCAL_FILES_DOCUMENT_ROOT=/label-studio/local
2525
- USE_USERNAME_FOR_LOGIN=true
26-
- LABEL_STUDIO_USERNAME=admin@huawei.com
27-
- LABEL_STUDIO_PASSWORD=admin1234
26+
- LABEL_STUDIO_USERNAME=admin@demo.com
27+
- LABEL_STUDIO_PASSWORD=demoadmin
2828
- LABEL_STUDIO_ENABLE_LEGACY_API_TOKEN=true
2929
- LABEL_STUDIO_USER_TOKEN=abc123abc123
30-
- LOG_LEVEL=INFO
30+
- LOG_LEVEL=DEBUG
3131
volumes:
3232
- label-studio-data:/label-studio/data:rw
3333
- dataset_volume:/label-studio/local:rw
34+
networks:
35+
- datamate
3436
command: label-studio-uwsgi
3537

36-
db:
38+
pg-db:
3739
image: pgautoupgrade/pgautoupgrade:13-alpine
3840
hostname: db
3941
restart: unless-stopped
@@ -42,9 +44,16 @@ services:
4244
- POSTGRES_USER=postgres
4345
volumes:
4446
- label-studio-db:/var/lib/postgresql/data
47+
networks:
48+
- datamate
4549

4650
volumes:
4751
label-studio-data:
4852
label-studio-db:
4953
dataset_volume:
50-
name: datamate-dataset-volume
54+
name: datamate-dataset-volume
55+
56+
networks:
57+
datamate:
58+
driver: bridge
59+
name: datamate-network

frontend/src/pages/DataAnnotation/Home/DataAnnotation.tsx

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,11 +47,10 @@ export default function DataAnnotation() {
4747
let mounted = true;
4848
(async () => {
4949
try {
50-
const cfg = await getConfigUsingGet();
51-
const url = cfg?.data?.labelStudioUrl || "";
52-
if (mounted) setLabelStudioBase((url).replace(/\/+$/, "") || null);
50+
const baseUrl = `http://${window.location.hostname}:8000`;
51+
if (mounted) setLabelStudioBase(baseUrl);
5352
} catch (e) {
54-
if (mounted) setLabelStudioBase(null);
53+
if (mounted) setLabelStudioBase(null);
5554
}
5655
})();
5756
return () => {
Lines changed: 29 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from pydantic_settings import BaseSettings
2+
from pydantic import model_validator
23
from typing import Optional, List
34
import os
45
from pathlib import Path
@@ -17,124 +18,60 @@ class Config:
1718
app_name: str = "Label Studio Adapter"
1819
app_version: str = "1.0.0"
1920
app_description: str = "Adapter for integrating Data Management System with Label Studio"
21+
22+
# 日志配置
23+
log_level: str = "INFO"
2024
debug: bool = True
25+
log_file_dir: str = "/var/log/datamate"
2126

2227
# 服务器配置
2328
host: str = "0.0.0.0"
2429
port: int = 8000
2530

2631
# CORS配置
27-
allowed_origins: List[str] = ["*"]
28-
allowed_methods: List[str] = ["*"]
29-
allowed_headers: List[str] = ["*"]
32+
# allowed_origins: List[str] = ["*"]
33+
# allowed_methods: List[str] = ["*"]
34+
# allowed_headers: List[str] = ["*"]
3035

3136
# MySQL数据库配置 (优先级1)
32-
mysql_host: Optional[str] = None
37+
mysql_host: str = "datamate-database"
3338
mysql_port: int = 3306
34-
mysql_user: Optional[str] = None
35-
mysql_password: Optional[str] = None
36-
mysql_database: Optional[str] = None
37-
38-
# PostgreSQL数据库配置 (优先级2)
39-
postgres_host: Optional[str] = None
40-
postgres_port: int = 5432
41-
postgres_user: Optional[str] = None
42-
postgres_password: Optional[str] = None
43-
postgres_database: Optional[str] = None
44-
45-
# SQLite数据库配置 (优先级3 - 兜底)
46-
sqlite_path: str = "data/labelstudio_adapter.db"
39+
mysql_user: str = "root"
40+
mysql_password: str = "password"
41+
mysql_database: str = "datamate"
4742

4843
# 直接数据库URL配置(如果提供,将覆盖上述配置)
49-
database_url: Optional[str] = None
44+
# 初始值为空字符串,在 model_validator 中会被设置为完整的 URL
45+
database_url: str = ""
46+
47+
@model_validator(mode='after')
48+
def build_database_url(self):
49+
"""如果没有提供 database_url,则根据 MySQL 配置构建"""
50+
if not self.database_url:
51+
if self.mysql_password and self.mysql_user:
52+
self.database_url = f"mysql+aiomysql://{self.mysql_user}:{self.mysql_password}@{self.mysql_host}:{self.mysql_port}/{self.mysql_database}"
53+
else:
54+
self.database_url = f"mysql+aiomysql://{self.mysql_host}:{self.mysql_port}/{self.mysql_database}"
55+
return self
5056

51-
# 日志配置
52-
log_level: str = "DEBUG"
5357

5458
# =========================
5559
# Label Studio 服务配置
5660
# =========================
57-
label_studio_base_url: str = "http://label-studio:8080"
58-
label_studio_username: Optional[str] = None # Label Studio 用户名(用于登录)
59-
label_studio_password: Optional[str] = None # Label Studio 密码(用于登录)
60-
label_studio_user_token: Optional[str] = None # Legacy Token
61+
label_studio_base_url: str = "http://label-studio:8000"
62+
label_studio_username: Optional[str] = "[email protected]" # Label Studio 用户名(用于登录)
63+
label_studio_password: Optional[str] = "demoadmin" # Label Studio 密码(用于登录)
64+
label_studio_user_token: Optional[str] = "abc123abc123" # Legacy Token
6165

62-
label_studio_local_storage_dataset_base_path: str = "/label-studio/local_files" # Label Studio容器中的本地存储基础路径
66+
label_studio_local_storage_dataset_base_path: str = "/label-studio/local" # Label Studio容器中的本地存储基础路径
6367
label_studio_file_path_prefix: str = "/data/local-files/?d=" # Label Studio本地文件服务路径前缀
6468

6569
ls_task_page_size: int = 1000
6670

67-
6871
# =========================
6972
# Data Management 服务配置
7073
# =========================
7174
dm_file_path_prefix: str = "/dataset" # DM存储文件夹前缀
7275

73-
74-
@property
75-
def computed_database_url(self) -> str:
76-
"""
77-
根据优先级自动选择数据库连接URL
78-
优先级:MySQL > PostgreSQL > SQLite3
79-
"""
80-
# 如果直接提供了database_url,优先使用
81-
if self.database_url:
82-
return self.database_url
83-
84-
# 优先级1: MySQL
85-
if all([self.mysql_host, self.mysql_user, self.mysql_password, self.mysql_database]):
86-
return f"mysql+aiomysql://{self.mysql_user}:{self.mysql_password}@{self.mysql_host}:{self.mysql_port}/{self.mysql_database}"
87-
88-
# 优先级2: PostgreSQL
89-
if all([self.postgres_host, self.postgres_user, self.postgres_password, self.postgres_database]):
90-
return f"postgresql+asyncpg://{self.postgres_user}:{self.postgres_password}@{self.postgres_host}:{self.postgres_port}/{self.postgres_database}"
91-
92-
# 优先级3: SQLite (兜底)
93-
sqlite_full_path = Path(self.sqlite_path).absolute()
94-
# 确保目录存在
95-
sqlite_full_path.parent.mkdir(parents=True, exist_ok=True)
96-
return f"sqlite+aiosqlite:///{sqlite_full_path}"
97-
98-
@property
99-
def sync_database_url(self) -> str:
100-
"""
101-
用于数据库迁移的同步连接URL
102-
将异步驱动替换为同步驱动
103-
"""
104-
async_url = self.computed_database_url
105-
106-
# 替换异步驱动为同步驱动
107-
sync_replacements = {
108-
"mysql+aiomysql://": "mysql+pymysql://",
109-
"postgresql+asyncpg://": "postgresql+psycopg2://",
110-
"sqlite+aiosqlite:///": "sqlite:///"
111-
}
112-
113-
for async_driver, sync_driver in sync_replacements.items():
114-
if async_url.startswith(async_driver):
115-
return async_url.replace(async_driver, sync_driver)
116-
117-
return async_url
118-
119-
def get_database_info(self) -> dict:
120-
"""获取数据库配置信息"""
121-
url = self.computed_database_url
122-
123-
if url.startswith("mysql"):
124-
db_type = "MySQL"
125-
elif url.startswith("postgresql"):
126-
db_type = "PostgreSQL"
127-
elif url.startswith("sqlite"):
128-
db_type = "SQLite"
129-
else:
130-
db_type = "Unknown"
131-
132-
return {
133-
"type": db_type,
134-
"url": url,
135-
"sync_url": self.sync_database_url
136-
}
137-
138-
13976
# 全局设置实例
14077
settings = Settings()

runtime/datamate-python/app/core/logging.py

Lines changed: 40 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -3,52 +3,68 @@
33
from pathlib import Path
44
from app.core.config import settings
55

6-
def setup_logging():
7-
"""配置应用程序日志"""
6+
class CenteredLevelNameFormatter(logging.Formatter):
7+
"""Center the level name in the log output"""
88

9-
# 创建logs目录
10-
log_dir = Path("logs")
11-
log_dir.mkdir(exist_ok=True)
9+
def format(self, record):
10+
# 将 levelname 居中对齐到8个字符
11+
record.levelname = record.levelname.center(8)
12+
return super().format(record)
13+
14+
def setup_logging():
1215

13-
# 配置日志格式
14-
log_format = "%(asctime)s - %(name)s - [%(levelname)s] - %(message)s"
16+
log_format = "%(asctime)s [%(levelname)s] - %(name)s - %(message)s"
1517
date_format = "%Y-%m-%d %H:%M:%S"
1618

17-
# 创建处理器
1819
console_handler = logging.StreamHandler(sys.stdout)
1920
console_handler.setLevel(getattr(logging, settings.log_level.upper()))
2021

22+
log_dir = Path(settings.log_file_dir)
23+
log_dir.mkdir(exist_ok=True)
2124
file_handler = logging.FileHandler(
22-
log_dir / "app.log",
25+
log_dir / "python-backend.log",
2326
encoding="utf-8"
2427
)
2528
file_handler.setLevel(getattr(logging, settings.log_level.upper()))
2629

27-
error_handler = logging.FileHandler(
28-
log_dir / "error.log",
29-
encoding="utf-8"
30-
)
31-
error_handler.setLevel(logging.ERROR)
32-
33-
# 设置格式
34-
formatter = logging.Formatter(log_format, date_format)
30+
# Style setting - Centered level names
31+
formatter = CenteredLevelNameFormatter(log_format, date_format)
3532
console_handler.setFormatter(formatter)
3633
file_handler.setFormatter(formatter)
37-
error_handler.setFormatter(formatter)
34+
file_handler.setFormatter(formatter)
3835

39-
# 配置根日志器
36+
# Root Logger
4037
root_logger = logging.getLogger()
4138
root_logger.setLevel(getattr(logging, settings.log_level.upper()))
4239
root_logger.addHandler(console_handler)
4340
root_logger.addHandler(file_handler)
44-
root_logger.addHandler(error_handler)
41+
root_logger.addHandler(file_handler)
42+
43+
# Uvicorn
44+
uvicorn_logger = logging.getLogger("uvicorn")
45+
uvicorn_logger.handlers.clear()
46+
uvicorn_logger.addHandler(console_handler)
47+
uvicorn_logger.setLevel(logging.INFO)
4548

46-
# 配置第三方库日志级别(减少详细日志)
47-
logging.getLogger("uvicorn").setLevel(logging.ERROR)
48-
logging.getLogger("sqlalchemy.engine").setLevel(logging.ERROR) # 隐藏SQL查询日志
49+
uvicorn_access = logging.getLogger("uvicorn.access")
50+
uvicorn_access.handlers.clear()
51+
uvicorn_access.addHandler(console_handler)
52+
uvicorn_access.setLevel(logging.DEBUG)
53+
54+
uvicorn_error = logging.getLogger("uvicorn.error")
55+
uvicorn_error.handlers.clear()
56+
uvicorn_error.addHandler(console_handler)
57+
uvicorn_error.setLevel(logging.ERROR)
58+
59+
# SQLAlchemy (ERROR only)
60+
sqlalchemy_logger = logging.getLogger("sqlalchemy.engine")
61+
sqlalchemy_logger.setLevel(logging.ERROR)
62+
sqlalchemy_logger.addHandler(console_handler)
63+
sqlalchemy_logger.setLevel(logging.ERROR)
64+
65+
# Minimize noise from HTTPX and HTTPCore
4966
logging.getLogger("httpx").setLevel(logging.ERROR)
5067
logging.getLogger("httpcore").setLevel(logging.ERROR)
5168

5269
def get_logger(name: str) -> logging.Logger:
53-
"""获取指定名称的日志器"""
5470
return logging.getLogger(name)

0 commit comments

Comments
 (0)