Method Trace Log
Non-intrusive AOP method tracing + trace tree + alerting for Spring Boot. Includes a standalone MCP server (methodTraceLog-mcp) that exposes the same capabilities to AI agents over stdio.


Features
Quick Start
Add the dependency
<dependency>
<groupId>io.github.wb04307201</groupId>
<artifactId>methodTraceLog-spring-boot-starter</artifactId>
<version>1.1.0</version>
</dependency>
Minimal configuration
method-trace-log:
log:
enable: true
file:
enable: true
path: ./logs
security:
api-key: change-me-in-production # required in production; empty disables auth (dev only)
management:
endpoints:
web:
exposure:
include: methodtrace # for the /actuator/methodtrace panel data
Open the panel
http://localhost:8080/methodTraceLog/panel — single page with 4 tabs: 概览 (overview) / 调用记录 (trace search + export) / 日志文件 (file viewer + real-time tail) / 反编译 (CFR decompile).
Configuration Reference
method-trace-log:
log:
enable: true # AOP master switch
sample-rate: 1.0 # 0.0 ~ 1.0; child spans inherit parent decision (clamped on startup)
exclude-patterns: [] # method-name blacklist (case-insensitive equals match); hit → proceed() directly, no events emitted. e.g. [equals, hashCode, toString, canEqual]
service-calls: # start-up enable flags
- { name: CustomLog, enable: false } # built-in services: SimpleLogService / SimpleMonitorService / AlertingService / CustomLog
trace-store: # where the in-memory tree lives
type: in-memory # in-memory | file | none
path: ./trace-store # only when type=file (auto-creates yyyy-MM-dd subdirs)
max-traces: 1000 # recent map cap (in-memory & file memory cache)
ttl-millis: 28800000 # 8h; clean() drops disk files older than this
rebuild-index-on-start: false # type=file only; rebuilds traceId→file index at startup (slows boot on large dirs)
file:
enable: true
path: ./logs
allowed-extensions: [.log, .txt, .out]
scan-lines: 10000 # stream-scan line cap; Files.lines() + limit() is lazy, so the file itself can be arbitrarily large — no size check
max-file-size: 100MB # per-file size cap (default; set to 0/null = no limit)
total-size-cap: 10GB # cumulative cap across all rolled files in the dir
# log-pattern: (\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{3})\s+\[([^\]]+)\]\s+(\w+)\s+([^\s]+)\s*-\s*(.*)
security:
api-key: change-me-in-production # empty = no auth
session:
ttl-millis: 28800000 # 8h sliding session for browser cookies
cors: # CORS for browser panels on different origins (opt-in: empty allowed-origins = disabled)
allowed-origins: [] # ["https://your-panel.example.com"]; ["*"] = all (incompatible with credentials)
allowed-methods: [GET, POST, OPTIONS, DELETE, PUT]
allowed-headers: [Content-Type, X-Api-Key, Authorization]
allow-credentials: false # true requires allowed-origins NOT be "*"
max-age: 0 # preflight cache seconds (0 = always re-validate)
decompile:
timeout-seconds: 10 # CFR daemon-thread timeout
otel: # requires opentelemetry-sdk on classpath
enable: false
endpoint: http://localhost:4318/v1/traces
service-name: method-trace-log
service-namespace: "" # Resource service.namespace label
export-delay-millis: 5000 # OTLP client built-in batching delay
max-queue-size: 2048
max-export-batch-size: 512
export-timeout-millis: 30000
propagate: # W3C traceparent propagation
http-inbound: true # TraceContextFilter reads traceparent
rest-client-outbound: true # RestClient.Builder interceptor
rest-template-interceptor: true # exposes a RestTemplate interceptor bean
alerting: # exception alerting — opt-in; off by default
enable: false # true registers AlertingService as an ICallService
webhook-url: "" # empty = log only; non-empty POSTs events (best-effort, async)
threshold:
error-count: 10 # errors per class#method within window to fire
window-seconds: 60 # sliding-window length
cooldown-seconds: 300 # suppress repeat alerts for the same key
classes: [] # whitelist prefix list; empty = alert on every class
HTTP Surface
All routes require X-Api-Key (or MTRACE_SESSION cookie) when security.api-key is non-empty, except /methodTraceLog/panel (HTML).
MDC Trace IDs
LogAspect puts traceid / spanid / pspanid into SLF4J MDC. Use them in your logback pattern to correlate logs across the call chain:
<pattern>%d{HH:mm:ss.SSS} [%thread] [trace=%X{traceid} span=%X{spanid}] %-5level %logger - %msg%n</pattern>
For HTTP boundaries, TraceContextFilter reads traceparent on the way in, and the RestClient interceptor writes it on the way out — upstream and downstream traceids join automatically.
Web Panel Authentication
When security.api-key is non-empty, the panel UI uses a top banner (not a modal) to collect the API Key:
- Banner is shown above the tab content the first time you visit
/methodTraceLog/panel without a valid session.
- Submitting a valid Key (button or Enter) hides the banner, sets the
MTRACE_SESSION cookie, and auto-loads the current tab's data — no manual refresh.
- Wrong / empty Key shows an inline error (
请输入 API Key / ❌ API Key 无效或鉴权未启用).
- Session is a sliding 8-hour cookie (
security.session.ttl-millis, default 28 800 000). It renews on use, so you only re-enter the Key after 8h of inactivity or after clicking the 🚪 注销 button.
- The logout button only appears in the header when a session is active; the
/methodTraceLog/logout endpoint invalidates the cookie and re-mounts the banner.
- In dev mode (
api-key: ""), the banner is suppressed entirely and no logout button is shown.
GET /methodTraceLog/session/status returns { authEnabled, sessionValid } so the panel JS can decide whether to show the banner. While the banner is up, the in-page mtlFetch queues 401 responses and replays them on successful login, so the user never sees an "unauthorized" toast during the login flow.
CLI / MCP clients should keep using the X-Api-Key header directly — the cookie session is purely a browser convenience.
@AspectLog Annotation
Method-level opt-in (the class doesn't have to be a @Component). Use it to rename the method in the trace tree and on the OTel span:
public class MyHelper {
@AspectLog("do-something")
public void doSomething(String s) { ... }
}
The trace list and OTel span name will show do-something instead of the raw method signature.
Custom ICallService
Extend AbstractCallService and implement consumer(ServiceCallInfo). It's auto-picked up by the CallServiceStrategy:
@Component
public class MyService extends AbstractCallService {
@Override
public void consumer(ServiceCallInfo info) {
// logActionEnum is one of BEFORE / AFTER_RETURN / AFTER_THROW
log.info("{} {}", info.getClassName(), info.getMethodName());
}
@Override public String getCallServiceName() { return "MyService"; }
@Override public String getCallServiceDesc() { return "My desc"; }
}
Start it disabled: service-calls: [{ name: MyService, enable: false }], then flip via the panel.
MCP Server
A separate, standalone Spring Boot process that speaks Model Context Protocol over stdio. It forwards @Tool calls to one or more hosts (each host = an app that has the starter on the classpath) over HTTP.
MCP server config (mcpServers JSON block) for AI clients like Claude Desktop, Cursor, Cline:
{
"mcpServers": {
"sql-forge-mcp": {
"command": "jbang.cmd",
"args": [
"io.github.wb04307201:methodTraceLog-mcp:1.1.0",
"--method-trace-log.mcp.hosts[0].name=local-dev",
"--method-trace-log.mcp.hosts[0].url=http://localhost:8080",
"--method-trace-log.mcp.hosts[0].description=Local dev",
"--method-trace-log.mcp.hosts[0].api-key=change-me-in-production"
]
}
}
}
15 tools exposed: getHosts, ping, getCallServices, setCallServiceEnable, getMethodTraceList, getMethodTraceByTraceId, getAlerts, getSlowMethods, decompileMethod, getLogFiles, queryLogContent, downloadLog, startMonitor, stopMonitor, getMonitorStatus.
Gotchas
LogAspect exclusions: framework-internal types (ICallService, MethodTraceLogEndPoint, LogFileService, LogFileRealTimeService) are excluded. Add yours to the pointcut if you have similar internal beans.
- Path traversal:
FileUtils.pathInspection is the whitelist for LogFileService and LogFileRealTimeService — [a-zA-Z0-9._-]+, no .., ≤ 255 chars.
- Log pattern: the default
log-pattern only matches the standard logback pattern. If you change <pattern> in logback.xml, update log-pattern in YAML or LogLineInfo.parse will not split keyword / level / time.
- CFR resources: decompile reads class bytes through the classloader's
getResourceAsStream — works uniformly for file paths, thin jars, and Spring Boot fat-jar nested jars. Don't parse the URL.getPath() string.
- Spring Boot fat-jar: the test module does not declare
spring-boot-maven-plugin. Use mvn package then java -cp target/classes;<classpath> cn.wubo.method.trace.log.MethodTraceLogTestApplication.
- Maven on this machine: invoke
mvn as /c/developer/apache-maven-3.9.16/bin/mvn — the default mvn shim is broken.