github.com/hanks177/podman/v4@v4.1.3-0.20220613032544-16d90015bc83/pkg/api/handlers/compat/containers_logs.go (about)

     1  package compat
     2  
     3  import (
     4  	"encoding/binary"
     5  	"fmt"
     6  	"io"
     7  	"net/http"
     8  	"strconv"
     9  	"strings"
    10  	"sync"
    11  	"time"
    12  
    13  	"github.com/hanks177/podman/v4/libpod"
    14  	"github.com/hanks177/podman/v4/libpod/logs"
    15  	"github.com/hanks177/podman/v4/pkg/api/handlers/utils"
    16  	api "github.com/hanks177/podman/v4/pkg/api/types"
    17  	"github.com/hanks177/podman/v4/pkg/util"
    18  	"github.com/gorilla/schema"
    19  	"github.com/pkg/errors"
    20  	log "github.com/sirupsen/logrus"
    21  )
    22  
    23  func LogsFromContainer(w http.ResponseWriter, r *http.Request) {
    24  	decoder := r.Context().Value(api.DecoderKey).(*schema.Decoder)
    25  	runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime)
    26  
    27  	query := struct {
    28  		Follow     bool   `schema:"follow"`
    29  		Stdout     bool   `schema:"stdout"`
    30  		Stderr     bool   `schema:"stderr"`
    31  		Since      string `schema:"since"`
    32  		Until      string `schema:"until"`
    33  		Timestamps bool   `schema:"timestamps"`
    34  		Tail       string `schema:"tail"`
    35  	}{
    36  		Tail: "all",
    37  	}
    38  	if err := decoder.Decode(&query, r.URL.Query()); err != nil {
    39  		utils.Error(w, http.StatusBadRequest, errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String()))
    40  		return
    41  	}
    42  
    43  	if !(query.Stdout || query.Stderr) {
    44  		msg := fmt.Sprintf("%s: you must choose at least one stream", http.StatusText(http.StatusBadRequest))
    45  		utils.Error(w, http.StatusBadRequest, errors.Errorf("%s for %s", msg, r.URL.String()))
    46  		return
    47  	}
    48  
    49  	name := utils.GetName(r)
    50  	ctnr, err := runtime.LookupContainer(name)
    51  	if err != nil {
    52  		utils.ContainerNotFound(w, name, err)
    53  		return
    54  	}
    55  
    56  	var tail int64 = -1
    57  	if query.Tail != "all" {
    58  		tail, err = strconv.ParseInt(query.Tail, 0, 64)
    59  		if err != nil {
    60  			utils.BadRequest(w, "tail", query.Tail, err)
    61  			return
    62  		}
    63  	}
    64  
    65  	var since time.Time
    66  	if _, found := r.URL.Query()["since"]; found {
    67  		since, err = util.ParseInputTime(query.Since, true)
    68  		if err != nil {
    69  			utils.BadRequest(w, "since", query.Since, err)
    70  			return
    71  		}
    72  	}
    73  
    74  	var until time.Time
    75  	if _, found := r.URL.Query()["until"]; found {
    76  		if query.Until != "0" {
    77  			until, err = util.ParseInputTime(query.Until, false)
    78  			if err != nil {
    79  				utils.BadRequest(w, "until", query.Until, err)
    80  				return
    81  			}
    82  		}
    83  	}
    84  
    85  	options := &logs.LogOptions{
    86  		Details:    true,
    87  		Follow:     query.Follow,
    88  		Since:      since,
    89  		Until:      until,
    90  		Tail:       tail,
    91  		Timestamps: query.Timestamps,
    92  	}
    93  
    94  	var wg sync.WaitGroup
    95  	options.WaitGroup = &wg
    96  
    97  	logChannel := make(chan *logs.LogLine, tail+1)
    98  	if err := runtime.Log(r.Context(), []*libpod.Container{ctnr}, options, logChannel); err != nil {
    99  		utils.InternalServerError(w, errors.Wrapf(err, "failed to obtain logs for Container '%s'", name))
   100  		return
   101  	}
   102  	go func() {
   103  		wg.Wait()
   104  		close(logChannel)
   105  	}()
   106  
   107  	w.WriteHeader(http.StatusOK)
   108  
   109  	var frame strings.Builder
   110  	header := make([]byte, 8)
   111  
   112  	writeHeader := true
   113  	// Docker does not write stream headers iff the container has a tty.
   114  	if !utils.IsLibpodRequest(r) {
   115  		inspectData, err := ctnr.Inspect(false)
   116  		if err != nil {
   117  			utils.InternalServerError(w, errors.Wrapf(err, "failed to obtain logs for Container '%s'", name))
   118  			return
   119  		}
   120  		writeHeader = !inspectData.Config.Tty
   121  	}
   122  
   123  	for line := range logChannel {
   124  		if _, found := r.URL.Query()["until"]; found {
   125  			if line.Time.After(until) && !until.IsZero() {
   126  				break
   127  			}
   128  		}
   129  
   130  		// Reset buffer we're ready to loop again
   131  		frame.Reset()
   132  		switch line.Device {
   133  		case "stdout":
   134  			if !query.Stdout {
   135  				continue
   136  			}
   137  			header[0] = 1
   138  		case "stderr":
   139  			if !query.Stderr {
   140  				continue
   141  			}
   142  			header[0] = 2
   143  		default:
   144  			// Logging and moving on is the best we can do here. We may have already sent
   145  			// a Status and Content-Type to client therefore we can no longer report an error.
   146  			log.Infof("unknown Device type '%s' in log file from Container %s", line.Device, ctnr.ID())
   147  			continue
   148  		}
   149  
   150  		if query.Timestamps {
   151  			frame.WriteString(line.Time.Format(time.RFC3339))
   152  			frame.WriteString(" ")
   153  		}
   154  
   155  		frame.WriteString(line.Msg)
   156  		if !line.Partial() {
   157  			frame.WriteString("\n")
   158  		}
   159  
   160  		if writeHeader {
   161  			binary.BigEndian.PutUint32(header[4:], uint32(frame.Len()))
   162  			if _, err := w.Write(header[0:8]); err != nil {
   163  				log.Errorf("unable to write log output header: %q", err)
   164  			}
   165  		}
   166  
   167  		if _, err := io.WriteString(w, frame.String()); err != nil {
   168  			log.Errorf("unable to write frame string: %q", err)
   169  		}
   170  		if flusher, ok := w.(http.Flusher); ok {
   171  			flusher.Flush()
   172  		}
   173  	}
   174  }