github.com/rochacon/deis@v1.0.2-0.20150903015341-6839b592a1ff/logger/syslogd/syslogd.go (about)

     1  package syslogd
     2  
     3  import (
     4  	"fmt"
     5  	"io"
     6  	"log"
     7  	"os"
     8  	"path"
     9  	"regexp"
    10  
    11  	"github.com/deis/deis/logger/syslog"
    12  
    13  	"github.com/deis/deis/logger/drain"
    14  )
    15  
    16  // LogRoot is the log path to store logs.
    17  var LogRoot string
    18  
    19  type handler struct {
    20  	// To simplify implementation of our handler we embed helper
    21  	// syslog.BaseHandler struct.
    22  	*syslog.BaseHandler
    23  	drainURI string
    24  }
    25  
    26  // Simple fiter for named/bind messages which can be used with BaseHandler
    27  func filter(m syslog.SyslogMessage) bool {
    28  	return true
    29  }
    30  
    31  func newHandler() *handler {
    32  	h := handler{
    33  		BaseHandler: syslog.NewBaseHandler(5, filter, false),
    34  	}
    35  
    36  	go h.mainLoop() // BaseHandler needs some goroutine that reads from its queue
    37  	return &h
    38  }
    39  
    40  // check if a file path exists
    41  func fileExists(path string) (bool, error) {
    42  	_, err := os.Stat(path)
    43  	if err == nil {
    44  		return true, nil
    45  	}
    46  	if os.IsNotExist(err) {
    47  		return false, nil
    48  	}
    49  	return false, err
    50  }
    51  
    52  func getLogFile(message string) (io.Writer, error) {
    53  	r := regexp.MustCompile(`^.* ([-_a-z0-9]+)\[[a-z0-9-_\.]+\].*`)
    54  	match := r.FindStringSubmatch(message)
    55  	if match == nil {
    56  		return nil, fmt.Errorf("Could not find app name in message: %s", message)
    57  	}
    58  	appName := match[1]
    59  	filePath := path.Join(LogRoot, appName+".log")
    60  	// check if file exists
    61  	exists, err := fileExists(filePath)
    62  	if err != nil {
    63  		return nil, err
    64  	}
    65  	// return a new file or the existing file for appending
    66  	var file io.Writer
    67  	if exists {
    68  		file, err = os.OpenFile(filePath, os.O_RDWR|os.O_APPEND, 0644)
    69  	} else {
    70  		file, err = os.OpenFile(filePath, os.O_RDWR|os.O_CREATE, 0644)
    71  	}
    72  	return file, err
    73  }
    74  
    75  func writeToDisk(m syslog.SyslogMessage) error {
    76  	file, err := getLogFile(m.String())
    77  	if err != nil {
    78  		return err
    79  	}
    80  	bytes := []byte(m.String() + "\n")
    81  	file.Write(bytes)
    82  	return nil
    83  }
    84  
    85  // mainLoop reads from BaseHandler queue using h.Get and logs messages to stdout
    86  func (h *handler) mainLoop() {
    87  	for {
    88  		m := h.Get()
    89  		if m == nil {
    90  			break
    91  		}
    92  		if h.drainURI != "" {
    93  			drain.SendToDrain(m.String(), h.drainURI)
    94  		}
    95  		err := writeToDisk(m)
    96  		if err != nil {
    97  			log.Println(err)
    98  		}
    99  	}
   100  	h.End()
   101  }
   102  
   103  // Listen starts a new syslog server which runs until it receives a signal.
   104  func Listen(exitChan, cleanupDone chan bool, drainChan chan string, bindAddr string) {
   105  	fmt.Println("Starting syslog...")
   106  	// If LogRoot doesn't exist, create it
   107  	// equivalent to Python's `if not os.path.exists(filename)`
   108  	if _, err := os.Stat(LogRoot); os.IsNotExist(err) {
   109  		if err = os.MkdirAll(LogRoot, 0777); err != nil {
   110  			log.Fatalf("unable to create LogRoot at %s: %v", LogRoot, err)
   111  		}
   112  	}
   113  	// Create a server with one handler and run one listen goroutine
   114  	s := syslog.NewServer()
   115  	h := newHandler()
   116  	s.AddHandler(h)
   117  	s.Listen(bindAddr)
   118  	fmt.Println("Syslog server started...")
   119  	fmt.Println("deis-logger running")
   120  
   121  	// Wait for terminating signal
   122  	for {
   123  		select {
   124  		case <-exitChan:
   125  			// Shutdown the server
   126  			fmt.Println("Shutting down...")
   127  			s.Shutdown()
   128  			cleanupDone <- true
   129  		case d := <-drainChan:
   130  			h.drainURI = d
   131  		}
   132  	}
   133  }