github.com/GoogleContainerTools/skaffold@v1.39.18/pkg/skaffold/docker/logger/log.go (about) 1 /* 2 Copyright 2021 The Skaffold 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 logger 18 19 import ( 20 "context" 21 "io" 22 "sync/atomic" 23 "time" 24 25 "github.com/ahmetb/dlog" 26 27 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/docker" 28 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/docker/tracker" 29 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/graph" 30 logstream "github.com/GoogleContainerTools/skaffold/pkg/skaffold/log/stream" 31 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/output" 32 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/output/log" 33 ) 34 35 type Logger struct { 36 out io.Writer 37 tracker *tracker.ContainerTracker 38 colorPicker output.ColorPicker 39 client docker.LocalDaemon 40 muted int32 41 } 42 43 func NewLogger(ctx context.Context, tracker *tracker.ContainerTracker, cfg docker.Config) (*Logger, error) { 44 cli, err := docker.NewAPIClient(ctx, cfg) 45 if err != nil { 46 return nil, err 47 } 48 return &Logger{ 49 tracker: tracker, 50 client: cli, 51 colorPicker: output.NewColorPicker(), 52 }, nil 53 } 54 55 func (l *Logger) RegisterArtifacts(artifacts []graph.Artifact) { 56 for _, artifact := range artifacts { 57 l.colorPicker.AddImage(artifact.Tag) 58 } 59 } 60 61 func (l *Logger) Start(ctx context.Context, out io.Writer) error { 62 if l == nil { 63 return nil 64 } 65 66 l.out = out 67 68 go func() { 69 for { 70 select { 71 case <-ctx.Done(): 72 return 73 case id := <-l.tracker.Notifier(): 74 go l.streamLogsFromContainer(ctx, id) 75 } 76 } 77 }() 78 return nil 79 } 80 81 func (l *Logger) streamLogsFromContainer(ctx context.Context, id string) { 82 tr, tw := io.Pipe() 83 go func() { 84 err := l.client.ContainerLogs(ctx, tw, id) 85 if err != nil { 86 // Don't print errors if the user interrupted the logs 87 // or if the logs were interrupted because of a configuration change 88 // TODO(nkubala)[07/23/21]: if container is lost, emit API event and attempt to reattach 89 // https://github.com/GoogleContainerTools/skaffold/issues/6281 90 if ctx.Err() != context.Canceled { 91 log.Entry(ctx).Warn(err) 92 } 93 } 94 _ = tw.Close() 95 }() 96 97 dr := dlog.NewReader(tr) // https://ahmet.im/blog/docker-logs-api-binary-format-explained/ 98 formatter := NewDockerLogFormatter(l.colorPicker, l.tracker, l.IsMuted, id) 99 if err := logstream.StreamRequest(ctx, l.out, formatter, dr); err != nil { 100 log.Entry(ctx).Errorf("streaming request: %s", err) 101 } 102 } 103 104 func (l *Logger) Stop() { 105 if l == nil { 106 return 107 } 108 109 l.tracker.Reset() 110 } 111 112 // Mute mutes the logs. 113 func (l *Logger) Mute() { 114 if l == nil { 115 // Logs are not activated. 116 return 117 } 118 119 atomic.StoreInt32(&l.muted, 1) 120 } 121 122 // Unmute unmutes the logs. 123 func (l *Logger) Unmute() { 124 if l == nil { 125 // Logs are not activated. 126 return 127 } 128 129 atomic.StoreInt32(&l.muted, 0) 130 } 131 132 func (l *Logger) IsMuted() bool { 133 if l == nil { 134 return true 135 } 136 137 return atomic.LoadInt32(&l.muted) == 1 138 } 139 140 func (l *Logger) SetSince(time.Time) { 141 // we always create a new container on Deploy, so this is a noop. 142 }