How to write log to file

GoLogging

Go Problem Overview


I'm trying to write to a log file with Go.

I have tried several approaches, all of which have failed. This is what I have tried:

func TestLogging(t *testing.T) {
    if !FileExists("logfile") {
        CreateFile("logfile")
    }
    f, err := os.Open("logfile")
    if err != nil {
        t.Fatalf("error: %v", err)
    }

    // attempt #1
    log.SetOutput(io.MultiWriter(os.Stderr, f))
    log.Println("hello, logfile")

    // attempt #2
    log.SetOutput(io.Writer(f))
    log.Println("hello, logfile")

    // attempt #3
    log.SetOutput(f)
    log.Println("hello, logfile")
}

func FileExists(name string) bool {
    if _, err := os.Stat(name); err != nil {
       if os.IsNotExist(err) {
            return false
        }
    }
    return true
}

func CreateFile(name string) error {
    fo, err := os.Create(name)
    if err != nil {
        return err
    }
    defer func() {
        fo.Close()
    }()
    return nil
}

The log file gets created, but nothing ever gets printed or appended to it. Why?

Go Solutions


Solution 1 - Go

os.Open() must have worked differently in the past, but this works for me:

f, err := os.OpenFile("testlogfile", os.O_RDWR | os.O_CREATE | os.O_APPEND, 0666)
if err != nil {
    log.Fatalf("error opening file: %v", err)
}
defer f.Close()

log.SetOutput(f)
log.Println("This is a test log entry")

Based on the Go docs, os.Open() can't work for log.SetOutput, because it opens the file "for reading:"

> func Open > > func Open(name string) (file *File, err error) Open opens the named > file for reading. If successful, methods on the returned file can be > used for reading; the associated file descriptor has mode O_RDONLY. If > there is an error, it will be of type *PathError.

EDIT

Moved defer f.Close() to after if err != nil check

Solution 2 - Go

I prefer the simplicity and flexibility of the 12 factor app recommendation for logging. To append to a log file you can use shell redirection. The default logger in Go writes to stderr (2).

./app 2>> logfile

See also: http://12factor.net/logs

Solution 3 - Go

I usually print the logs on screen and write into a file as well. Hope this helps someone.

