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 }