Skip to content

前后端统一日志系统 + MCP Server 设计方案 #8

@hailaz

Description

@hailaz

前后端统一日志系统 + MCP Server 设计方案

创建日期:2026-05-11
状态:设计完成,待实施


Context

问题

  1. 前端没有统一日志系统,只有散落的console.*调用(约26个文件),没有全局错误边界,没有错误上报能力
  2. 后端有完善的日志封装(pkg/logger/),但前后端日志完全割裂,无法通过traceID串联
  3. Claude Code无法直接访问项目日志,调试时需要手动查看终端或日志文件

目标

  1. 创建前端统一logger模块,与后端日志格式对齐
  2. 前端日志上报到后端,写入同一日志文件
  3. 创建MCP Server,让Claude Code能实时查看和过滤日志

一、整体架构

Browser (Vue 3 App)                          Go Backend (lina-core)
+---------------------+                      +---------------------------+
|  Frontend Logger    |  -- POST /api/v1/    |  Log Receive API          |
|  (packages/effects/ |     logs/batch -->   |  (api/log + controller)   |
|   logger/)          |                      |                           |
|                     |                      |  Backend Logger           |
|  - debug/info/warn/ |                      |  (pkg/logger/)            |
|    error            |                      |  - glog wrapper           |
|  - structured JSON  |                      |  - structured + traceID   |
|  - traceID from resp|                      |                           |
|  - batch buffer +   |                      |  Log Files (disk)         |
|    periodic flush   |                      |  {Y-m-d}.log              |
+---------------------+                      +---------------------------+
                                                        |
                                                        v
                                               +---------------------------+
                                               |  MCP Server               |
                                               |  (hack/tools/log-mcp/)    |
                                               |                           |
                                               |  Tools:                   |
                                               |  - list_logs              |
                                               |  - tail_logs (SSE/stream) |
                                               |  - filter_logs            |
                                               |  - get_log_stats          |
                                               |                           |
                                               |  Sources:                 |
                                               |  - Backend log files      |
                                               |  - Frontend log buffer    |
                                               |    (via API query)        |
                                               +---------------------------+
                                                        |
                                                        v
                                               Claude Code (MCP Client)

数据流

[前端] Logger.info("message")
    ↓ 内存缓冲
[前端] LogTransport.flush()
    ↓ POST /api/v1/logs/batch
[后端] LogController.Batch()
    ↓ logger.Infof(ctx, "[FRONTEND] [info] message")
[后端] GoFrame glog → 写入日志文件
    ↑ 读取
[MCP Server] logreader → 过滤/解析
    ↓ stdio
[Claude Code] 查看日志

二、TraceID 传递机制

[Backend generates TraceID]
        |
        v
GoFrame HTTP middleware adds TraceID to context
        |
        v
Response middleware adds X-Trace-Id header to every HTTP response
        |
        v
Frontend Axios response interceptor extracts X-Trace-Id
        |
        v
Frontend Logger stores currentTraceId (module-level)
        |
        v
Every LogEntry includes traceId field
        |
        v
POST /api/v1/logs/batch sends traceId to backend
        |
        v
Backend log handler writes [FRONTEND] log with traceId
        |
        v
MCP server can filter all logs (backend + frontend) by the same traceId

2.1 后端:暴露TraceID响应头

修改文件apps/lina-core/internal/service/middleware/middleware_response.go

Response方法中,r.Response.WriteJson(response)之前添加:

// Expose traceID in response header for frontend correlation
if traceID := r.GetTraceId(); traceID != "" {
    r.Response.Header().Set("X-Trace-Id", traceID)
}

GoFrame的r.GetTraceId()在请求上下文中自动注入traceID(当logger.extensions.traceIDEnabled: true时)。

2.2 前端:提取并存储TraceID

apps/web-antd/src/api/request.ts添加响应拦截器:

let currentTraceId: string | undefined;

client.addResponseInterceptor({
  fulfilled: (response) => {
    const traceId = response.headers?.['x-trace-id'];
    if (traceId) {
      currentTraceId = traceId;
    }
    return response;
  },
});

export function getCurrentTraceId() { return currentTraceId; }

2.3 配置开关

TraceID传播遵循现有的logger.extensions.traceIDEnabled配置。禁用时后端不生成traceID,前端logger无traceID运行。


三、后端日志接收API

3.1 API定义

新建文件apps/lina-core/api/log/v1/log_batch.go

package v1

import "github.com/gogf/gf/v2/frame/g"

