github.com/gondor/docker@v1.9.0-rc1/daemon/logger/jsonfilelog/jsonfilelog.go (about) 1 // Package jsonfilelog provides the default Logger implementation for 2 // Docker logging. This logger logs to files on the host server in the 3 // JSON format. 4 package jsonfilelog 5 6 import ( 7 "bytes" 8 "encoding/json" 9 "fmt" 10 "io" 11 "os" 12 "strconv" 13 "sync" 14 "time" 15 16 "gopkg.in/fsnotify.v1" 17 18 "github.com/Sirupsen/logrus" 19 "github.com/docker/docker/daemon/logger" 20 "github.com/docker/docker/pkg/ioutils" 21 "github.com/docker/docker/pkg/jsonlog" 22 "github.com/docker/docker/pkg/pubsub" 23 "github.com/docker/docker/pkg/tailfile" 24 "github.com/docker/docker/pkg/timeutils" 25 "github.com/docker/docker/pkg/units" 26 ) 27 28 const ( 29 // Name is the name of the file that the jsonlogger logs to. 30 Name = "json-file" 31 maxJSONDecodeRetry = 10 32 ) 33 34 // JSONFileLogger is Logger implementation for default Docker logging. 35 type JSONFileLogger struct { 36 buf *bytes.Buffer 37 f *os.File // store for closing 38 mu sync.Mutex // protects buffer 39 capacity int64 //maximum size of each file 40 n int //maximum number of files 41 ctx logger.Context 42 readers map[*logger.LogWatcher]struct{} // stores the active log followers 43 notifyRotate *pubsub.Publisher 44 extra []byte // json-encoded extra attributes 45 } 46 47 func init() { 48 if err := logger.RegisterLogDriver(Name, New); err != nil { 49 logrus.Fatal(err) 50 } 51 if err := logger.RegisterLogOptValidator(Name, ValidateLogOpt); err != nil { 52 logrus.Fatal(err) 53 } 54 } 55 56 // New creates new JSONFileLogger which writes to filename passed in 57 // on given context. 58 func New(ctx logger.Context) (logger.Logger, error) { 59 log, err := os.OpenFile(ctx.LogPath, os.O_RDWR|os.O_APPEND|os.O_CREATE, 0600) 60 if err != nil { 61 return nil, err 62 } 63 var capval int64 = -1 64 if capacity, ok := ctx.Config["max-size"]; ok { 65 var err error 66 capval, err = units.FromHumanSize(capacity) 67 if err != nil { 68 return nil, err 69 } 70 } 71 var maxFiles = 1 72 if maxFileString, ok := ctx.Config["max-file"]; ok { 73 maxFiles, err = strconv.Atoi(maxFileString) 74 if err != nil { 75 return nil, err 76 } 77 if maxFiles < 1 { 78 return nil, fmt.Errorf("max-file cannot be less than 1") 79 } 80 } 81 82 var extra []byte 83 if attrs := ctx.ExtraAttributes(nil); len(attrs) > 0 { 84 var err error 85 extra, err = json.Marshal(attrs) 86 if err != nil { 87 return nil, err 88 } 89 } 90 91 return &JSONFileLogger{ 92 f: log, 93 buf: bytes.NewBuffer(nil), 94 ctx: ctx, 95 capacity: capval, 96 n: maxFiles, 97 readers: make(map[*logger.LogWatcher]struct{}), 98 notifyRotate: pubsub.NewPublisher(0, 1), 99 extra: extra, 100 }, nil 101 } 102 103 // Log converts logger.Message to jsonlog.JSONLog and serializes it to file. 104 func (l *JSONFileLogger) Log(msg *logger.Message) error { 105 l.mu.Lock() 106 defer l.mu.Unlock() 107 108 timestamp, err := timeutils.FastMarshalJSON(msg.Timestamp) 109 if err != nil { 110 return err 111 } 112 err = (&jsonlog.JSONLogs{ 113 Log: append(msg.Line, '\n'), 114 Stream: msg.Source, 115 Created: timestamp, 116 RawAttrs: l.extra, 117 }).MarshalJSONBuf(l.buf) 118 if err != nil { 119 return err 120 } 121 l.buf.WriteByte('\n') 122 _, err = writeLog(l) 123 return err 124 } 125 126 func writeLog(l *JSONFileLogger) (int64, error) { 127 if l.capacity == -1 { 128 return writeToBuf(l) 129 } 130 meta, err := l.f.Stat() 131 if err != nil { 132 return -1, err 133 } 134 if meta.Size() >= l.capacity { 135 name := l.f.Name() 136 if err := l.f.Close(); err != nil { 137 return -1, err 138 } 139 if err := rotate(name, l.n); err != nil { 140 return -1, err 141 } 142 file, err := os.OpenFile(name, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0666) 143 if err != nil { 144 return -1, err 145 } 146 l.f = file 147 l.notifyRotate.Publish(struct{}{}) 148 } 149 return writeToBuf(l) 150 } 151 152 func writeToBuf(l *JSONFileLogger) (int64, error) { 153 i, err := l.buf.WriteTo(l.f) 154 if err != nil { 155 l.buf = bytes.NewBuffer(nil) 156 } 157 return i, err 158 } 159 160 func rotate(name string, n int) error { 161 if n < 2 { 162 return nil 163 } 164 for i := n - 1; i > 1; i-- { 165 oldFile := name + "." + strconv.Itoa(i) 166 replacingFile := name + "." + strconv.Itoa(i-1) 167 if err := backup(oldFile, replacingFile); err != nil { 168 return err 169 } 170 } 171 if err := backup(name+".1", name); err != nil { 172 return err 173 } 174 return nil 175 } 176 177 // backup renames a file from curr to old, creating an empty file curr if it does not exist. 178 func backup(old, curr string) error { 179 if _, err := os.Stat(old); !os.IsNotExist(err) { 180 err := os.Remove(old) 181 if err != nil { 182 return err 183 } 184 } 185 if _, err := os.Stat(curr); os.IsNotExist(err) { 186 f, err := os.Create(curr) 187 if err != nil { 188 return err 189 } 190 f.Close() 191 } 192 return os.Rename(curr, old) 193 } 194 195 // ValidateLogOpt looks for json specific log options max-file & max-size. 196 func ValidateLogOpt(cfg map[string]string) error { 197 for key := range cfg { 198 switch key { 199 case "max-file": 200 case "max-size": 201 case "labels": 202 case "env": 203 default: 204 return fmt.Errorf("unknown log opt '%s' for json-file log driver", key) 205 } 206 } 207 return nil 208 } 209 210 // LogPath returns the location the given json logger logs to. 211 func (l *JSONFileLogger) LogPath() string { 212 return l.ctx.LogPath 213 } 214 215 // Close closes underlying file and signals all readers to stop. 216 func (l *JSONFileLogger) Close() error { 217 l.mu.Lock() 218 err := l.f.Close() 219 for r := range l.readers { 220 r.Close() 221 delete(l.readers, r) 222 } 223 l.mu.Unlock() 224 return err 225 } 226 227 // Name returns name of this logger. 228 func (l *JSONFileLogger) Name() string { 229 return Name 230 } 231 232 func decodeLogLine(dec *json.Decoder, l *jsonlog.JSONLog) (*logger.Message, error) { 233 l.Reset() 234 if err := dec.Decode(l); err != nil { 235 return nil, err 236 } 237 msg := &logger.Message{ 238 Source: l.Stream, 239 Timestamp: l.Created, 240 Line: []byte(l.Log), 241 } 242 return msg, nil 243 } 244 245 // ReadLogs implements the logger's LogReader interface for the logs 246 // created by this driver. 247 func (l *JSONFileLogger) ReadLogs(config logger.ReadConfig) *logger.LogWatcher { 248 logWatcher := logger.NewLogWatcher() 249 250 go l.readLogs(logWatcher, config) 251 return logWatcher 252 } 253 254 func (l *JSONFileLogger) readLogs(logWatcher *logger.LogWatcher, config logger.ReadConfig) { 255 defer close(logWatcher.Msg) 256 257 pth := l.ctx.LogPath 258 var files []io.ReadSeeker 259 for i := l.n; i > 1; i-- { 260 f, err := os.Open(fmt.Sprintf("%s.%d", pth, i-1)) 261 if err != nil { 262 if !os.IsNotExist(err) { 263 logWatcher.Err <- err 264 break 265 } 266 continue 267 } 268 defer f.Close() 269 files = append(files, f) 270 } 271 272 latestFile, err := os.Open(pth) 273 if err != nil { 274 logWatcher.Err <- err 275 return 276 } 277 defer latestFile.Close() 278 279 files = append(files, latestFile) 280 tailer := ioutils.MultiReadSeeker(files...) 281 282 if config.Tail != 0 { 283 tailFile(tailer, logWatcher, config.Tail, config.Since) 284 } 285 286 if !config.Follow { 287 return 288 } 289 290 if config.Tail >= 0 { 291 latestFile.Seek(0, os.SEEK_END) 292 } 293 294 l.mu.Lock() 295 l.readers[logWatcher] = struct{}{} 296 l.mu.Unlock() 297 298 notifyRotate := l.notifyRotate.Subscribe() 299 followLogs(latestFile, logWatcher, notifyRotate, config.Since) 300 301 l.mu.Lock() 302 delete(l.readers, logWatcher) 303 l.mu.Unlock() 304 305 l.notifyRotate.Evict(notifyRotate) 306 } 307 308 func tailFile(f io.ReadSeeker, logWatcher *logger.LogWatcher, tail int, since time.Time) { 309 var rdr io.Reader = f 310 if tail > 0 { 311 ls, err := tailfile.TailFile(f, tail) 312 if err != nil { 313 logWatcher.Err <- err 314 return 315 } 316 rdr = bytes.NewBuffer(bytes.Join(ls, []byte("\n"))) 317 } 318 dec := json.NewDecoder(rdr) 319 l := &jsonlog.JSONLog{} 320 for { 321 msg, err := decodeLogLine(dec, l) 322 if err != nil { 323 if err != io.EOF { 324 logWatcher.Err <- err 325 } 326 return 327 } 328 if !since.IsZero() && msg.Timestamp.Before(since) { 329 continue 330 } 331 logWatcher.Msg <- msg 332 } 333 } 334 335 func followLogs(f *os.File, logWatcher *logger.LogWatcher, notifyRotate chan interface{}, since time.Time) { 336 dec := json.NewDecoder(f) 337 l := &jsonlog.JSONLog{} 338 fileWatcher, err := fsnotify.NewWatcher() 339 if err != nil { 340 logWatcher.Err <- err 341 return 342 } 343 defer fileWatcher.Close() 344 if err := fileWatcher.Add(f.Name()); err != nil { 345 logWatcher.Err <- err 346 return 347 } 348 349 var retries int 350 for { 351 msg, err := decodeLogLine(dec, l) 352 if err != nil { 353 if err != io.EOF { 354 // try again because this shouldn't happen 355 if _, ok := err.(*json.SyntaxError); ok && retries <= maxJSONDecodeRetry { 356 dec = json.NewDecoder(f) 357 retries++ 358 continue 359 } 360 logWatcher.Err <- err 361 return 362 } 363 364 select { 365 case <-fileWatcher.Events: 366 dec = json.NewDecoder(f) 367 continue 368 case <-fileWatcher.Errors: 369 logWatcher.Err <- err 370 return 371 case <-logWatcher.WatchClose(): 372 return 373 case <-notifyRotate: 374 fileWatcher.Remove(f.Name()) 375 376 f, err = os.Open(f.Name()) 377 if err != nil { 378 logWatcher.Err <- err 379 return 380 } 381 if err := fileWatcher.Add(f.Name()); err != nil { 382 logWatcher.Err <- err 383 } 384 dec = json.NewDecoder(f) 385 continue 386 } 387 } 388 389 retries = 0 // reset retries since we've succeeded 390 if !since.IsZero() && msg.Timestamp.Before(since) { 391 continue 392 } 393 select { 394 case logWatcher.Msg <- msg: 395 case <-logWatcher.WatchClose(): 396 logWatcher.Msg <- msg 397 for { 398 msg, err := decodeLogLine(dec, l) 399 if err != nil { 400 return 401 } 402 if !since.IsZero() && msg.Timestamp.Before(since) { 403 continue 404 } 405 logWatcher.Msg <- msg 406 } 407 } 408 } 409 }