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  }