package logutil import ( "bytes" "fmt" "io" "mime/multipart" "net/http" "os" "path/filepath" "go.uber.org/zap" "go.uber.org/zap/zapcore" "gopkg.in/natefinch/lumberjack.v2" ) type Logger struct { *zap.SugaredLogger logPath string } func NewLogger(logPath string) *Logger { envLogLevel := os.Getenv("LOG_LEVEL") var zapLevel zapcore.Level switch envLogLevel { case "DEBUG": zapLevel = zapcore.DebugLevel case "INFO": zapLevel = zapcore.InfoLevel case "WARN": zapLevel = zapcore.WarnLevel case "ERROR": zapLevel = zapcore.ErrorLevel case "FATAL": zapLevel = zapcore.FatalLevel default: zapLevel = zapcore.DebugLevel } consoleCfg := zapcore.EncoderConfig{ TimeKey: "time", LevelKey: "level", NameKey: "logger", MessageKey: "msg", CallerKey: "", StacktraceKey: "", LineEnding: zapcore.DefaultLineEnding, EncodeLevel: zapcore.CapitalColorLevelEncoder, EncodeTime: zapcore.ISO8601TimeEncoder, EncodeDuration: zapcore.StringDurationEncoder, } consoleEncoder := zapcore.NewConsoleEncoder(consoleCfg) consoleEncoderCore := zapcore.NewCore(consoleEncoder, zapcore.AddSync(os.Stdout), zapLevel) // Set up file logging with overwrite mode lj := lumberjack.Logger{ Filename: logPath, MaxSize: 10, // megabytes MaxBackups: 20, } lj.Rotate() logFile := zapcore.AddSync(&lj) fmt.Println("Logging to", logPath) fileCfg := zapcore.EncoderConfig{ TimeKey: "time", LevelKey: "level", NameKey: "logger", MessageKey: "msg", CallerKey: "", StacktraceKey: "", LineEnding: zapcore.DefaultLineEnding, EncodeLevel: zapcore.CapitalLevelEncoder, EncodeTime: zapcore.ISO8601TimeEncoder, EncodeDuration: zapcore.StringDurationEncoder, } fileEncoder := zapcore.NewConsoleEncoder(fileCfg) fileEncoderCore := zapcore.NewCore(fileEncoder, zapcore.AddSync(logFile), zapLevel) cores := zapcore.NewTee(consoleEncoderCore, fileEncoderCore) logger := zap.New(cores, zap.AddCaller(), zap.Development()) defer logger.Sync() // flushes buffer, if any sugar := logger.Sugar() zLogger := &Logger{ SugaredLogger: sugar, logPath: logPath, } return zLogger } func (l *Logger) Printf(format string, v ...interface{}) { l.SugaredLogger.Infof(format, v...) } func (l *Logger) Named(name string) *Logger { return &Logger{ SugaredLogger: l.SugaredLogger.Named(name), logPath: l.logPath, } } // GetLogsFromFile opens the file at l.logPath, reads its contents, and returns them as a string. func (l *Logger) GetLogsFromFile() (string, error) { file, err := os.Open(l.logPath) if err != nil { return "", err } defer file.Close() data, err := io.ReadAll(file) if err != nil { return "", err } return string(data), nil } // UploadLogFile opens the file at l.logPath, uploads it to 0x0.st, and returns the URL of the uploaded file. func (l *Logger) UploadLogFile() (string, error) { file, err := os.Open(l.logPath) if err != nil { return "", err } defer file.Close() // Prepare a form that you will submit to that URL. var b bytes.Buffer w := multipart.NewWriter(&b) fw, err := w.CreateFormFile("file", filepath.Base(l.logPath)) if err != nil { return "", err } // Copy the file into the form file if _, err = io.Copy(fw, file); err != nil { return "", err } // Close the multipart writer to set the terminating boundary if err = w.Close(); err != nil { return "", err } // Create a request and add the proper headers. req, err := http.NewRequest(http.MethodPost, "https://0x0.st/", &b) if err != nil { return "", err } req.Header.Set("Content-Type", w.FormDataContentType()) // Send the request client := &http.Client{} resp, err := client.Do(req) if err != nil { return "", err } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return "", fmt.Errorf("failed to upload log file, server responded with status code: %d", resp.StatusCode) } // Read the response body responseBody, err := io.ReadAll(resp.Body) if err != nil { return "", err } return string(responseBody), nil }