package logutil import ( "bytes" "fmt" "io" "mime/multipart" "net/http" "os" "path/filepath" "go.uber.org/zap" "go.uber.org/zap/zapcore" ) 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 } zapCfg := zap.NewDevelopmentConfig() zapCfg.Level.SetLevel(zapLevel) 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) 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) // Set up file logging with overwrite mode logFile, err := os.OpenFile(logPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644) if err != nil { panic("cannot open log file: " + err.Error()) } fmt.Println("Logging to", logPath) core := zapcore.NewTee( zapcore.NewCore(consoleEncoder, zapcore.AddSync(os.Stdout), zapCfg.Level), zapcore.NewCore(fileEncoder, zapcore.AddSync(logFile), zapCfg.Level), ) logger := zap.New(core, 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("POST", "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 }