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