github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/apiserver/logtransfer.go (about)

     1  // Copyright 2016 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package apiserver
     5  
     6  import (
     7  	"net/http"
     8  	"time"
     9  
    10  	"github.com/juju/errors"
    11  	"github.com/juju/loggo"
    12  
    13  	"github.com/juju/juju/apiserver/common"
    14  	"github.com/juju/juju/apiserver/logsink"
    15  	corelogger "github.com/juju/juju/core/logger"
    16  	"github.com/juju/juju/rpc/params"
    17  	"github.com/juju/juju/state"
    18  )
    19  
    20  type migrationLoggingStrategy struct {
    21  	apiServerLoggers *apiServerLoggers
    22  
    23  	recordLogger RecordLogger
    24  	releaser     func()
    25  	tracker      *logTracker
    26  }
    27  
    28  // newMigrationLogWriteCloserFunc returns a function that will create a
    29  // logsink.LoggingStrategy given an *http.Request, that writes log
    30  // messages to the state database and tracks their migration.
    31  func newMigrationLogWriteCloserFunc(ctxt httpContext, apiServerLoggers *apiServerLoggers) logsink.NewLogWriteCloserFunc {
    32  	return func(req *http.Request) (logsink.LogWriteCloser, error) {
    33  		strategy := &migrationLoggingStrategy{apiServerLoggers: apiServerLoggers}
    34  		if err := strategy.init(ctxt, req); err != nil {
    35  			return nil, errors.Annotate(err, "initialising migration logsink session")
    36  		}
    37  		return strategy, nil
    38  	}
    39  }
    40  
    41  func (s *migrationLoggingStrategy) init(ctxt httpContext, req *http.Request) error {
    42  	// Require MigrationModeNone because logtransfer happens after the
    43  	// model proper is completely imported.
    44  	st, err := ctxt.stateForMigration(req, state.MigrationModeNone)
    45  	if err != nil {
    46  		return errors.Trace(err)
    47  	}
    48  
    49  	// Here the log messages are expected to be coming from another
    50  	// Juju controller, so the version number provided should be the
    51  	// Juju version of the source controller. Require this to be
    52  	// passed, even though we don't use it anywhere at the moment - it
    53  	// provides future-proofing if we need to do some kind of
    54  	// conversion of log messages from an old client.
    55  	_, err = common.JujuClientVersionFromRequest(req)
    56  	if err != nil {
    57  		st.Release()
    58  		return errors.Trace(err)
    59  	}
    60  
    61  	s.recordLogger = s.apiServerLoggers.getLogger(st.State)
    62  	s.tracker = newLogTracker(st.State)
    63  	s.releaser = func() {
    64  		if removed := st.Release(); removed {
    65  			s.apiServerLoggers.removeLogger(st.State)
    66  		}
    67  	}
    68  	return nil
    69  }
    70  
    71  // Close is part of the logsink.LogWriteCloser interface.
    72  func (s *migrationLoggingStrategy) Close() error {
    73  	err := errors.Annotate(
    74  		s.tracker.Close(),
    75  		"closing last-sent tracker",
    76  	)
    77  	s.releaser()
    78  	return err
    79  }
    80  
    81  // WriteLog is part of the logsink.LogWriteCloser interface.
    82  func (s *migrationLoggingStrategy) WriteLog(m params.LogRecord) error {
    83  	level, _ := loggo.ParseLevel(m.Level)
    84  	err := s.recordLogger.Log([]corelogger.LogRecord{{
    85  		Time:     m.Time,
    86  		Entity:   m.Entity,
    87  		Module:   m.Module,
    88  		Location: m.Location,
    89  		Level:    level,
    90  		Message:  m.Message,
    91  		Labels:   m.Labels,
    92  	}})
    93  	if err == nil {
    94  		err = s.tracker.Track(m.Time)
    95  	}
    96  	return errors.Annotate(err, "logging to DB failed")
    97  }
    98  
    99  // trackingPeriod is used to limit the number of database writes
   100  // made in order to record the ID of the log record last persisted.
   101  const trackingPeriod = 2 * time.Minute
   102  
   103  func newLogTracker(st *state.State) *logTracker {
   104  	return &logTracker{
   105  		tracker: state.NewLastSentLogTracker(
   106  			st, st.ModelUUID(), "migration-logtransfer",
   107  		),
   108  	}
   109  }
   110  
   111  // logTracker assumes that log messages are sent in time order (which
   112  // is how they come from debug-log). If not, this won't give
   113  // meaningful values, and transferring logs could produce large
   114  // numbers of duplicates if restarted.
   115  type logTracker struct {
   116  	tracker     *state.LastSentLogTracker
   117  	trackedTime time.Time
   118  	seenTime    time.Time
   119  }
   120  
   121  func (l *logTracker) Track(t time.Time) error {
   122  	l.seenTime = t
   123  	if t.Sub(l.trackedTime) < trackingPeriod {
   124  		return nil
   125  	}
   126  	l.trackedTime = t
   127  	return errors.Trace(l.tracker.Set(0, t.UnixNano()))
   128  }
   129  
   130  func (l *logTracker) Close() error {
   131  	err := l.tracker.Set(0, l.seenTime.UnixNano())
   132  	if err != nil {
   133  		l.tracker.Close()
   134  		return errors.Trace(err)
   135  	}
   136  	return errors.Trace(l.tracker.Close())
   137  }