// BatchReq accepts a batch of frontend log entries for ingestion.
type BatchReq struct {
    g.Meta  `path:"/logs/batch" method:"post" tags:"Log Management" summary:"Ingest frontend log batch" dc:"Accepts a batch of frontend browser log entries for unified server-side storage and observability."`
    Entries []LogEntry `json:"entries" dc:"Array of frontend log entries" eg:"[{\"level\":\"info\",\"message\":\"test\"}]"`
}

// LogEntry represents a single frontend log record.
type LogEntry struct {
    Level     string                 `json:"level" dc:"Log level: debug, info, warn, error" eg:"info"`
    Message   string                 `json:"message" dc:"Log message content" eg:"user login succeeded"`
    TraceId   string                 `json:"traceId,omitempty" dc:"Request trace ID from backend response header" eg:"abc123"`
    Module    string                 `json:"module,omitempty" dc:"Frontend module name" eg:"auth"`
    Timestamp string                 `json:"timestamp" dc:"ISO 8601 timestamp" eg:"2026-05-11T10:30:00Z"`
    Context   map[string]interface{} `json:"context,omitempty" dc:"Additional structured context fields"`
    Stack     string                 `json:"stack,omitempty" dc:"Error stack trace if applicable"`
}

// BatchRes confirms log ingestion.
type BatchRes struct {
    Accepted int `json:"accepted" dc:"Number of entries accepted" eg:"5"`
}

3.2 Controller

新建文件apps/lina-core/internal/controller/log/log_new.go

package log

import logapi "lina-core/api/log"

// ControllerV1 handles frontend log ingestion.
type ControllerV1 struct{}

// NewV1 creates a log controller instance.
func NewV1() logapi.IV1 {
    return &ControllerV1{}
}

新建文件apps/lina-core/internal/controller/log/log_v1_batch.go

Controller逻辑:

  • 遍历entries,用logger.Infof(ctx, "[FRONTEND] [%s] %s %v", level, message, context)写入
  • 带traceID的entry会自动被GoFrame日志系统捕获
  • 返回accepted数量

3.3 路由注册

修改文件apps/lina-core/internal/cmd/cmd_http_routes.go

bindPublicStaticAPIRoutes调用中添加logCtrl.Batch(公开路由,无需认证):

logCtrl := logctrl.NewV1()

bindPublicStaticAPIRoutes(
    group,
    healthCtrl.Get,
    authCtrl.Login,
    logCtrl.Batch,  // 新增
    i18nCtrl.RuntimeLocales,
    // ...
)

公开路由的原因:前端可能在认证失败时也需要上报日志。

3.4 日志格式

前端日志写入后端日志文件时使用[FRONTEND]前缀标记。结构化JSON模式下的示例:

{
  "time": "2026-05-11T10:30:00+08:00",
  "level": "INFO",
  "content": "[FRONTEND] [info] user login succeeded",
  "traceId": "abc123",
  "caller": "log/log_v1_batch.go:45"
}

3.5 配置

修改文件apps/lina-core/manifest/config/config.yaml

# Frontend log ingestion configuration
logIngest:
  # Whether to accept frontend log entries
  enabled: true
  # Maximum entries per batch request
  maxBatchSize: 100
  # Rate limit: max batches per minute per IP
  rateLimit: 60

四、前端Logger模块

4.1 文件位置

apps/lina-vben/packages/effects/logger/

requesthooks等高层effects模块位置一致。

packages/effects/logger/
  package.json
  src/
    index.ts          # 公开API导出
    logger.ts         # Logger类
    types.ts          # 类型定义
    transport.ts      # 批量缓冲 + 定时上报
    trace.ts          # traceID提取

4.2 类型定义

// types.ts
export enum LogLevel {
  DEBUG = 'debug',
  INFO = 'info',
  WARN = 'warn',
  ERROR = 'error',
}

export interface LogEntry {
  level: LogLevel;
  message: string;
  timestamp: string;       // ISO 8601
  traceId?: string;
  source: 'frontend';
  module?: string;         // caller module name, e.g. 'user', 'auth'
  context?: Record<string, unknown>;
  stack?: string;          // error stack trace when applicable
}

export interface LoggerConfig {
  level: LogLevel;              // minimum level to emit
  enableConsole: boolean;       // mirror to console in dev
  enableTransport: boolean;     // send to backend
  bufferSize: number;           // max entries before forced flush
  flushInterval: number;        // ms between periodic flushes
  endpoint: string;             // backend API path
}

