File size: 1,779 Bytes
ed6f503
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
import asyncio
import json
from datetime import datetime
from fastapi import FastAPI, Request
from fastapi.responses import StreamingResponse
from pydantic import BaseModel

app = FastAPI(title="HF Log Streams Service")

# 全局广播队列(支持无限多客户端同时订阅)
log_queue: asyncio.Queue = asyncio.Queue()

class LogEntry(BaseModel):
    level: str = "info"
    message: str
    source: str = "external"
    extra: dict | None = None

@app.post("/ingest")
async def ingest_log(log: LogEntry):
    timestamp = datetime.now().isoformat()
    entry = {
        "timestamp": timestamp,
        "level": log.level.upper(),
        "message": log.message,
        "source": log.source,
        "extra": log.extra or {}
    }
    
    # 1. 自动出现在 Space 官方 Logs(实时可见)
    print(f"[{timestamp}] [{entry['level']}] {log.message} | source={log.source}")
    
    # 2. 广播给所有 SSE 订阅者
    await log_queue.put(entry)
    
    return {"status": "ok", "timestamp": timestamp}

@app.get("/stream")
async def stream_logs(request: Request):
    async def event_generator():
        while True:
            if await request.is_disconnected():
                break
            try:
                log_entry = await asyncio.wait_for(log_queue.get(), timeout=30.0)
                yield f"data: {json.dumps(log_entry)}\n\n"
            except asyncio.TimeoutError:
                yield ": ping\n\n"  # 保持连接
    
    return StreamingResponse(
        event_generator(),
        media_type="text/event-stream",
        headers={"Cache-Control": "no-cache", "Connection": "keep-alive"}
    )

@app.get("/")
async def root():
    return {"message": "Log Streams 服务已就绪!POST /ingest 写日志,GET /stream 订阅实时流"}