github.com/jiasir/deis@v1.12.2/logger/storage/file/adapter.go (about) 1 package file 2 3 import ( 4 "fmt" 5 "os" 6 "os/exec" 7 "path" 8 "strconv" 9 "strings" 10 "sync" 11 ) 12 13 type adapter struct { 14 logRoot string 15 files map[string]*os.File 16 mutex sync.Mutex 17 } 18 19 // NewStorageAdapter returns a pointer to a new instance of a file-based storage.Adapter. 20 func NewStorageAdapter(logRoot string) (*adapter, error) { 21 src, err := os.Stat(logRoot) 22 if err != nil { 23 return nil, fmt.Errorf("Directory %s does not exist", logRoot) 24 } 25 if !src.IsDir() { 26 return nil, fmt.Errorf("%s is not a directory", logRoot) 27 } 28 return &adapter{logRoot: logRoot, files: make(map[string]*os.File)}, nil 29 } 30 31 // Write adds a log message to to an app-specific log file 32 func (a *adapter) Write(app string, message string) error { 33 // Check first if we might actually have to add to the map of file pointers so we can avoid 34 // waiting for / obtaining a lock unnecessarily 35 f, ok := a.files[app] 36 if !ok { 37 // Ensure only one goroutine at a time can be adding a file pointer to the map of file 38 // pointers 39 a.mutex.Lock() 40 defer a.mutex.Unlock() 41 f, ok = a.files[app] 42 if !ok { 43 var err error 44 f, err = a.getFile(app) 45 if err != nil { 46 return err 47 } 48 a.files[app] = f 49 } 50 } 51 if _, err := f.WriteString(message + "\n"); err != nil { 52 return err 53 } 54 return nil 55 } 56 57 // Read retrieves a specified number of log lines from an app-specific log file 58 func (a *adapter) Read(app string, lines int) ([]string, error) { 59 if lines <= 0 { 60 return []string{}, nil 61 } 62 filePath := a.getFilePath(app) 63 exists, err := fileExists(filePath) 64 if err != nil { 65 return nil, err 66 } 67 if !exists { 68 return nil, fmt.Errorf("Could not find logs for '%s'", app) 69 } 70 logBytes, err := exec.Command("tail", "-n", strconv.Itoa(lines), filePath).Output() 71 if err != nil { 72 return nil, err 73 } 74 logStrs := strings.Split(string(logBytes), "\n") 75 return logStrs[:len(logStrs)-1], nil 76 } 77 78 // Destroy deletes stored logs for the specified application 79 func (a *adapter) Destroy(app string) error { 80 // Check first if the map of file pointers even contains the file pointer we want so we can avoid 81 // waiting for / obtaining a lock unnecessarily 82 f, ok := a.files[app] 83 if ok { 84 // Ensure no other goroutine is trying to modify the file pointer map while we're trying to 85 // clean up 86 a.mutex.Lock() 87 defer a.mutex.Unlock() 88 exists, err := fileExists(f.Name()) 89 if err != nil { 90 return err 91 } 92 if exists { 93 if err := os.Remove(f.Name()); err != nil { 94 return err 95 } 96 } 97 delete(a.files, app) 98 } 99 return nil 100 } 101 102 func (a *adapter) Reopen() error { 103 // Ensure no other goroutine is trying to add a file pointer to the map of file pointers while 104 // we're trying to clear it out 105 a.mutex.Lock() 106 defer a.mutex.Unlock() 107 a.files = make(map[string]*os.File) 108 return nil 109 } 110 111 func (a *adapter) getFile(app string) (*os.File, error) { 112 filePath := a.getFilePath(app) 113 exists, err := fileExists(filePath) 114 if err != nil { 115 return nil, err 116 } 117 // return a new file or the existing file for appending 118 var file *os.File 119 if exists { 120 file, err = os.OpenFile(filePath, os.O_RDWR|os.O_APPEND, 0644) 121 } else { 122 file, err = os.OpenFile(filePath, os.O_RDWR|os.O_CREATE, 0644) 123 } 124 return file, err 125 } 126 127 func (a *adapter) getFilePath(app string) string { 128 return path.Join(a.logRoot, app+".log") 129 } 130 131 func fileExists(path string) (bool, error) { 132 _, err := os.Stat(path) 133 if err == nil { 134 return true, nil 135 } 136 if os.IsNotExist(err) { 137 return false, nil 138 } 139 return false, err 140 }