18F/e-QIP-prototype

View on GitHub
api/log/log.go

Summary

Maintainability
A
0 mins
Test Coverage
package log

import (
    "log/syslog"
    "net/url"
    "os"
    "path"

    "github.com/18F/e-QIP-prototype/api"
    "github.com/rifflock/lfshook"
    "github.com/sirupsen/logrus"
    logrus_syslog "github.com/sirupsen/logrus/hooks/syslog"
)

// Service implementation of logging using `logrus`.
type Service struct {
    Log    *logrus.Logger
    fields api.LogFields
}

// AddField will add a field using the name and value to the logger.
func (service *Service) AddField(name string, value interface{}) {
    if _, ok := service.fields[name]; !ok {
        service.fields = service.mergeFields(api.LogFields{name: value})
    }
}

// Print outputs a message to the log.
func (service *Service) Print(message string, fields api.LogFields) {
    service.Log.WithFields(logrus.Fields(service.mergeFields(fields))).Print(message)
}

// PrintError outputs a message and error to the log.
func (service *Service) PrintError(message string, err error, fields api.LogFields) {
    service.Log.WithFields(logrus.Fields(service.mergeFields(fields))).WithError(err).Print(message)
}

// Debug outputs a debug message to the log.
func (service *Service) Debug(message string, fields api.LogFields) {
    service.Log.WithFields(logrus.Fields(service.mergeFields(fields))).Debug(message)
}

// DebugError outputs a debug message and error to the log.
func (service *Service) DebugError(message string, err error, fields api.LogFields) {
    service.Log.WithFields(logrus.Fields(service.mergeFields(fields))).WithError(err).Debug(message)
}

// Warn outputs a warning message to the log.
func (service *Service) Warn(message string, fields api.LogFields) {
    service.Log.WithFields(logrus.Fields(service.mergeFields(fields))).Warn(message)
}

// WarnError outputs a warning message and error to the log.
func (service *Service) WarnError(message string, err error, fields api.LogFields) {
    service.Log.WithFields(logrus.Fields(service.mergeFields(fields))).WithError(err).Warn(message)
}

// Info outputs a informative message to the log.
func (service *Service) Info(message string, fields api.LogFields) {
    service.Log.WithFields(logrus.Fields(service.mergeFields(fields))).Info(message)
}

// InfoError outputs a informative message and error to the log.
func (service *Service) InfoError(message string, err error, fields api.LogFields) {
    service.Log.WithFields(logrus.Fields(service.mergeFields(fields))).WithError(err).Info(message)
}

// Fatal outputs a fatal message to the log.
func (service *Service) Fatal(message string, fields api.LogFields) {
    service.Log.WithFields(logrus.Fields(service.mergeFields(fields))).Fatal(message)
}

// FatalError outputs a fatal message and error to the log.
func (service *Service) FatalError(message string, err error, fields api.LogFields) {
    service.Log.WithFields(logrus.Fields(service.mergeFields(fields))).WithError(err).Fatal(message)
}

// Panic outputs a panic message to the log.
func (service *Service) Panic(message string, fields api.LogFields) {
    service.Log.WithFields(logrus.Fields(service.mergeFields(fields))).Panic(message)
}

// PanicError outputs a panic message and error to the log.
func (service *Service) PanicError(message string, err error, fields api.LogFields) {
    service.Log.WithFields(logrus.Fields(service.mergeFields(fields))).WithError(err).Panic(message)
}

// NewLogger creates a custome logger with environment hooks.
func NewLogger() *logrus.Logger {
    log := logrus.New()

    // Set log level
    level, err := logrus.ParseLevel(os.Getenv(api.LogLevel))
    if err == nil {
        log.SetLevel(level)
    }

    // Apply environment specific hooks
    hookLocalFile(log)
    hookLocalDirectory(log)
    hookSyslog(log)

    // Return the logger with dynamic hooks
    return log
}

func (service *Service) mergeFields(fields api.LogFields) api.LogFields {
    merged := api.LogFields{}
    for k, v := range service.fields {
        if _, ok := merged[k]; !ok {
            merged[k] = v
        }
    }
    for k, v := range fields {
        if _, ok := merged[k]; !ok {
            merged[k] = v
        }
    }
    return merged
}

// Support for logging to an append only log file.
func hookLocalFile(log *logrus.Logger) {
    logFile := os.Getenv(api.LogFile)
    if logFile == "" {
        return
    }

    hook := lfshook.NewHook(lfshook.PathMap{
        logrus.InfoLevel:  logFile,
        logrus.WarnLevel:  logFile,
        logrus.ErrorLevel: logFile,
    }, nil)
    log.AddHook(hook)
}

// Support for logging to multiple files within a given directory.
func hookLocalDirectory(log *logrus.Logger) {
    logDir := os.Getenv(api.LogDirectory)
    if logDir == "" {
        return
    }

    hook := lfshook.NewHook(lfshook.PathMap{
        logrus.InfoLevel:  path.Join(logDir, "info.log"),
        logrus.WarnLevel:  path.Join(logDir, "warning.log"),
        logrus.ErrorLevel: path.Join(logDir, "error.log"),
    }, nil)
    log.AddHook(hook)
}

// Support for logging to a syslog server.
func hookSyslog(log *logrus.Logger) {
    connectionString := os.Getenv(api.LogSyslog)
    if connectionString == "" {
        return
    }

    protocol := ""
    host := ""
    uri, err := url.Parse(connectionString)
    if err == nil {
        protocol = uri.Scheme
        host = uri.Host
    }

    connectionCert := os.Getenv(api.LogSyslogCert)
    if connectionCert == "" {
        hook, err := logrus_syslog.NewSyslogHook(protocol, host, syslog.LOG_INFO, "")
        if err == nil {
            log.AddHook(hook)
        }
    } else {
        // Currently this is not implemented due to a dependency conflict. The Go standard
        // library implementation of syslog does not implement TLS.
        //
        // To use the folling we require these libraries to be imported:
        //
        //   syslog "github.com/RackSec/srslog"
        //   logrus_syslog "github.com/shinji62/logrus-syslog-ng"
        //
        // hook, err := logrus_syslog.NewSyslogHookTLS(host, syslog.LOG_INFO, "", connectionCert)
        // if err == nil {
        //     log.AddHook(hook)
        // }
    }

    if err != nil {
        log.WithError(err).Error("Unable to connect to syslog daemon")
    }
}