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  }