mirror of
https://github.com/YspCoder/clawgo.git
synced 2026-04-12 14:07:30 +08:00
fix logs
This commit is contained in:
@@ -62,7 +62,7 @@ func agentCmd() {
|
||||
agentLoop := agent.NewAgentLoop(cfg, msgBus, provider, cronService)
|
||||
|
||||
startupInfo := agentLoop.GetStartupInfo()
|
||||
logger.InfoCF("agent", "Agent initialized",
|
||||
logger.InfoCF("agent", logger.C0098,
|
||||
map[string]interface{}{
|
||||
"tools_count": startupInfo["tools"].(map[string]interface{})["count"],
|
||||
"skills_total": startupInfo["skills"].(map[string]interface{})["total"],
|
||||
|
||||
@@ -614,7 +614,7 @@ func runGatewayStartupCompactionCheck(parent context.Context, agentLoop *agent.A
|
||||
defer cancel()
|
||||
|
||||
report := agentLoop.RunStartupSelfCheckAllSessions(checkCtx)
|
||||
logger.InfoCF("gateway", "Startup compaction check completed", map[string]interface{}{
|
||||
logger.InfoCF("gateway", logger.C0110, map[string]interface{}{
|
||||
"sessions_total": report.TotalSessions,
|
||||
"sessions_compacted": report.CompactedSessions,
|
||||
})
|
||||
@@ -641,20 +641,20 @@ func runGatewayBootstrapInit(parent context.Context, cfg *config.Config, agentLo
|
||||
prompt := "System startup bootstrap: read BOOTSTRAP.md and perform one-time self-initialization checks now. If already initialized, return concise status only."
|
||||
resp, err := agentLoop.ProcessDirect(initCtx, prompt, "system:bootstrap:init")
|
||||
if err != nil {
|
||||
logger.ErrorCF("gateway", "Bootstrap init model call failed", map[string]interface{}{logger.FieldError: err.Error()})
|
||||
logger.ErrorCF("gateway", logger.C0111, map[string]interface{}{logger.FieldError: err.Error()})
|
||||
return
|
||||
}
|
||||
line := fmt.Sprintf("%s\n%s\n", time.Now().UTC().Format(time.RFC3339), strings.TrimSpace(resp))
|
||||
if err := os.WriteFile(markerPath, []byte(line), 0644); err != nil {
|
||||
logger.ErrorCF("gateway", "Bootstrap init marker write failed", map[string]interface{}{logger.FieldError: err.Error()})
|
||||
logger.ErrorCF("gateway", logger.C0112, map[string]interface{}{logger.FieldError: err.Error()})
|
||||
return
|
||||
}
|
||||
// Bootstrap only runs once. After successful initialization marker is written,
|
||||
// remove BOOTSTRAP.md to avoid repeated first-run guidance.
|
||||
if err := os.Remove(bootstrapPath); err != nil && !os.IsNotExist(err) {
|
||||
logger.WarnCF("gateway", "Bootstrap file cleanup failed", map[string]interface{}{logger.FieldError: err.Error()})
|
||||
logger.WarnCF("gateway", logger.C0113, map[string]interface{}{logger.FieldError: err.Error()})
|
||||
}
|
||||
logger.InfoC("gateway", "Bootstrap init model call completed")
|
||||
logger.InfoC("gateway", logger.C0114)
|
||||
}
|
||||
|
||||
func applyMaximumPermissionPolicy(cfg *config.Config) {
|
||||
@@ -854,7 +854,7 @@ func buildGatewayRuntime(ctx context.Context, cfg *config.Config, msgBus *bus.Me
|
||||
fmt.Printf(" • Tools: %d loaded\n", toolsInfo["count"])
|
||||
fmt.Printf(" • Skills: %d/%d available\n", skillsInfo["available"], skillsInfo["total"])
|
||||
|
||||
logger.InfoCF("agent", "Agent initialized",
|
||||
logger.InfoCF("agent", logger.C0098,
|
||||
map[string]interface{}{
|
||||
"tools_count": toolsInfo["count"],
|
||||
"skills_total": skillsInfo["total"],
|
||||
|
||||
@@ -196,7 +196,7 @@ func (cb *ContextBuilder) BuildMessages(history []providers.Message, summary str
|
||||
}
|
||||
|
||||
// Log system prompt summary for debugging (debug mode only)
|
||||
logger.DebugCF("agent", "System prompt built",
|
||||
logger.DebugCF("agent", logger.C0143,
|
||||
map[string]interface{}{
|
||||
"total_chars": len(systemPrompt),
|
||||
"total_lines": strings.Count(systemPrompt, "\n") + 1,
|
||||
@@ -208,7 +208,7 @@ func (cb *ContextBuilder) BuildMessages(history []providers.Message, summary str
|
||||
if len(preview) > 500 {
|
||||
preview = preview[:500] + "... (truncated)"
|
||||
}
|
||||
logger.DebugCF("agent", "System prompt preview",
|
||||
logger.DebugCF("agent", logger.C0144,
|
||||
map[string]interface{}{
|
||||
"preview": preview,
|
||||
})
|
||||
|
||||
@@ -326,7 +326,7 @@ func (al *AgentLoop) buildSessionShards(ctx context.Context) []chan bus.InboundM
|
||||
}
|
||||
}(shards[i])
|
||||
}
|
||||
logger.InfoCF("agent", "Session-sharded dispatcher enabled", map[string]interface{}{"shards": count})
|
||||
logger.InfoCF("agent", logger.C0149, map[string]interface{}{"shards": count})
|
||||
return shards
|
||||
}
|
||||
|
||||
@@ -377,7 +377,7 @@ func (al *AgentLoop) tryFallbackProviders(ctx context.Context, msg bus.InboundMe
|
||||
al.ekg.Record(ekg.Event{Session: msg.SessionKey, Channel: msg.Channel, Source: "provider_fallback", Status: st, Provider: name, Model: al.model, ErrSig: errSig, Log: lg})
|
||||
}
|
||||
if err == nil {
|
||||
logger.WarnCF("agent", "LLM fallback provider switched", map[string]interface{}{"provider": name})
|
||||
logger.WarnCF("agent", logger.C0150, map[string]interface{}{"provider": name})
|
||||
return resp, name, nil
|
||||
}
|
||||
lastErr = err
|
||||
@@ -655,12 +655,13 @@ func (al *AgentLoop) processMessage(ctx context.Context, msg bus.InboundMessage)
|
||||
}
|
||||
// Add message preview to log
|
||||
preview := truncate(msg.Content, 80)
|
||||
logger.InfoCF("agent", fmt.Sprintf("Processing message from %s:%s: %s", msg.Channel, msg.SenderID, preview),
|
||||
logger.InfoCF("agent", logger.C0171,
|
||||
map[string]interface{}{
|
||||
"channel": msg.Channel,
|
||||
"chat_id": msg.ChatID,
|
||||
"sender_id": msg.SenderID,
|
||||
"session_key": msg.SessionKey,
|
||||
"preview": preview,
|
||||
})
|
||||
|
||||
// Route system messages to processSystemMessage
|
||||
@@ -769,7 +770,7 @@ func (al *AgentLoop) processMessage(ctx context.Context, msg bus.InboundMessage)
|
||||
for iteration < maxAllowed {
|
||||
iteration++
|
||||
|
||||
logger.DebugCF("agent", "LLM iteration",
|
||||
logger.DebugCF("agent", logger.C0151,
|
||||
map[string]interface{}{
|
||||
"iteration": iteration,
|
||||
"max": al.maxIterations,
|
||||
@@ -789,7 +790,7 @@ func (al *AgentLoop) processMessage(ctx context.Context, msg bus.InboundMessage)
|
||||
}
|
||||
|
||||
// Log LLM request details
|
||||
logger.DebugCF("agent", "LLM request",
|
||||
logger.DebugCF("agent", logger.C0152,
|
||||
map[string]interface{}{
|
||||
"iteration": iteration,
|
||||
"model": al.model,
|
||||
@@ -801,7 +802,7 @@ func (al *AgentLoop) processMessage(ctx context.Context, msg bus.InboundMessage)
|
||||
})
|
||||
|
||||
// Log full messages (detailed)
|
||||
logger.DebugCF("agent", "Full LLM request",
|
||||
logger.DebugCF("agent", logger.C0153,
|
||||
map[string]interface{}{
|
||||
"iteration": iteration,
|
||||
"messages_json": formatMessagesForLog(messages),
|
||||
@@ -852,9 +853,9 @@ func (al *AgentLoop) processMessage(ctx context.Context, msg bus.InboundMessage)
|
||||
errText := strings.ToLower(err.Error())
|
||||
if strings.Contains(errText, "no tool call found for function call output") {
|
||||
removed := al.sessions.PurgeOrphanToolOutputs(msg.SessionKey)
|
||||
logger.WarnCF("agent", "Purged orphan tool outputs after provider pairing error", map[string]interface{}{"session_key": msg.SessionKey, "removed": removed})
|
||||
logger.WarnCF("agent", logger.C0154, map[string]interface{}{"session_key": msg.SessionKey, "removed": removed})
|
||||
}
|
||||
logger.ErrorCF("agent", "LLM call failed",
|
||||
logger.ErrorCF("agent", logger.C0155,
|
||||
map[string]interface{}{
|
||||
"iteration": iteration,
|
||||
"error": err.Error(),
|
||||
@@ -864,7 +865,7 @@ func (al *AgentLoop) processMessage(ctx context.Context, msg bus.InboundMessage)
|
||||
|
||||
if len(response.ToolCalls) == 0 {
|
||||
finalContent = response.Content
|
||||
logger.InfoCF("agent", "LLM response without tool calls (direct answer)",
|
||||
logger.InfoCF("agent", logger.C0156,
|
||||
map[string]interface{}{
|
||||
"iteration": iteration,
|
||||
"content_chars": len(finalContent),
|
||||
@@ -876,7 +877,7 @@ func (al *AgentLoop) processMessage(ctx context.Context, msg bus.InboundMessage)
|
||||
for _, tc := range response.ToolCalls {
|
||||
toolNames = append(toolNames, tc.Name)
|
||||
}
|
||||
logger.InfoCF("agent", "LLM requested tool calls",
|
||||
logger.InfoCF("agent", logger.C0157,
|
||||
map[string]interface{}{
|
||||
"tools": toolNames,
|
||||
"count": len(toolNames),
|
||||
@@ -918,9 +919,10 @@ func (al *AgentLoop) processMessage(ctx context.Context, msg bus.InboundMessage)
|
||||
// Log tool call with arguments preview
|
||||
argsJSON, _ := json.Marshal(tc.Arguments)
|
||||
argsPreview := truncate(string(argsJSON), 200)
|
||||
logger.InfoCF("agent", fmt.Sprintf("Tool call: %s(%s)", tc.Name, argsPreview),
|
||||
logger.InfoCF("agent", logger.C0172,
|
||||
map[string]interface{}{
|
||||
"tool": tc.Name,
|
||||
"args": argsPreview,
|
||||
"iteration": iteration,
|
||||
})
|
||||
|
||||
@@ -996,8 +998,11 @@ func (al *AgentLoop) processMessage(ctx context.Context, msg bus.InboundMessage)
|
||||
|
||||
// Log response preview (original content)
|
||||
responsePreview := truncate(finalContent, 120)
|
||||
logger.InfoCF("agent", fmt.Sprintf("Response to %s:%s: %s", msg.Channel, msg.SenderID, responsePreview),
|
||||
logger.InfoCF("agent", logger.C0173,
|
||||
map[string]interface{}{
|
||||
"channel": msg.Channel,
|
||||
"sender_id": msg.SenderID,
|
||||
"preview": responsePreview,
|
||||
"iterations": iteration,
|
||||
"final_length": len(finalContent),
|
||||
"user_length": len(userContent),
|
||||
@@ -1030,7 +1035,7 @@ func (al *AgentLoop) appendDailySummaryLog(msg bus.InboundMessage, response stri
|
||||
truncate(strings.ReplaceAll(respText, "\n", " "), 220),
|
||||
)
|
||||
if err := ms.AppendToday(line); err != nil {
|
||||
logger.WarnCF("agent", "append daily summary log failed", map[string]interface{}{logger.FieldError: err.Error()})
|
||||
logger.WarnCF("agent", logger.C0158, map[string]interface{}{logger.FieldError: err.Error()})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1089,7 +1094,7 @@ func (al *AgentLoop) processSystemMessage(ctx context.Context, msg bus.InboundMe
|
||||
return "", fmt.Errorf("processSystemMessage called with non-system message channel: %s", msg.Channel)
|
||||
}
|
||||
|
||||
logger.InfoCF("agent", "Processing system message",
|
||||
logger.InfoCF("agent", logger.C0159,
|
||||
map[string]interface{}{
|
||||
"sender_id": msg.SenderID,
|
||||
"chat_id": msg.ChatID,
|
||||
@@ -1158,7 +1163,7 @@ func (al *AgentLoop) processSystemMessage(ctx context.Context, msg bus.InboundMe
|
||||
}
|
||||
|
||||
// Log LLM request details
|
||||
logger.DebugCF("agent", "LLM request",
|
||||
logger.DebugCF("agent", logger.C0152,
|
||||
map[string]interface{}{
|
||||
"iteration": iteration,
|
||||
"model": al.model,
|
||||
@@ -1170,7 +1175,7 @@ func (al *AgentLoop) processSystemMessage(ctx context.Context, msg bus.InboundMe
|
||||
})
|
||||
|
||||
// Log full messages (detailed)
|
||||
logger.DebugCF("agent", "Full LLM request",
|
||||
logger.DebugCF("agent", logger.C0153,
|
||||
map[string]interface{}{
|
||||
"iteration": iteration,
|
||||
"messages_json": formatMessagesForLog(messages),
|
||||
@@ -1198,7 +1203,7 @@ func (al *AgentLoop) processSystemMessage(ctx context.Context, msg bus.InboundMe
|
||||
errText := strings.ToLower(err.Error())
|
||||
if strings.Contains(errText, "no tool call found for function call output") {
|
||||
removed := al.sessions.PurgeOrphanToolOutputs(sessionKey)
|
||||
logger.WarnCF("agent", "Purged orphan tool outputs after provider pairing error (system)", map[string]interface{}{"session_key": sessionKey, "removed": removed})
|
||||
logger.WarnCF("agent", logger.C0160, map[string]interface{}{"session_key": sessionKey, "removed": removed})
|
||||
if removed > 0 {
|
||||
// Rebuild context from cleaned history and retry current iteration.
|
||||
history = al.sessions.GetHistory(sessionKey)
|
||||
@@ -1225,11 +1230,11 @@ func (al *AgentLoop) processSystemMessage(ctx context.Context, msg bus.InboundMe
|
||||
originChatID,
|
||||
responseLang,
|
||||
)
|
||||
logger.WarnCF("agent", "Heartbeat session reset after repeated provider pairing error", map[string]interface{}{"session_key": sessionKey})
|
||||
logger.WarnCF("agent", logger.C0161, map[string]interface{}{"session_key": sessionKey})
|
||||
continue
|
||||
}
|
||||
}
|
||||
logger.ErrorCF("agent", "LLM call failed in system message",
|
||||
logger.ErrorCF("agent", logger.C0162,
|
||||
map[string]interface{}{
|
||||
"iteration": iteration,
|
||||
"error": err.Error(),
|
||||
@@ -1297,7 +1302,7 @@ func (al *AgentLoop) processSystemMessage(ctx context.Context, msg bus.InboundMe
|
||||
|
||||
al.sessions.Save(al.sessions.GetOrCreate(sessionKey))
|
||||
|
||||
logger.InfoCF("agent", "System message processing completed",
|
||||
logger.InfoCF("agent", logger.C0163,
|
||||
map[string]interface{}{
|
||||
"iterations": iteration,
|
||||
"final_length": len(finalContent),
|
||||
|
||||
@@ -37,7 +37,7 @@ func (mb *MessageBus) PublishInbound(msg InboundMessage) {
|
||||
|
||||
defer func() {
|
||||
if recover() != nil {
|
||||
logger.WarnCF("bus", "PublishInbound on closed channel recovered", map[string]interface{}{
|
||||
logger.WarnCF("bus", logger.C0129, map[string]interface{}{
|
||||
logger.FieldChannel: msg.Channel,
|
||||
logger.FieldChatID: msg.ChatID,
|
||||
"session_key": msg.SessionKey,
|
||||
@@ -48,7 +48,7 @@ func (mb *MessageBus) PublishInbound(msg InboundMessage) {
|
||||
select {
|
||||
case ch <- msg:
|
||||
case <-time.After(queueWriteTimeout):
|
||||
logger.ErrorCF("bus", "PublishInbound timeout (queue full)", map[string]interface{}{
|
||||
logger.ErrorCF("bus", logger.C0130, map[string]interface{}{
|
||||
logger.FieldChannel: msg.Channel,
|
||||
logger.FieldChatID: msg.ChatID,
|
||||
"session_key": msg.SessionKey,
|
||||
@@ -76,7 +76,7 @@ func (mb *MessageBus) PublishOutbound(msg OutboundMessage) {
|
||||
|
||||
defer func() {
|
||||
if recover() != nil {
|
||||
logger.WarnCF("bus", "PublishOutbound on closed channel recovered", map[string]interface{}{
|
||||
logger.WarnCF("bus", logger.C0131, map[string]interface{}{
|
||||
logger.FieldChannel: msg.Channel,
|
||||
logger.FieldChatID: msg.ChatID,
|
||||
})
|
||||
@@ -86,7 +86,7 @@ func (mb *MessageBus) PublishOutbound(msg OutboundMessage) {
|
||||
select {
|
||||
case ch <- msg:
|
||||
case <-time.After(queueWriteTimeout):
|
||||
logger.ErrorCF("bus", "PublishOutbound timeout (queue full)", map[string]interface{}{
|
||||
logger.ErrorCF("bus", logger.C0132, map[string]interface{}{
|
||||
logger.FieldChannel: msg.Channel,
|
||||
logger.FieldChatID: msg.ChatID,
|
||||
})
|
||||
|
||||
@@ -129,7 +129,7 @@ func messageDigest(s string) string {
|
||||
|
||||
func (c *BaseChannel) HandleMessage(senderID, chatID, content string, media []string, metadata map[string]string) {
|
||||
if !c.IsAllowed(senderID) {
|
||||
logger.WarnCF("channels", "Message rejected by allowlist", map[string]interface{}{
|
||||
logger.WarnCF("channels", logger.C0001, map[string]interface{}{
|
||||
logger.FieldChannel: c.name,
|
||||
logger.FieldSenderID: senderID,
|
||||
logger.FieldChatID: chatID,
|
||||
@@ -140,10 +140,10 @@ func (c *BaseChannel) HandleMessage(senderID, chatID, content string, media []st
|
||||
if metadata != nil {
|
||||
if messageID := strings.TrimSpace(metadata["message_id"]); messageID != "" {
|
||||
if c.seenRecently(c.name+":"+messageID, inboundMessageIDDedupeTTL) {
|
||||
logger.WarnCF("channels", "Duplicate inbound message skipped", map[string]interface{}{
|
||||
logger.WarnCF("channels", logger.C0002, map[string]interface{}{
|
||||
logger.FieldChannel: c.name,
|
||||
"message_id": messageID,
|
||||
logger.FieldChatID: chatID,
|
||||
"message_id": messageID,
|
||||
logger.FieldChatID: chatID,
|
||||
})
|
||||
return
|
||||
}
|
||||
@@ -152,7 +152,7 @@ func (c *BaseChannel) HandleMessage(senderID, chatID, content string, media []st
|
||||
// Fallback dedupe when platform omits/changes message_id (short window, same sender/chat/content).
|
||||
contentKey := c.name + ":content:" + chatID + ":" + senderID + ":" + messageDigest(content)
|
||||
if c.seenRecently(contentKey, inboundContentDedupeTTL) {
|
||||
logger.WarnCF("channels", "Duplicate inbound content skipped", map[string]interface{}{
|
||||
logger.WarnCF("channels", logger.C0003, map[string]interface{}{
|
||||
logger.FieldChannel: c.name,
|
||||
logger.FieldChatID: chatID,
|
||||
})
|
||||
|
||||
@@ -49,7 +49,7 @@ func (c *DingTalkChannel) Start(ctx context.Context) error {
|
||||
if c.IsRunning() {
|
||||
return nil
|
||||
}
|
||||
logger.InfoC("dingtalk", "Starting DingTalk channel (Stream Mode)")
|
||||
logger.InfoC("dingtalk", logger.C0115)
|
||||
|
||||
runCtx, cancel := context.WithCancel(ctx)
|
||||
c.runCancel.set(cancel)
|
||||
@@ -72,7 +72,7 @@ func (c *DingTalkChannel) Start(ctx context.Context) error {
|
||||
}
|
||||
|
||||
c.setRunning(true)
|
||||
logger.InfoC("dingtalk", "DingTalk channel started (Stream Mode)")
|
||||
logger.InfoC("dingtalk", logger.C0116)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -81,7 +81,7 @@ func (c *DingTalkChannel) Stop(ctx context.Context) error {
|
||||
if !c.IsRunning() {
|
||||
return nil
|
||||
}
|
||||
logger.InfoC("dingtalk", "Stopping DingTalk channel")
|
||||
logger.InfoC("dingtalk", logger.C0117)
|
||||
|
||||
c.runCancel.cancelAndClear()
|
||||
|
||||
@@ -90,7 +90,7 @@ func (c *DingTalkChannel) Stop(ctx context.Context) error {
|
||||
}
|
||||
|
||||
c.setRunning(false)
|
||||
logger.InfoC("dingtalk", "DingTalk channel stopped")
|
||||
logger.InfoC("dingtalk", logger.C0118)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -111,7 +111,7 @@ func (c *DingTalkChannel) Send(ctx context.Context, msg bus.OutboundMessage) err
|
||||
return fmt.Errorf("invalid session_webhook type for chat %s", msg.ChatID)
|
||||
}
|
||||
|
||||
logger.InfoCF("dingtalk", "DingTalk outbound message", map[string]interface{}{
|
||||
logger.InfoCF("dingtalk", logger.C0119, map[string]interface{}{
|
||||
logger.FieldChatID: msg.ChatID,
|
||||
logger.FieldPreview: truncateString(msg.Content, 100),
|
||||
"platform": "dingtalk",
|
||||
@@ -159,7 +159,7 @@ func (c *DingTalkChannel) onChatBotMessageReceived(ctx context.Context, data *ch
|
||||
"session_webhook": data.SessionWebhook,
|
||||
}
|
||||
|
||||
logger.InfoCF("dingtalk", "DingTalk inbound message", map[string]interface{}{
|
||||
logger.InfoCF("dingtalk", logger.C0120, map[string]interface{}{
|
||||
"sender_name": senderNick,
|
||||
logger.FieldSenderID: senderID,
|
||||
logger.FieldChatID: chatID,
|
||||
|
||||
@@ -37,7 +37,7 @@ func NewDiscordChannel(cfg config.DiscordConfig, bus *bus.MessageBus) (*DiscordC
|
||||
}
|
||||
|
||||
func (c *DiscordChannel) Start(ctx context.Context) error {
|
||||
logger.InfoC("discord", "Starting Discord bot")
|
||||
logger.InfoC("discord", logger.C0069)
|
||||
|
||||
c.session.AddHandler(c.handleMessage)
|
||||
|
||||
@@ -51,7 +51,7 @@ func (c *DiscordChannel) Start(ctx context.Context) error {
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get bot user: %w", err)
|
||||
}
|
||||
logger.InfoCF("discord", "Discord bot connected", map[string]interface{}{
|
||||
logger.InfoCF("discord", logger.C0070, map[string]interface{}{
|
||||
"username": botUser.Username,
|
||||
"user_id": botUser.ID,
|
||||
})
|
||||
@@ -60,7 +60,7 @@ func (c *DiscordChannel) Start(ctx context.Context) error {
|
||||
}
|
||||
|
||||
func (c *DiscordChannel) Stop(ctx context.Context) error {
|
||||
logger.InfoC("discord", "Stopping Discord bot")
|
||||
logger.InfoC("discord", logger.C0071)
|
||||
c.setRunning(false)
|
||||
|
||||
if err := c.session.Close(); err != nil {
|
||||
@@ -145,7 +145,7 @@ func (c *DiscordChannel) handleMessage(s *discordgo.Session, m *discordgo.Messag
|
||||
content = "[media only]"
|
||||
}
|
||||
|
||||
logger.DebugCF("discord", "Received message", map[string]interface{}{
|
||||
logger.DebugCF("discord", logger.C0072, map[string]interface{}{
|
||||
"sender_name": senderName,
|
||||
logger.FieldSenderID: senderID,
|
||||
logger.FieldPreview: truncateString(content, 50),
|
||||
@@ -186,7 +186,7 @@ func isAudioFile(filename, contentType string) bool {
|
||||
func (c *DiscordChannel) downloadAttachment(url, filename string) string {
|
||||
mediaDir := filepath.Join(os.TempDir(), "clawgo_media")
|
||||
if err := os.MkdirAll(mediaDir, 0755); err != nil {
|
||||
logger.WarnCF("discord", "Failed to create media directory", map[string]interface{}{
|
||||
logger.WarnCF("discord", logger.C0073, map[string]interface{}{
|
||||
logger.FieldError: err.Error(),
|
||||
})
|
||||
return ""
|
||||
@@ -196,7 +196,7 @@ func (c *DiscordChannel) downloadAttachment(url, filename string) string {
|
||||
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
logger.WarnCF("discord", "Failed to download attachment", map[string]interface{}{
|
||||
logger.WarnCF("discord", logger.C0074, map[string]interface{}{
|
||||
logger.FieldError: err.Error(),
|
||||
})
|
||||
return ""
|
||||
@@ -204,7 +204,7 @@ func (c *DiscordChannel) downloadAttachment(url, filename string) string {
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
logger.WarnCF("discord", "Attachment download returned non-200", map[string]interface{}{
|
||||
logger.WarnCF("discord", logger.C0075, map[string]interface{}{
|
||||
"status_code": resp.StatusCode,
|
||||
})
|
||||
return ""
|
||||
@@ -212,7 +212,7 @@ func (c *DiscordChannel) downloadAttachment(url, filename string) string {
|
||||
|
||||
out, err := os.Create(localPath)
|
||||
if err != nil {
|
||||
logger.WarnCF("discord", "Failed to create local attachment file", map[string]interface{}{
|
||||
logger.WarnCF("discord", logger.C0076, map[string]interface{}{
|
||||
logger.FieldError: err.Error(),
|
||||
})
|
||||
return ""
|
||||
@@ -221,13 +221,13 @@ func (c *DiscordChannel) downloadAttachment(url, filename string) string {
|
||||
|
||||
_, err = io.Copy(out, resp.Body)
|
||||
if err != nil {
|
||||
logger.WarnCF("discord", "Failed to write local attachment file", map[string]interface{}{
|
||||
logger.WarnCF("discord", logger.C0077, map[string]interface{}{
|
||||
logger.FieldError: err.Error(),
|
||||
})
|
||||
return ""
|
||||
}
|
||||
|
||||
logger.DebugCF("discord", "Attachment downloaded successfully", map[string]interface{}{
|
||||
logger.DebugCF("discord", logger.C0078, map[string]interface{}{
|
||||
"path": localPath,
|
||||
})
|
||||
return localPath
|
||||
|
||||
@@ -86,7 +86,7 @@ func (c *FeishuChannel) Start(ctx context.Context) error {
|
||||
c.mu.Unlock()
|
||||
|
||||
c.setRunning(true)
|
||||
logger.InfoC("feishu", "Feishu channel started (websocket mode)")
|
||||
logger.InfoC("feishu", logger.C0043)
|
||||
|
||||
runChannelTask("feishu", "websocket", func() error {
|
||||
return wsClient.Start(runCtx)
|
||||
@@ -107,7 +107,7 @@ func (c *FeishuChannel) Stop(ctx context.Context) error {
|
||||
c.runCancel.cancelAndClear()
|
||||
|
||||
c.setRunning(false)
|
||||
logger.InfoC("feishu", "Feishu channel stopped")
|
||||
logger.InfoC("feishu", logger.C0044)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -132,7 +132,7 @@ func (c *FeishuChannel) Send(ctx context.Context, msg bus.OutboundMessage) error
|
||||
for i, t := range tables {
|
||||
link, lerr := c.createFeishuSheetFromTable(ctx, t.Name, t.Rows)
|
||||
if lerr != nil {
|
||||
logger.WarnCF("feishu", "create sheet from markdown table failed", map[string]interface{}{logger.FieldError: lerr.Error(), logger.FieldChatID: msg.ChatID})
|
||||
logger.WarnCF("feishu", logger.C0045, map[string]interface{}{logger.FieldError: lerr.Error(), logger.FieldChatID: msg.ChatID})
|
||||
continue
|
||||
}
|
||||
links = append(links, fmt.Sprintf("表格%d: %s", i+1, link))
|
||||
@@ -160,7 +160,7 @@ func (c *FeishuChannel) Send(ctx context.Context, msg bus.OutboundMessage) error
|
||||
return err
|
||||
}
|
||||
|
||||
logger.InfoCF("feishu", "Feishu message sent", map[string]interface{}{
|
||||
logger.InfoCF("feishu", logger.C0046, map[string]interface{}{
|
||||
logger.FieldChatID: msg.ChatID,
|
||||
"msg_type": msgType,
|
||||
"has_media": strings.TrimSpace(workMsg.Media) != "",
|
||||
@@ -184,7 +184,7 @@ func (c *FeishuChannel) handleMessageReceive(ctx context.Context, event *larkim.
|
||||
}
|
||||
chatType := strings.ToLower(strings.TrimSpace(stringValue(message.ChatType)))
|
||||
if !c.isAllowedChat(chatID, chatType) {
|
||||
logger.WarnCF("feishu", "Feishu message rejected by chat allowlist", map[string]interface{}{
|
||||
logger.WarnCF("feishu", logger.C0047, map[string]interface{}{
|
||||
logger.FieldSenderID: extractFeishuSenderID(sender),
|
||||
logger.FieldChatID: chatID,
|
||||
"chat_type": chatType,
|
||||
@@ -202,7 +202,7 @@ func (c *FeishuChannel) handleMessageReceive(ctx context.Context, event *larkim.
|
||||
content = "[empty message]"
|
||||
}
|
||||
if !c.shouldHandleGroupMessage(chatType, content) {
|
||||
logger.DebugCF("feishu", "Ignoring group message without mention/command", map[string]interface{}{
|
||||
logger.DebugCF("feishu", logger.C0048, map[string]interface{}{
|
||||
logger.FieldSenderID: senderID,
|
||||
logger.FieldChatID: chatID,
|
||||
})
|
||||
@@ -223,7 +223,7 @@ func (c *FeishuChannel) handleMessageReceive(ctx context.Context, event *larkim.
|
||||
metadata["tenant_key"] = *sender.TenantKey
|
||||
}
|
||||
|
||||
logger.InfoCF("feishu", "Feishu message received", map[string]interface{}{
|
||||
logger.InfoCF("feishu", logger.C0049, map[string]interface{}{
|
||||
logger.FieldSenderID: senderID,
|
||||
logger.FieldChatID: chatID,
|
||||
logger.FieldPreview: truncateString(content, 80),
|
||||
@@ -246,7 +246,7 @@ func (c *FeishuChannel) resolveInboundMedia(ctx context.Context, mediaRefs []str
|
||||
if path, err := c.downloadFeishuMediaByKey(ctx, "image", key); err == nil {
|
||||
out = append(out, path)
|
||||
} else {
|
||||
logger.WarnCF("feishu", "download inbound image failed", map[string]interface{}{logger.FieldError: err.Error(), "image_key": key})
|
||||
logger.WarnCF("feishu", logger.C0050, map[string]interface{}{logger.FieldError: err.Error(), "image_key": key})
|
||||
out = append(out, ref)
|
||||
}
|
||||
continue
|
||||
@@ -256,7 +256,7 @@ func (c *FeishuChannel) resolveInboundMedia(ctx context.Context, mediaRefs []str
|
||||
if path, err := c.downloadFeishuMediaByKey(ctx, "file", key); err == nil {
|
||||
out = append(out, path)
|
||||
} else {
|
||||
logger.WarnCF("feishu", "download inbound file failed", map[string]interface{}{logger.FieldError: err.Error(), "file_key": key})
|
||||
logger.WarnCF("feishu", logger.C0051, map[string]interface{}{logger.FieldError: err.Error(), "file_key": key})
|
||||
out = append(out, ref)
|
||||
}
|
||||
continue
|
||||
@@ -674,7 +674,7 @@ func (c *FeishuChannel) createFeishuSheetFromTable(ctx context.Context, name str
|
||||
}
|
||||
}
|
||||
if err := c.setFeishuSheetPublicEditable(ctx, spToken); err != nil {
|
||||
logger.WarnCF("feishu", "set sheet permission failed", map[string]interface{}{logger.FieldError: err.Error(), "sheet_token": spToken})
|
||||
logger.WarnCF("feishu", logger.C0052, map[string]interface{}{logger.FieldError: err.Error(), "sheet_token": spToken})
|
||||
}
|
||||
if createResp.Data.Spreadsheet.Url != nil && strings.TrimSpace(*createResp.Data.Spreadsheet.Url) != "" {
|
||||
return strings.TrimSpace(*createResp.Data.Spreadsheet.Url), nil
|
||||
|
||||
@@ -38,7 +38,7 @@ func NewMaixCamChannel(cfg config.MaixCamConfig, bus *bus.MessageBus) (*MaixCamC
|
||||
}
|
||||
|
||||
func (c *MaixCamChannel) Start(ctx context.Context) error {
|
||||
logger.InfoC("maixcam", "Starting MaixCam channel server")
|
||||
logger.InfoC("maixcam", logger.C0079)
|
||||
|
||||
addr := fmt.Sprintf("%s:%d", c.config.Host, c.config.Port)
|
||||
listener, err := net.Listen("tcp", addr)
|
||||
@@ -49,7 +49,7 @@ func (c *MaixCamChannel) Start(ctx context.Context) error {
|
||||
c.listener = listener
|
||||
c.setRunning(true)
|
||||
|
||||
logger.InfoCF("maixcam", "MaixCam server listening", map[string]interface{}{
|
||||
logger.InfoCF("maixcam", logger.C0080, map[string]interface{}{
|
||||
"host": c.config.Host,
|
||||
"port": c.config.Port,
|
||||
})
|
||||
@@ -60,25 +60,25 @@ func (c *MaixCamChannel) Start(ctx context.Context) error {
|
||||
}
|
||||
|
||||
func (c *MaixCamChannel) acceptConnections(ctx context.Context) {
|
||||
logger.DebugC("maixcam", "Starting connection acceptor")
|
||||
logger.DebugC("maixcam", logger.C0081)
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
logger.InfoC("maixcam", "Stopping connection acceptor")
|
||||
logger.InfoC("maixcam", logger.C0082)
|
||||
return
|
||||
default:
|
||||
conn, err := c.listener.Accept()
|
||||
if err != nil {
|
||||
if c.IsRunning() {
|
||||
logger.ErrorCF("maixcam", "Failed to accept connection", map[string]interface{}{
|
||||
logger.ErrorCF("maixcam", logger.C0083, map[string]interface{}{
|
||||
logger.FieldError: err.Error(),
|
||||
})
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
logger.InfoCF("maixcam", "New connection from MaixCam device", map[string]interface{}{
|
||||
logger.InfoCF("maixcam", logger.C0084, map[string]interface{}{
|
||||
"remote_addr": conn.RemoteAddr().String(),
|
||||
})
|
||||
|
||||
@@ -92,14 +92,14 @@ func (c *MaixCamChannel) acceptConnections(ctx context.Context) {
|
||||
}
|
||||
|
||||
func (c *MaixCamChannel) handleConnection(conn net.Conn, ctx context.Context) {
|
||||
logger.DebugC("maixcam", "Handling MaixCam connection")
|
||||
logger.DebugC("maixcam", logger.C0085)
|
||||
|
||||
defer func() {
|
||||
conn.Close()
|
||||
c.clientsMux.Lock()
|
||||
delete(c.clients, conn)
|
||||
c.clientsMux.Unlock()
|
||||
logger.DebugC("maixcam", "Connection closed")
|
||||
logger.DebugC("maixcam", logger.C0086)
|
||||
}()
|
||||
|
||||
decoder := json.NewDecoder(conn)
|
||||
@@ -112,7 +112,7 @@ func (c *MaixCamChannel) handleConnection(conn net.Conn, ctx context.Context) {
|
||||
var msg MaixCamMessage
|
||||
if err := decoder.Decode(&msg); err != nil {
|
||||
if err.Error() != "EOF" {
|
||||
logger.ErrorCF("maixcam", "Failed to decode message", map[string]interface{}{
|
||||
logger.ErrorCF("maixcam", logger.C0087, map[string]interface{}{
|
||||
logger.FieldError: err.Error(),
|
||||
})
|
||||
}
|
||||
@@ -129,18 +129,18 @@ func (c *MaixCamChannel) processMessage(msg MaixCamMessage, conn net.Conn) {
|
||||
case "person_detected":
|
||||
c.handlePersonDetection(msg)
|
||||
case "heartbeat":
|
||||
logger.DebugC("maixcam", "Received heartbeat")
|
||||
logger.DebugC("maixcam", logger.C0088)
|
||||
case "status":
|
||||
c.handleStatusUpdate(msg)
|
||||
default:
|
||||
logger.WarnCF("maixcam", "Unknown message type", map[string]interface{}{
|
||||
logger.WarnCF("maixcam", logger.C0089, map[string]interface{}{
|
||||
"message_type": msg.Type,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (c *MaixCamChannel) handlePersonDetection(msg MaixCamMessage) {
|
||||
logger.InfoCF("maixcam", "Person detected event", map[string]interface{}{
|
||||
logger.InfoCF("maixcam", logger.C0090, map[string]interface{}{
|
||||
logger.FieldSenderID: "maixcam",
|
||||
logger.FieldChatID: "default",
|
||||
"timestamp": msg.Timestamp,
|
||||
@@ -178,13 +178,13 @@ func (c *MaixCamChannel) handlePersonDetection(msg MaixCamMessage) {
|
||||
}
|
||||
|
||||
func (c *MaixCamChannel) handleStatusUpdate(msg MaixCamMessage) {
|
||||
logger.InfoCF("maixcam", "Status update from MaixCam", map[string]interface{}{
|
||||
logger.InfoCF("maixcam", logger.C0091, map[string]interface{}{
|
||||
"status": msg.Data,
|
||||
})
|
||||
}
|
||||
|
||||
func (c *MaixCamChannel) Stop(ctx context.Context) error {
|
||||
logger.InfoC("maixcam", "Stopping MaixCam channel")
|
||||
logger.InfoC("maixcam", logger.C0092)
|
||||
c.setRunning(false)
|
||||
|
||||
if c.listener != nil {
|
||||
@@ -199,7 +199,7 @@ func (c *MaixCamChannel) Stop(ctx context.Context) error {
|
||||
}
|
||||
c.clients = make(map[net.Conn]bool)
|
||||
|
||||
logger.InfoC("maixcam", "MaixCam channel stopped")
|
||||
logger.InfoC("maixcam", logger.C0093)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -212,7 +212,7 @@ func (c *MaixCamChannel) Send(ctx context.Context, msg bus.OutboundMessage) erro
|
||||
defer c.clientsMux.RUnlock()
|
||||
|
||||
if len(c.clients) == 0 {
|
||||
logger.WarnC("maixcam", "No MaixCam devices connected")
|
||||
logger.WarnC("maixcam", logger.C0094)
|
||||
return fmt.Errorf("no connected MaixCam devices")
|
||||
}
|
||||
|
||||
@@ -231,7 +231,7 @@ func (c *MaixCamChannel) Send(ctx context.Context, msg bus.OutboundMessage) erro
|
||||
var sendErr error
|
||||
for conn := range c.clients {
|
||||
if _, err := conn.Write(data); err != nil {
|
||||
logger.ErrorCF("maixcam", "Failed to send to client", map[string]interface{}{
|
||||
logger.ErrorCF("maixcam", logger.C0095, map[string]interface{}{
|
||||
"client": conn.RemoteAddr().String(),
|
||||
logger.FieldError: err.Error(),
|
||||
})
|
||||
|
||||
@@ -71,39 +71,39 @@ func NewManager(cfg *config.Config, messageBus *bus.MessageBus) (*Manager, error
|
||||
}
|
||||
|
||||
func (m *Manager) initChannels() error {
|
||||
logger.InfoC("channels", "Initializing channel manager")
|
||||
logger.InfoC("channels", logger.C0004)
|
||||
|
||||
if m.config.Channels.Telegram.Enabled {
|
||||
logger.DebugCF("channels", "Attempting to initialize Telegram channel", map[string]interface{}{
|
||||
logger.DebugCF("channels", logger.C0005, map[string]interface{}{
|
||||
"has_token": m.config.Channels.Telegram.Token != "",
|
||||
})
|
||||
if m.config.Channels.Telegram.Token == "" {
|
||||
logger.WarnC("channels", "Telegram token is empty, skipping")
|
||||
logger.WarnC("channels", logger.C0006)
|
||||
} else {
|
||||
telegram, err := NewTelegramChannel(m.config.Channels.Telegram, m.bus)
|
||||
if err != nil {
|
||||
logger.ErrorCF("channels", "Failed to initialize Telegram channel", map[string]interface{}{
|
||||
logger.ErrorCF("channels", logger.C0007, map[string]interface{}{
|
||||
logger.FieldError: err.Error(),
|
||||
})
|
||||
} else {
|
||||
m.channels["telegram"] = telegram
|
||||
logger.InfoC("channels", "Telegram channel enabled successfully")
|
||||
logger.InfoC("channels", logger.C0008)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if m.config.Channels.WhatsApp.Enabled {
|
||||
if m.config.Channels.WhatsApp.BridgeURL == "" {
|
||||
logger.WarnC("channels", "WhatsApp bridge URL is empty, skipping")
|
||||
logger.WarnC("channels", logger.C0009)
|
||||
} else {
|
||||
whatsapp, err := NewWhatsAppChannel(m.config.Channels.WhatsApp, m.bus)
|
||||
if err != nil {
|
||||
logger.ErrorCF("channels", "Failed to initialize WhatsApp channel", map[string]interface{}{
|
||||
logger.ErrorCF("channels", logger.C0010, map[string]interface{}{
|
||||
logger.FieldError: err.Error(),
|
||||
})
|
||||
} else {
|
||||
m.channels["whatsapp"] = whatsapp
|
||||
logger.InfoC("channels", "WhatsApp channel enabled successfully")
|
||||
logger.InfoC("channels", logger.C0011)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -111,27 +111,27 @@ func (m *Manager) initChannels() error {
|
||||
if m.config.Channels.Feishu.Enabled {
|
||||
feishu, err := NewFeishuChannel(m.config.Channels.Feishu, m.bus)
|
||||
if err != nil {
|
||||
logger.ErrorCF("channels", "Failed to initialize Feishu channel", map[string]interface{}{
|
||||
logger.ErrorCF("channels", logger.C0012, map[string]interface{}{
|
||||
logger.FieldError: err.Error(),
|
||||
})
|
||||
} else {
|
||||
m.channels["feishu"] = feishu
|
||||
logger.InfoC("channels", "Feishu channel enabled successfully")
|
||||
logger.InfoC("channels", logger.C0013)
|
||||
}
|
||||
}
|
||||
|
||||
if m.config.Channels.Discord.Enabled {
|
||||
if m.config.Channels.Discord.Token == "" {
|
||||
logger.WarnC("channels", "Discord token is empty, skipping")
|
||||
logger.WarnC("channels", logger.C0014)
|
||||
} else {
|
||||
discord, err := NewDiscordChannel(m.config.Channels.Discord, m.bus)
|
||||
if err != nil {
|
||||
logger.ErrorCF("channels", "Failed to initialize Discord channel", map[string]interface{}{
|
||||
logger.ErrorCF("channels", logger.C0015, map[string]interface{}{
|
||||
logger.FieldError: err.Error(),
|
||||
})
|
||||
} else {
|
||||
m.channels["discord"] = discord
|
||||
logger.InfoC("channels", "Discord channel enabled successfully")
|
||||
logger.InfoC("channels", logger.C0016)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -139,44 +139,44 @@ func (m *Manager) initChannels() error {
|
||||
if m.config.Channels.MaixCam.Enabled {
|
||||
maixcam, err := NewMaixCamChannel(m.config.Channels.MaixCam, m.bus)
|
||||
if err != nil {
|
||||
logger.ErrorCF("channels", "Failed to initialize MaixCam channel", map[string]interface{}{
|
||||
logger.ErrorCF("channels", logger.C0017, map[string]interface{}{
|
||||
logger.FieldError: err.Error(),
|
||||
})
|
||||
} else {
|
||||
m.channels["maixcam"] = maixcam
|
||||
logger.InfoC("channels", "MaixCam channel enabled successfully")
|
||||
logger.InfoC("channels", logger.C0018)
|
||||
}
|
||||
}
|
||||
|
||||
if m.config.Channels.QQ.Enabled {
|
||||
qq, err := NewQQChannel(m.config.Channels.QQ, m.bus)
|
||||
if err != nil {
|
||||
logger.ErrorCF("channels", "Failed to initialize QQ channel", map[string]interface{}{
|
||||
logger.ErrorCF("channels", logger.C0019, map[string]interface{}{
|
||||
logger.FieldError: err.Error(),
|
||||
})
|
||||
} else {
|
||||
m.channels["qq"] = qq
|
||||
logger.InfoC("channels", "QQ channel enabled successfully")
|
||||
logger.InfoC("channels", logger.C0020)
|
||||
}
|
||||
}
|
||||
|
||||
if m.config.Channels.DingTalk.Enabled {
|
||||
if m.config.Channels.DingTalk.ClientID == "" {
|
||||
logger.WarnC("channels", "DingTalk Client ID is empty, skipping")
|
||||
logger.WarnC("channels", logger.C0021)
|
||||
} else {
|
||||
dingtalk, err := NewDingTalkChannel(m.config.Channels.DingTalk, m.bus)
|
||||
if err != nil {
|
||||
logger.ErrorCF("channels", "Failed to initialize DingTalk channel", map[string]interface{}{
|
||||
logger.ErrorCF("channels", logger.C0022, map[string]interface{}{
|
||||
logger.FieldError: err.Error(),
|
||||
})
|
||||
} else {
|
||||
m.channels["dingtalk"] = dingtalk
|
||||
logger.InfoC("channels", "DingTalk channel enabled successfully")
|
||||
logger.InfoC("channels", logger.C0023)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
logger.InfoCF("channels", "Channel initialization completed", map[string]interface{}{
|
||||
logger.InfoCF("channels", logger.C0024, map[string]interface{}{
|
||||
"enabled_channels": len(m.channels),
|
||||
})
|
||||
m.refreshSnapshot()
|
||||
@@ -196,7 +196,7 @@ func (m *Manager) StartAll(ctx context.Context) error {
|
||||
m.mu.Lock()
|
||||
if len(m.channels) == 0 {
|
||||
m.mu.Unlock()
|
||||
logger.WarnC("channels", "No channels enabled")
|
||||
logger.WarnC("channels", logger.C0025)
|
||||
return nil
|
||||
}
|
||||
channelsSnapshot := make(map[string]Channel, len(m.channels))
|
||||
@@ -207,7 +207,7 @@ func (m *Manager) StartAll(ctx context.Context) error {
|
||||
m.dispatchTask = &asyncTask{cancel: cancel}
|
||||
m.mu.Unlock()
|
||||
|
||||
logger.InfoC("channels", "Starting all channels")
|
||||
logger.InfoC("channels", logger.C0026)
|
||||
go m.dispatchOutbound(dispatchCtx)
|
||||
|
||||
var g errgroup.Group
|
||||
@@ -215,9 +215,9 @@ func (m *Manager) StartAll(ctx context.Context) error {
|
||||
name := name
|
||||
channel := channel
|
||||
g.Go(func() error {
|
||||
logger.InfoCF("channels", "Starting channel", map[string]interface{}{logger.FieldChannel: name})
|
||||
logger.InfoCF("channels", logger.C0027, map[string]interface{}{logger.FieldChannel: name})
|
||||
if err := channel.Start(ctx); err != nil {
|
||||
logger.ErrorCF("channels", "Failed to start channel", map[string]interface{}{logger.FieldChannel: name, logger.FieldError: err.Error()})
|
||||
logger.ErrorCF("channels", logger.C0028, map[string]interface{}{logger.FieldChannel: name, logger.FieldError: err.Error()})
|
||||
return fmt.Errorf("%s: %w", name, err)
|
||||
}
|
||||
return nil
|
||||
@@ -226,7 +226,7 @@ func (m *Manager) StartAll(ctx context.Context) error {
|
||||
if err := g.Wait(); err != nil {
|
||||
return err
|
||||
}
|
||||
logger.InfoC("channels", "All channels started")
|
||||
logger.InfoC("channels", logger.C0029)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -240,7 +240,7 @@ func (m *Manager) StopAll(ctx context.Context) error {
|
||||
m.dispatchTask = nil
|
||||
m.mu.Unlock()
|
||||
|
||||
logger.InfoC("channels", "Stopping all channels")
|
||||
logger.InfoC("channels", logger.C0030)
|
||||
if task != nil {
|
||||
task.cancel()
|
||||
}
|
||||
@@ -250,9 +250,9 @@ func (m *Manager) StopAll(ctx context.Context) error {
|
||||
name := name
|
||||
channel := channel
|
||||
g.Go(func() error {
|
||||
logger.InfoCF("channels", "Stopping channel", map[string]interface{}{logger.FieldChannel: name})
|
||||
logger.InfoCF("channels", logger.C0031, map[string]interface{}{logger.FieldChannel: name})
|
||||
if err := channel.Stop(ctx); err != nil {
|
||||
logger.ErrorCF("channels", "Error stopping channel", map[string]interface{}{logger.FieldChannel: name, logger.FieldError: err.Error()})
|
||||
logger.ErrorCF("channels", logger.C0032, map[string]interface{}{logger.FieldChannel: name, logger.FieldError: err.Error()})
|
||||
return fmt.Errorf("%s: %w", name, err)
|
||||
}
|
||||
return nil
|
||||
@@ -261,7 +261,7 @@ func (m *Manager) StopAll(ctx context.Context) error {
|
||||
if err := g.Wait(); err != nil {
|
||||
return err
|
||||
}
|
||||
logger.InfoC("channels", "All channels stopped")
|
||||
logger.InfoC("channels", logger.C0033)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -285,7 +285,7 @@ func (m *Manager) RestartChannel(ctx context.Context, name string) error {
|
||||
return fmt.Errorf("channel %s not found", name)
|
||||
}
|
||||
|
||||
logger.InfoCF("channels", "Restarting channel", map[string]interface{}{"channel": name})
|
||||
logger.InfoCF("channels", logger.C0034, map[string]interface{}{"channel": name})
|
||||
_ = channel.Stop(ctx)
|
||||
return channel.Start(ctx)
|
||||
}
|
||||
@@ -335,21 +335,21 @@ func (m *Manager) shouldSkipOutboundDuplicate(msg bus.OutboundMessage) bool {
|
||||
}
|
||||
|
||||
func (m *Manager) dispatchOutbound(ctx context.Context) {
|
||||
logger.InfoC("channels", "Outbound dispatcher started")
|
||||
logger.InfoC("channels", logger.C0035)
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
logger.InfoC("channels", "Outbound dispatcher stopped")
|
||||
logger.InfoC("channels", logger.C0036)
|
||||
return
|
||||
default:
|
||||
msg, ok := m.bus.SubscribeOutbound(ctx)
|
||||
if !ok {
|
||||
logger.InfoC("channels", "Outbound dispatcher stopped (bus closed)")
|
||||
logger.InfoC("channels", logger.C0037)
|
||||
return
|
||||
}
|
||||
if m.shouldSkipOutboundDuplicate(msg) {
|
||||
logger.WarnCF("channels", "Duplicate outbound message skipped", map[string]interface{}{
|
||||
logger.WarnCF("channels", logger.C0038, map[string]interface{}{
|
||||
logger.FieldChannel: msg.Channel,
|
||||
logger.FieldChatID: msg.ChatID,
|
||||
})
|
||||
@@ -365,7 +365,7 @@ func (m *Manager) dispatchOutbound(ctx context.Context) {
|
||||
// Internal/system pseudo channels are not externally dispatchable.
|
||||
continue
|
||||
}
|
||||
logger.WarnCF("channels", "Unknown channel for outbound message", map[string]interface{}{
|
||||
logger.WarnCF("channels", logger.C0039, map[string]interface{}{
|
||||
logger.FieldChannel: msg.Channel,
|
||||
})
|
||||
continue
|
||||
@@ -377,9 +377,9 @@ func (m *Manager) dispatchOutbound(ctx context.Context) {
|
||||
}
|
||||
if action != "send" {
|
||||
if ac, ok := channel.(ActionCapable); !ok || !ac.SupportsAction(action) {
|
||||
logger.WarnCF("channels", "Channel does not support outbound action", map[string]interface{}{
|
||||
logger.WarnCF("channels", logger.C0040, map[string]interface{}{
|
||||
logger.FieldChannel: msg.Channel,
|
||||
"action": action,
|
||||
"action": action,
|
||||
})
|
||||
continue
|
||||
}
|
||||
@@ -387,7 +387,7 @@ func (m *Manager) dispatchOutbound(ctx context.Context) {
|
||||
|
||||
if m.outboundLimit != nil {
|
||||
if err := m.outboundLimit.Wait(ctx); err != nil {
|
||||
logger.WarnCF("channels", "Outbound rate limiter canceled", map[string]interface{}{logger.FieldError: err.Error()})
|
||||
logger.WarnCF("channels", logger.C0041, map[string]interface{}{logger.FieldError: err.Error()})
|
||||
continue
|
||||
}
|
||||
}
|
||||
@@ -396,7 +396,7 @@ func (m *Manager) dispatchOutbound(ctx context.Context) {
|
||||
go func(c Channel, outbound bus.OutboundMessage) {
|
||||
defer func() { <-m.dispatchSem }()
|
||||
if err := c.Send(ctx, outbound); err != nil {
|
||||
logger.ErrorCF("channels", "Error sending message to channel", map[string]interface{}{
|
||||
logger.ErrorCF("channels", logger.C0042, map[string]interface{}{
|
||||
logger.FieldChannel: outbound.Channel,
|
||||
logger.FieldError: err.Error(),
|
||||
})
|
||||
|
||||
@@ -47,7 +47,7 @@ func (c *QQChannel) Start(ctx context.Context) error {
|
||||
return fmt.Errorf("QQ app_id and app_secret not configured")
|
||||
}
|
||||
|
||||
logger.InfoC("qq", "Starting QQ bot (WebSocket mode)")
|
||||
logger.InfoC("qq", logger.C0099)
|
||||
|
||||
// Create token source
|
||||
credentials := &token.QQBotCredentials{
|
||||
@@ -80,7 +80,7 @@ func (c *QQChannel) Start(ctx context.Context) error {
|
||||
return fmt.Errorf("failed to get websocket info: %w", err)
|
||||
}
|
||||
|
||||
logger.InfoCF("qq", "Got WebSocket info", map[string]interface{}{
|
||||
logger.InfoCF("qq", logger.C0100, map[string]interface{}{
|
||||
"shards": wsInfo.Shards,
|
||||
})
|
||||
|
||||
@@ -95,7 +95,7 @@ func (c *QQChannel) Start(ctx context.Context) error {
|
||||
})
|
||||
|
||||
c.setRunning(true)
|
||||
logger.InfoC("qq", "QQ bot started successfully")
|
||||
logger.InfoC("qq", logger.C0101)
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -104,7 +104,7 @@ func (c *QQChannel) Stop(ctx context.Context) error {
|
||||
if !c.IsRunning() {
|
||||
return nil
|
||||
}
|
||||
logger.InfoC("qq", "Stopping QQ bot")
|
||||
logger.InfoC("qq", logger.C0102)
|
||||
c.setRunning(false)
|
||||
|
||||
c.runCancel.cancelAndClear()
|
||||
@@ -124,7 +124,7 @@ func (c *QQChannel) Send(ctx context.Context, msg bus.OutboundMessage) error {
|
||||
// Send C2C message
|
||||
_, err := c.api.PostC2CMessage(ctx, msg.ChatID, msgToCreate)
|
||||
if err != nil {
|
||||
logger.ErrorCF("qq", "Failed to send C2C message", map[string]interface{}{
|
||||
logger.ErrorCF("qq", logger.C0103, map[string]interface{}{
|
||||
logger.FieldError: err.Error(),
|
||||
})
|
||||
return err
|
||||
@@ -146,18 +146,18 @@ func (c *QQChannel) handleC2CMessage() event.C2CMessageEventHandler {
|
||||
if data.Author != nil && data.Author.ID != "" {
|
||||
senderID = data.Author.ID
|
||||
} else {
|
||||
logger.WarnC("qq", "Received message with no sender ID")
|
||||
logger.WarnC("qq", logger.C0104)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Extract message content
|
||||
content := data.Content
|
||||
if content == "" {
|
||||
logger.DebugC("qq", "Received empty message, ignoring")
|
||||
logger.DebugC("qq", logger.C0105)
|
||||
return nil
|
||||
}
|
||||
|
||||
logger.InfoCF("qq", "Received C2C message", map[string]interface{}{
|
||||
logger.InfoCF("qq", logger.C0106, map[string]interface{}{
|
||||
logger.FieldSenderID: senderID,
|
||||
logger.FieldChatID: senderID,
|
||||
logger.FieldMessageContentLength: len(content),
|
||||
@@ -187,18 +187,18 @@ func (c *QQChannel) handleGroupATMessage() event.GroupATMessageEventHandler {
|
||||
if data.Author != nil && data.Author.ID != "" {
|
||||
senderID = data.Author.ID
|
||||
} else {
|
||||
logger.WarnC("qq", "Received group message with no sender ID")
|
||||
logger.WarnC("qq", logger.C0107)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Extract message content (remove bot mention)
|
||||
content := data.Content
|
||||
if content == "" {
|
||||
logger.DebugC("qq", "Received empty group message, ignoring")
|
||||
logger.DebugC("qq", logger.C0108)
|
||||
return nil
|
||||
}
|
||||
|
||||
logger.InfoCF("qq", "Received group AT message", map[string]interface{}{
|
||||
logger.InfoCF("qq", logger.C0109, map[string]interface{}{
|
||||
logger.FieldSenderID: senderID,
|
||||
"group_id": data.GroupID,
|
||||
logger.FieldMessageContentLength: len(content),
|
||||
|
||||
@@ -33,15 +33,15 @@ const (
|
||||
|
||||
type TelegramChannel struct {
|
||||
*BaseChannel
|
||||
bot *telego.Bot
|
||||
config config.TelegramConfig
|
||||
chatIDs map[string]int64
|
||||
chatIDsMu sync.RWMutex
|
||||
updates <-chan telego.Update
|
||||
runCancel cancelGuard
|
||||
handleSem chan struct{}
|
||||
handleWG sync.WaitGroup
|
||||
botUsername string
|
||||
bot *telego.Bot
|
||||
config config.TelegramConfig
|
||||
chatIDs map[string]int64
|
||||
chatIDsMu sync.RWMutex
|
||||
updates <-chan telego.Update
|
||||
runCancel cancelGuard
|
||||
handleSem chan struct{}
|
||||
handleWG sync.WaitGroup
|
||||
botUsername string
|
||||
}
|
||||
|
||||
func (c *TelegramChannel) SupportsAction(action string) bool {
|
||||
@@ -62,11 +62,11 @@ func NewTelegramChannel(cfg config.TelegramConfig, bus *bus.MessageBus) (*Telegr
|
||||
base := NewBaseChannel("telegram", cfg, bus, cfg.AllowFrom)
|
||||
|
||||
return &TelegramChannel{
|
||||
BaseChannel: base,
|
||||
bot: bot,
|
||||
config: cfg,
|
||||
chatIDs: make(map[string]int64),
|
||||
handleSem: make(chan struct{}, telegramMaxConcurrentHandlers),
|
||||
BaseChannel: base,
|
||||
bot: bot,
|
||||
config: cfg,
|
||||
chatIDs: make(map[string]int64),
|
||||
handleSem: make(chan struct{}, telegramMaxConcurrentHandlers),
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -91,7 +91,7 @@ func (c *TelegramChannel) Start(ctx context.Context) error {
|
||||
if c.IsRunning() {
|
||||
return nil
|
||||
}
|
||||
logger.InfoC("telegram", "Starting Telegram bot (polling mode)")
|
||||
logger.InfoC("telegram", logger.C0054)
|
||||
|
||||
runCtx, cancel := context.WithCancel(ctx)
|
||||
c.runCancel.set(cancel)
|
||||
@@ -111,7 +111,7 @@ func (c *TelegramChannel) Start(ctx context.Context) error {
|
||||
return fmt.Errorf("failed to get bot info: %w", err)
|
||||
}
|
||||
c.botUsername = strings.ToLower(strings.TrimSpace(botInfo.Username))
|
||||
logger.InfoCF("telegram", "Telegram bot connected", map[string]interface{}{
|
||||
logger.InfoCF("telegram", logger.C0055, map[string]interface{}{
|
||||
"username": botInfo.Username,
|
||||
})
|
||||
|
||||
@@ -122,7 +122,7 @@ func (c *TelegramChannel) Start(ctx context.Context) error {
|
||||
return
|
||||
case update, ok := <-updates:
|
||||
if !ok {
|
||||
logger.WarnC("telegram", "Updates channel closed unexpectedly, attempting to restart polling...")
|
||||
logger.WarnC("telegram", logger.C0056)
|
||||
c.setRunning(false)
|
||||
|
||||
select {
|
||||
@@ -133,7 +133,7 @@ func (c *TelegramChannel) Start(ctx context.Context) error {
|
||||
|
||||
newUpdates, err := c.bot.UpdatesViaLongPolling(runCtx, nil)
|
||||
if err != nil {
|
||||
logger.ErrorCF("telegram", "Failed to restart updates polling", map[string]interface{}{
|
||||
logger.ErrorCF("telegram", logger.C0057, map[string]interface{}{
|
||||
logger.FieldError: err.Error(),
|
||||
})
|
||||
continue
|
||||
@@ -142,7 +142,7 @@ func (c *TelegramChannel) Start(ctx context.Context) error {
|
||||
updates = newUpdates
|
||||
c.updates = newUpdates
|
||||
c.setRunning(true)
|
||||
logger.InfoC("telegram", "Updates polling restarted successfully")
|
||||
logger.InfoC("telegram", logger.C0058)
|
||||
continue
|
||||
}
|
||||
if update.Message != nil {
|
||||
@@ -161,7 +161,7 @@ func (c *TelegramChannel) Stop(ctx context.Context) error {
|
||||
if !c.IsRunning() {
|
||||
return nil
|
||||
}
|
||||
logger.InfoC("telegram", "Stopping Telegram bot")
|
||||
logger.InfoC("telegram", logger.C0059)
|
||||
c.setRunning(false)
|
||||
c.runCancel.cancelAndClear()
|
||||
|
||||
@@ -173,7 +173,7 @@ func (c *TelegramChannel) Stop(ctx context.Context) error {
|
||||
select {
|
||||
case <-done:
|
||||
case <-time.After(telegramStopWaitHandlersPeriod):
|
||||
logger.WarnC("telegram", "Timeout waiting for telegram message handlers to stop")
|
||||
logger.WarnC("telegram", logger.C0060)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -195,7 +195,7 @@ func (c *TelegramChannel) dispatchHandleMessage(runCtx context.Context, message
|
||||
defer func() { <-c.handleSem }()
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
logger.ErrorCF("telegram", "Recovered panic in telegram message handler", map[string]interface{}{
|
||||
logger.ErrorCF("telegram", logger.C0061, map[string]interface{}{
|
||||
"panic": fmt.Sprintf("%v", r),
|
||||
})
|
||||
}
|
||||
@@ -219,7 +219,7 @@ func (c *TelegramChannel) handleCallbackQuery(ctx context.Context, query *telego
|
||||
})
|
||||
cancel()
|
||||
|
||||
logger.InfoCF("telegram", "Callback query received", map[string]interface{}{
|
||||
logger.InfoCF("telegram", logger.C0062, map[string]interface{}{
|
||||
"sender_id": senderID,
|
||||
"data": query.Data,
|
||||
})
|
||||
@@ -308,7 +308,7 @@ func (c *TelegramChannel) Send(ctx context.Context, msg bus.OutboundMessage) err
|
||||
cancelSend()
|
||||
|
||||
if err != nil {
|
||||
logger.WarnCF("telegram", "HTML parse failed, fallback to plain text", map[string]interface{}{
|
||||
logger.WarnCF("telegram", logger.C0063, map[string]interface{}{
|
||||
logger.FieldError: err.Error(),
|
||||
})
|
||||
plain := plainTextFromTelegramHTML(htmlContent)
|
||||
@@ -472,7 +472,7 @@ func (c *TelegramChannel) handleMessage(runCtx context.Context, message *telego.
|
||||
return
|
||||
}
|
||||
if user.IsBot {
|
||||
logger.DebugCF("telegram", "Ignoring bot-originated message", map[string]interface{}{
|
||||
logger.DebugCF("telegram", logger.C0064, map[string]interface{}{
|
||||
"user_id": user.ID,
|
||||
})
|
||||
return
|
||||
@@ -549,27 +549,27 @@ func (c *TelegramChannel) handleMessage(runCtx context.Context, message *telego.
|
||||
}
|
||||
|
||||
if !c.isAllowedChat(chatID, message.Chat.Type) {
|
||||
logger.WarnCF("telegram", "Telegram message rejected by chat allowlist", map[string]interface{}{
|
||||
logger.WarnCF("telegram", logger.C0065, map[string]interface{}{
|
||||
logger.FieldSenderID: senderID,
|
||||
logger.FieldChatID: chatID,
|
||||
})
|
||||
return
|
||||
}
|
||||
if !c.shouldHandleGroupMessage(message, content) {
|
||||
logger.DebugCF("telegram", "Ignoring group message without mention/command", map[string]interface{}{
|
||||
logger.DebugCF("telegram", logger.C0048, map[string]interface{}{
|
||||
logger.FieldSenderID: senderID,
|
||||
logger.FieldChatID: chatID,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
logger.InfoCF("telegram", "Telegram message received", map[string]interface{}{
|
||||
logger.InfoCF("telegram", logger.C0066, map[string]interface{}{
|
||||
logger.FieldSenderID: senderID,
|
||||
logger.FieldPreview: truncateString(content, 50),
|
||||
})
|
||||
|
||||
if !c.IsAllowed(senderID) {
|
||||
logger.WarnCF("telegram", "Telegram message rejected by allowlist", map[string]interface{}{
|
||||
logger.WarnCF("telegram", logger.C0067, map[string]interface{}{
|
||||
logger.FieldSenderID: senderID,
|
||||
logger.FieldChatID: chatID,
|
||||
})
|
||||
@@ -633,7 +633,6 @@ func min(a, b int) int {
|
||||
return b
|
||||
}
|
||||
|
||||
|
||||
func splitTelegramMarkdown(s string, maxRunes int) []string {
|
||||
if s == "" {
|
||||
return []string{""}
|
||||
|
||||
@@ -52,12 +52,14 @@ func runChannelTask(name, taskName string, task func() error, onFailure func(err
|
||||
go func() {
|
||||
if err := task(); err != nil {
|
||||
if errors.Is(err, context.Canceled) {
|
||||
logger.InfoCF(name, taskName+" stopped", map[string]interface{}{
|
||||
"reason": "context canceled",
|
||||
logger.InfoCF(name, logger.C0168, map[string]interface{}{
|
||||
"task_name": taskName,
|
||||
"reason": "context canceled",
|
||||
})
|
||||
return
|
||||
}
|
||||
logger.ErrorCF(name, taskName+" failed", map[string]interface{}{
|
||||
logger.ErrorCF(name, logger.C0169, map[string]interface{}{
|
||||
"task_name": taskName,
|
||||
logger.FieldError: err.Error(),
|
||||
})
|
||||
if onFailure != nil {
|
||||
|
||||
@@ -41,7 +41,7 @@ func (c *WhatsAppChannel) Start(ctx context.Context) error {
|
||||
if c.IsRunning() {
|
||||
return nil
|
||||
}
|
||||
logger.InfoCF("whatsapp", "Starting WhatsApp channel", map[string]interface{}{
|
||||
logger.InfoCF("whatsapp", logger.C0121, map[string]interface{}{
|
||||
"url": c.url,
|
||||
})
|
||||
runCtx, cancel := context.WithCancel(ctx)
|
||||
@@ -61,7 +61,7 @@ func (c *WhatsAppChannel) Start(ctx context.Context) error {
|
||||
c.mu.Unlock()
|
||||
|
||||
c.setRunning(true)
|
||||
logger.InfoC("whatsapp", "WhatsApp channel connected")
|
||||
logger.InfoC("whatsapp", logger.C0122)
|
||||
|
||||
go c.listen(runCtx)
|
||||
|
||||
@@ -72,7 +72,7 @@ func (c *WhatsAppChannel) Stop(ctx context.Context) error {
|
||||
if !c.IsRunning() {
|
||||
return nil
|
||||
}
|
||||
logger.InfoC("whatsapp", "Stopping WhatsApp channel")
|
||||
logger.InfoC("whatsapp", logger.C0123)
|
||||
c.runCancel.cancelAndClear()
|
||||
|
||||
c.mu.Lock()
|
||||
@@ -80,7 +80,7 @@ func (c *WhatsAppChannel) Stop(ctx context.Context) error {
|
||||
|
||||
if c.conn != nil {
|
||||
if err := c.conn.Close(); err != nil {
|
||||
logger.WarnCF("whatsapp", "Error closing WhatsApp connection", map[string]interface{}{
|
||||
logger.WarnCF("whatsapp", logger.C0124, map[string]interface{}{
|
||||
logger.FieldError: err.Error(),
|
||||
})
|
||||
}
|
||||
@@ -146,12 +146,12 @@ func (c *WhatsAppChannel) listen(ctx context.Context) {
|
||||
return
|
||||
}
|
||||
if websocket.IsCloseError(err, websocket.CloseNormalClosure, websocket.CloseGoingAway) || errors.Is(err, net.ErrClosed) {
|
||||
logger.InfoCF("whatsapp", "WhatsApp connection closed", map[string]interface{}{
|
||||
logger.InfoCF("whatsapp", logger.C0125, map[string]interface{}{
|
||||
logger.FieldError: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
logger.WarnCF("whatsapp", "WhatsApp read error", map[string]interface{}{
|
||||
logger.WarnCF("whatsapp", logger.C0126, map[string]interface{}{
|
||||
logger.FieldError: err.Error(),
|
||||
})
|
||||
if !sleepWithContext(ctx, backoff) {
|
||||
@@ -164,7 +164,7 @@ func (c *WhatsAppChannel) listen(ctx context.Context) {
|
||||
|
||||
var msg map[string]interface{}
|
||||
if err := json.Unmarshal(message, &msg); err != nil {
|
||||
logger.WarnCF("whatsapp", "Failed to unmarshal WhatsApp message", map[string]interface{}{
|
||||
logger.WarnCF("whatsapp", logger.C0127, map[string]interface{}{
|
||||
logger.FieldError: err.Error(),
|
||||
})
|
||||
continue
|
||||
@@ -216,7 +216,7 @@ func (c *WhatsAppChannel) handleIncomingMessage(msg map[string]interface{}) {
|
||||
metadata["user_name"] = userName
|
||||
}
|
||||
|
||||
logger.InfoCF("whatsapp", "WhatsApp message received", map[string]interface{}{
|
||||
logger.InfoCF("whatsapp", logger.C0128, map[string]interface{}{
|
||||
logger.FieldSenderID: senderID,
|
||||
logger.FieldPreview: truncateString(content, 50),
|
||||
})
|
||||
|
||||
179
pkg/logger/codes.go
Normal file
179
pkg/logger/codes.go
Normal file
@@ -0,0 +1,179 @@
|
||||
package logger
|
||||
|
||||
type CodeID int
|
||||
|
||||
const (
|
||||
C0001 CodeID = iota + 1
|
||||
C0002
|
||||
C0003
|
||||
C0004
|
||||
C0005
|
||||
C0006
|
||||
C0007
|
||||
C0008
|
||||
C0009
|
||||
C0010
|
||||
C0011
|
||||
C0012
|
||||
C0013
|
||||
C0014
|
||||
C0015
|
||||
C0016
|
||||
C0017
|
||||
C0018
|
||||
C0019
|
||||
C0020
|
||||
C0021
|
||||
C0022
|
||||
C0023
|
||||
C0024
|
||||
C0025
|
||||
C0026
|
||||
C0027
|
||||
C0028
|
||||
C0029
|
||||
C0030
|
||||
C0031
|
||||
C0032
|
||||
C0033
|
||||
C0034
|
||||
C0035
|
||||
C0036
|
||||
C0037
|
||||
C0038
|
||||
C0039
|
||||
C0040
|
||||
C0041
|
||||
C0042
|
||||
C0043
|
||||
C0044
|
||||
C0045
|
||||
C0046
|
||||
C0047
|
||||
C0048
|
||||
C0049
|
||||
C0050
|
||||
C0051
|
||||
C0052
|
||||
C0053
|
||||
C0054
|
||||
C0055
|
||||
C0056
|
||||
C0057
|
||||
C0058
|
||||
C0059
|
||||
C0060
|
||||
C0061
|
||||
C0062
|
||||
C0063
|
||||
C0064
|
||||
C0065
|
||||
C0066
|
||||
C0067
|
||||
C0068
|
||||
C0069
|
||||
C0070
|
||||
C0071
|
||||
C0072
|
||||
C0073
|
||||
C0074
|
||||
C0075
|
||||
C0076
|
||||
C0077
|
||||
C0078
|
||||
C0079
|
||||
C0080
|
||||
C0081
|
||||
C0082
|
||||
C0083
|
||||
C0084
|
||||
C0085
|
||||
C0086
|
||||
C0087
|
||||
C0088
|
||||
C0089
|
||||
C0090
|
||||
C0091
|
||||
C0092
|
||||
C0093
|
||||
C0094
|
||||
C0095
|
||||
C0096
|
||||
C0097
|
||||
C0098
|
||||
C0099
|
||||
C0100
|
||||
C0101
|
||||
C0102
|
||||
C0103
|
||||
C0104
|
||||
C0105
|
||||
C0106
|
||||
C0107
|
||||
C0108
|
||||
C0109
|
||||
C0110
|
||||
C0111
|
||||
C0112
|
||||
C0113
|
||||
C0114
|
||||
C0115
|
||||
C0116
|
||||
C0117
|
||||
C0118
|
||||
C0119
|
||||
C0120
|
||||
C0121
|
||||
C0122
|
||||
C0123
|
||||
C0124
|
||||
C0125
|
||||
C0126
|
||||
C0127
|
||||
C0128
|
||||
C0129
|
||||
C0130
|
||||
C0131
|
||||
C0132
|
||||
C0133
|
||||
C0134
|
||||
C0135
|
||||
C0136
|
||||
C0137
|
||||
C0138
|
||||
C0139
|
||||
C0140
|
||||
C0141
|
||||
C0142
|
||||
C0143
|
||||
C0144
|
||||
C0145
|
||||
C0146
|
||||
C0147
|
||||
C0148
|
||||
C0149
|
||||
C0150
|
||||
C0151
|
||||
C0152
|
||||
C0153
|
||||
C0154
|
||||
C0155
|
||||
C0156
|
||||
C0157
|
||||
C0158
|
||||
C0159
|
||||
C0160
|
||||
C0161
|
||||
C0162
|
||||
C0163
|
||||
C0164
|
||||
C0165
|
||||
C0166
|
||||
C0167
|
||||
C0168
|
||||
C0169
|
||||
C0170
|
||||
C0171
|
||||
C0172
|
||||
C0173
|
||||
)
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
@@ -49,7 +50,8 @@ type LogEntry struct {
|
||||
Level string `json:"level"`
|
||||
Timestamp string `json:"timestamp"`
|
||||
Component string `json:"component,omitempty"`
|
||||
Message string `json:"message"`
|
||||
Code int `json:"code,omitempty"`
|
||||
Message string `json:"message,omitempty"`
|
||||
Fields map[string]interface{} `json:"fields,omitempty"`
|
||||
Caller string `json:"caller,omitempty"`
|
||||
}
|
||||
@@ -106,9 +108,9 @@ func EnableFileLoggingWithRotation(filePath string, maxSizeMB, maxAgeDays int) e
|
||||
logger.maxSizeBytes = int64(maxSizeMB) * 1024 * 1024
|
||||
logger.maxAgeDays = maxAgeDays
|
||||
if err := logger.cleanupOldLogFiles(); err != nil {
|
||||
log.Println("Failed to clean up old log files:", err)
|
||||
log.Println(C0145, err)
|
||||
}
|
||||
log.Println("File logging enabled:", filePath)
|
||||
log.Println(C0146, filePath)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -122,20 +124,28 @@ func DisableFileLogging() {
|
||||
logger.filePath = ""
|
||||
logger.maxSizeBytes = 0
|
||||
logger.maxAgeDays = 0
|
||||
log.Println("File logging disabled")
|
||||
log.Println(C0147)
|
||||
}
|
||||
}
|
||||
|
||||
func logMessage(level LogLevel, component string, message string, fields map[string]interface{}) {
|
||||
func logMessage(level LogLevel, component string, code CodeID, fields map[string]interface{}) {
|
||||
if level < currentLevel {
|
||||
return
|
||||
}
|
||||
|
||||
if fields == nil {
|
||||
fields = map[string]interface{}{}
|
||||
}
|
||||
|
||||
entryCode := int(code)
|
||||
entryMessage := extractSystemError(fields)
|
||||
|
||||
entry := LogEntry{
|
||||
Level: logLevelNames[level],
|
||||
Timestamp: time.Now().UTC().Format(time.RFC3339),
|
||||
Component: component,
|
||||
Message: message,
|
||||
Code: entryCode,
|
||||
Message: entryMessage,
|
||||
Fields: fields,
|
||||
}
|
||||
|
||||
@@ -150,7 +160,7 @@ func logMessage(level LogLevel, component string, message string, fields map[str
|
||||
jsonData, err := json.Marshal(entry)
|
||||
if err == nil {
|
||||
if err := logger.writeLine(append(jsonData, '\n')); err != nil {
|
||||
log.Println("Failed to write file log:", err)
|
||||
log.Println(C0148, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -164,9 +174,12 @@ func logMessage(level LogLevel, component string, message string, fields map[str
|
||||
entry.Timestamp,
|
||||
logLevelNames[level],
|
||||
formatComponent(component),
|
||||
message,
|
||||
formatCode(entry.Code),
|
||||
fieldStr,
|
||||
)
|
||||
if entry.Message != "" {
|
||||
logLine += " msg=" + entry.Message
|
||||
}
|
||||
|
||||
log.Println(logLine)
|
||||
|
||||
@@ -175,6 +188,24 @@ func logMessage(level LogLevel, component string, message string, fields map[str
|
||||
}
|
||||
}
|
||||
|
||||
func extractSystemError(fields map[string]interface{}) string {
|
||||
if fields == nil {
|
||||
return ""
|
||||
}
|
||||
v, ok := fields[FieldError]
|
||||
if !ok || v == nil {
|
||||
return ""
|
||||
}
|
||||
return strings.TrimSpace(fmt.Sprintf("%v", v))
|
||||
}
|
||||
|
||||
func formatCode(code int) string {
|
||||
if code <= 0 {
|
||||
return "code=0"
|
||||
}
|
||||
return "code=" + strconv.Itoa(code)
|
||||
}
|
||||
|
||||
func (l *Logger) writeLine(line []byte) error {
|
||||
l.fileMu.Lock()
|
||||
defer l.fileMu.Unlock()
|
||||
@@ -272,82 +303,82 @@ func formatFields(fields map[string]interface{}) string {
|
||||
return fmt.Sprintf("{%s}", strings.Join(parts, ", "))
|
||||
}
|
||||
|
||||
func Debug(message string) {
|
||||
logMessage(DEBUG, "", message, nil)
|
||||
func Debug(code CodeID) {
|
||||
logMessage(DEBUG, "", code, nil)
|
||||
}
|
||||
|
||||
func DebugC(component string, message string) {
|
||||
logMessage(DEBUG, component, message, nil)
|
||||
func DebugC(component string, code CodeID) {
|
||||
logMessage(DEBUG, component, code, nil)
|
||||
}
|
||||
|
||||
func DebugF(message string, fields map[string]interface{}) {
|
||||
logMessage(DEBUG, "", message, fields)
|
||||
func DebugF(code CodeID, fields map[string]interface{}) {
|
||||
logMessage(DEBUG, "", code, fields)
|
||||
}
|
||||
|
||||
func DebugCF(component string, message string, fields map[string]interface{}) {
|
||||
logMessage(DEBUG, component, message, fields)
|
||||
func DebugCF(component string, code CodeID, fields map[string]interface{}) {
|
||||
logMessage(DEBUG, component, code, fields)
|
||||
}
|
||||
|
||||
func Info(message string) {
|
||||
logMessage(INFO, "", message, nil)
|
||||
func Info(code CodeID) {
|
||||
logMessage(INFO, "", code, nil)
|
||||
}
|
||||
|
||||
func InfoC(component string, message string) {
|
||||
logMessage(INFO, component, message, nil)
|
||||
func InfoC(component string, code CodeID) {
|
||||
logMessage(INFO, component, code, nil)
|
||||
}
|
||||
|
||||
func InfoF(message string, fields map[string]interface{}) {
|
||||
logMessage(INFO, "", message, fields)
|
||||
func InfoF(code CodeID, fields map[string]interface{}) {
|
||||
logMessage(INFO, "", code, fields)
|
||||
}
|
||||
|
||||
func InfoCF(component string, message string, fields map[string]interface{}) {
|
||||
logMessage(INFO, component, message, fields)
|
||||
func InfoCF(component string, code CodeID, fields map[string]interface{}) {
|
||||
logMessage(INFO, component, code, fields)
|
||||
}
|
||||
|
||||
func Warn(message string) {
|
||||
logMessage(WARN, "", message, nil)
|
||||
func Warn(code CodeID) {
|
||||
logMessage(WARN, "", code, nil)
|
||||
}
|
||||
|
||||
func WarnC(component string, message string) {
|
||||
logMessage(WARN, component, message, nil)
|
||||
func WarnC(component string, code CodeID) {
|
||||
logMessage(WARN, component, code, nil)
|
||||
}
|
||||
|
||||
func WarnF(message string, fields map[string]interface{}) {
|
||||
logMessage(WARN, "", message, fields)
|
||||
func WarnF(code CodeID, fields map[string]interface{}) {
|
||||
logMessage(WARN, "", code, fields)
|
||||
}
|
||||
|
||||
func WarnCF(component string, message string, fields map[string]interface{}) {
|
||||
logMessage(WARN, component, message, fields)
|
||||
func WarnCF(component string, code CodeID, fields map[string]interface{}) {
|
||||
logMessage(WARN, component, code, fields)
|
||||
}
|
||||
|
||||
func Error(message string) {
|
||||
logMessage(ERROR, "", message, nil)
|
||||
func Error(code CodeID) {
|
||||
logMessage(ERROR, "", code, nil)
|
||||
}
|
||||
|
||||
func ErrorC(component string, message string) {
|
||||
logMessage(ERROR, component, message, nil)
|
||||
func ErrorC(component string, code CodeID) {
|
||||
logMessage(ERROR, component, code, nil)
|
||||
}
|
||||
|
||||
func ErrorF(message string, fields map[string]interface{}) {
|
||||
logMessage(ERROR, "", message, fields)
|
||||
func ErrorF(code CodeID, fields map[string]interface{}) {
|
||||
logMessage(ERROR, "", code, fields)
|
||||
}
|
||||
|
||||
func ErrorCF(component string, message string, fields map[string]interface{}) {
|
||||
logMessage(ERROR, component, message, fields)
|
||||
func ErrorCF(component string, code CodeID, fields map[string]interface{}) {
|
||||
logMessage(ERROR, component, code, fields)
|
||||
}
|
||||
|
||||
func Fatal(message string) {
|
||||
logMessage(FATAL, "", message, nil)
|
||||
func Fatal(code CodeID) {
|
||||
logMessage(FATAL, "", code, nil)
|
||||
}
|
||||
|
||||
func FatalC(component string, message string) {
|
||||
logMessage(FATAL, component, message, nil)
|
||||
func FatalC(component string, code CodeID) {
|
||||
logMessage(FATAL, component, code, nil)
|
||||
}
|
||||
|
||||
func FatalF(message string, fields map[string]interface{}) {
|
||||
logMessage(FATAL, "", message, fields)
|
||||
func FatalF(code CodeID, fields map[string]interface{}) {
|
||||
logMessage(FATAL, "", code, fields)
|
||||
}
|
||||
|
||||
func FatalCF(component string, message string, fields map[string]interface{}) {
|
||||
logMessage(FATAL, component, message, fields)
|
||||
func FatalCF(component string, code CodeID, fields map[string]interface{}) {
|
||||
logMessage(FATAL, component, code, fields)
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ func (p *HTTPProvider) Chat(ctx context.Context, messages []Message, tools []Too
|
||||
return nil, fmt.Errorf("API base not configured")
|
||||
}
|
||||
|
||||
logger.DebugCF("provider", "HTTP chat request", map[string]interface{}{
|
||||
logger.DebugCF("provider", logger.C0133, map[string]interface{}{
|
||||
"api_base": p.apiBase,
|
||||
"protocol": p.protocol,
|
||||
"model": model,
|
||||
@@ -421,7 +421,7 @@ func (p *HTTPProvider) callChatCompletionsStream(ctx context.Context, messages [
|
||||
}
|
||||
body, _ := json.Marshal(map[string]interface{}{
|
||||
"choices": []map[string]interface{}{{
|
||||
"message": map[string]interface{}{"content": fullText.String()},
|
||||
"message": map[string]interface{}{"content": fullText.String()},
|
||||
"finish_reason": "stop",
|
||||
}},
|
||||
})
|
||||
|
||||
@@ -17,14 +17,14 @@ import (
|
||||
type AlertFunc func(msg string)
|
||||
|
||||
type Service struct {
|
||||
cfgPath string
|
||||
workspace string
|
||||
interval time.Duration
|
||||
autoHeal bool
|
||||
onAlert AlertFunc
|
||||
runner *lifecycle.LoopRunner
|
||||
mu sync.RWMutex
|
||||
lastAlerts map[string]time.Time
|
||||
cfgPath string
|
||||
workspace string
|
||||
interval time.Duration
|
||||
autoHeal bool
|
||||
onAlert AlertFunc
|
||||
runner *lifecycle.LoopRunner
|
||||
mu sync.RWMutex
|
||||
lastAlerts map[string]time.Time
|
||||
mgr *channels.Manager
|
||||
healingChannels map[string]bool
|
||||
}
|
||||
@@ -34,13 +34,13 @@ func NewService(cfgPath, workspace string, intervalSec int, autoHeal bool, onAle
|
||||
intervalSec = 60
|
||||
}
|
||||
return &Service{
|
||||
cfgPath: cfgPath,
|
||||
workspace: workspace,
|
||||
interval: time.Duration(intervalSec) * time.Second,
|
||||
autoHeal: autoHeal,
|
||||
onAlert: onAlert,
|
||||
runner: lifecycle.NewLoopRunner(),
|
||||
lastAlerts: map[string]time.Time{},
|
||||
cfgPath: cfgPath,
|
||||
workspace: workspace,
|
||||
interval: time.Duration(intervalSec) * time.Second,
|
||||
autoHeal: autoHeal,
|
||||
onAlert: onAlert,
|
||||
runner: lifecycle.NewLoopRunner(),
|
||||
lastAlerts: map[string]time.Time{},
|
||||
healingChannels: map[string]bool{},
|
||||
}
|
||||
}
|
||||
@@ -53,7 +53,7 @@ func (s *Service) Start() {
|
||||
if !s.runner.Start(s.loop) {
|
||||
return
|
||||
}
|
||||
logger.InfoCF("sentinel", "Sentinel started", map[string]interface{}{
|
||||
logger.InfoCF("sentinel", logger.C0134, map[string]interface{}{
|
||||
"interval": s.interval.String(),
|
||||
"auto_heal": s.autoHeal,
|
||||
})
|
||||
@@ -63,7 +63,7 @@ func (s *Service) Stop() {
|
||||
if !s.runner.Stop() {
|
||||
return
|
||||
}
|
||||
logger.InfoC("sentinel", "Sentinel stopped")
|
||||
logger.InfoC("sentinel", logger.C0135)
|
||||
}
|
||||
|
||||
func (s *Service) loop(stopCh <-chan struct{}) {
|
||||
@@ -124,12 +124,12 @@ func (s *Service) checkChannels() []string {
|
||||
delete(s.healingChannels, n)
|
||||
s.mu.Unlock()
|
||||
}()
|
||||
logger.InfoCF("sentinel", "Attempting auto-heal for channel", map[string]interface{}{"channel": n})
|
||||
logger.InfoCF("sentinel", logger.C0136, map[string]interface{}{"channel": n})
|
||||
// Use a fresh context for restart to avoid being canceled by sentinel loop
|
||||
if rErr := s.mgr.RestartChannel(context.Background(), n); rErr != nil {
|
||||
logger.ErrorCF("sentinel", "Auto-heal restart failed", map[string]interface{}{"channel": n, "error": rErr.Error()})
|
||||
logger.ErrorCF("sentinel", logger.C0137, map[string]interface{}{"channel": n, "error": rErr.Error()})
|
||||
} else {
|
||||
logger.InfoCF("sentinel", "Auto-heal successful", map[string]interface{}{"channel": n})
|
||||
logger.InfoCF("sentinel", logger.C0138, map[string]interface{}{"channel": n})
|
||||
}
|
||||
}(name)
|
||||
}
|
||||
@@ -210,7 +210,7 @@ func (s *Service) alert(msg string) {
|
||||
s.lastAlerts[msg] = now
|
||||
s.mu.Unlock()
|
||||
|
||||
logger.WarnCF("sentinel", msg, nil)
|
||||
logger.WarnCF("sentinel", logger.C0170, map[string]interface{}{"alert": msg})
|
||||
if s.onAlert != nil {
|
||||
s.onAlert(msg)
|
||||
}
|
||||
|
||||
@@ -32,16 +32,16 @@ func (s *Server) Start() error {
|
||||
Handler: s.withCORS(mux),
|
||||
}
|
||||
|
||||
logger.InfoCF("server", "Starting HTTP server", map[string]interface{}{
|
||||
logger.InfoCF("server", logger.C0139, map[string]interface{}{
|
||||
"addr": addr,
|
||||
})
|
||||
|
||||
// Check/log indicating it's ready for reverse proxying (per requirement)
|
||||
logger.InfoC("server", "Server ready for reverse proxying")
|
||||
logger.InfoC("server", logger.C0140)
|
||||
|
||||
go func() {
|
||||
if err := s.server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
||||
logger.ErrorCF("server", "HTTP server failed", map[string]interface{}{
|
||||
logger.ErrorCF("server", logger.C0141, map[string]interface{}{
|
||||
logger.FieldError: err.Error(),
|
||||
})
|
||||
}
|
||||
@@ -52,7 +52,7 @@ func (s *Server) Start() error {
|
||||
|
||||
func (s *Server) Stop(ctx context.Context) error {
|
||||
if s.server != nil {
|
||||
logger.InfoC("server", "Stopping HTTP server")
|
||||
logger.InfoC("server", logger.C0142)
|
||||
return s.server.Shutdown(ctx)
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -40,7 +40,7 @@ func (r *ToolRegistry) Get(name string) (Tool, bool) {
|
||||
}
|
||||
|
||||
func (r *ToolRegistry) Execute(ctx context.Context, name string, args map[string]interface{}) (string, error) {
|
||||
logger.InfoCF("tool", "Tool execution started",
|
||||
logger.InfoCF("tool", logger.C0164,
|
||||
map[string]interface{}{
|
||||
"tool": name,
|
||||
"args": args,
|
||||
@@ -48,7 +48,7 @@ func (r *ToolRegistry) Execute(ctx context.Context, name string, args map[string
|
||||
|
||||
tool, ok := r.Get(name)
|
||||
if !ok {
|
||||
logger.ErrorCF("tool", "Tool not found",
|
||||
logger.ErrorCF("tool", logger.C0165,
|
||||
map[string]interface{}{
|
||||
"tool": name,
|
||||
})
|
||||
@@ -60,14 +60,14 @@ func (r *ToolRegistry) Execute(ctx context.Context, name string, args map[string
|
||||
duration := time.Since(start)
|
||||
|
||||
if err != nil {
|
||||
logger.ErrorCF("tool", "Tool execution failed",
|
||||
logger.ErrorCF("tool", logger.C0166,
|
||||
map[string]interface{}{
|
||||
"tool": name,
|
||||
"duration": duration.Milliseconds(),
|
||||
logger.FieldError: err.Error(),
|
||||
})
|
||||
} else {
|
||||
logger.InfoCF("tool", "Tool execution completed",
|
||||
logger.InfoCF("tool", logger.C0167,
|
||||
map[string]interface{}{
|
||||
"tool": name,
|
||||
"duration_ms": duration.Milliseconds(),
|
||||
|
||||
698
webui/public/log-codes.json
Normal file
698
webui/public/log-codes.json
Normal file
@@ -0,0 +1,698 @@
|
||||
{
|
||||
"version": 1,
|
||||
"generated_at": "Tue Mar 3 02:06:08 2026 UTC",
|
||||
"items": [
|
||||
{
|
||||
"code": 1,
|
||||
"text": "Message rejected by allowlist"
|
||||
},
|
||||
{
|
||||
"code": 2,
|
||||
"text": "Duplicate inbound message skipped"
|
||||
},
|
||||
{
|
||||
"code": 3,
|
||||
"text": "Duplicate inbound content skipped"
|
||||
},
|
||||
{
|
||||
"code": 4,
|
||||
"text": "Initializing channel manager"
|
||||
},
|
||||
{
|
||||
"code": 5,
|
||||
"text": "Attempting to initialize Telegram channel"
|
||||
},
|
||||
{
|
||||
"code": 6,
|
||||
"text": "Telegram token is empty, skipping"
|
||||
},
|
||||
{
|
||||
"code": 7,
|
||||
"text": "Failed to initialize Telegram channel"
|
||||
},
|
||||
{
|
||||
"code": 8,
|
||||
"text": "Telegram channel enabled successfully"
|
||||
},
|
||||
{
|
||||
"code": 9,
|
||||
"text": "WhatsApp bridge URL is empty, skipping"
|
||||
},
|
||||
{
|
||||
"code": 10,
|
||||
"text": "Failed to initialize WhatsApp channel"
|
||||
},
|
||||
{
|
||||
"code": 11,
|
||||
"text": "WhatsApp channel enabled successfully"
|
||||
},
|
||||
{
|
||||
"code": 12,
|
||||
"text": "Failed to initialize Feishu channel"
|
||||
},
|
||||
{
|
||||
"code": 13,
|
||||
"text": "Feishu channel enabled successfully"
|
||||
},
|
||||
{
|
||||
"code": 14,
|
||||
"text": "Discord token is empty, skipping"
|
||||
},
|
||||
{
|
||||
"code": 15,
|
||||
"text": "Failed to initialize Discord channel"
|
||||
},
|
||||
{
|
||||
"code": 16,
|
||||
"text": "Discord channel enabled successfully"
|
||||
},
|
||||
{
|
||||
"code": 17,
|
||||
"text": "Failed to initialize MaixCam channel"
|
||||
},
|
||||
{
|
||||
"code": 18,
|
||||
"text": "MaixCam channel enabled successfully"
|
||||
},
|
||||
{
|
||||
"code": 19,
|
||||
"text": "Failed to initialize QQ channel"
|
||||
},
|
||||
{
|
||||
"code": 20,
|
||||
"text": "QQ channel enabled successfully"
|
||||
},
|
||||
{
|
||||
"code": 21,
|
||||
"text": "DingTalk Client ID is empty, skipping"
|
||||
},
|
||||
{
|
||||
"code": 22,
|
||||
"text": "Failed to initialize DingTalk channel"
|
||||
},
|
||||
{
|
||||
"code": 23,
|
||||
"text": "DingTalk channel enabled successfully"
|
||||
},
|
||||
{
|
||||
"code": 24,
|
||||
"text": "Channel initialization completed"
|
||||
},
|
||||
{
|
||||
"code": 25,
|
||||
"text": "No channels enabled"
|
||||
},
|
||||
{
|
||||
"code": 26,
|
||||
"text": "Starting all channels"
|
||||
},
|
||||
{
|
||||
"code": 27,
|
||||
"text": "Starting channel"
|
||||
},
|
||||
{
|
||||
"code": 28,
|
||||
"text": "Failed to start channel"
|
||||
},
|
||||
{
|
||||
"code": 29,
|
||||
"text": "All channels started"
|
||||
},
|
||||
{
|
||||
"code": 30,
|
||||
"text": "Stopping all channels"
|
||||
},
|
||||
{
|
||||
"code": 31,
|
||||
"text": "Stopping channel"
|
||||
},
|
||||
{
|
||||
"code": 32,
|
||||
"text": "Error stopping channel"
|
||||
},
|
||||
{
|
||||
"code": 33,
|
||||
"text": "All channels stopped"
|
||||
},
|
||||
{
|
||||
"code": 34,
|
||||
"text": "Restarting channel"
|
||||
},
|
||||
{
|
||||
"code": 35,
|
||||
"text": "Outbound dispatcher started"
|
||||
},
|
||||
{
|
||||
"code": 36,
|
||||
"text": "Outbound dispatcher stopped"
|
||||
},
|
||||
{
|
||||
"code": 37,
|
||||
"text": "Outbound dispatcher stopped (bus closed)"
|
||||
},
|
||||
{
|
||||
"code": 38,
|
||||
"text": "Duplicate outbound message skipped"
|
||||
},
|
||||
{
|
||||
"code": 39,
|
||||
"text": "Unknown channel for outbound message"
|
||||
},
|
||||
{
|
||||
"code": 40,
|
||||
"text": "Channel does not support outbound action"
|
||||
},
|
||||
{
|
||||
"code": 41,
|
||||
"text": "Outbound rate limiter canceled"
|
||||
},
|
||||
{
|
||||
"code": 42,
|
||||
"text": "Error sending message to channel"
|
||||
},
|
||||
{
|
||||
"code": 43,
|
||||
"text": "Feishu channel started (websocket mode)"
|
||||
},
|
||||
{
|
||||
"code": 44,
|
||||
"text": "Feishu channel stopped"
|
||||
},
|
||||
{
|
||||
"code": 45,
|
||||
"text": "create sheet from markdown table failed"
|
||||
},
|
||||
{
|
||||
"code": 46,
|
||||
"text": "Feishu message sent"
|
||||
},
|
||||
{
|
||||
"code": 47,
|
||||
"text": "Feishu message rejected by chat allowlist"
|
||||
},
|
||||
{
|
||||
"code": 48,
|
||||
"text": "Ignoring group message without mention/command"
|
||||
},
|
||||
{
|
||||
"code": 49,
|
||||
"text": "Feishu message received"
|
||||
},
|
||||
{
|
||||
"code": 50,
|
||||
"text": "download inbound image failed"
|
||||
},
|
||||
{
|
||||
"code": 51,
|
||||
"text": "download inbound file failed"
|
||||
},
|
||||
{
|
||||
"code": 52,
|
||||
"text": "set sheet permission failed"
|
||||
},
|
||||
{
|
||||
"code": 53,
|
||||
"text": "Upload failed"
|
||||
},
|
||||
{
|
||||
"code": 54,
|
||||
"text": "Starting Telegram bot (polling mode)"
|
||||
},
|
||||
{
|
||||
"code": 55,
|
||||
"text": "Telegram bot connected"
|
||||
},
|
||||
{
|
||||
"code": 56,
|
||||
"text": "Updates channel closed unexpectedly, attempting to restart polling..."
|
||||
},
|
||||
{
|
||||
"code": 57,
|
||||
"text": "Failed to restart updates polling"
|
||||
},
|
||||
{
|
||||
"code": 58,
|
||||
"text": "Updates polling restarted successfully"
|
||||
},
|
||||
{
|
||||
"code": 59,
|
||||
"text": "Stopping Telegram bot"
|
||||
},
|
||||
{
|
||||
"code": 60,
|
||||
"text": "Timeout waiting for telegram message handlers to stop"
|
||||
},
|
||||
{
|
||||
"code": 61,
|
||||
"text": "Recovered panic in telegram message handler"
|
||||
},
|
||||
{
|
||||
"code": 62,
|
||||
"text": "Callback query received"
|
||||
},
|
||||
{
|
||||
"code": 63,
|
||||
"text": "HTML parse failed, fallback to plain text"
|
||||
},
|
||||
{
|
||||
"code": 64,
|
||||
"text": "Ignoring bot-originated message"
|
||||
},
|
||||
{
|
||||
"code": 65,
|
||||
"text": "Telegram message rejected by chat allowlist"
|
||||
},
|
||||
{
|
||||
"code": 66,
|
||||
"text": "Telegram message received"
|
||||
},
|
||||
{
|
||||
"code": 67,
|
||||
"text": "Telegram message rejected by allowlist"
|
||||
},
|
||||
{
|
||||
"code": 68,
|
||||
"text": "Failed to fetch job details"
|
||||
},
|
||||
{
|
||||
"code": 69,
|
||||
"text": "Starting Discord bot"
|
||||
},
|
||||
{
|
||||
"code": 70,
|
||||
"text": "Discord bot connected"
|
||||
},
|
||||
{
|
||||
"code": 71,
|
||||
"text": "Stopping Discord bot"
|
||||
},
|
||||
{
|
||||
"code": 72,
|
||||
"text": "Received message"
|
||||
},
|
||||
{
|
||||
"code": 73,
|
||||
"text": "Failed to create media directory"
|
||||
},
|
||||
{
|
||||
"code": 74,
|
||||
"text": "Failed to download attachment"
|
||||
},
|
||||
{
|
||||
"code": 75,
|
||||
"text": "Attachment download returned non-200"
|
||||
},
|
||||
{
|
||||
"code": 76,
|
||||
"text": "Failed to create local attachment file"
|
||||
},
|
||||
{
|
||||
"code": 77,
|
||||
"text": "Failed to write local attachment file"
|
||||
},
|
||||
{
|
||||
"code": 78,
|
||||
"text": "Attachment downloaded successfully"
|
||||
},
|
||||
{
|
||||
"code": 79,
|
||||
"text": "Starting MaixCam channel server"
|
||||
},
|
||||
{
|
||||
"code": 80,
|
||||
"text": "MaixCam server listening"
|
||||
},
|
||||
{
|
||||
"code": 81,
|
||||
"text": "Starting connection acceptor"
|
||||
},
|
||||
{
|
||||
"code": 82,
|
||||
"text": "Stopping connection acceptor"
|
||||
},
|
||||
{
|
||||
"code": 83,
|
||||
"text": "Failed to accept connection"
|
||||
},
|
||||
{
|
||||
"code": 84,
|
||||
"text": "New connection from MaixCam device"
|
||||
},
|
||||
{
|
||||
"code": 85,
|
||||
"text": "Handling MaixCam connection"
|
||||
},
|
||||
{
|
||||
"code": 86,
|
||||
"text": "Connection closed"
|
||||
},
|
||||
{
|
||||
"code": 87,
|
||||
"text": "Failed to decode message"
|
||||
},
|
||||
{
|
||||
"code": 88,
|
||||
"text": "Received heartbeat"
|
||||
},
|
||||
{
|
||||
"code": 89,
|
||||
"text": "Unknown message type"
|
||||
},
|
||||
{
|
||||
"code": 90,
|
||||
"text": "Person detected event"
|
||||
},
|
||||
{
|
||||
"code": 91,
|
||||
"text": "Status update from MaixCam"
|
||||
},
|
||||
{
|
||||
"code": 92,
|
||||
"text": "Stopping MaixCam channel"
|
||||
},
|
||||
{
|
||||
"code": 93,
|
||||
"text": "MaixCam channel stopped"
|
||||
},
|
||||
{
|
||||
"code": 94,
|
||||
"text": "No MaixCam devices connected"
|
||||
},
|
||||
{
|
||||
"code": 95,
|
||||
"text": "Failed to send to client"
|
||||
},
|
||||
{
|
||||
"code": 96,
|
||||
"text": "load recent logs failed"
|
||||
},
|
||||
{
|
||||
"code": 97,
|
||||
"text": "Log stream error:"
|
||||
},
|
||||
{
|
||||
"code": 98,
|
||||
"text": "Agent initialized"
|
||||
},
|
||||
{
|
||||
"code": 99,
|
||||
"text": "Starting QQ bot (WebSocket mode)"
|
||||
},
|
||||
{
|
||||
"code": 100,
|
||||
"text": "Got WebSocket info"
|
||||
},
|
||||
{
|
||||
"code": 101,
|
||||
"text": "QQ bot started successfully"
|
||||
},
|
||||
{
|
||||
"code": 102,
|
||||
"text": "Stopping QQ bot"
|
||||
},
|
||||
{
|
||||
"code": 103,
|
||||
"text": "Failed to send C2C message"
|
||||
},
|
||||
{
|
||||
"code": 104,
|
||||
"text": "Received message with no sender ID"
|
||||
},
|
||||
{
|
||||
"code": 105,
|
||||
"text": "Received empty message, ignoring"
|
||||
},
|
||||
{
|
||||
"code": 106,
|
||||
"text": "Received C2C message"
|
||||
},
|
||||
{
|
||||
"code": 107,
|
||||
"text": "Received group message with no sender ID"
|
||||
},
|
||||
{
|
||||
"code": 108,
|
||||
"text": "Received empty group message, ignoring"
|
||||
},
|
||||
{
|
||||
"code": 109,
|
||||
"text": "Received group AT message"
|
||||
},
|
||||
{
|
||||
"code": 110,
|
||||
"text": "Startup compaction check completed"
|
||||
},
|
||||
{
|
||||
"code": 111,
|
||||
"text": "Bootstrap init model call failed"
|
||||
},
|
||||
{
|
||||
"code": 112,
|
||||
"text": "Bootstrap init marker write failed"
|
||||
},
|
||||
{
|
||||
"code": 113,
|
||||
"text": "Bootstrap file cleanup failed"
|
||||
},
|
||||
{
|
||||
"code": 114,
|
||||
"text": "Bootstrap init model call completed"
|
||||
},
|
||||
{
|
||||
"code": 115,
|
||||
"text": "Starting DingTalk channel (Stream Mode)"
|
||||
},
|
||||
{
|
||||
"code": 116,
|
||||
"text": "DingTalk channel started (Stream Mode)"
|
||||
},
|
||||
{
|
||||
"code": 117,
|
||||
"text": "Stopping DingTalk channel"
|
||||
},
|
||||
{
|
||||
"code": 118,
|
||||
"text": "DingTalk channel stopped"
|
||||
},
|
||||
{
|
||||
"code": 119,
|
||||
"text": "DingTalk outbound message"
|
||||
},
|
||||
{
|
||||
"code": 120,
|
||||
"text": "DingTalk inbound message"
|
||||
},
|
||||
{
|
||||
"code": 121,
|
||||
"text": "Starting WhatsApp channel"
|
||||
},
|
||||
{
|
||||
"code": 122,
|
||||
"text": "WhatsApp channel connected"
|
||||
},
|
||||
{
|
||||
"code": 123,
|
||||
"text": "Stopping WhatsApp channel"
|
||||
},
|
||||
{
|
||||
"code": 124,
|
||||
"text": "Error closing WhatsApp connection"
|
||||
},
|
||||
{
|
||||
"code": 125,
|
||||
"text": "WhatsApp connection closed"
|
||||
},
|
||||
{
|
||||
"code": 126,
|
||||
"text": "WhatsApp read error"
|
||||
},
|
||||
{
|
||||
"code": 127,
|
||||
"text": "Failed to unmarshal WhatsApp message"
|
||||
},
|
||||
{
|
||||
"code": 128,
|
||||
"text": "WhatsApp message received"
|
||||
},
|
||||
{
|
||||
"code": 129,
|
||||
"text": "PublishInbound on closed channel recovered"
|
||||
},
|
||||
{
|
||||
"code": 130,
|
||||
"text": "PublishInbound timeout (queue full)"
|
||||
},
|
||||
{
|
||||
"code": 131,
|
||||
"text": "PublishOutbound on closed channel recovered"
|
||||
},
|
||||
{
|
||||
"code": 132,
|
||||
"text": "PublishOutbound timeout (queue full)"
|
||||
},
|
||||
{
|
||||
"code": 133,
|
||||
"text": "HTTP chat request"
|
||||
},
|
||||
{
|
||||
"code": 134,
|
||||
"text": "Sentinel started"
|
||||
},
|
||||
{
|
||||
"code": 135,
|
||||
"text": "Sentinel stopped"
|
||||
},
|
||||
{
|
||||
"code": 136,
|
||||
"text": "Attempting auto-heal for channel"
|
||||
},
|
||||
{
|
||||
"code": 137,
|
||||
"text": "Auto-heal restart failed"
|
||||
},
|
||||
{
|
||||
"code": 138,
|
||||
"text": "Auto-heal successful"
|
||||
},
|
||||
{
|
||||
"code": 139,
|
||||
"text": "Starting HTTP server"
|
||||
},
|
||||
{
|
||||
"code": 140,
|
||||
"text": "Server ready for reverse proxying"
|
||||
},
|
||||
{
|
||||
"code": 141,
|
||||
"text": "HTTP server failed"
|
||||
},
|
||||
{
|
||||
"code": 142,
|
||||
"text": "Stopping HTTP server"
|
||||
},
|
||||
{
|
||||
"code": 143,
|
||||
"text": "System prompt built"
|
||||
},
|
||||
{
|
||||
"code": 144,
|
||||
"text": "System prompt preview"
|
||||
},
|
||||
{
|
||||
"code": 145,
|
||||
"text": "Failed to clean up old log files:"
|
||||
},
|
||||
{
|
||||
"code": 146,
|
||||
"text": "File logging enabled:"
|
||||
},
|
||||
{
|
||||
"code": 147,
|
||||
"text": "File logging disabled"
|
||||
},
|
||||
{
|
||||
"code": 148,
|
||||
"text": "Failed to write file log:"
|
||||
},
|
||||
{
|
||||
"code": 149,
|
||||
"text": "Session-sharded dispatcher enabled"
|
||||
},
|
||||
{
|
||||
"code": 150,
|
||||
"text": "LLM fallback provider switched"
|
||||
},
|
||||
{
|
||||
"code": 151,
|
||||
"text": "LLM iteration"
|
||||
},
|
||||
{
|
||||
"code": 152,
|
||||
"text": "LLM request"
|
||||
},
|
||||
{
|
||||
"code": 153,
|
||||
"text": "Full LLM request"
|
||||
},
|
||||
{
|
||||
"code": 154,
|
||||
"text": "Purged orphan tool outputs after provider pairing error"
|
||||
},
|
||||
{
|
||||
"code": 155,
|
||||
"text": "LLM call failed"
|
||||
},
|
||||
{
|
||||
"code": 156,
|
||||
"text": "LLM response without tool calls (direct answer)"
|
||||
},
|
||||
{
|
||||
"code": 157,
|
||||
"text": "LLM requested tool calls"
|
||||
},
|
||||
{
|
||||
"code": 158,
|
||||
"text": "append daily summary log failed"
|
||||
},
|
||||
{
|
||||
"code": 159,
|
||||
"text": "Processing system message"
|
||||
},
|
||||
{
|
||||
"code": 160,
|
||||
"text": "Purged orphan tool outputs after provider pairing error (system)"
|
||||
},
|
||||
{
|
||||
"code": 161,
|
||||
"text": "Heartbeat session reset after repeated provider pairing error"
|
||||
},
|
||||
{
|
||||
"code": 162,
|
||||
"text": "LLM call failed in system message"
|
||||
},
|
||||
{
|
||||
"code": 163,
|
||||
"text": "System message processing completed"
|
||||
},
|
||||
{
|
||||
"code": 164,
|
||||
"text": "Tool execution started"
|
||||
},
|
||||
{
|
||||
"code": 165,
|
||||
"text": "Tool not found"
|
||||
},
|
||||
{
|
||||
"code": 166,
|
||||
"text": "Tool execution failed"
|
||||
},
|
||||
{
|
||||
"code": 167,
|
||||
"text": "Tool execution completed"
|
||||
},
|
||||
{
|
||||
"code": 168,
|
||||
"text": "Channel task stopped"
|
||||
},
|
||||
{
|
||||
"code": 169,
|
||||
"text": "Channel task failed"
|
||||
},
|
||||
{
|
||||
"code": 170,
|
||||
"text": "Sentinel alert"
|
||||
},
|
||||
{
|
||||
"code": 171,
|
||||
"text": "Processing inbound message"
|
||||
},
|
||||
{
|
||||
"code": 172,
|
||||
"text": "Tool call invoked"
|
||||
},
|
||||
{
|
||||
"code": 173,
|
||||
"text": "Response generated"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -14,6 +14,7 @@ import Memory from './pages/Memory';
|
||||
import TaskAudit from './pages/TaskAudit';
|
||||
import EKG from './pages/EKG';
|
||||
import Tasks from './pages/Tasks';
|
||||
import LogCodes from './pages/LogCodes';
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
@@ -25,6 +26,7 @@ export default function App() {
|
||||
<Route index element={<Dashboard />} />
|
||||
<Route path="chat" element={<Chat />} />
|
||||
<Route path="logs" element={<Logs />} />
|
||||
<Route path="log-codes" element={<LogCodes />} />
|
||||
<Route path="skills" element={<Skills />} />
|
||||
<Route path="config" element={<Config />} />
|
||||
<Route path="cron" element={<Cron />} />
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import { LayoutDashboard, MessageSquare, Settings, Clock, Server, Terminal, Zap, FolderOpen, ClipboardList, ListTodo, BrainCircuit } from 'lucide-react';
|
||||
import { LayoutDashboard, MessageSquare, Settings, Clock, Server, Terminal, Zap, FolderOpen, ClipboardList, ListTodo, BrainCircuit, Hash } from 'lucide-react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useAppContext } from '../context/AppContext';
|
||||
import NavItem from './NavItem';
|
||||
@@ -15,6 +15,7 @@ const Sidebar: React.FC = () => {
|
||||
{ icon: <LayoutDashboard className="w-5 h-5" />, label: t('dashboard'), to: '/' },
|
||||
{ icon: <MessageSquare className="w-5 h-5" />, label: t('chat'), to: '/chat' },
|
||||
{ icon: <Terminal className="w-5 h-5" />, label: t('logs'), to: '/logs' },
|
||||
{ icon: <Hash className="w-5 h-5" />, label: t('logCodes'), to: '/log-codes' },
|
||||
{ icon: <Zap className="w-5 h-5" />, label: t('skills'), to: '/skills' },
|
||||
],
|
||||
},
|
||||
|
||||
@@ -11,6 +11,7 @@ const resources = {
|
||||
cronJobs: 'Cron Jobs',
|
||||
nodes: 'Nodes',
|
||||
logs: 'Real-time Logs',
|
||||
logCodes: 'Log Codes',
|
||||
skills: 'Skills',
|
||||
memory: 'Memory',
|
||||
taskAudit: 'Task Audit',
|
||||
@@ -195,6 +196,7 @@ const resources = {
|
||||
cronJobs: '定时任务',
|
||||
nodes: '节点',
|
||||
logs: '实时日志',
|
||||
logCodes: '日志编号',
|
||||
skills: '技能管理',
|
||||
memory: '记忆文件',
|
||||
taskAudit: '任务审计',
|
||||
|
||||
@@ -64,7 +64,7 @@ const Chat: React.FC = () => {
|
||||
const ur = await fetch(`/webui/api/upload${q}`, { method: 'POST', body: fd });
|
||||
const uj = await ur.json(); media = uj.path || '';
|
||||
} catch (e) {
|
||||
console.error('Upload failed', e);
|
||||
console.error('L0053', e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -103,7 +103,7 @@ const Cron: React.FC = () => {
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Failed to fetch job details', e);
|
||||
console.error('L0068', e);
|
||||
}
|
||||
} else {
|
||||
setEditingCron(null);
|
||||
|
||||
81
webui/src/pages/LogCodes.tsx
Normal file
81
webui/src/pages/LogCodes.tsx
Normal file
@@ -0,0 +1,81 @@
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useAppContext } from '../context/AppContext';
|
||||
|
||||
type CodeItem = {
|
||||
code: number;
|
||||
text: string;
|
||||
};
|
||||
|
||||
const LogCodes: React.FC = () => {
|
||||
const { t } = useTranslation();
|
||||
const { q } = useAppContext();
|
||||
const [items, setItems] = useState<CodeItem[]>([]);
|
||||
const [kw, setKw] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
const load = async () => {
|
||||
try {
|
||||
const paths = [`/webui/log-codes.json${q}`, '/log-codes.json'];
|
||||
for (const p of paths) {
|
||||
const r = await fetch(p);
|
||||
if (!r.ok) continue;
|
||||
const j = await r.json();
|
||||
if (Array.isArray(j?.items)) {
|
||||
setItems(j.items);
|
||||
return;
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
setItems([]);
|
||||
}
|
||||
};
|
||||
load();
|
||||
}, [q]);
|
||||
|
||||
const filtered = useMemo(() => {
|
||||
const k = kw.trim().toLowerCase();
|
||||
if (!k) return items;
|
||||
return items.filter((it) => String(it.code).includes(k) || it.text.toLowerCase().includes(k));
|
||||
}, [items, kw]);
|
||||
|
||||
return (
|
||||
<div className="p-8 max-w-7xl mx-auto space-y-6">
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<h1 className="text-2xl font-semibold tracking-tight">{t('logCodes')}</h1>
|
||||
<input
|
||||
value={kw}
|
||||
onChange={(e) => setKw(e.target.value)}
|
||||
placeholder="Search code/text"
|
||||
className="w-72 bg-zinc-900 border border-zinc-800 rounded-lg px-3 py-2 text-sm focus:outline-none focus:border-indigo-500"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="bg-zinc-950 border border-zinc-800 rounded-2xl overflow-hidden">
|
||||
<table className="w-full text-sm">
|
||||
<thead className="bg-zinc-900/90 border-b border-zinc-800">
|
||||
<tr className="text-zinc-400">
|
||||
<th className="text-left p-3 font-medium w-40">Code</th>
|
||||
<th className="text-left p-3 font-medium">Template</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{filtered.map((it) => (
|
||||
<tr key={it.code} className="border-b border-zinc-900 hover:bg-zinc-900/40">
|
||||
<td className="p-3 font-mono text-indigo-300">{it.code}</td>
|
||||
<td className="p-3 text-zinc-200 break-all">{it.text}</td>
|
||||
</tr>
|
||||
))}
|
||||
{filtered.length === 0 && (
|
||||
<tr>
|
||||
<td className="p-6 text-zinc-500" colSpan={2}>No codes</td>
|
||||
</tr>
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default LogCodes;
|
||||
@@ -8,6 +8,7 @@ const Logs: React.FC = () => {
|
||||
const { t } = useTranslation();
|
||||
const { q } = useAppContext();
|
||||
const [logs, setLogs] = useState<LogEntry[]>([]);
|
||||
const [codeMap, setCodeMap] = useState<Record<number, string>>({});
|
||||
const [isStreaming, setIsStreaming] = useState(true);
|
||||
const [showRaw, setShowRaw] = useState(false);
|
||||
const logEndRef = useRef<HTMLDivElement>(null);
|
||||
@@ -22,7 +23,7 @@ const Logs: React.FC = () => {
|
||||
setLogs(j.logs.map(normalizeLog));
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('load recent logs failed', e);
|
||||
console.error('L0096', e);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -59,11 +60,38 @@ const Logs: React.FC = () => {
|
||||
}
|
||||
} catch (e: any) {
|
||||
if (e.name !== 'AbortError') {
|
||||
console.error('Log stream error:', e);
|
||||
console.error('L0097', e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const loadCodeMap = async () => {
|
||||
try {
|
||||
const paths = [`/webui/log-codes.json${q}`, '/log-codes.json'];
|
||||
for (const p of paths) {
|
||||
const r = await fetch(p);
|
||||
if (!r.ok) continue;
|
||||
const j = await r.json();
|
||||
if (Array.isArray(j?.items)) {
|
||||
const m: Record<number, string> = {};
|
||||
j.items.forEach((it: any) => {
|
||||
if (typeof it?.code === 'number' && typeof it?.text === 'string') {
|
||||
m[it.code] = it.text;
|
||||
}
|
||||
});
|
||||
setCodeMap(m);
|
||||
return;
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
setCodeMap({});
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
loadCodeMap();
|
||||
}, [q]);
|
||||
|
||||
useEffect(() => {
|
||||
loadRecent();
|
||||
if (isStreaming) {
|
||||
@@ -84,13 +112,29 @@ const Logs: React.FC = () => {
|
||||
const clearLogs = () => setLogs([]);
|
||||
|
||||
const normalizeLog = (v: any): LogEntry => ({
|
||||
time: typeof v?.time === 'string' && v.time ? v.time : new Date().toISOString(),
|
||||
time: typeof v?.time === 'string' && v.time ? v.time : (typeof v?.timestamp === 'string' && v.timestamp ? v.timestamp : new Date().toISOString()),
|
||||
level: typeof v?.level === 'string' && v.level ? v.level : 'INFO',
|
||||
msg: typeof v?.msg === 'string' ? v.msg : JSON.stringify(v),
|
||||
code: typeof v?.code === 'number' ? v.code : undefined,
|
||||
msg: typeof v?.msg === 'string' ? v.msg : (typeof v?.message === 'string' ? v.message : JSON.stringify(v)),
|
||||
__raw: JSON.stringify(v),
|
||||
...v,
|
||||
});
|
||||
|
||||
const toCode = (v: any): number | undefined => {
|
||||
if (typeof v === 'number' && Number.isFinite(v) && v > 0) return v;
|
||||
if (typeof v === 'string') {
|
||||
if (/^L\d{4}$/.test(v)) return Number(v.slice(1));
|
||||
const n = Number(v);
|
||||
if (Number.isFinite(n) && n > 0) return n;
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
const decode = (v: any) => {
|
||||
const c = toCode(v);
|
||||
if (!c) return v;
|
||||
return codeMap[c] || v;
|
||||
};
|
||||
|
||||
const formatTime = (raw: string) => {
|
||||
try {
|
||||
if (!raw) return '--:--:--';
|
||||
@@ -189,16 +233,19 @@ const Logs: React.FC = () => {
|
||||
<tbody>
|
||||
{logs.map((log, i) => {
|
||||
const lvl = (log.level || 'INFO').toUpperCase();
|
||||
const errText = (log as any).error || (lvl === 'ERROR' ? log.msg : '');
|
||||
const message = lvl === 'ERROR' ? ((log as any).message || log.msg || '') : ((log as any).message || log.msg || '');
|
||||
const rawCode = (log as any).code ?? (log as any).message ?? log.msg ?? '';
|
||||
const message = String(decode(rawCode) || '');
|
||||
const errRaw = (log as any).message || (log as any).error || (lvl === 'ERROR' ? rawCode : '');
|
||||
const errText = String(decode(errRaw) || '');
|
||||
const caller = (log as any).caller || (log as any).source || '';
|
||||
const code = toCode(rawCode);
|
||||
return (
|
||||
<tr key={i} className="border-b border-zinc-900 hover:bg-zinc-900/40 align-top">
|
||||
<td className="p-2 text-zinc-500 whitespace-nowrap">{formatTime(log.time)}</td>
|
||||
<td className={`p-2 font-semibold whitespace-nowrap ${getLevelColor(lvl)}`}>{lvl}</td>
|
||||
<td className="p-2 text-zinc-200 break-all">{message}</td>
|
||||
<td className="p-2 text-red-300 break-all">{errText}</td>
|
||||
<td className="p-2 text-zinc-500 break-all">{caller}</td>
|
||||
<td className="p-2 text-zinc-500 break-all">{code ? `${code} | ${caller}` : caller}</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -25,7 +25,9 @@ export type Lang = 'en' | 'zh';
|
||||
export type LogEntry = {
|
||||
time: string;
|
||||
level: string;
|
||||
code?: number;
|
||||
msg: string;
|
||||
message?: string;
|
||||
[key: string]: any;
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user