github.com/ungtb10d/git-lfs@v2.5.2+incompatible/tasklog/log.go (about) 1 package tasklog 2 3 import ( 4 "fmt" 5 "io" 6 "io/ioutil" 7 "strings" 8 "sync" 9 "time" 10 11 "github.com/olekukonko/ts" 12 ) 13 14 const ( 15 DefaultLoggingThrottle = 200 * time.Millisecond 16 ) 17 18 // Logger logs a series of tasks to an io.Writer, processing each task in order 19 // until completion . 20 type Logger struct { 21 // sink is the writer to write to. 22 sink io.Writer 23 24 // widthFn is a function that returns the width of the terminal that 25 // this logger is running within. 26 widthFn func() int 27 28 // throttle is the minimum amount of time that must pass between each 29 // instant data is logged. 30 throttle time.Duration 31 32 // queue is the incoming, unbuffered queue of tasks to enqueue. 33 queue chan Task 34 // tasks is the set of tasks to process. 35 tasks chan Task 36 // wg is a WaitGroup that is incremented when new tasks are enqueued, 37 // and decremented when tasks finish. 38 wg *sync.WaitGroup 39 } 40 41 // NewLogger retuns a new *Logger instance that logs to "sink" and uses the 42 // current terminal width as the width of the line. 43 func NewLogger(sink io.Writer) *Logger { 44 if sink == nil { 45 sink = ioutil.Discard 46 } 47 48 l := &Logger{ 49 sink: sink, 50 throttle: DefaultLoggingThrottle, 51 widthFn: func() int { 52 size, err := ts.GetSize() 53 if err != nil { 54 return 80 55 } 56 return size.Col() 57 }, 58 queue: make(chan Task), 59 tasks: make(chan Task), 60 wg: new(sync.WaitGroup), 61 } 62 63 go l.consume() 64 65 return l 66 } 67 68 // Close closes the queue and does not allow new Tasks to be `enqueue()`'d. It 69 // waits until the currently running Task has completed. 70 func (l *Logger) Close() { 71 if l == nil { 72 return 73 } 74 75 close(l.queue) 76 77 l.wg.Wait() 78 } 79 80 // Waitier creates and enqueues a new *WaitingTask. 81 func (l *Logger) Waiter(msg string) *WaitingTask { 82 t := NewWaitingTask(msg) 83 l.Enqueue(t) 84 85 return t 86 } 87 88 // Percentage creates and enqueues a new *PercentageTask. 89 func (l *Logger) Percentage(msg string, total uint64) *PercentageTask { 90 t := NewPercentageTask(msg, total) 91 l.Enqueue(t) 92 93 return t 94 } 95 96 // List creates and enqueues a new *ListTask. 97 func (l *Logger) List(msg string) *ListTask { 98 t := NewListTask(msg) 99 l.Enqueue(t) 100 101 return t 102 } 103 104 // List creates and enqueues a new *SimpleTask. 105 func (l *Logger) Simple() *SimpleTask { 106 t := NewSimpleTask() 107 l.Enqueue(t) 108 109 return t 110 } 111 112 // Enqueue enqueues the given Tasks "ts". 113 func (l *Logger) Enqueue(ts ...Task) { 114 if l == nil { 115 for _, t := range ts { 116 if t == nil { 117 // NOTE: Do not allow nil tasks which are unable 118 // to be completed. 119 continue 120 } 121 go func(t Task) { 122 for range t.Updates() { 123 // Discard all updates. 124 } 125 }(t) 126 } 127 return 128 } 129 130 l.wg.Add(len(ts)) 131 for _, t := range ts { 132 if t == nil { 133 // NOTE: See above. 134 continue 135 } 136 l.queue <- t 137 } 138 } 139 140 // consume creates a pseudo-infinte buffer between the incoming set of tasks and 141 // the queue of tasks to work on. 142 func (l *Logger) consume() { 143 go func() { 144 // Process the single next task in sequence until completion, 145 // then consume the next task. 146 for task := range l.tasks { 147 l.logTask(task) 148 } 149 }() 150 151 defer close(l.tasks) 152 153 pending := make([]Task, 0) 154 155 for { 156 // If there is a pending task, "peek" it off of the set of 157 // pending tasks. 158 var next Task 159 if len(pending) > 0 { 160 next = pending[0] 161 } 162 163 if next == nil { 164 // If there was no pending task, wait for either a) 165 // l.queue to close, or b) a new task to be submitted. 166 task, ok := <-l.queue 167 if !ok { 168 // If the queue is closed, no more new tasks may 169 // be added. 170 return 171 } 172 173 // Otherwise, add a new task to the set of tasks to 174 // process immediately, since there is no current 175 // buffer. 176 l.tasks <- task 177 } else { 178 // If there is a pending task, wait for either a) a 179 // write to process the task to become non-blocking, or 180 // b) a new task to enter the queue. 181 select { 182 case task, ok := <-l.queue: 183 if !ok { 184 // If the queue is closed, no more tasks 185 // may be added. 186 return 187 } 188 // Otherwise, add the next task to the set of 189 // pending, active tasks. 190 pending = append(pending, task) 191 case l.tasks <- next: 192 // Or "pop" the peeked task off of the pending 193 // set. 194 pending = pending[1:] 195 } 196 } 197 } 198 } 199 200 // logTask logs the set of updates from a given task to the sink, then logs a 201 // "done" message, and then marks the task as done. 202 // 203 // By default, the *Logger throttles log entry updates to once per the duration 204 // of time specified by `l.throttle time.Duration`. 205 // 206 // If the duration if 0, or the task is "durable" (by implementing 207 // github.com/git-lfs/git-lfs/tasklog#DurableTask), then all entries will be 208 // logged. 209 func (l *Logger) logTask(task Task) { 210 defer l.wg.Done() 211 212 logAll := !task.Throttled() 213 var last time.Time 214 215 var update *Update 216 for update = range task.Updates() { 217 if logAll || l.throttle == 0 || !update.Throttled(last.Add(l.throttle)) { 218 l.logLine(update.S) 219 last = update.At 220 } 221 } 222 223 if update != nil { 224 // If a task sent no updates, the last recorded update will be 225 // nil. Given this, only log a message when there was at least 226 // (1) update. 227 l.log(fmt.Sprintf("%s, done\n", update.S)) 228 } 229 230 if v, ok := task.(interface { 231 // OnComplete is called after the Task "task" is closed, but 232 // before new tasks are accepted. 233 OnComplete() 234 }); ok { 235 // If the Task implements this interface, call it and block 236 // before accepting new tasks. 237 v.OnComplete() 238 } 239 } 240 241 // logLine writes a complete line and moves the cursor to the beginning of the 242 // line. 243 // 244 // It returns the number of bytes "n" written to the sink and the error "err", 245 // if one was encountered. 246 func (l *Logger) logLine(str string) (n int, err error) { 247 padding := strings.Repeat(" ", maxInt(0, l.widthFn()-len(str))) 248 249 return l.log(str + padding + "\r") 250 } 251 252 // log writes a string verbatim to the sink. 253 // 254 // It returns the number of bytes "n" written to the sink and the error "err", 255 // if one was encountered. 256 func (l *Logger) log(str string) (n int, err error) { 257 return fmt.Fprint(l.sink, str) 258 } 259 260 func maxInt(a, b int) int { 261 if a > b { 262 return a 263 } 264 return b 265 }