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, &params.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  }