github.com/mwhudson/juju@v0.0.0-20160512215208-90ff01f3497f/apiserver/debuglog.go (about) 1 // Copyright 2014 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package apiserver 5 6 import ( 7 "io" 8 "net" 9 "net/http" 10 "net/url" 11 "strconv" 12 "syscall" 13 14 "github.com/juju/errors" 15 "github.com/juju/loggo" 16 "golang.org/x/net/websocket" 17 18 "github.com/juju/juju/apiserver/common" 19 "github.com/juju/juju/apiserver/params" 20 "github.com/juju/juju/state" 21 ) 22 23 // debugLogHandler takes requests to watch the debug log. 24 // 25 // It provides the underlying framework for the 2 debug-log 26 // variants. The supplied handle func allows for varied handling of 27 // requests. 28 type debugLogHandler struct { 29 ctxt httpContext 30 handle debugLogHandlerFunc 31 } 32 33 type debugLogHandlerFunc func( 34 state.LoggingState, 35 *debugLogParams, 36 debugLogSocket, 37 <-chan struct{}, 38 ) error 39 40 func newDebugLogHandler( 41 ctxt httpContext, 42 handle debugLogHandlerFunc, 43 ) *debugLogHandler { 44 return &debugLogHandler{ 45 ctxt: ctxt, 46 handle: handle, 47 } 48 } 49 50 // ServeHTTP will serve up connections as a websocket for the 51 // debug-log API. 52 // 53 // Args for the HTTP request are as follows: 54 // includeEntity -> []string - lists entity tags to include in the response 55 // - tags may finish with a '*' to match a prefix e.g.: unit-mysql-*, machine-2 56 // - if none are set, then all lines are considered included 57 // includeModule -> []string - lists logging modules to include in the response 58 // - if none are set, then all lines are considered included 59 // excludeEntity -> []string - lists entity tags to exclude from the response 60 // - as with include, it may finish with a '*' 61 // excludeModule -> []string - lists logging modules to exclude from the response 62 // limit -> uint - show *at most* this many lines 63 // backlog -> uint 64 // - go back this many lines from the end before starting to filter 65 // - has no meaning if 'replay' is true 66 // level -> string one of [TRACE, DEBUG, INFO, WARNING, ERROR] 67 // replay -> string - one of [true, false], if true, start the file from the start 68 // noTail -> string - one of [true, false], if true, existing logs are sent back, 69 // - but the command does not wait for new ones. 70 func (h *debugLogHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { 71 server := websocket.Server{ 72 Handler: func(conn *websocket.Conn) { 73 socket := &debugLogSocketImpl{conn} 74 defer socket.Close() 75 76 logger.Infof("debug log handler starting") 77 // Validate before authenticate because the authentication is 78 // dependent on the state connection that is determined during the 79 // validation. 80 st, _, err := h.ctxt.stateForRequestAuthenticatedUser(req) 81 if err != nil { 82 socket.sendError(err) 83 return 84 } 85 params, err := readDebugLogParams(req.URL.Query()) 86 if err != nil { 87 socket.sendError(err) 88 return 89 } 90 91 if err := h.handle(st, params, socket, h.ctxt.stop()); err != nil { 92 if isBrokenPipe(err) { 93 logger.Tracef("debug-log handler stopped (client disconnected)") 94 } else { 95 logger.Errorf("debug-log handler error: %v", err) 96 } 97 } 98 }, 99 } 100 server.ServeHTTP(w, req) 101 } 102 103 func isBrokenPipe(err error) bool { 104 err = errors.Cause(err) 105 if opErr, ok := err.(*net.OpError); ok { 106 return opErr.Err == syscall.EPIPE 107 } 108 return false 109 } 110 111 // debugLogSocket describes the functionality required for the 112 // debuglog handlers to send logs to the client. 113 type debugLogSocket interface { 114 io.Writer 115 116 // sendOk sends a nil error response, indicating there were no errors. 117 sendOk() 118 119 // sendError sends a JSON-encoded error response. 120 sendError(err error) 121 } 122 123 // debugLogSocketImpl implements the debugLogSocket interface. It 124 // wraps a websocket.Conn and provides a few debug-log specific helper 125 // methods. 126 type debugLogSocketImpl struct { 127 *websocket.Conn 128 } 129 130 // sendOk implements debugLogSocket. 131 func (s *debugLogSocketImpl) sendOk() { 132 s.sendError(nil) 133 } 134 135 // sendError implements debugLogSocket. 136 func (s *debugLogSocketImpl) sendError(err error) { 137 sendJSON(s.Conn, ¶ms.ErrorResult{ 138 Error: common.ServerError(err), 139 }) 140 } 141 142 // debugLogParams contains the parsed debuglog API request parameters. 143 type debugLogParams struct { 144 maxLines uint 145 fromTheStart bool 146 noTail bool 147 backlog uint 148 filterLevel loggo.Level 149 includeEntity []string 150 excludeEntity []string 151 includeModule []string 152 excludeModule []string 153 } 154 155 func readDebugLogParams(queryMap url.Values) (*debugLogParams, error) { 156 params := new(debugLogParams) 157 158 if value := queryMap.Get("maxLines"); value != "" { 159 num, err := strconv.ParseUint(value, 10, 64) 160 if err != nil { 161 return nil, errors.Errorf("maxLines value %q is not a valid unsigned number", value) 162 } 163 params.maxLines = uint(num) 164 } 165 166 if value := queryMap.Get("replay"); value != "" { 167 replay, err := strconv.ParseBool(value) 168 if err != nil { 169 return nil, errors.Errorf("replay value %q is not a valid boolean", value) 170 } 171 params.fromTheStart = replay 172 } 173 174 if value := queryMap.Get("noTail"); value != "" { 175 noTail, err := strconv.ParseBool(value) 176 if err != nil { 177 return nil, errors.Errorf("noTail value %q is not a valid boolean", value) 178 } 179 params.noTail = noTail 180 } 181 182 if value := queryMap.Get("backlog"); value != "" { 183 num, err := strconv.ParseUint(value, 10, 64) 184 if err != nil { 185 return nil, errors.Errorf("backlog value %q is not a valid unsigned number", value) 186 } 187 params.backlog = uint(num) 188 } 189 190 if value := queryMap.Get("level"); value != "" { 191 var ok bool 192 level, ok := loggo.ParseLevel(value) 193 if !ok || level < loggo.TRACE || level > loggo.ERROR { 194 return nil, errors.Errorf("level value %q is not one of %q, %q, %q, %q, %q", 195 value, loggo.TRACE, loggo.DEBUG, loggo.INFO, loggo.WARNING, loggo.ERROR) 196 } 197 params.filterLevel = level 198 } 199 200 params.includeEntity = queryMap["includeEntity"] 201 params.excludeEntity = queryMap["excludeEntity"] 202 params.includeModule = queryMap["includeModule"] 203 params.excludeModule = queryMap["excludeModule"] 204 205 return params, nil 206 }