Files
iotlcm/onelink-mock/internal/service/runtime_log.go
T

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))
}