package lagger import ( "errors" "fmt" "log/syslog" "net/http" "os" "strings" "github.com/gin-gonic/gin" "github.com/gofrs/uuid" "github.com/sirupsen/logrus" logrus_syslog "github.com/sirupsen/logrus/hooks/syslog" "github.rackspace.com/gorax/libs/nsiuuid" ) var server = "syslog.nsi.rackspace.com" type NSILogger struct { logger *logrus.Logger hostname string appname string } //Lagger the interface used to return the fields required to log type Lagger interface { Fields() logrus.Fields Message() string } /* Example Usage: Optional: Set a syslog destination s := nsiSyslog.SetDestination("my.syslog.com") Initialization: log, err := nsiSyslog.NewSyslog("hostname_or_env_var", "application_name", logrus.InfoLevel) if err != nil { return nil, errors.New("Unable to connect to syslog server") } Usage: log.Info(log.String("Info Message")) log.Info(log.WebLog(r, username, "post complete")) */ // SetDestination changes the default destination of the syslog collector func SetDestination(s string) string { server = s return server } // NewLogger will attempt an initialization of a syslog client, if successful, will return // if unsuccessful, will attempt a init of a local logger. Final is to return an error. func NewLogger(host string, app string, level logrus.Level) (*NSILogger, error) { // initialize logging // Try using syslog first lag, err := New(host, app, level, true) if err != nil { // fall back to local logging lag, err = New(host, app, level, false) if err != nil { // Some other error is keeping us from launching return nil, err } } return lag, nil } // New Initializes a new syslog client func New(host string, app string, level logrus.Level, syslogEnable bool) (*NSILogger, error) { if host == "" { return nil, errors.New("no hostname provided") } var sl syslog.Priority switch level { case logrus.ErrorLevel: sl = syslog.LOG_ERR case logrus.InfoLevel: sl = syslog.LOG_INFO case logrus.WarnLevel: sl = syslog.LOG_WARNING default: sl = syslog.LOG_DEBUG } log := logrus.New() if syslogEnable { hook, err := logrus_syslog.NewSyslogHook("udp", fmt.Sprintf("%s:514", server), sl, strings.ToUpper(app)) if err != nil { return nil, errors.New(fmt.Sprintf("Unable to connect to %v syslog server", fmt.Sprintf("%s:514", server))) } log.Hooks.Add(hook) } f := new(logrus.TextFormatter) f.DisableColors = true log.Formatter = f log.Level = level log.Out = os.Stderr n := &NSILogger{ logger: log, hostname: host, appname: app, } return n, nil } // NewSyslog initializes a new syslog client func NewSyslog(host string, app string, level logrus.Level) (*NSILogger, error) { if host == "" { host, _ = os.Hostname() } var sl syslog.Priority switch level { case logrus.ErrorLevel: sl = syslog.LOG_ERR case logrus.InfoLevel: sl = syslog.LOG_INFO case logrus.WarnLevel: sl = syslog.LOG_WARNING default: sl = syslog.LOG_DEBUG } hook, err := logrus_syslog.NewSyslogHook("udp", fmt.Sprintf("%s:514", server), sl, strings.ToUpper(app)) if err != nil { return nil, errors.New(fmt.Sprintf("Unable to connect to %v syslog server", fmt.Sprintf("%s:514", server))) } log := logrus.New() log.Hooks.Add(hook) f := new(logrus.TextFormatter) f.DisableColors = true log.Formatter = f log.WithFields(logrus.Fields{ "host": host, "application": app, }).Infof("Starting %s on %s logging to %s", app, host, server) log.Level = level log.Out = os.Stderr n := &NSILogger{ logger: log, hostname: host, appname: app, } return n, nil } func MustInitSyslog(host string, app string, level logrus.Level) *NSILogger { l, err := NewSyslog(host, app, level) if err != nil { panic(err) } return l } func (n *NSILogger) SetLevel(l logrus.Level) { n.logger.Level = l } func (n *NSILogger) Infoln(v ...interface{}) { n.logger.WithFields(logrus.Fields{"host": n.hostname, "application": n.appname}).Infoln(v) } func (n *NSILogger) Info(l Lagger) { n.logger.WithFields(l.Fields()).Infof(l.Message()) } func (n *NSILogger) Warn(l Lagger) { n.logger.WithFields(l.Fields()).Warnf(l.Message()) } func (n *NSILogger) Debug(l Lagger) { n.logger.WithFields(l.Fields()).Debugf(l.Message()) } func (n *NSILogger) Error(l Lagger) { n.logger.WithFields(l.Fields()).Errorf(l.Message()) } func (n *NSILogger) Fatal(l Lagger) { n.logger.WithFields(l.Fields()).Fatalf(l.Message()) } func (n *NSILogger) Panic(l Lagger) { n.logger.WithFields(l.Fields()).Panicf(l.Message()) } type WebLog struct { hostname string appname string user string remoteIP string path string message string uuid string } // WebLog returns an object useful for logging a request that came via http func (n NSILogger) WebLog(r *http.Request, username, message string) WebLog { ip := r.RemoteAddr if r.Header.Get("x-forwarded-for") != "" { ip = r.Header.Get("x-forwarded-for") } return WebLog{ hostname: n.hostname, appname: n.appname, user: username, uuid: nsiuuid.FromContext(r.Context()).String(), remoteIP: ip, path: r.URL.Host + r.URL.Path, message: message, } } // GinLog returns an object useful for logging a request that came via http func (n NSILogger) GinLog(c *gin.Context, message string) WebLog { return WebLog{ hostname: n.hostname, appname: n.appname, user: c.MustGet("userID").(string), uuid: c.MustGet("X-Request-Id").(uuid.UUID).String(), remoteIP: c.ClientIP(), path: c.Request.URL.Path, message: message, } } // GInfo is a helper to log info messages using the Gin logger func (n NSILogger) GInfo(c *gin.Context, message string) { n.Info(n.GinLog(c, message)) } func (w WebLog) Fields() logrus.Fields { return logrus.Fields{ "host": w.hostname, "application": w.appname, "user": w.user, "uuid": w.uuid, "remote_addr": w.remoteIP, "path": w.path, } } func (w WebLog) Message() string { return w.message } type AppMessage struct { hostname string appname string message string } // AppMessage returns an object for logging a single message along with the hostname and appname func (n NSILogger) AppMessage(message string) AppMessage { var t AppMessage t.hostname = n.hostname t.appname = n.appname t.message = message return t } func (m AppMessage) Fields() logrus.Fields { return logrus.Fields{ "host": m.hostname, "application": m.appname, } } func (m AppMessage) Message() string { return m.message } type CommandLog struct { hostname string appname string user string remoteIP string path string message string command string uuid string } // CommandLog returns an object for logging a request from a device command execution source // such as the ssh api func (n NSILogger) CommandLog(r *http.Request, username, message, command string) CommandLog { var ip string if r.Header.Get("x-forwarded-for") != "" { ip = r.Header.Get("x-forwarded-for") } else { ip = r.RemoteAddr } return CommandLog{ hostname: n.hostname, appname: n.appname, user: username, uuid: nsiuuid.FromContext(r.Context()).String(), remoteIP: ip, command: command, path: r.URL.Host + r.URL.Path, message: message, } } func (c CommandLog) Fields() logrus.Fields { return logrus.Fields{ "host": c.hostname, "application": c.appname, "user": c.user, "uuid": c.uuid, "command": c.command, "remote_addr": c.remoteIP, "path": c.path, } } func (c CommandLog) Message() string { return c.message }