4.3 Logger类

// logger.ts
class Logger {
  constructor(config?: Partial<LoggerConfig>)

  // 包级风格API,对齐后端模式
  debug(message: string, context?: Record<string, unknown>): void
  info(message: string, context?: Record<string, unknown>): void
  warn(message: string, context?: Record<string, unknown>): void
  error(message: string, context?: Record<string, unknown>): void

  // 格式化版本(对齐后端 Infof/Warningf 风格)
  debugf(format: string, ...args: unknown[]): void
  infof(format: string, ...args: unknown[]): void
  warnf(format: string, ...args: unknown[]): void
  errorf(format: string, ...args: unknown[]): void

  // 模块级子logger
  child(module: string): Logger

  // 立即刷新待发送条目
  flush(): Promise<void>

  // 手动设置traceID(通常自动提取)
  setTraceId(traceId: string): void
}

export function createLogger(config?: Partial<LoggerConfig>): Logger

使用示例

import { createLogger } from '@vben/logger';

const logger = createLogger({ level: LogLevel.DEBUG });

// 简单用法
logger.info('user login succeeded', { username: 'admin', ip: '127.0.0.1' });

// 格式化用法
logger.infof('user login succeeded username=%s ip=%s', 'admin', '127.0.0.1');

// 模块级子logger
const authLogger = logger.child('auth');
authLogger.warn('token about to expire', { userId: 123, expiresIn: '5m' });

// 错误带堆栈
try {
  // ...
} catch (err) {
  logger.error('request failed', { url: '/api/v1/user', error: err });
}

4.4 Transport

// transport.ts
class LogTransport {
  private buffer: LogEntry[] = [];
  private flushTimer: ReturnType<typeof setInterval> | null = null;

  constructor(config: LoggerConfig)

  // 添加到缓冲区;缓冲区满时触发flush
  enqueue(entry: LogEntry): void

  // POST缓冲条目到后端,清空缓冲区
  async flush(): Promise<void>

  // 启动定时flush
  start(): void

  // 停止定时器,flush剩余
  stop(): Promise<void>
}
  • 内存缓冲,达到bufferSize(默认50条)或定时(默认30秒)触发flush
  • POST到/api/v1/logs/batch
  • 页面卸载时用navigator.sendBeacon兜底

4.5 TraceID提取

// trace.ts
export function extractTraceId(headers: Record<string, string>): string | undefined {
  return headers['x-trace-id'] || headers['X-Trace-Id'] || undefined;
}

4.6 package.json

{
  "name": "@vben/logger",
  "version": "5.6.0",
  "type": "module",
  "sideEffects": ["**/*.css"],
  "exports": {
    ".": {
      "types": "./src/index.ts",
      "default": "./src/index.ts"
    }
  },
  "dependencies": {
    "@vben/request": "workspace:*"
  }
}

五、前端集成

5.1 bootstrap.ts 安装全局错误处理

修改文件apps/lina-vben/apps/web-antd/src/bootstrap.ts

createApp(App)之后添加:

import { createLogger } from '@vben/logger';

const logger = createLogger({ level: 'info' });

// Vue全局错误处理器
app.config.errorHandler = (err, instance, info) => {
  logger.error('uncaught vue error', {
    error: err instanceof Error ? err.message : String(err),
    stack: err instanceof Error ? err.stack : undefined,
    componentInfo: info,
  });
};

// 未处理的Promise rejection
window.addEventListener('unhandledrejection', (event) => {
  logger.error('unhandled promise rejection', {
    reason: event.reason?.message || String(event.reason),
  });
});

5.2 迁移console调用

将26个文件中的console.log/warn/error替换为logger调用:

// Before
console.error('component adapter not found:', name);

// After
import { logger } from '#/logger';
logger.error('component adapter not found', { name });

关键文件:

  • src/api/request.ts
  • src/adapter/component/index.ts
  • src/components/upload/src/hook.ts
  • src/plugins/access-filter.ts
  • src/plugins/slot-registry.ts
  • 多个views/system/下的组件

六、MCP Server

6.1 技术选型:Go

