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 }