github.com/tickoalcantara12/micro/v3@v3.0.0-20221007104245-9d75b9bcbab9/service/runtime/kubernetes/logs.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/runtime/kubernetes/logs.go 14 15 package kubernetes 16 17 import ( 18 "bufio" 19 "strconv" 20 "sync" 21 "time" 22 23 "github.com/tickoalcantara12/micro/v3/service/errors" 24 "github.com/tickoalcantara12/micro/v3/service/logger" 25 "github.com/tickoalcantara12/micro/v3/service/runtime" 26 "github.com/tickoalcantara12/micro/v3/service/runtime/kubernetes/client" 27 ) 28 29 type klog struct { 30 client client.Client 31 serviceName string 32 version string 33 options runtime.LogsOptions 34 } 35 36 func (k *klog) podLogs(podName string, stream *kubeStream) error { 37 p := make(map[string]string) 38 p["follow"] = "true" 39 40 opts := []client.LogOption{ 41 client.LogParams(p), 42 client.LogNamespace(k.options.Namespace), 43 } 44 45 // get the logs for the pod 46 body, err := k.client.Log(&client.Resource{ 47 Name: podName, 48 Kind: "pod", 49 }, opts...) 50 51 if err != nil { 52 stream.err = err 53 stream.Stop() 54 return err 55 } 56 57 s := bufio.NewScanner(body) 58 defer body.Close() 59 60 for { 61 select { 62 case <-stream.stop: 63 return stream.Error() 64 default: 65 if s.Scan() { 66 record := runtime.Log{ 67 Message: s.Text(), 68 } 69 70 // send the records to the stream 71 // there can be multiple pods doing this 72 select { 73 case stream.stream <- record: 74 case <-stream.stop: 75 return stream.Error() 76 } 77 } else { 78 // TODO: is there a blocking call 79 // rather than a sleep loop? 80 time.Sleep(time.Second) 81 } 82 } 83 } 84 } 85 86 func (k *klog) getMatchingPods() ([]string, error) { 87 r := &client.Resource{ 88 Kind: "pod", 89 Value: new(client.PodList), 90 } 91 92 l := make(map[string]string) 93 94 l["name"] = client.Format(k.serviceName) 95 96 if len(k.version) > 0 { 97 l["version"] = client.Format(k.version) 98 } 99 100 // TODO: specify micro:service 101 // l["micro"] = "service" 102 103 opts := []client.GetOption{ 104 client.GetLabels(l), 105 client.GetNamespace(k.options.Namespace), 106 } 107 108 if err := k.client.Get(r, opts...); err != nil { 109 return nil, err 110 } 111 112 var matches []string 113 114 for _, p := range r.Value.(*client.PodList).Items { 115 // find labels that match the name 116 if p.Metadata.Labels["name"] == client.Format(k.serviceName) { 117 matches = append(matches, p.Metadata.Name) 118 } 119 } 120 121 return matches, nil 122 } 123 124 func (k *klog) Read() ([]runtime.Log, error) { 125 pods, err := k.getMatchingPods() 126 if err != nil { 127 return nil, err 128 } 129 if len(pods) == 0 { 130 return nil, errors.NotFound("runtime.logs", "no such service") 131 } 132 133 var records []runtime.Log 134 135 for _, pod := range pods { 136 logParams := make(map[string]string) 137 138 //if !opts.Since.Equal(time.Time{}) { 139 // logParams["sinceSeconds"] = strconv.Itoa(int(time.Since(opts.Since).Seconds())) 140 //} 141 142 if k.options.Count != 0 { 143 logParams["tailLines"] = strconv.Itoa(int(k.options.Count)) 144 } 145 146 if k.options.Stream == true { 147 logParams["follow"] = "true" 148 } 149 150 opts := []client.LogOption{ 151 client.LogParams(logParams), 152 client.LogNamespace(k.options.Namespace), 153 } 154 155 logs, err := k.client.Log(&client.Resource{ 156 Name: pod, 157 Kind: "pod", 158 }, opts...) 159 160 if err != nil { 161 return nil, err 162 } 163 defer logs.Close() 164 165 s := bufio.NewScanner(logs) 166 167 for s.Scan() { 168 record := runtime.Log{ 169 Message: s.Text(), 170 } 171 // record.Metadata["pod"] = pod 172 records = append(records, record) 173 } 174 } 175 176 // sort the records 177 // sort.Slice(records, func(i, j int) bool { return records[i].Timestamp.Before(records[j].Timestamp) }) 178 179 return records, nil 180 } 181 182 func (k *klog) Stream() (runtime.LogStream, error) { 183 // find the matching pods 184 pods, err := k.getMatchingPods() 185 if err != nil { 186 return nil, err 187 } 188 189 if len(pods) == 0 { 190 return nil, errors.NotFound("runtime.logs", "no such service") 191 } 192 193 stream := &kubeStream{ 194 stream: make(chan runtime.Log), 195 stop: make(chan bool), 196 } 197 198 var wg sync.WaitGroup 199 200 // stream from the individual pods 201 for _, pod := range pods { 202 wg.Add(1) 203 204 go func(podName string) { 205 if err := k.podLogs(podName, stream); err != nil { 206 logger.Errorf("Error streaming from pod: %v", err) 207 } 208 209 wg.Done() 210 }(pod) 211 } 212 213 go func() { 214 // wait until all pod log watchers are done 215 wg.Wait() 216 217 // do any cleanup 218 stream.Stop() 219 220 // close the stream 221 close(stream.stream) 222 }() 223 224 return stream, nil 225 } 226 227 // NewLog returns a configured Kubernetes logger 228 func newLog(c client.Client, serviceName, version string, opts ...runtime.LogsOption) *klog { 229 options := runtime.LogsOptions{ 230 Namespace: client.DefaultNamespace, 231 } 232 for _, o := range opts { 233 o(&options) 234 } 235 236 klog := &klog{ 237 serviceName: serviceName, 238 version: version, 239 client: c, 240 options: options, 241 } 242 243 return klog 244 }