// Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. // isulad-tools is licensed under the Mulan PSL v1. // You can use this software according to the terms and conditions of the Mulan PSL v1. // You may obtain a copy of Mulan PSL v1 at: // http://license.coscl.org.cn/MulanPSL // THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR // PURPOSE. // See the Mulan PSL v1 for more details. // Description: common utils // Author: zhangwei // Create: 2018-01-18 package utils import ( "crypto/rand" "encoding/hex" "fmt" "io" "log" "log/syslog" "os" "path/filepath" "strings" "syscall" "time" libctr_utils "github.com/opencontainers/runc/libcontainer/utils" "github.com/sirupsen/logrus" ) // SyslogHook to send logs via syslog. type syslogHook struct { logger *log.Logger } // Creates a hook to be added to an instance of logger. This is called with // `hook, err := newSyslogHook("default", "udp", "localhost:514", syslog.LOG_DEBUG, "")` // `if err == nil { log.Hooks.Add(hook) }` func newSyslogHook(network, raddr string, priority syslog.Priority, tag string) (*syslogHook, error) { var logger *log.Logger var err error if network == "default" { logger, err = syslog.NewLogger(priority, log.Lshortfile) if err != nil { return nil, err } logger.SetPrefix(tag) } else { w, err := syslog.Dial(network, raddr, priority, "") if err != nil { return nil, err } logger = log.New(w, tag, log.Lshortfile) } return &syslogHook{logger}, err } func (hook *syslogHook) Fire(entry *logrus.Entry) error { line, err := entry.String() if err != nil { fmt.Fprintf(os.Stderr, "Unable to read entry, %v", err) return err } if err := hook.logger.Output(8, line); err != nil { logrus.Errorf("hook.logger.Output err: %s", err) } return nil } func (hook *syslogHook) Levels() []logrus.Level { return logrus.AllLevels } const ( syslogUDPPrefix = "udp://" syslogTCPPrefix = "tcp://" syslogUnixSock = "unix://" syslogDefaultUDPService = "localhost:541" syslogDefaultTCPService = "localhost:541" ) // SyslogService is a structure which records the syslog service type and serivce address. type SyslogService struct { Type string Addr string } // ParseSyslogService parses syslog service from input string func ParseSyslogService(service string) (*SyslogService, error) { var serviceType, serviceAddr string if service == "" { serviceType = "default" serviceAddr = "" } else if strings.HasPrefix(service, syslogUDPPrefix) { serviceType = "udp" serviceAddr := service[len(syslogUDPPrefix):] if serviceAddr == "" { serviceAddr = syslogDefaultUDPService } } else if strings.HasPrefix(service, syslogTCPPrefix) { serviceType = "tcp" serviceAddr = service[len(syslogTCPPrefix):] if serviceAddr == "" { serviceAddr = syslogDefaultTCPService } } else if strings.HasPrefix(service, syslogUnixSock) { // syslog package will use empty string as network, // and syslog will lookup the unix socket on host, we do not care. serviceType = "" serviceAddr = service[len(syslogUnixSock):] } else { return nil, fmt.Errorf("Unspported syslog network: %s", service) } serv := &SyslogService{ Type: serviceType, Addr: serviceAddr, } return serv, nil } // HookSyslog will hook syslog service to logrus // syslog supports 4 kinds of service: // 1. default socket: "" // 2. unix socket: "unix:///dev/log" // 3. udp port: "udp://localhost:541" // 4. tcp port: "tcp://localhost:541" // by default, if we output to local syslog, use default will be fine. // syslog Tag: // syslog will use tag to separate the output stream. func HookSyslog(service, tag string) error { serv, err := ParseSyslogService(service) if err != nil { return err } hook, err := newSyslogHook(serv.Type, serv.Addr, syslog.LOG_INFO|syslog.LOG_USER, tag) if err != nil { return fmt.Errorf("Unable to connect to syslog daemon") } logrus.AddHook(hook) return nil } // NewPipe creates a pair of new socket pipe. func NewPipe() (parent, child *os.File, err error) { fds, err := syscall.Socketpair(syscall.AF_LOCAL, syscall.SOCK_STREAM|syscall.SOCK_CLOEXEC, 0) if err != nil { return nil, nil, err } return os.NewFile(uintptr(fds[1]), "parent"), os.NewFile(uintptr(fds[0]), "child"), nil } // WriteJSON write json data to io stream func WriteJSON(w io.Writer, v interface{}) error { return libctr_utils.WriteJSON(w, v) } // RandomID returns a 8-bit ramdon string which read from rand.Reader first, // and if failed, will use time stamp as random id func RandomID() string { id := make([]byte, 32) if _, err := io.ReadFull(rand.Reader, id); err != nil { cur := time.Now() return fmt.Sprint(cur.UnixNano()) } return hex.EncodeToString(id)[:8] } // RandomFile will find a non-existing file in given folder. func RandomFile(folder string) string { path := "" for { id := RandomID() path = filepath.Join(folder, id) if _, err := os.Stat(path); os.IsNotExist(err) { break } } return path }