github.com/annwntech/go-micro/v2@v2.9.5/debug/log/kubernetes/kubernetes.go (about) 1 // Package kubernetes is a logger implementing (github.com/annwntech/go-micro/v2/debug/log).Log 2 package kubernetes 3 4 import ( 5 "bufio" 6 "encoding/json" 7 "fmt" 8 "os" 9 "sort" 10 "strconv" 11 "time" 12 13 "github.com/annwntech/go-micro/v2/debug/log" 14 "github.com/annwntech/go-micro/v2/util/kubernetes/client" 15 ) 16 17 type klog struct { 18 client client.Client 19 20 log.Options 21 } 22 23 func (k *klog) podLogStream(podName string, stream *kubeStream) { 24 p := make(map[string]string) 25 p["follow"] = "true" 26 27 // get the logs for the pod 28 body, err := k.client.Log(&client.Resource{ 29 Name: podName, 30 Kind: "pod", 31 }, client.LogParams(p)) 32 33 if err != nil { 34 fmt.Fprintf(os.Stderr, err.Error()) 35 return 36 } 37 38 s := bufio.NewScanner(body) 39 defer body.Close() 40 41 for { 42 select { 43 case <-stream.stop: 44 return 45 default: 46 if s.Scan() { 47 record := k.parse(s.Text()) 48 stream.stream <- record 49 } else { 50 // TODO: is there a blocking call 51 // rather than a sleep loop? 52 time.Sleep(time.Second) 53 } 54 } 55 } 56 } 57 58 func (k *klog) getMatchingPods() ([]string, error) { 59 r := &client.Resource{ 60 Kind: "pod", 61 Value: new(client.PodList), 62 } 63 64 l := make(map[string]string) 65 66 l["name"] = client.Format(k.Options.Name) 67 // TODO: specify micro:service 68 // l["micro"] = "service" 69 70 if err := k.client.Get(r, client.GetLabels(l)); err != nil { 71 return nil, err 72 } 73 74 var matches []string 75 76 for _, p := range r.Value.(*client.PodList).Items { 77 // find labels that match the name 78 if p.Metadata.Labels["name"] == client.Format(k.Options.Name) { 79 matches = append(matches, p.Metadata.Name) 80 } 81 } 82 83 return matches, nil 84 } 85 86 func (k *klog) parse(line string) log.Record { 87 record := log.Record{} 88 89 if err := json.Unmarshal([]byte(line), &record); err != nil { 90 record.Timestamp = time.Now().UTC() 91 record.Message = line 92 record.Metadata = make(map[string]string) 93 } 94 95 record.Metadata["service"] = k.Options.Name 96 97 return record 98 } 99 100 func (k *klog) Read(options ...log.ReadOption) ([]log.Record, error) { 101 opts := &log.ReadOptions{} 102 for _, o := range options { 103 o(opts) 104 } 105 106 pods, err := k.getMatchingPods() 107 if err != nil { 108 return nil, err 109 } 110 111 var records []log.Record 112 113 for _, pod := range pods { 114 logParams := make(map[string]string) 115 116 if !opts.Since.Equal(time.Time{}) { 117 logParams["sinceSeconds"] = strconv.Itoa(int(time.Since(opts.Since).Seconds())) 118 } 119 120 if opts.Count != 0 { 121 logParams["tailLines"] = strconv.Itoa(opts.Count) 122 } 123 124 if opts.Stream == true { 125 logParams["follow"] = "true" 126 } 127 128 logs, err := k.client.Log(&client.Resource{ 129 Name: pod, 130 Kind: "pod", 131 }, client.LogParams(logParams)) 132 133 if err != nil { 134 return nil, err 135 } 136 defer logs.Close() 137 138 s := bufio.NewScanner(logs) 139 140 for s.Scan() { 141 record := k.parse(s.Text()) 142 record.Metadata["pod"] = pod 143 records = append(records, record) 144 } 145 } 146 147 // sort the records 148 sort.Slice(records, func(i, j int) bool { return records[i].Timestamp.Before(records[j].Timestamp) }) 149 150 return records, nil 151 } 152 153 func (k *klog) Write(l log.Record) error { 154 return write(l) 155 } 156 157 func (k *klog) Stream() (log.Stream, error) { 158 // find the matching pods 159 pods, err := k.getMatchingPods() 160 if err != nil { 161 return nil, err 162 } 163 164 stream := &kubeStream{ 165 stream: make(chan log.Record), 166 stop: make(chan bool), 167 } 168 169 // stream from the individual pods 170 for _, pod := range pods { 171 go k.podLogStream(pod, stream) 172 } 173 174 return stream, nil 175 } 176 177 // NewLog returns a configured Kubernetes logger 178 func NewLog(opts ...log.Option) log.Log { 179 klog := &klog{} 180 for _, o := range opts { 181 o(&klog.Options) 182 } 183 184 if len(os.Getenv("KUBERNETES_SERVICE_HOST")) > 0 { 185 klog.client = client.NewClusterClient() 186 } else { 187 klog.client = client.NewLocalClient() 188 } 189 return klog 190 }