github.com/coreos/mantle@v0.13.0/platform/journal.go (about)

     1  // Copyright 2017 CoreOS, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package platform
    16  
    17  import (
    18  	"compress/gzip"
    19  	"context"
    20  	"fmt"
    21  	"io"
    22  	"io/ioutil"
    23  	"os"
    24  	"path/filepath"
    25  
    26  	"github.com/coreos/pkg/multierror"
    27  
    28  	"github.com/coreos/mantle/network/journal"
    29  	"github.com/coreos/mantle/util"
    30  )
    31  
    32  // Journal manages recording the journal of a Machine.
    33  type Journal struct {
    34  	journal     io.WriteCloser
    35  	journalRaw  io.WriteCloser
    36  	journalPath string
    37  	recorder    *journal.Recorder
    38  	cancel      context.CancelFunc
    39  }
    40  
    41  // wrapper that also closes the underlying file
    42  type gzWriteCloser struct {
    43  	*gzip.Writer
    44  	underlying io.Closer
    45  }
    46  
    47  func (g gzWriteCloser) Close() error {
    48  	var err multierror.Error
    49  	if e := g.Writer.Close(); e != nil {
    50  		err = append(err, e)
    51  	}
    52  	if e := g.underlying.Close(); e != nil {
    53  		err = append(err, e)
    54  	}
    55  	return err.AsError()
    56  }
    57  
    58  // NewJournal creates a Journal recorder that will log to "journal.txt"
    59  // and "journal-raw.txt.gz" inside the given output directory.
    60  func NewJournal(dir string) (*Journal, error) {
    61  	p := filepath.Join(dir, "journal.txt")
    62  	j, err := os.OpenFile(p, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0666)
    63  	if err != nil {
    64  		return nil, err
    65  	}
    66  
    67  	pr := filepath.Join(dir, "journal-raw.txt.gz")
    68  	jr, err := os.OpenFile(pr, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0666)
    69  	if err != nil {
    70  		return nil, err
    71  	}
    72  	// gzip to save space; a single test can generate well over 1M of logs
    73  	jrz, err := gzip.NewWriterLevel(jr, gzip.BestCompression)
    74  	if err != nil {
    75  		return nil, err
    76  	}
    77  	jrzc := gzWriteCloser{
    78  		underlying: jr,
    79  		Writer:     jrz,
    80  	}
    81  
    82  	return &Journal{
    83  		journal:     j,
    84  		journalRaw:  jrzc,
    85  		recorder:    journal.NewRecorder(journal.ShortWriter(j), jrzc),
    86  		journalPath: p,
    87  	}, nil
    88  }
    89  
    90  // Start begins/resumes streaming the system journal to journal.txt.
    91  func (j *Journal) Start(ctx context.Context, m Machine) error {
    92  	if j.cancel != nil {
    93  		j.cancel()
    94  		j.cancel = nil
    95  		j.recorder.Wait() // Just need to consume the status.
    96  	}
    97  	ctx, cancel := context.WithCancel(ctx)
    98  
    99  	start := func() error {
   100  		client, err := m.SSHClient()
   101  		if err != nil {
   102  			return err
   103  		}
   104  
   105  		return j.recorder.StartSSH(ctx, client)
   106  	}
   107  
   108  	// Retry for a while because this should be run before CheckMachine
   109  	if err := util.Retry(sshRetries, sshTimeout, start); err != nil {
   110  		cancel()
   111  		return fmt.Errorf("ssh journalctl failed: %v", err)
   112  	}
   113  
   114  	j.cancel = cancel
   115  	return nil
   116  }
   117  
   118  // There is no guarantee that anything is returned if called before Destroy
   119  func (j *Journal) Read() ([]byte, error) {
   120  	f, err := os.Open(j.journalPath)
   121  	if err != nil {
   122  		return nil, fmt.Errorf("reading journal: %v", err)
   123  	}
   124  	defer f.Close()
   125  	return ioutil.ReadAll(f)
   126  }
   127  
   128  func (j *Journal) Destroy() {
   129  	if j.cancel != nil {
   130  		j.cancel()
   131  		if err := j.recorder.Wait(); err != nil {
   132  			plog.Errorf("j.recorder.Wait() failed: %v", err)
   133  		}
   134  	}
   135  	if err := j.journal.Close(); err != nil {
   136  		plog.Errorf("Failed to close journal: %v", err)
   137  	}
   138  	if err := j.journalRaw.Close(); err != nil {
   139  		plog.Errorf("Failed to close raw journal: %v", err)
   140  	}
   141  }