// Copyright 2015 Light Code Labs, LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package httpserver import ( "bytes" "io" "log" "net" "os" "strings" "sync" "github.com/caddyserver/caddy" gsyslog "github.com/hashicorp/go-syslog" ) var remoteSyslogPrefixes = map[string]string{ "syslog+tcp://": "tcp", "syslog+udp://": "udp", "syslog://": "udp", } // Logger is shared between errors and log plugins and supports both logging to // a file (with an optional file roller), local and remote syslog servers. type Logger struct { Output string *log.Logger Roller *LogRoller writer io.Writer fileMu *sync.RWMutex V4ipMask net.IPMask V6ipMask net.IPMask IPMaskExists bool Exceptions []string } // NewTestLogger creates logger suitable for testing purposes func NewTestLogger(buffer *bytes.Buffer) *Logger { return &Logger{ Logger: log.New(buffer, "", 0), fileMu: new(sync.RWMutex), } } // Println wraps underlying logger with mutex func (l Logger) Println(args ...interface{}) { l.fileMu.RLock() l.Logger.Println(args...) l.fileMu.RUnlock() } // Printf wraps underlying logger with mutex func (l Logger) Printf(format string, args ...interface{}) { l.fileMu.RLock() l.Logger.Printf(format, args...) l.fileMu.RUnlock() } func (l Logger) MaskIP(ip string) string { var reqIP net.IP // If unable to parse, simply return IP as provided. reqIP = net.ParseIP(ip) if reqIP == nil { return ip } if reqIP.To4() != nil { return reqIP.Mask(l.V4ipMask).String() } return reqIP.Mask(l.V6ipMask).String() } // ShouldLog returns true if the path is not exempted from // being logged (i.e. it is not found in l.Exceptions). func (l Logger) ShouldLog(path string) bool { for _, exc := range l.Exceptions { if Path(path).Matches(exc) { return false } } return true } // Attach binds logger Start and Close functions to // controller's OnStartup and OnShutdown hooks. func (l *Logger) Attach(controller *caddy.Controller) { if controller != nil { // Opens file or connect to local/remote syslog controller.OnStartup(l.Start) // Closes file or disconnects from local/remote syslog controller.OnShutdown(l.Close) } } type syslogAddress struct { network string address string } func parseSyslogAddress(location string) *syslogAddress { for prefix, network := range remoteSyslogPrefixes { if strings.HasPrefix(location, prefix) { return &syslogAddress{ network: network, address: strings.TrimPrefix(location, prefix), } } } return nil } // Start initializes logger opening files or local/remote syslog connections func (l *Logger) Start() error { // initialize mutex on start l.fileMu = new(sync.RWMutex) var err error selectwriter: switch l.Output { case "", "stderr": l.writer = os.Stderr case "stdout": l.writer = os.Stdout case "syslog": l.writer, err = gsyslog.NewLogger(gsyslog.LOG_ERR, "LOCAL0", "caddy") if err != nil { return err } default: if address := parseSyslogAddress(l.Output); address != nil { l.writer, err = gsyslog.DialLogger(address.network, address.address, gsyslog.LOG_ERR, "LOCAL0", "caddy") if err != nil { return err } break selectwriter } var file *os.File file, err = os.OpenFile(l.Output, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644) if err != nil { return err } if l.Roller != nil && !l.Roller.Disabled { file.Close() l.Roller.Filename = l.Output l.writer = l.Roller.GetLogWriter() } else { l.writer = file } } l.Logger = log.New(l.writer, "", log.Flags()) return nil } // Close closes open log files or connections to syslog. func (l *Logger) Close() error { // don't close stdout or stderr if l.writer == os.Stdout || l.writer == os.Stderr { return nil } // Will close local/remote syslog connections too :) if closer, ok := l.writer.(io.WriteCloser); ok { l.fileMu.Lock() err := closer.Close() l.fileMu.Unlock() return err } return nil }