From 8b15256f6908d5fc3906536ce2d62dcf8574cc0d Mon Sep 17 00:00:00 2001 From: spiritlhl <103393591+spiritLHLS@users.noreply.github.com> Date: Wed, 1 May 2024 08:17:44 +0000 Subject: [PATCH] init --- README.md | 3 + backtrace/asn.go | 143 +++++++++++ backtrace/backtrace.go | 81 +++++++ backtrace/backtrace_test.go | 20 ++ backtrace/generate.go | 38 +++ backtrace/listen_linux.go | 29 +++ backtrace/listen_windows.go | 35 +++ backtrace/trace.go | 467 ++++++++++++++++++++++++++++++++++++ defaultset/defaultset.go | 35 +++ defaultset/zap.go | 40 +++ go.mod | 14 ++ go.sum | 18 ++ main.go | 16 ++ 13 files changed, 939 insertions(+) create mode 100644 backtrace/asn.go create mode 100644 backtrace/backtrace.go create mode 100644 backtrace/backtrace_test.go create mode 100644 backtrace/generate.go create mode 100644 backtrace/listen_linux.go create mode 100644 backtrace/listen_windows.go create mode 100644 backtrace/trace.go create mode 100644 defaultset/defaultset.go create mode 100644 defaultset/zap.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 main.go diff --git a/README.md b/README.md index 51d1cb4..d473585 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,5 @@ # backtrace + +[![Hits](https://hits.seeyoufarm.com/api/count/incr/badge.svg?url=https%3A%2F%2Fgithub.com%2Foneclickvirt%2Fbacktrace&count_bg=%2323E01C&title_bg=%23555555&icon=sonarcloud.svg&icon_color=%23E7E7E7&title=hits&edge_flat=false)](https://hits.seeyoufarm.com) + 基于 https://github.com/zhanghanyun/backtrace 的重构和优化版本 diff --git a/backtrace/asn.go b/backtrace/asn.go new file mode 100644 index 0000000..8997564 --- /dev/null +++ b/backtrace/asn.go @@ -0,0 +1,143 @@ +package backtrace + +import ( + "fmt" + "net" + "strings" + + . "github.com/oneclickvirt/backtrace/defaultset" +) + +type Result struct { + i int + s string +} + +var ( + ips = []string{ + "219.141.136.12", "202.106.50.1", "221.179.155.161", + "202.96.209.133", "210.22.97.1", "211.136.112.200", + "58.60.188.222", "210.21.196.6", "120.196.165.24", + "61.139.2.69", "119.6.6.6", "211.137.96.205", + } + names = []string{ + "北京电信", "北京联通", "北京移动", + "上海电信", "上海联通", "上海移动", + "广州电信", "广州联通", "广州移动", + "成都电信", "成都联通", "成都移动", + } + m = map[string]string{ + // [] 前的字符串个数,中文占2个字符串 + "AS4809a": " 电信CN2GIA [优质线路]", // 18 + "AS4809b": " 电信CN2GT [精品线路]", // 18 + "AS4134": " 电信163 [普通线路]", // 18 + "AS9929": " 联通9929 [优质线路]", // 18 + "AS4837": " 联通4837 [普通线路]", // 18 + "AS58807": "移动CMIN2 [精品线路]", // 18 + "AS9808": " 移动CMI [普通线路]", // 18 + "AS58453": "移动CMI [普通线路]", // 18 + } +) + +func removeDuplicates(elements []string) []string { + encountered := map[string]bool{} // 用于存储已经遇到的元素 + result := []string{} // 存储去重后的结果 + for v := range elements { // 遍历切片中的元素 + if encountered[elements[v]] == true { // 如果该元素已经遇到过 + // 存在过就不加入了 + } else { + encountered[elements[v]] = true // 将该元素标记为已经遇到 + result = append(result, elements[v]) // 将该元素加入到结果切片中 + } + } + return result // 返回去重后的结果切片 +} + +func trace(ch chan Result, i int, cmin2 []string) { + hops, err := Trace(net.ParseIP(ips[i])) + if err != nil { + s := fmt.Sprintf("%v %-15s %v", names[i], ips[i], err) + ch <- Result{i, s} + return + } + var asns []string + for _, h := range hops { + for _, n := range h.Nodes { + asn := ipAsn(n.IP.String(), cmin2) + if asn != "" { + asns = append(asns, asn) + } + } + } + // 处理CN2不同路线的区别 + if asns != nil && len(asns) > 0 { + var tempText string + asns = removeDuplicates(asns) + tempText += fmt.Sprintf("%v ", names[i]) + hasAS4134 := false + hasAS4809 := false + for _, asn := range asns { + if asn == "AS4134" { + hasAS4134 = true + } + if asn == "AS4809" { + hasAS4809 = true + } + } + if hasAS4134 && hasAS4809 { + // 同时包含 AS4134 和 AS4809 属于 CN2GT + asns = append(asns, "AS4809b") + } else if hasAS4809 { + // 仅包含 AS4809 属于 CN2GIA + asns = append(asns, "AS4809a") + } + tempText += fmt.Sprintf("%v %-15s ", names[i], ips[i]) + for _, asn := range asns { + asnDescription := m[asn] + switch asn { + case "": + continue + case "AS9929": + tempText += DarkGreen(asnDescription) + " " + case "AS4809a": + tempText += DarkGreen(asnDescription) + " " + case "AS4809b": + tempText += Green(asnDescription) + " " + case "AS58807": + tempText += Green(asnDescription) + " " + default: + tempText += White(asnDescription) + " " + } + } + ch <- Result{i, tempText} + } else { + s := fmt.Sprintf("%v %-15s %v", names[i], ips[i], Red("检测不到ASN")) + ch <- Result{i, s} + } +} + +func ipAsn(ip string, cmin2 []string) string { + switch { + case strings.HasPrefix(ip, "59.43"): + return "AS4809" + case strings.HasPrefix(ip, "202.97"): + return "AS4134" + case strings.HasPrefix(ip, "218.105") || strings.HasPrefix(ip, "210.51"): + return "AS9929" + case strings.HasPrefix(ip, "219.158"): + return "AS4837" + case strings.HasPrefix(ip, "223.118") || strings.HasPrefix(ip, "223.119") || + strings.HasPrefix(ip, "223.120"): + for _, prefix := range cmin2 { + if strings.HasPrefix(ip, prefix) { + return "AS58807" + } + } + return "AS58453" + case strings.HasPrefix(ip, "103.11.109") || strings.HasPrefix(ip, "45.204.69") || + strings.HasPrefix(ip, "223.121"): + return "AS58453" + default: + return "" + } +} diff --git a/backtrace/backtrace.go b/backtrace/backtrace.go new file mode 100644 index 0000000..36dcbab --- /dev/null +++ b/backtrace/backtrace.go @@ -0,0 +1,81 @@ +package backtrace + +import ( + "encoding/json" + "fmt" + "log" + "net/http" + "time" + + . "github.com/oneclickvirt/backtrace/defaultset" +) + +type IpInfo struct { + Ip string `json:"ip"` + City string `json:"city"` + Region string `json:"region"` + Country string `json:"country"` + Org string `json:"org"` +} + +func BackTrace() { + var ( + s [12]string // 对应 ips 目标地址数量 + c = make(chan Result) + t = time.After(time.Second * 10) + cmin2 = []string{ + // 以下均为 /24 地址 + "223.118.32", + "223.119.32", "223.119.34", "223.119.35", "223.119.36", "223.119.37", "223.119.100", "223.119.253", + "223.120.165"} + prefixes = []string{ + "223.119.8.0/21", + "223.120.128.0/17", + "223.120.134/23", + "223.120.136/23", + "223.120.138/23", + "223.120.154/23", + "223.120.158/23", + "223.120.164/22", + "223.120.168/22", + "223.120.172/22", + "223.120.174/23", + "223.120.184/22", + "223.120.188/22", + "223.120.192/23", + "223.120.200/23", + "223.120.210/23", + "223.120.212/23", + } + ) + // 生成CMIN2的IPV4前缀地址 + for _, prefix := range prefixes { + cmin2 = append(cmin2, GeneratePrefixList(prefix)...) + } + rsp, err := http.Get("http://ipinfo.io") + if err != nil { + log.Fatalln("Get ip info err", err) + } + info := IpInfo{} + err = json.NewDecoder(rsp.Body).Decode(&info) + if err != nil { + log.Fatalln("json decode err", err) + } + fmt.Println(Green("国家: ") + White(info.Country) + Green(" 城市: ") + White(info.City) + + Green(" 服务商: ") + Blue(info.Org)) + for i := range ips { + go trace(c, i, cmin2) + } +loop: + for range s { + select { + case o := <-c: + s[o.i] = o.s + case <-t: + break loop + } + } + for _, r := range s { + fmt.Println(r) + } +} diff --git a/backtrace/backtrace_test.go b/backtrace/backtrace_test.go new file mode 100644 index 0000000..a634aa9 --- /dev/null +++ b/backtrace/backtrace_test.go @@ -0,0 +1,20 @@ +package backtrace + +import ( + "testing" +) + +//func TestGeneratePrefixMap(t *testing.T) { +// prefix := "223.119.8.0/21" +// prefixList := GeneratePrefixList(prefix) +// if prefixList != nil { +// // 打印生成的IP地址前缀列表 +// for _, ip := range prefixList { +// fmt.Println(ip) +// } +// } +//} + +func TestBackTrace(t *testing.T) { + BackTrace() +} diff --git a/backtrace/generate.go b/backtrace/generate.go new file mode 100644 index 0000000..9383b97 --- /dev/null +++ b/backtrace/generate.go @@ -0,0 +1,38 @@ +package backtrace + +import ( + "net" +) + +// GeneratePrefixList 生成指定前缀的IP地址列表 +func GeneratePrefixList(prefix string) []string { + // 解析CIDR表示法的IP地址 + _, ipNet, err := net.ParseCIDR(prefix) + if err != nil { + return nil + } + // 获取IP地址的32位整数表示 + ip := ipNet.IP.To4() + start := binaryIPToInt(ip) + maskSize, _ := ipNet.Mask.Size() + end := start | (1<<(32-maskSize) - 1) + // 生成IP地址列表 + var prefixList []string + for i := start; i <= end; i++ { + if (i-start)%256 == 0 { + tempText := intToBinaryIP(i).String() + prefixList = append(prefixList, tempText[:len(tempText)-2]) + } + } + return prefixList +} + +// 将IP地址转换为32位整数 +func binaryIPToInt(ip net.IP) uint32 { + return (uint32(ip[0]) << 24) | (uint32(ip[1]) << 16) | (uint32(ip[2]) << 8) | uint32(ip[3]) +} + +// 将32位整数转换为IP地址 +func intToBinaryIP(i uint32) net.IP { + return net.IPv4(byte(i>>24), byte(i>>16), byte(i>>8), byte(i)) +} diff --git a/backtrace/listen_linux.go b/backtrace/listen_linux.go new file mode 100644 index 0000000..c2738b1 --- /dev/null +++ b/backtrace/listen_linux.go @@ -0,0 +1,29 @@ +//go:build linux +// +build linux + +package backtrace + +import ( + "net" + "syscall" +) + +func (t *Tracer) listen(network string, laddr *net.IPAddr) (*net.IPConn, error) { + conn, err := net.ListenIP(network, laddr) + if err != nil { + return nil, err + } + raw, err := conn.SyscallConn() + if err != nil { + conn.Close() + return nil, err + } + _ = raw.Control(func(fd uintptr) { + err = syscall.SetsockoptInt(int(fd), syscall.IPPROTO_IP, syscall.IP_HDRINCL, 1) + }) + if err != nil { + conn.Close() + return nil, err + } + return conn, nil +} diff --git a/backtrace/listen_windows.go b/backtrace/listen_windows.go new file mode 100644 index 0000000..af837dc --- /dev/null +++ b/backtrace/listen_windows.go @@ -0,0 +1,35 @@ +//go:build windows +// +build windows + +package backtrace + +import ( + . "github.com/oneclickvirt/backtrace/defaultset" + "golang.org/x/sys/windows" + "net" +) + +func (t *Tracer) listen(network string, laddr *net.IPAddr) (*net.IPConn, error) { + InitLogger() + defer Logger.Sync() + conn, err := net.ListenIP(network, laddr) + if err != nil { + Logger.Info(err.Error()) + return nil, err + } + raw, err := conn.SyscallConn() + if err != nil { + Logger.Info(err.Error()) + conn.Close() + return nil, err + } + _ = raw.Control(func(fd uintptr) { + err = windows.SetsockoptInt(windows.Handle(fd), windows.IPPROTO_IP, windows.IP_HDRINCL, 1) + }) + if err != nil { + Logger.Info(err.Error()) + conn.Close() + return nil, err + } + return conn, nil +} diff --git a/backtrace/trace.go b/backtrace/trace.go new file mode 100644 index 0000000..6fa094b --- /dev/null +++ b/backtrace/trace.go @@ -0,0 +1,467 @@ +package backtrace + +import ( + "context" + "errors" + "golang.org/x/net/icmp" + "golang.org/x/net/ipv4" + "golang.org/x/net/ipv6" + "net" + "sort" + "sync" + "sync/atomic" + "time" +) + +// DefaultConfig is the default configuration for Tracer. +var DefaultConfig = Config{ + Delay: 50 * time.Millisecond, + Timeout: 500 * time.Millisecond, + MaxHops: 15, + Count: 1, + Networks: []string{"ip4:icmp", "ip4:ip"}, +} + +// DefaultTracer is a tracer with DefaultConfig. +var DefaultTracer = &Tracer{ + Config: DefaultConfig, +} + +// Config is a configuration for Tracer. +type Config struct { + Delay time.Duration + Timeout time.Duration + MaxHops int + Count int + Networks []string + Addr *net.IPAddr +} + +// Tracer is a traceroute tool based on raw IP packets. +// It can handle multiple sessions simultaneously. +type Tracer struct { + Config + + once sync.Once + conn *net.IPConn + err error + + mu sync.RWMutex + sess map[string][]*Session + seq uint32 +} + +// Trace starts sending IP packets increasing TTL until MaxHops and calls h for each reply. +func (t *Tracer) Trace(ctx context.Context, ip net.IP, h func(reply *Reply)) error { + sess, err := t.NewSession(ip) + if err != nil { + return err + } + defer sess.Close() + + delay := time.NewTicker(t.Delay) + defer delay.Stop() + + max := t.MaxHops + for n := 0; n < t.Count; n++ { + for ttl := 1; ttl <= t.MaxHops && ttl <= max; ttl++ { + err = sess.Ping(ttl) + if err != nil { + return err + } + select { + case <-delay.C: + case r := <-sess.Receive(): + if max > r.Hops && ip.Equal(r.IP) { + max = r.Hops + } + h(r) + case <-ctx.Done(): + return ctx.Err() + } + } + } + if sess.isDone(max) { + return nil + } + deadline := time.After(t.Timeout) + for { + select { + case r := <-sess.Receive(): + if max > r.Hops && ip.Equal(r.IP) { + max = r.Hops + } + h(r) + if sess.isDone(max) { + return nil + } + case <-deadline: + return nil + case <-ctx.Done(): + return ctx.Err() + } + } +} + +// NewSession returns new tracer session. +func (t *Tracer) NewSession(ip net.IP) (*Session, error) { + t.once.Do(t.init) + if t.err != nil { + return nil, t.err + } + return newSession(t, shortIP(ip)), nil +} + +func (t *Tracer) init() { + for _, network := range t.Networks { + t.conn, t.err = t.listen(network, t.Addr) + if t.err != nil { + continue + } + go t.serve(t.conn) + return + } +} + +// Close closes listening socket. +// Tracer can not be used after Close is called. +func (t *Tracer) Close() { + t.mu.Lock() + defer t.mu.Unlock() + if t.conn != nil { + t.conn.Close() + } +} + +func (t *Tracer) serve(conn *net.IPConn) error { + defer conn.Close() + buf := make([]byte, 1500) + for { + n, from, err := conn.ReadFromIP(buf) + if err != nil { + return err + } + err = t.serveData(from.IP, buf[:n]) + if err != nil { + continue + } + } +} + +func (t *Tracer) serveData(from net.IP, b []byte) error { + if from.To4() == nil { + // TODO: implement ProtocolIPv6ICMP + return errUnsupportedProtocol + } + now := time.Now() + msg, err := icmp.ParseMessage(ProtocolICMP, b) + if err != nil { + return err + } + if msg.Type == ipv4.ICMPTypeEchoReply { + echo := msg.Body.(*icmp.Echo) + return t.serveReply(from, &packet{from, uint16(echo.ID), 1, now}) + } + b = getReplyData(msg) + if len(b) < ipv4.HeaderLen { + return errMessageTooShort + } + switch b[0] >> 4 { + case ipv4.Version: + ip, err := ipv4.ParseHeader(b) + if err != nil { + return err + } + return t.serveReply(ip.Dst, &packet{from, uint16(ip.ID), ip.TTL, now}) + case ipv6.Version: + ip, err := ipv6.ParseHeader(b) + if err != nil { + return err + } + return t.serveReply(ip.Dst, &packet{from, uint16(ip.FlowLabel), ip.HopLimit, now}) + default: + return errUnsupportedProtocol + } +} + +func (t *Tracer) sendRequest(dst net.IP, ttl int) (*packet, error) { + id := uint16(atomic.AddUint32(&t.seq, 1)) + b := newPacket(id, dst, ttl) + req := &packet{dst, id, ttl, time.Now()} + _, err := t.conn.WriteToIP(b, &net.IPAddr{IP: dst}) + if err != nil { + return nil, err + } + return req, nil +} + +func (t *Tracer) addSession(s *Session) { + t.mu.Lock() + defer t.mu.Unlock() + if t.sess == nil { + t.sess = make(map[string][]*Session) + } + t.sess[string(s.ip)] = append(t.sess[string(s.ip)], s) +} + +func (t *Tracer) removeSession(s *Session) { + t.mu.Lock() + defer t.mu.Unlock() + a := t.sess[string(s.ip)] + for i, it := range a { + if it == s { + t.sess[string(s.ip)] = append(a[:i], a[i+1:]...) + return + } + } +} + +func (t *Tracer) serveReply(dst net.IP, res *packet) error { + t.mu.RLock() + defer t.mu.RUnlock() + a := t.sess[string(shortIP(dst))] + for _, s := range a { + s.handle(res) + } + return nil +} + +// Session is a tracer session. +type Session struct { + t *Tracer + ip net.IP + ch chan *Reply + + mu sync.RWMutex + probes []*packet +} + +// NewSession returns new session. +func NewSession(ip net.IP) (*Session, error) { + return DefaultTracer.NewSession(ip) +} + +func newSession(t *Tracer, ip net.IP) *Session { + s := &Session{ + t: t, + ip: ip, + ch: make(chan *Reply, 64), + } + t.addSession(s) + return s +} + +// Ping sends single ICMP packet with specified TTL. +func (s *Session) Ping(ttl int) error { + req, err := s.t.sendRequest(s.ip, ttl+1) + if err != nil { + return err + } + s.mu.Lock() + s.probes = append(s.probes, req) + s.mu.Unlock() + return nil +} + +// Receive returns channel to receive ICMP replies. +func (s *Session) Receive() <-chan *Reply { + return s.ch +} + +// isDone returns true if session does not have unresponsed requests with TTL <= ttl. +func (s *Session) isDone(ttl int) bool { + s.mu.RLock() + defer s.mu.RUnlock() + for _, r := range s.probes { + if r.TTL <= ttl { + return false + } + } + return true +} + +func (s *Session) handle(res *packet) { + now := res.Time + n := 0 + var req *packet + s.mu.Lock() + for _, r := range s.probes { + if now.Sub(r.Time) > s.t.Timeout { + continue + } + if r.ID == res.ID { + req = r + continue + } + s.probes[n] = r + n++ + } + s.probes = s.probes[:n] + s.mu.Unlock() + if req == nil { + return + } + hops := req.TTL - res.TTL + 1 + if hops < 1 { + hops = 1 + } + select { + case s.ch <- &Reply{ + IP: res.IP, + RTT: res.Time.Sub(req.Time), + Hops: hops, + }: + default: + } +} + +// Close closes tracer session. +func (s *Session) Close() { + s.t.removeSession(s) +} + +type packet struct { + IP net.IP + ID uint16 + TTL int + Time time.Time +} + +func shortIP(ip net.IP) net.IP { + if v := ip.To4(); v != nil { + return v + } + return ip +} + +func getReplyData(msg *icmp.Message) []byte { + switch b := msg.Body.(type) { + case *icmp.TimeExceeded: + return b.Data + case *icmp.DstUnreach: + return b.Data + case *icmp.ParamProb: + return b.Data + } + return nil +} + +var ( + errMessageTooShort = errors.New("message too short") + errUnsupportedProtocol = errors.New("unsupported protocol") + errNoReplyData = errors.New("no reply data") +) + +func newPacket(id uint16, dst net.IP, ttl int) []byte { + // TODO: reuse buffers... + msg := icmp.Message{ + Type: ipv4.ICMPTypeEcho, + Body: &icmp.Echo{ + ID: int(id), + Seq: int(id), + }, + } + p, _ := msg.Marshal(nil) + ip := &ipv4.Header{ + Version: ipv4.Version, + Len: ipv4.HeaderLen, + TotalLen: ipv4.HeaderLen + len(p), + TOS: 16, + ID: int(id), + Dst: dst, + Protocol: ProtocolICMP, + TTL: ttl, + } + buf, err := ip.Marshal() + if err != nil { + return nil + } + return append(buf, p...) +} + +// IANA Assigned Internet Protocol Numbers +const ( + ProtocolICMP = 1 + ProtocolTCP = 6 + ProtocolUDP = 17 + ProtocolIPv6ICMP = 58 +) + +// Reply is a reply packet. +type Reply struct { + IP net.IP + RTT time.Duration + Hops int +} + +// Node is a detected network node. +type Node struct { + IP net.IP + RTT []time.Duration +} + +// Hop is a set of detected nodes. +type Hop struct { + Nodes []*Node + Distance int +} + +// Add adds node from r. +func (h *Hop) Add(r *Reply) *Node { + var node *Node + for _, it := range h.Nodes { + if it.IP.Equal(r.IP) { + node = it + break + } + } + if node == nil { + node = &Node{IP: r.IP} + h.Nodes = append(h.Nodes, node) + } + node.RTT = append(node.RTT, r.RTT) + return node +} + +// Trace is a simple traceroute tool using DefaultTracer. +func Trace(ip net.IP) ([]*Hop, error) { + hops := make([]*Hop, 0, DefaultTracer.MaxHops) + touch := func(dist int) *Hop { + for _, h := range hops { + if h.Distance == dist { + return h + } + } + h := &Hop{Distance: dist} + hops = append(hops, h) + return h + } + err := DefaultTracer.Trace(context.Background(), ip, func(r *Reply) { + touch(r.Hops).Add(r) + }) + if err != nil && err != context.DeadlineExceeded { + return nil, err + } + sort.Slice(hops, func(i, j int) bool { + return hops[i].Distance < hops[j].Distance + }) + last := len(hops) - 1 + for i := last; i >= 0; i-- { + h := hops[i] + if len(h.Nodes) == 1 && ip.Equal(h.Nodes[0].IP) { + continue + } + if i == last { + break + } + i++ + node := hops[i].Nodes[0] + i++ + for _, it := range hops[i:] { + node.RTT = append(node.RTT, it.Nodes[0].RTT...) + } + hops = hops[:i] + break + } + return hops, nil +} diff --git a/defaultset/defaultset.go b/defaultset/defaultset.go new file mode 100644 index 0000000..d75a8fb --- /dev/null +++ b/defaultset/defaultset.go @@ -0,0 +1,35 @@ +package defaultset + +import "fmt" + +func Red(text string) string { + return fmt.Sprintf("\033[31m\033[01m%s\033[0m", text) +} + +func Green(text string) string { + return fmt.Sprintf("\033[32m\033[01m%s\033[0m", text) +} + +func DarkGreen(text string) string { + return fmt.Sprintf("\033[32m\033[02m%s\033[0m", text) +} + +func Yellow(text string) string { + return fmt.Sprintf("\033[33m\033[01m%s\033[0m", text) +} + +func Blue(text string) string { + return fmt.Sprintf("\033[36m\033[01m%s\033[0m", text) +} + +func Purple(text string) string { + return fmt.Sprintf("\033[35m\033[01m%s\033[0m", text) +} + +func Cyan(text string) string { + return fmt.Sprintf("\033[36m\033[01m%s\033[0m", text) +} + +func White(text string) string { + return fmt.Sprintf("\033[37m\033[01m%s\033[0m", text) +} diff --git a/defaultset/zap.go b/defaultset/zap.go new file mode 100644 index 0000000..28996f9 --- /dev/null +++ b/defaultset/zap.go @@ -0,0 +1,40 @@ +package defaultset + +import ( + "go.uber.org/zap" + "go.uber.org/zap/zapcore" +) + +var Logger *zap.Logger + +func getZapConfig() zap.Config { + cfg := zap.Config{ + Encoding: "console", // 日志编码格式 + Level: zap.NewAtomicLevelAt(zap.InfoLevel), // 日志级别 + OutputPaths: []string{"ecs.log"}, // 输出路径,可以是多个文件 + ErrorOutputPaths: []string{}, // 错误输出路径,可以是多个文件 + EncoderConfig: zapcore.EncoderConfig{ + TimeKey: "timestamp", // 时间字段名称 + LevelKey: "level", // 日志级别字段名称 + NameKey: "logger", // 日志记录器名称字段名称 + CallerKey: "caller", // 调用者信息字段名称 + MessageKey: "message", // 日志消息字段名称 + StacktraceKey: "stacktrace", // 堆栈跟踪信息字段名称 + EncodeLevel: zapcore.LowercaseLevelEncoder, // 小写格式的日志级别编码器 + EncodeTime: zapcore.ISO8601TimeEncoder, // ISO8601 时间格式编码器 + EncodeCaller: zapcore.ShortCallerEncoder, // 编码短调用者信息 + }, + } + return cfg +} + +// InitLogger 初始化日志记录器 +func InitLogger() { + // 配置日志记录器 + cfg := getZapConfig() + var err error + Logger, err = cfg.Build() + if err != nil { + panic("failed to initialize logger: " + err.Error()) + } +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..f105308 --- /dev/null +++ b/go.mod @@ -0,0 +1,14 @@ +module github.com/oneclickvirt/backtrace + +go 1.21.5 + +require ( + go.uber.org/zap v1.27.0 + golang.org/x/net v0.24.0 + golang.org/x/sys v0.19.0 +) + +require ( + github.com/stretchr/testify v1.9.0 // indirect + go.uber.org/multierr v1.11.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..a600202 --- /dev/null +++ b/go.sum @@ -0,0 +1,18 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= +go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= +golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= +golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= +golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/main.go b/main.go new file mode 100644 index 0000000..89b9049 --- /dev/null +++ b/main.go @@ -0,0 +1,16 @@ +package main + +import ( + "fmt" + "net/http" + "github.com/oneclickvirt/backtrace/backtrace" + . "github.com/oneclickvirt/backtrace/defaultset" +) + +func main() { + go func() { + http.Get("https://hits.seeyoufarm.com/api/count/incr/badge.svg?url=https%3A%2F%2Fgithub.com%2Foneclickvirt%2Fbacktrace&count_bg=%2323E01C&title_bg=%23555555&icon=sonarcloud.svg&icon_color=%23E7E7E7&title=hits&edge_flat=false") + }() + fmt.Println(Green("项目地址:"), Yellow("https://github.com/oneclickvirt/backtrace")) + backtrace.BackTrace() +}