github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/apiserver/observer/request_notifier.go (about)

     1  // Copyright 2016 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  package observer
     4  
     5  import (
     6  	"net/http"
     7  	"time"
     8  
     9  	"github.com/juju/clock"
    10  	"github.com/juju/loggo"
    11  	"gopkg.in/juju/names.v2"
    12  
    13  	"github.com/juju/juju/pubsub/apiserver"
    14  	"github.com/juju/juju/rpc"
    15  	"github.com/juju/juju/rpc/jsoncodec"
    16  )
    17  
    18  // Hub defines the only method of the apiserver centralhub that
    19  // the observer uses.
    20  type Hub interface {
    21  	Publish(topic string, data interface{}) (<-chan struct{}, error)
    22  }
    23  
    24  // RequestObserver serves as a sink for API server requests and
    25  // responses.
    26  type RequestObserver struct {
    27  	clock              clock.Clock
    28  	hub                Hub
    29  	logger             loggo.Logger
    30  	connLogger         loggo.Logger
    31  	pingLogger         loggo.Logger
    32  	apiConnectionCount func() int64
    33  
    34  	// state represents information that's built up as methods on this
    35  	// type are called. We segregate this to ensure it's clear what
    36  	// information is transient in case we want to extract it
    37  	// later. It's an anonymous struct so this doesn't leak outside
    38  	// this type.
    39  	state struct {
    40  		id                 uint64
    41  		websocketConnected time.Time
    42  		tag                string
    43  		model              string
    44  		agent              bool
    45  		fromController     bool
    46  	}
    47  }
    48  
    49  // RequestObservercontext provides information needed for a
    50  // RequestObserverContext to operate correctly.
    51  type RequestObserverContext struct {
    52  
    53  	// Clock is the clock to use for all time operations on this type.
    54  	Clock clock.Clock
    55  
    56  	// Hub refers to the pubsub Hub which will have connection
    57  	// and disconnection events published.
    58  	Hub Hub
    59  
    60  	// Logger is the log to use to write log statements.
    61  	Logger loggo.Logger
    62  }
    63  
    64  // NewRequestObserver returns a new RPCObserver.
    65  func NewRequestObserver(ctx RequestObserverContext) *RequestObserver {
    66  	// Ideally we should have a logging context so we can log into the correct
    67  	// model rather than the api server for everything.
    68  	module := ctx.Logger.Name()
    69  	return &RequestObserver{
    70  		clock:      ctx.Clock,
    71  		hub:        ctx.Hub,
    72  		logger:     ctx.Logger,
    73  		connLogger: loggo.GetLogger(module + ".connection"),
    74  		pingLogger: loggo.GetLogger(module + ".ping"),
    75  	}
    76  }
    77  
    78  func (n *RequestObserver) isAgent(entity names.Tag) bool {
    79  	switch entity.(type) {
    80  	case names.UnitTag, names.MachineTag, names.ApplicationTag:
    81  		return true
    82  	default:
    83  		return false
    84  	}
    85  }
    86  
    87  // Login implements Observer.
    88  func (n *RequestObserver) Login(entity names.Tag, model names.ModelTag, fromController bool, userData string) {
    89  	n.state.tag = entity.String()
    90  	n.state.fromController = fromController
    91  	if n.isAgent(entity) {
    92  		n.state.agent = true
    93  		n.state.model = model.Id()
    94  		// Don't log connections from the controller to the model.
    95  		if !n.state.fromController {
    96  			n.connLogger.Infof("agent login: %s for %s", n.state.tag, n.state.model)
    97  		}
    98  		n.hub.Publish(apiserver.ConnectTopic, apiserver.APIConnection{
    99  			AgentTag:        n.state.tag,
   100  			ControllerAgent: fromController,
   101  			ModelUUID:       model.Id(),
   102  			ConnectionID:    n.state.id,
   103  			UserData:        userData,
   104  		})
   105  	}
   106  }
   107  
   108  // Join implements Observer.
   109  func (n *RequestObserver) Join(req *http.Request, connectionID uint64) {
   110  	n.state.id = connectionID
   111  	n.state.websocketConnected = n.clock.Now()
   112  
   113  	n.logger.Debugf(
   114  		"[%X] API connection from %s",
   115  		n.state.id,
   116  		req.RemoteAddr,
   117  	)
   118  }
   119  
   120  // Leave implements Observer.
   121  func (n *RequestObserver) Leave() {
   122  	if n.state.agent {
   123  		// Don't log disconnections from the controller to the model.
   124  		if !n.state.fromController {
   125  			n.connLogger.Infof("agent disconnected: %s for %s", n.state.tag, n.state.model)
   126  		}
   127  		n.hub.Publish(apiserver.DisconnectTopic, apiserver.APIConnection{
   128  			AgentTag:        n.state.tag,
   129  			ControllerAgent: n.state.fromController,
   130  			ModelUUID:       n.state.model,
   131  			ConnectionID:    n.state.id,
   132  		})
   133  	}
   134  	n.logger.Debugf(
   135  		"[%X] %s API connection terminated after %v",
   136  		n.state.id,
   137  		n.state.tag,
   138  		time.Since(n.state.websocketConnected),
   139  	)
   140  }
   141  
   142  // RPCObserver implements Observer.
   143  func (n *RequestObserver) RPCObserver() rpc.Observer {
   144  	return &rpcObserver{
   145  		clock:      n.clock,
   146  		logger:     n.logger,
   147  		pingLogger: n.pingLogger,
   148  		id:         n.state.id,
   149  		tag:        n.state.tag,
   150  	}
   151  }
   152  
   153  // rpcObserver serves as a sink for RPC requests and responses.
   154  type rpcObserver struct {
   155  	clock        clock.Clock
   156  	logger       loggo.Logger
   157  	pingLogger   loggo.Logger
   158  	id           uint64
   159  	tag          string
   160  	requestStart time.Time
   161  }
   162  
   163  // ServerRequest implements rpc.Observer.
   164  func (n *rpcObserver) ServerRequest(hdr *rpc.Header, body interface{}) {
   165  	n.requestStart = n.clock.Now()
   166  
   167  	if hdr.Request.Type == "Pinger" && hdr.Request.Action == "Ping" {
   168  		n.logRequestTrace(n.pingLogger, hdr, body)
   169  		return
   170  	}
   171  
   172  	// TODO(rog) 2013-10-11 remove secrets from some requests.
   173  	// Until secrets are removed, we only log the body of the requests at trace level
   174  	// which is below the default level of debug.
   175  	if n.logger.IsTraceEnabled() {
   176  		n.logRequestTrace(n.logger, hdr, body)
   177  	} else {
   178  		n.logger.Debugf("<- [%X] %s %s", n.id, n.tag, jsoncodec.DumpRequest(hdr, "'params redacted'"))
   179  	}
   180  }
   181  
   182  // ServerReply implements rpc.Observer.
   183  func (n *rpcObserver) ServerReply(req rpc.Request, hdr *rpc.Header, body interface{}) {
   184  	if req.Type == "Pinger" && req.Action == "Ping" {
   185  		n.logReplyTrace(n.pingLogger, hdr, body)
   186  		return
   187  	}
   188  
   189  	// TODO(rog) 2013-10-11 remove secrets from some responses.
   190  	// Until secrets are removed, we only log the body of the requests at trace level
   191  	// which is below the default level of debug.
   192  	if n.logger.IsTraceEnabled() {
   193  		n.logReplyTrace(n.logger, hdr, body)
   194  	} else {
   195  		n.logger.Debugf(
   196  			"-> [%X] %s %s %s %s[%q].%s",
   197  			n.id,
   198  			n.tag,
   199  			time.Since(n.requestStart),
   200  			jsoncodec.DumpRequest(hdr, "'body redacted'"),
   201  			req.Type,
   202  			req.Id,
   203  			req.Action,
   204  		)
   205  	}
   206  }
   207  
   208  func (n *rpcObserver) logRequestTrace(logger loggo.Logger, hdr *rpc.Header, body interface{}) {
   209  	n.logTrace(logger, "<-", hdr, body)
   210  }
   211  
   212  func (n *rpcObserver) logReplyTrace(logger loggo.Logger, hdr *rpc.Header, body interface{}) {
   213  	n.logTrace(logger, "->", hdr, body)
   214  }
   215  
   216  func (n *rpcObserver) logTrace(logger loggo.Logger, prefix string, hdr *rpc.Header, body interface{}) {
   217  	logger.Tracef("%s [%X] %s %s", prefix, n.id, n.tag, jsoncodec.DumpRequest(hdr, body))
   218  }