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