github.com/containerd/nerdctl/v2@v2.0.0-beta.5.0.20240520001846-b5758f54fa28/pkg/logging/jsonfile/jsonfile.go (about) 1 /* 2 Copyright The containerd Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package jsonfile 18 19 import ( 20 "container/ring" 21 "encoding/json" 22 "fmt" 23 "io" 24 "path/filepath" 25 "strconv" 26 "strings" 27 "sync" 28 "time" 29 30 "github.com/containerd/log" 31 timetypes "github.com/docker/docker/api/types/time" 32 ) 33 34 // Entry is compatible with Docker "json-file" logs 35 type Entry struct { 36 Log string `json:"log,omitempty"` // line, including "\r\n" 37 Stream string `json:"stream,omitempty"` // "stdout" or "stderr" 38 Time time.Time `json:"time"` // e.g. "2020-12-11T20:29:41.939902251Z" 39 } 40 41 func Path(dataStore, ns, id string) string { 42 // the file name corresponds to Docker 43 return filepath.Join(dataStore, "containers", ns, id, id+"-json.log") 44 } 45 46 func Encode(stdout <-chan string, stderr <-chan string, writer io.Writer) error { 47 enc := json.NewEncoder(writer) 48 var encMu sync.Mutex 49 var wg sync.WaitGroup 50 wg.Add(2) 51 f := func(dataChan <-chan string, name string) { 52 defer wg.Done() 53 e := &Entry{ 54 Stream: name, 55 } 56 for logEntry := range dataChan { 57 e.Log = logEntry + "\n" 58 e.Time = time.Now().UTC() 59 encMu.Lock() 60 encErr := enc.Encode(e) 61 encMu.Unlock() 62 if encErr != nil { 63 log.L.WithError(encErr).Errorf("failed to encode JSON") 64 return 65 } 66 } 67 } 68 go f(stdout, "stdout") 69 go f(stderr, "stderr") 70 wg.Wait() 71 return nil 72 } 73 74 func writeEntry(e *Entry, stdout, stderr io.Writer, refTime time.Time, timestamps bool, since string, until string) error { 75 output := []byte{} 76 77 if since != "" { 78 ts, err := timetypes.GetTimestamp(since, refTime) 79 if err != nil { 80 return fmt.Errorf("invalid value for \"since\": %w", err) 81 } 82 v := strings.Split(ts, ".") 83 i, err := strconv.ParseInt(v[0], 10, 64) 84 if err != nil { 85 return err 86 } 87 if e.Time.Before(time.Unix(i, 0)) { 88 return nil 89 } 90 } 91 92 if until != "" { 93 ts, err := timetypes.GetTimestamp(until, refTime) 94 if err != nil { 95 return fmt.Errorf("invalid value for \"until\": %w", err) 96 } 97 v := strings.Split(ts, ".") 98 i, err := strconv.ParseInt(v[0], 10, 64) 99 if err != nil { 100 return err 101 } 102 if e.Time.After(time.Unix(i, 0)) { 103 return nil 104 } 105 } 106 107 if timestamps { 108 output = append(output, []byte(e.Time.Format(time.RFC3339Nano))...) 109 output = append(output, ' ') 110 } 111 112 output = append(output, []byte(e.Log)...) 113 114 var writeTo io.Writer 115 switch e.Stream { 116 case "stdout": 117 writeTo = stdout 118 case "stderr": 119 writeTo = stderr 120 default: 121 log.L.Errorf("unknown stream name %q, entry=%+v", e.Stream, e) 122 } 123 124 if writeTo != nil { 125 _, err := writeTo.Write(output) 126 return err 127 } 128 return nil 129 } 130 131 func Decode(stdout, stderr io.Writer, r io.Reader, timestamps bool, since string, until string, tail uint) error { 132 var buff *ring.Ring 133 if tail != 0 { 134 buff = ring.New(int(tail)) 135 } 136 137 dec := json.NewDecoder(r) 138 now := time.Now() 139 for { 140 var e Entry 141 if err := dec.Decode(&e); err == io.EOF { 142 break 143 } else if err != nil { 144 return err 145 } 146 147 if buff == nil { 148 // Write out the entry directly 149 err := writeEntry(&e, stdout, stderr, now, timestamps, since, until) 150 if err != nil { 151 log.L.Errorf("error while writing log entry to output stream: %s", err) 152 } 153 } else { 154 // Else place the entry in a ring buffer 155 buff.Value = &e 156 buff = buff.Next() 157 } 158 } 159 160 if buff != nil { 161 // The ring should now contain up to `tail` elements and be set to 162 // internally point to the oldest element in the ring. 163 buff.Do(func(e interface{}) { 164 if e == nil { 165 // unallocated ring element 166 return 167 } 168 cast, ok := e.(*Entry) 169 if !ok { 170 log.L.Errorf("failed to cast Entry struct: %#v", e) 171 return 172 } 173 174 err := writeEntry(cast, stdout, stderr, now, timestamps, since, until) 175 if err != nil { 176 log.L.Errorf("error while writing log entry to output stream: %s", err) 177 } 178 }) 179 } 180 return nil 181 }