Initial commit: LogHive centralized log management system

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
v6ole
2026-05-09 14:55:14 +08:00
commit abfd07331e
54 changed files with 3816 additions and 0 deletions
+35
View File
@@ -0,0 +1,35 @@
from app.schemas.log import (
LogEntry,
LogBatchRequest,
LogQueryParams,
LogSearchResult,
LogStatsResult,
)
from app.schemas.project import (
ProjectCreate,
ProjectUpdate,
ProjectResponse,
ProjectWithToken,
)
from app.schemas.alert import (
AlertRuleCreate,
AlertRuleUpdate,
AlertRuleResponse,
AlertHistoryResponse,
)
__all__ = [
"LogEntry",
"LogBatchRequest",
"LogQueryParams",
"LogSearchResult",
"LogStatsResult",
"ProjectCreate",
"ProjectUpdate",
"ProjectResponse",
"ProjectWithToken",
"AlertRuleCreate",
"AlertRuleUpdate",
"AlertRuleResponse",
"AlertHistoryResponse",
]
+63
View File
@@ -0,0 +1,63 @@
"""Alert schemas."""
from datetime import datetime
from typing import List, Optional
from pydantic import BaseModel, Field
# ── Alert Rule ─────────────────────────────────────────────────
class AlertRuleCreate(BaseModel):
project_id: str = Field(..., min_length=1, max_length=36)
name: str = Field(..., min_length=1, max_length=128)
level: str = Field(default="warning", pattern="^(info|warning|critical)$")
field: str = Field(default="level", max_length=64)
operator: str = Field(default="gte", pattern="^(gt|gte|lt|lte|eq)$")
threshold: float = Field(..., ge=0)
window_minutes: int = Field(default=5, ge=1, le=1440)
notify_channels: List[str] = Field(default_factory=list)
class AlertRuleUpdate(BaseModel):
name: Optional[str] = Field(default=None, max_length=128)
level: Optional[str] = Field(default=None, pattern="^(info|warning|critical)$")
field: Optional[str] = Field(default=None, max_length=64)
operator: Optional[str] = Field(default=None, pattern="^(gt|gte|lt|lte|eq)$")
threshold: Optional[float] = Field(default=None, ge=0)
window_minutes: Optional[int] = Field(default=None, ge=1, le=1440)
is_enabled: Optional[bool] = None
notify_channels: Optional[List[str]] = None
class AlertRuleResponse(BaseModel):
id: str
project_id: str
name: str
level: str
field: str
operator: str
threshold: float
window_minutes: int
is_enabled: bool
notify_channels: List[str]
created_at: datetime
model_config = {"from_attributes": True}
# ── Alert History ──────────────────────────────────────────────
class AlertHistoryResponse(BaseModel):
id: str
rule_id: str
project_id: str
level: str
message: str
triggered_value: Optional[float] = None
is_acknowledged: bool
triggered_at: datetime
model_config = {"from_attributes": True}
+64
View File
@@ -0,0 +1,64 @@
"""Log entry schemas."""
from datetime import datetime
from typing import Any, Dict, List, Optional
from pydantic import BaseModel, Field
class LogEntry(BaseModel):
"""A single log entry pushed by a project."""
timestamp: Optional[datetime] = None
level: str = Field(default="info", pattern="^(debug|info|warning|error|critical)$")
logger: str = Field(default="root", max_length=128)
message: str = Field(..., min_length=1, max_length=65536)
module: Optional[str] = Field(default=None, max_length=256)
function: Optional[str] = Field(default=None, max_length=128)
line_no: Optional[int] = None
trace_id: Optional[str] = Field(default=None, max_length=64)
exception: Optional[str] = None
extra: Dict[str, Any] = Field(default_factory=dict)
class LogBatchRequest(BaseModel):
"""Batch of log entries pushed in a single request."""
project: str = Field(..., min_length=1, max_length=128)
entries: List[LogEntry] = Field(..., min_length=1, max_length=5000)
class LogQueryParams(BaseModel):
"""Parameters for log search / query."""
project: Optional[str] = None
level: Optional[str] = Field(
default=None, pattern="^(debug|info|warning|error|critical)$"
)
query: Optional[str] = Field(default=None, max_length=1024)
trace_id: Optional[str] = Field(default=None, max_length=64)
start_time: Optional[datetime] = None
end_time: Optional[datetime] = None
page: int = Field(default=1, ge=1)
page_size: int = Field(default=50, ge=1, le=1000)
sort_by: str = Field(default="timestamp", max_length=32)
sort_order: str = Field(default="desc", pattern="^(asc|desc)$")
class LogSearchResult(BaseModel):
"""Search result wrapper."""
total: int
page: int
page_size: int
hits: List[Dict[str, Any]]
class LogStatsResult(BaseModel):
"""Aggregated log statistics."""
project: Optional[str] = None
time_buckets: List[Dict[str, Any]]
level_counts: Dict[str, int]
top_loggers: List[Dict[str, Any]]
total: int
+34
View File
@@ -0,0 +1,34 @@
"""Project schemas."""
from datetime import datetime
from typing import Optional
from pydantic import BaseModel, Field
class ProjectCreate(BaseModel):
name: str = Field(..., min_length=1, max_length=128, description="Project name")
description: str = Field(default="", max_length=1024)
class ProjectUpdate(BaseModel):
name: Optional[str] = Field(default=None, max_length=128)
description: Optional[str] = Field(default=None, max_length=1024)
is_active: Optional[bool] = None
class ProjectResponse(BaseModel):
id: str
name: str
description: str
is_active: bool
created_at: datetime
updated_at: datetime
model_config = {"from_attributes": True}
class ProjectWithToken(ProjectResponse):
api_key: str
model_config = {"from_attributes": True}