github.com/drone/runner-go@v1.12.0/livelog/livelog.go (about)

     1  // Copyright 2019 Drone.IO Inc. All rights reserved.
     2  // Use of this source code is governed by the Polyform License
     3  // that can be found in the LICENSE file.
     4  
     5  // Package livelog provides a Writer that collects pipeline
     6  // output and streams to the central server.
     7  package livelog
     8  
     9  import (
    10  	"context"
    11  	"strings"
    12  	"sync"
    13  	"time"
    14  
    15  	"github.com/drone/drone-go/drone"
    16  	"github.com/drone/runner-go/client"
    17  )
    18  
    19  // defaultLimit is the default maximum log size in bytes.
    20  const defaultLimit = 5242880 // 5MB
    21  
    22  // Writer is an io.Writer that sends logs to the server.
    23  type Writer struct {
    24  	sync.Mutex
    25  
    26  	client client.Client
    27  
    28  	id    int64
    29  	num   int
    30  	now   time.Time
    31  	size  int
    32  	limit int
    33  
    34  	interval time.Duration
    35  	pending  []*drone.Line
    36  	history  []*drone.Line
    37  
    38  	closed bool
    39  	close  chan struct{}
    40  	ready  chan struct{}
    41  }
    42  
    43  // New returns a new Wrtier.
    44  func New(client client.Client, id int64) *Writer {
    45  	b := &Writer{
    46  		client:   client,
    47  		id:       id,
    48  		now:      time.Now(),
    49  		limit:    defaultLimit,
    50  		interval: time.Second,
    51  		close:    make(chan struct{}),
    52  		ready:    make(chan struct{}, 1),
    53  	}
    54  	go b.start()
    55  	return b
    56  }
    57  
    58  // SetLimit sets the Writer limit.
    59  func (b *Writer) SetLimit(limit int) {
    60  	b.limit = limit
    61  }
    62  
    63  // SetInterval sets the Writer flusher interval.
    64  func (b *Writer) SetInterval(interval time.Duration) {
    65  	b.interval = interval
    66  }
    67  
    68  // Write uploads the live log stream to the server.
    69  func (b *Writer) Write(p []byte) (n int, err error) {
    70  	for _, part := range split(p) {
    71  		line := &drone.Line{
    72  			Number:    b.num,
    73  			Message:   part,
    74  			Timestamp: int64(time.Since(b.now).Seconds()),
    75  		}
    76  
    77  		for b.size+len(p) > b.limit {
    78  			b.stop() // buffer is full, step streaming data
    79  			b.size -= len(b.history[0].Message)
    80  			b.history = b.history[1:]
    81  		}
    82  
    83  		b.size = b.size + len(part)
    84  		b.num++
    85  
    86  		if b.stopped() == false {
    87  			b.Lock()
    88  			b.pending = append(b.pending, line)
    89  			b.Unlock()
    90  		}
    91  
    92  		b.Lock()
    93  		b.history = append(b.history, line)
    94  		b.Unlock()
    95  	}
    96  
    97  	select {
    98  	case b.ready <- struct{}{}:
    99  	default:
   100  	}
   101  
   102  	return len(p), nil
   103  }
   104  
   105  // Close closes the writer and uploads the full contents to
   106  // the server.
   107  func (b *Writer) Close() error {
   108  	if b.stop() {
   109  		b.flush()
   110  	}
   111  	return b.upload()
   112  }
   113  
   114  // upload uploads the full log history to the server.
   115  func (b *Writer) upload() error {
   116  	return b.client.Upload(
   117  		context.Background(), b.id, b.history)
   118  }
   119  
   120  // flush batch uploads all buffered logs to the server.
   121  func (b *Writer) flush() error {
   122  	b.Lock()
   123  	lines := b.copy()
   124  	b.clear()
   125  	b.Unlock()
   126  	if len(lines) == 0 {
   127  		return nil
   128  	}
   129  	return b.client.Batch(
   130  		context.Background(), b.id, lines)
   131  }
   132  
   133  // copy returns a copy of the buffered lines.
   134  func (b *Writer) copy() []*drone.Line {
   135  	return append(b.pending[:0:0], b.pending...)
   136  }
   137  
   138  // clear clears the buffer.
   139  func (b *Writer) clear() {
   140  	b.pending = b.pending[:0]
   141  }
   142  
   143  func (b *Writer) stop() bool {
   144  	b.Lock()
   145  	var closed bool
   146  	if b.closed == false {
   147  		close(b.close)
   148  		closed = true
   149  		b.closed = true
   150  	}
   151  	b.Unlock()
   152  	return closed
   153  }
   154  
   155  func (b *Writer) stopped() bool {
   156  	b.Lock()
   157  	closed := b.closed
   158  	b.Unlock()
   159  	return closed
   160  }
   161  
   162  func (b *Writer) start() error {
   163  	for {
   164  		select {
   165  		case <-b.close:
   166  			return nil
   167  		case <-b.ready:
   168  			select {
   169  			case <-b.close:
   170  				return nil
   171  			case <-time.After(b.interval):
   172  				// we intentionally ignore errors. log streams
   173  				// are ephemeral and are considered low prioirty
   174  				// because they are not required for drone to
   175  				// operator, and the impact of failure is minimal
   176  				b.flush()
   177  			}
   178  		}
   179  	}
   180  }
   181  
   182  func split(p []byte) []string {
   183  	s := string(p)
   184  	v := []string{s}
   185  	// kubernetes buffers the output and may combine
   186  	// multiple lines into a single block of output.
   187  	// Split into multiple lines.
   188  	//
   189  	// note that docker output always inclines a line
   190  	// feed marker. This needs to be accounted for when
   191  	// splitting the output into multiple lines.
   192  	if strings.Contains(strings.TrimSuffix(s, "\n"), "\n") {
   193  		v = strings.SplitAfter(s, "\n")
   194  	}
   195  	return v
   196  }