Files
iotlcm/internal/service/runtime_log.go
T
MiFaZhan 4e2cd43a55 chore: 初始开源发布
iotlcm-onelink-mock:OneLink 挡板服务(本地测试替身)。

- Go module 迁移至 git.5iot.com/iotlcm-public/iotlcm-onelink-mock
- 修复同毫秒内多次生成卡导致 ICCID/IMSI 唯一约束冲突
- 补 Apache-2.0 LICENSE,README 重写并新增 docs/deploy.md
- 移除内部部署资产(CI 触发器 / 镜像推送脚本 / 指定环境部署文档)
- 测试凭据改为 demo_appid / demo_password,真实手机号与内网 IP 脱敏
- 统一 LF 行尾并加 .gitattributes
2026-10-22 15:08:27 +08:00

130 lines
2.9 KiB
Go

package service
import (
"bytes"
"net/http"
"strconv"
"strings"
"sync"
"time"
)
const defaultRuntimeLogCapacity = 500
type RuntimeLog struct {
ID int64 `json:"id"`
CreatedAt string `json:"createdAt"`
Level string `json:"level"`
Message string `json:"message"`
}
type RuntimeLogResult struct {
Items []RuntimeLog `json:"items"`
Total int `json:"total"`
}
type RuntimeLogStore struct {
mu sync.Mutex
logs []RuntimeLog
partial []byte
nextID int64
capacity int
now func() time.Time
}
func NewRuntimeLogStore(capacities ...int) *RuntimeLogStore {
capacity := defaultRuntimeLogCapacity
if len(capacities) > 0 {
capacity = capacities[0]
}
return newRuntimeLogStore(capacity, time.Now)
}
func newRuntimeLogStore(capacity int, now func() time.Time) *RuntimeLogStore {
if capacity < 1 {
capacity = 1
}
return &RuntimeLogStore{
logs: make([]RuntimeLog, 0, capacity),
capacity: capacity,
now: now,
}
}
func (s *RuntimeLogStore) Write(p []byte) (int, error) {
s.mu.Lock()
defer s.mu.Unlock()
written := len(p)
s.partial = append(s.partial, p...)
for {
index := bytes.IndexByte(s.partial, '\n')
if index < 0 {
break
}
s.addLineLocked(string(bytes.TrimSuffix(s.partial[:index], []byte{'\r'})))
s.partial = append(s.partial[:0], s.partial[index+1:]...)
}
return written, nil
}
func (s *RuntimeLogStore) Latest(limit int) RuntimeLogResult {
s.mu.Lock()
defer s.mu.Unlock()
if limit < 1 || limit > s.capacity {
limit = s.capacity
}
start := len(s.logs) - limit
if start < 0 {
start = 0
}
items := make([]RuntimeLog, len(s.logs)-start)
for i := range items {
items[i] = s.logs[len(s.logs)-1-i]
}
return RuntimeLogResult{Items: items, Total: len(s.logs)}
}
func (s *RuntimeLogStore) addLineLocked(message string) {
message = strings.TrimSpace(message)
if message == "" {
return
}
s.nextID++
item := RuntimeLog{
ID: s.nextID,
CreatedAt: s.now().Format("2006-01-02 15:04:05"),
Level: runtimeLogLevel(message),
Message: message,
}
if len(s.logs) == s.capacity {
copy(s.logs, s.logs[1:])
s.logs[len(s.logs)-1] = item
return
}
s.logs = append(s.logs, item)
}
func runtimeLogLevel(message string) string {
upper := strings.ToUpper(message)
switch {
case strings.Contains(upper, "FATAL"), strings.Contains(upper, "PANIC"), strings.Contains(upper, "ERROR"), strings.Contains(upper, "[ERRO]"):
return "ERROR"
case strings.Contains(upper, "WARN"), strings.Contains(upper, "[WARN]"):
return "WARN"
case strings.Contains(upper, "DEBUG"), strings.Contains(upper, "[DEBU]"):
return "DEBUG"
default:
return "INFO"
}
}
func (s *Service) adminRuntimeLogs(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
w.WriteHeader(http.StatusMethodNotAllowed)
return
}
limit, _ := strconv.Atoi(r.URL.Query().Get("limit"))
writeJSON(w, http.StatusOK, s.runtimeLogs.Latest(limit))
}