Files

116 lines
3.4 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package cli
import (
"context"
"encoding/json"
"flag"
"fmt"
"os"
"time"
"github.com/matthewlu070111/smart-srun/core/internal/config"
"github.com/matthewlu070111/smart-srun/core/internal/daemon"
"github.com/matthewlu070111/smart-srun/core/internal/domain"
)
func onlineLog(ctx context.Context, client onlineClient, args []string, stdout, stderr *os.File) int {
usage := func() int {
return onlineError(stderr, domain.Errorf(domain.CodeInvalidArgument, "用法:log [tail|follow] [-n 1..1000] [--channel plugin|network] [--json];log runtime [--json];follow 仅支持文本输出"))
}
mode := "follow"
explicitMode := false
if len(args) > 0 && (args[0] == "tail" || args[0] == "follow" || args[0] == "runtime") {
mode = args[0]
args = args[1:]
explicitMode = true
}
f := commandFlags()
lines := f.Int("n", 100, "")
channel := f.String("channel", "plugin", "")
asJSON := f.Bool("json", false, "")
if f.Parse(args) != nil || f.NArg() != 0 || *lines < 1 || *lines > 1000 || (*channel != "plugin" && *channel != "network") {
return usage()
}
if mode == "runtime" {
if f.NFlag() > 0 && (f.NFlag() != 1 || !*asJSON) {
return usage()
}
return runtimeLog(ctx, client, stdout, stderr)
}
// Legacy `log -n N` is a one-shot tail. Explicit follow stays a stream.
if !explicitMode {
f.Visit(func(v *flag.Flag) {
if v.Name == "n" || v.Name == "json" {
mode = "tail"
}
})
}
if *asJSON && mode == "follow" {
return usage()
}
var cursor uint64
for {
raw, err := client.call(ctx, "log.tail", daemon.LogTailParams{Channel: *channel, Lines: *lines, Cursor: cursor})
if ctx.Err() != nil {
return ExitCancelled
}
if err != nil {
return onlineError(stderr, err)
}
var page daemon.LogTailResult
if json.Unmarshal(raw, &page) != nil {
return onlineError(stderr, domain.Errorf(domain.CodeProtocolInvalid, "日志响应格式无效"))
}
if *asJSON {
return writeJSON(stdout, stderr, page)
}
if page.Dropped {
fmt.Fprintln(stderr, "部分旧日志已不在缓存中。")
}
for _, line := range page.Lines {
if _, err := fmt.Fprintln(stdout, line); err != nil {
return ExitActionFailed
}
}
if mode == "tail" {
return ExitOK
}
cursor = page.Cursor
timer := time.NewTimer(time.Second)
select {
case <-ctx.Done():
timer.Stop()
return ExitCancelled
case <-timer.C:
}
}
}
func runtimeLog(ctx context.Context, client onlineClient, stdout, stderr *os.File) int {
cfg, err := readOnlineConfig(ctx, client)
if err != nil {
return onlineError(stderr, err)
}
// An explicit allowlist: diagnostic output never includes credential fields.
view := struct {
Revision uint64 `json:"config_revision"`
School string `json:"school"`
Account string `json:"account_id"`
AccessMode domain.AccessMode `json:"access_mode"`
Interface string `json:"iface"`
Login config.EffectiveLoginShape `json:"login"`
}{Revision: cfg.Revision, School: cfg.School, Account: cfg.Selection.ActiveCampusID}
for _, account := range cfg.CampusAccounts {
if account.ID == view.Account {
view.AccessMode = account.AccessMode
view.Interface = account.WiredIface
if account.AccessMode == domain.AccessModeWiFi {
view.Interface = cfg.STAIface
}
view.Login = config.EffectiveLogin(cfg, account)
break
}
}
return writeJSON(stdout, stderr, view)
}