f, err := os.OpenFile("/tmp/orders.log", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
if err != nil {
	log.Fatalf("error opening file: %v", err)
}
defer f.Close()
wrt := io.MultiWriter(os.Stdout, f)
log.SetOutput(wrt)
log.Println(" Orders API Called")

Solution 4 - Go

This works for me

  1. created a package called logger.go

    package logger
    
    import (
      "flag"
      "os"
      "log"
      "go/build"
    )
    
    var (
      Log      *log.Logger
    )
    
    
    func init() {
        // set location of log file
        var logpath = build.Default.GOPATH + "/src/chat/logger/info.log"
    
       flag.Parse()
       var file, err1 = os.Create(logpath)
    
       if err1 != nil {
          panic(err1)
       }
          Log = log.New(file, "", log.LstdFlags|log.Lshortfile)
          Log.Println("LogFile : " + logpath)
    }
    
  2. import the package wherever you want to log e.g main.go

         package main
    
         import (
            "logger"
         )
    
         const (
            VERSION = "0.13"
          )
    
         func main() {
    
             // time to use our logger, print version, processID and number of running process
             logger.Log.Printf("Server v%s pid=%d started with processes: %d", VERSION, os.Getpid(),runtime.GOMAXPROCS(runtime.NumCPU()))
    
         }
    
    
    
     
    

Solution 5 - Go

If you run binary on linux machine you could use shell script.

overwrite into a file

./binaryapp > binaryapp.log

append into a file

./binaryapp >> binaryapp.log

overwrite stderr into a file

./binaryapp &> binaryapp.error.log

append stderr into a file

./binaryapp &>> binalyapp.error.log

it can be more dynamic using shell script file.

Solution 6 - Go

The default logger in Go writes to stderr (2). redirect to file

import ( 
    "syscall"
    "os" 
 )
func main(){
  fErr, err = os.OpenFile("Errfile", os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0600)
  syscall.Dup2(int(fErr.Fd()), 1) /* -- stdout */
  syscall.Dup2(int(fErr.Fd()), 2) /* -- stderr */

}

Solution 7 - Go

Declare up top in your global var so all your processes can access if needed.

package main

import (
	"log"
    "os"
)
var (
	outfile, _ = os.Create("path/to/my.log") // update path for your needs
	l      = log.New(outfile, "", 0)
)

func main() {
    l.Println("hello, log!!!")
}

Solution 8 - Go

Building on Allison and Deepak's answer, I started using logrus and really like it:

var log = logrus.New()

func init() {

	// log to console and file
	f, err := os.OpenFile("crawler.log", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
	if err != nil {
		log.Fatalf("error opening file: %v", err)
	}
	wrt := io.MultiWriter(os.Stdout, f)

	log.SetOutput(wrt)
}

I have a defer f.Close() in the main function

Solution 9 - Go

I'm writing logs to the files, which are generate on daily basis (per day one log file is getting generated). This approach is working fine for me :

var (
	serverLogger *log.Logger
)

func init() {
	// set location of log file
	date := time.Now().Format("2006-01-02")
	var logpath = os.Getenv(constant.XDirectoryPath) + constant.LogFilePath + date + constant.LogFileExtension
	os.MkdirAll(os.Getenv(constant.XDirectoryPath)+constant.LogFilePath, os.ModePerm)
	flag.Parse()
	var file, err1 = os.OpenFile(logpath, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)

	if err1 != nil {
		panic(err1)
	}
	mw := io.MultiWriter(os.Stdout, file)
	serverLogger = log.New(mw, constant.Empty, log.LstdFlags)
	serverLogger.Println("LogFile : " + logpath)
}

// LogServer logs to server's log file
func LogServer(logLevel enum.LogLevel, message string) {
	_, file, no, ok := runtime.Caller(1)
	logLineData := "logger_server.go"
	if ok {
		file = shortenFilePath(file)
		logLineData = fmt.Sprintf(file + constant.ColonWithSpace + strconv.Itoa(no) + constant.HyphenWithSpace)
	}
	serverLogger.Println(logLineData + logLevel.String() + constant.HyphenWithSpace + message)
}

// ShortenFilePath Shortens file path to a/b/c/d.go tp d.go
func shortenFilePath(file string) string {
	short := file
	for i := len(file) - 1; i > 0; i-- {
		if file[i] == constant.ForwardSlash {
			short = file[i+1:]
			break
		}
	}
	file = short
	return file
}

"shortenFilePath()" method used to get the name of the file from full path of file. and "LogServer()" method is used to create a formatted log statement (contains : filename, line number, log level, error statement etc...)

Solution 10 - Go

To help others, I create a basic log function to handle the logging in both cases, if you want the output to stdout, then turn debug on, its straight forward to do a switch flag so you can choose your output.

func myLog(msg ...interface{}) {
	defer func() { r := recover(); if r != nil { fmt.Print("Error detected logging:", r) } }()
	if conf.DEBUG {
		fmt.Println(msg)
	} else {
		logfile, err := os.OpenFile(conf.LOGDIR+"/"+conf.AppName+".log", os.O_RDWR | os.O_CREATE | os.O_APPEND,0666)
		if !checkErr(err) {
			log.SetOutput(logfile)
			log.Println(msg)
		}
		defer logfile.Close()
	}
}



Solution 11 - Go

maybe this will help you (if the log file exists use it, if it does not exist create it):

package main

import (
    "flag"
    "log"
    "os"
)
//Se declara la variable Log. Esta será usada para registrar los eventos.
var (
    Log *log.Logger = Loggerx()
)

func Loggerx() *log.Logger {
    LOG_FILE_LOCATION := os.Getenv("LOG_FILE_LOCATION")
        //En el caso que la variable de entorno exista, el sistema usa la configuración del docker.
    if LOG_FILE_LOCATION == "" {
        LOG_FILE_LOCATION = "../logs/" + APP_NAME + ".log"
    } else {
        LOG_FILE_LOCATION = LOG_FILE_LOCATION + APP_NAME + ".log"
    }
    flag.Parse()
        //Si el archivo existe se rehusa, es decir, no elimina el archivo log y crea uno nuevo.
    if _, err := os.Stat(LOG_FILE_LOCATION); os.IsNotExist(err) {
        file, err1 := os.Create(LOG_FILE_LOCATION)
        if err1 != nil {
            panic(err1)
        }
                //si no existe,se crea uno nuevo.
        return log.New(file, "", log.Ldate|log.Ltime|log.Lshortfile)
    } else {
                //si existe se rehusa.
        file, err := os.OpenFile(LOG_FILE_LOCATION, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0666)
        if err != nil {
            panic(err)
        }
        return log.New(file, "", log.Ldate|log.Ltime|log.Lshortfile)
    }
}

For more detail: https://su9.co/9BAE74B

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionAllison AView Question on Stackoverflow
Solution 1 - GoAllison AView Answer on Stackoverflow
Solution 2 - GoPhilip NelsonView Answer on Stackoverflow
Solution 3 - GodeepakssnView Answer on Stackoverflow
Solution 4 - Gophilip mudenyoView Answer on Stackoverflow
Solution 5 - GoAdzimzfView Answer on Stackoverflow
Solution 6 - GosergeyView Answer on Stackoverflow
Solution 7 - GoopenwonkView Answer on Stackoverflow
Solution 8 - GoPeggyScottView Answer on Stackoverflow
Solution 9 - GoHardik BohraView Answer on Stackoverflow
Solution 10 - GoCyberienceView Answer on Stackoverflow
Solution 11 - GoJose G. MejíaView Answer on Stackoverflow