github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/apiserver/logsink.go (about) 1 // Copyright 2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package apiserver 5 6 import ( 7 "io" 8 "net/http" 9 "os" 10 "path/filepath" 11 "strings" 12 "time" 13 14 "github.com/juju/errors" 15 "github.com/juju/utils" 16 "golang.org/x/net/websocket" 17 "gopkg.in/natefinch/lumberjack.v2" 18 19 "github.com/juju/juju/apiserver/common" 20 "github.com/juju/juju/apiserver/params" 21 "github.com/juju/juju/state" 22 ) 23 24 func newLogSinkHandler(h httpContext, logDir string) http.Handler { 25 26 logPath := filepath.Join(logDir, "logsink.log") 27 if err := primeLogFile(logPath); err != nil { 28 // This isn't a fatal error so log and continue if priming 29 // fails. 30 logger.Errorf("Unable to prime %s (proceeding anyway): %v", logPath, err) 31 } 32 33 return &logSinkHandler{ 34 ctxt: h, 35 fileLogger: &lumberjack.Logger{ 36 Filename: logPath, 37 MaxSize: 300, // MB 38 MaxBackups: 2, 39 }, 40 } 41 } 42 43 // primeLogFile ensures the logsink log file is created with the 44 // correct mode and ownership. 45 func primeLogFile(path string) error { 46 f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0600) 47 if err != nil { 48 return errors.Trace(err) 49 } 50 f.Close() 51 err = utils.ChownPath(path, "syslog") 52 return errors.Trace(err) 53 } 54 55 type logSinkHandler struct { 56 ctxt httpContext 57 fileLogger io.WriteCloser 58 } 59 60 // ServeHTTP implements the http.Handler interface. 61 func (h *logSinkHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { 62 server := websocket.Server{ 63 Handler: func(socket *websocket.Conn) { 64 defer socket.Close() 65 66 st, entity, err := h.ctxt.stateForRequestAuthenticatedAgent(req) 67 if err != nil { 68 h.sendError(socket, req, err) 69 return 70 } 71 tag := entity.Tag() 72 73 filePrefix := st.ModelUUID() + " " + tag.String() + ":" 74 dbLogger := state.NewDbLogger(st, tag) 75 defer dbLogger.Close() 76 77 // If we get to here, no more errors to report, so we report a nil 78 // error. This way the first line of the socket is always a json 79 // formatted simple error. 80 h.sendError(socket, req, nil) 81 82 logCh := h.receiveLogs(socket) 83 for { 84 select { 85 case <-h.ctxt.stop(): 86 return 87 case m := <-logCh: 88 fileErr := h.logToFile(filePrefix, m) 89 if fileErr != nil { 90 logger.Errorf("logging to logsink.log failed: %v", fileErr) 91 } 92 dbErr := dbLogger.Log(m.Time, m.Module, m.Location, m.Level, m.Message) 93 if dbErr != nil { 94 logger.Errorf("logging to DB failed: %v", err) 95 } 96 if fileErr != nil || dbErr != nil { 97 return 98 } 99 } 100 } 101 }, 102 } 103 server.ServeHTTP(w, req) 104 } 105 106 func (h *logSinkHandler) receiveLogs(socket *websocket.Conn) <-chan params.LogRecord { 107 logCh := make(chan params.LogRecord) 108 109 go func() { 110 var m params.LogRecord 111 for { 112 // Receive() blocks until data arrives but will also be 113 // unblocked when the API handler calls socket.Close as it 114 // finishes. 115 if err := websocket.JSON.Receive(socket, &m); err != nil { 116 logger.Debugf("logsink receive error: %v", err) 117 return 118 } 119 120 // Send the log message. 121 select { 122 case <-h.ctxt.stop(): 123 return 124 case logCh <- m: 125 } 126 } 127 }() 128 129 return logCh 130 } 131 132 // sendError sends a JSON-encoded error response. 133 func (h *logSinkHandler) sendError(w io.Writer, req *http.Request, err error) { 134 if err != nil { 135 logger.Errorf("returning error from %s %s: %s", req.Method, req.URL.Path, errors.Details(err)) 136 } 137 sendJSON(w, ¶ms.ErrorResult{ 138 Error: common.ServerError(err), 139 }) 140 } 141 142 // logToFile writes a single log message to the logsink log file. 143 func (h *logSinkHandler) logToFile(prefix string, m params.LogRecord) error { 144 _, err := h.fileLogger.Write([]byte(strings.Join([]string{ 145 prefix, 146 m.Time.In(time.UTC).Format("2006-01-02 15:04:05"), 147 m.Level.String(), 148 m.Module, 149 m.Location, 150 m.Message, 151 }, " ") + "\n")) 152 return err 153 }