I'm trying to write to a log file with Golang.
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?
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 modeO_RDONLY
. If there is an error, it will be of type*PathError
.
EDIT
Moved defer f.Close()
to after if err != nil
check
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
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 */
}
This works for me
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)
}
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()))
}
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!!!")
}
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")
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.
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...)
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