理由:

  • 项目Go-first,所有后端工具都在Go(hack/tools/
  • MCP Go SDK成熟(github.com/mark3labs/mcp-go
  • 需要读取GoFrame日志文件,Go处理文件系统自然
  • 可以添加到现有go.work工作区
  • 独立二进制,无需Node.js依赖

6.2 文件位置

hack/tools/log-mcp/
  main.go           # MCP server入口
  server.go         # MCP server配置和工具注册
  tool_logs.go      # list_logs, filter_logs工具实现
  tool_tail.go      # tail_logs工具实现
  tool_stats.go     # get_log_stats工具实现
  logreader.go      # 日志文件发现、解析、过滤
  config.go         # 配置
  go.mod
  go.sum

添加到go.work

use (
    // ...existing...
    ./hack/tools/log-mcp
)

6.3 配置

hack/tools/log-mcp/config.yaml

logMcp:
  # 后端日志文件目录(与主配置的logger.path相同)
  backendLogPath: ""
  # 后端日志文件模式
  backendLogFile: "{Y-m-d}.log"
  # LinaPro后端API地址(查询前端日志)
  backendApiUrl: "http://localhost:8080"
  # 单次查询最大返回行数
  maxLines: 1000
  # Tail轮询间隔
  tailInterval: "2s"

6.4 MCP工具定义

Tool 1: list_logs

列出可用日志文件。

{
  "name": "list_logs",
  "description": "List available log files from backend and frontend sources",
  "inputSchema": {
    "type": "object",
    "properties": {
      "source": {
        "type": "string",
        "enum": ["backend", "frontend", "all"],
        "description": "Which log source to list",
        "default": "all"
      },
      "date": {
        "type": "string",
        "description": "Filter by date (YYYY-MM-DD), defaults to today"
      }
    }
  }
}

返回:[{ filename, source, date, size, lineCount }]

Tool 2: filter_logs

查询和过滤历史日志。

{
  "name": "filter_logs",
  "description": "Query and filter log entries from backend or frontend logs",
  "inputSchema": {
    "type": "object",
    "properties": {
      "source": { "type": "string", "enum": ["backend", "frontend", "all"], "default": "all" },
      "level": { "type": "string", "enum": ["debug", "info", "warn", "error", "all"], "default": "all" },
      "keyword": { "type": "string", "description": "Search keyword (case-insensitive)" },
      "traceId": { "type": "string", "description": "Filter by specific trace ID" },
      "module": { "type": "string", "description": "Filter by frontend module name" },
      "since": { "type": "string", "description": "Start time (ISO 8601 or relative like '1h', '30m')" },
      "until": { "type": "string", "description": "End time (ISO 8601)" },
      "limit": { "type": "number", "description": "Max entries to return (default 100, max 1000)", "default": 100 }
    }
  }
}

返回:匹配的日志条目数组。

Tool 3: tail_logs

实时日志跟踪(tail -f模式)。

{
  "name": "tail_logs",
  "description": "Stream new log entries in real-time (tail -f mode)",
  "inputSchema": {
    "type": "object",
    "properties": {
      "source": { "type": "string", "enum": ["backend", "frontend", "all"], "default": "all" },
      "level": { "type": "string", "enum": ["debug", "info", "warn", "error", "all"], "default": "all" },
      "keyword": { "type": "string", "description": "Only show lines containing this keyword" },
      "follow": { "type": "boolean", "description": "true to start streaming, false to stop", "default": true }
    }
  }
}

实现:MCP server维护tail状态,follow=true时启动goroutine轮询文件末尾,follow=false时停止。

Tool 4: get_log_stats

日志统计。

{
  "name": "get_log_stats",
  "description": "Get log statistics: entry counts by level, error rate, recent error summary",
  "inputSchema": {
    "type": "object",
    "properties": {
      "source": { "type": "string", "enum": ["backend", "frontend", "all"], "default": "all" },
      "since": { "type": "string", "description": "Start time for stats (default: last 1 hour)" }
    }
  }
}

返回:{ total, byLevel: {debug, info, warn, error}, recentErrors: [...], topModules: [...] }

6.5 日志文件读取

logreader.go负责:

  1. 扫描日志目录,发现{Y-m-d}.log文件
  2. 解析文本格式(2026-05-11 10:30:00.000 [INFO] content {trace-id})和JSON格式
  3. 按级别、关键词、traceID、时间范围过滤
  4. 文件末尾跟踪(os.File.Seek + 轮询)
  5. 检测文件轮换(新一天、文件重命名)

6.6 Claude Code集成

.claude/settings.json中配置:

{
  "mcpServers": {
    "lina-logs": {
      "command": "go",
      "args": ["run", ".", "--log-path", "./temp/logs"],
      "cwd": "hack/tools/log-mcp"
    }
  }
}

七、关键文件清单

新建文件

文件 说明
apps/lina-vben/packages/effects/logger/package.json 前端logger包配置
apps/lina-vben/packages/effects/logger/src/index.ts 公开API导出
apps/lina-vben/packages/effects/logger/src/logger.ts Logger类实现
apps/lina-vben/packages/effects/logger/src/types.ts 类型定义
apps/lina-vben/packages/effects/logger/src/transport.ts 批量缓冲上报
apps/lina-vben/packages/effects/logger/src/trace.ts traceID提取
apps/lina-core/api/log/v1/log_batch.go 日志接收API DTO
apps/lina-core/internal/controller/log/log_new.go Controller构造函数
apps/lina-core/internal/controller/log/log_v1_batch.go 批量接收实现
hack/tools/log-mcp/main.go MCP server入口
hack/tools/log-mcp/server.go MCP server配置
hack/tools/log-mcp/tool_logs.go list/filter工具
hack/tools/log-mcp/tool_tail.go tail工具
hack/tools/log-mcp/tool_stats.go 统计工具
hack/tools/log-mcp/logreader.go 日志文件读取
hack/tools/log-mcp/config.go 配置
hack/tools/log-mcp/go.mod Go模块定义

修改文件

文件 修改内容
apps/lina-core/internal/service/middleware/middleware_response.go 添加X-Trace-Id响应头
apps/lina-core/internal/cmd/cmd_http_routes.go 注册log controller路由
apps/lina-core/manifest/config/config.yaml 添加logIngest配置段
apps/lina-vben/apps/web-antd/src/api/request.ts 添加traceID拦截器
apps/lina-vben/apps/web-antd/src/bootstrap.ts 安装全局错误处理器
apps/lina-vben/apps/web-antd/package.json 添加@vben/logger依赖
go.work 添加log-mcp模块
26个console.*文件 迁移到logger

八、实现步骤

Phase 1: 后端 - TraceID 响应头暴露(1-2小时)

  1. 修改middleware_response.go设置X-Trace-Id响应头
  2. curl验证响应头包含X-Trace-Id
  3. 编写单元测试

Phase 2: 后端 - 日志接收API(2-3小时)

  1. 创建api/log/ DTO文件
  2. 实现controller/log/批量接收
  3. cmd_http_routes.go注册路由(公开组)
  4. 添加logIngest配置段
  5. 编写单元测试
  6. 运行make ctrl生成骨架

Phase 3: 前端 - Logger模块(3-4小时)

  1. 创建packages/effects/logger/包结构
  2. 实现Logger类(级别过滤、子logger、格式化输出)
  3. 实现LogTransport(批量缓冲、定时flush、sendBeacon兜底)
  4. 实现trace.ts
  5. 编写单元测试
  6. 发布为@vben/logger workspace包

Phase 4: 前端 - 集成(2-3小时)

  1. 添加@vben/logger依赖到apps/web-antd/package.json
  2. 修改request.ts添加traceID响应拦截器
  3. 修改bootstrap.ts创建全局logger实例并安装错误处理器
  4. 迁移26个console.*调用到logger
  5. 配置transport POST到/api/v1/logs/batch

Phase 5: MCP Server(4-5小时)

  1. 创建hack/tools/log-mcp/ Go模块
  2. 实现logreader.go:文件发现、文本/JSON解析、过滤、跟踪
  3. 实现tool_logs.golist_logsfilter_logs
  4. 实现tool_tail.gotail_logs(follow/unfollow)
  5. 实现tool_stats.goget_log_stats聚合
  6. 实现server.go:MCP server设置、工具注册、stdio传输
  7. 添加到go.work
  8. 测试Claude Code MCP客户端
  9. 编写README文档

九、验证方式

Phase 验证方法
Phase 1 curl -v http://localhost:8080/api/v1/health 检查响应头包含X-Trace-Id
Phase 2 curl -X POST http://localhost:8080/api/v1/logs/batch -d '{"entries":[{"level":"info","message":"test"}]}' 返回accepted=1,日志文件包含[FRONTEND]
Phase 3 前端代码import { createLogger } from '@vben/logger',调用logger.info('test'),Network面板有请求
Phase 4 触发前端错误,全局错误处理器捕获并上报
Phase 5 启动MCP server,Claude Code调用list_logsfilter_logstail_logs工具

本方案遵循LinaPro项目的SDD开发流程,实施前建议先创建OpenSpec变更提案。

Metadata

Metadata

Assignees

No one assigned

    Labels

    featureFeasible feature request reviewed by lina-community-issue-review

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions