Cómo escribir un registro en un archivo

108

Estoy intentando escribir en un archivo de registro con Go.

He intentado varios enfoques, todos los cuales han fallado. Esto es lo que he probado:

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
}

Se crea el archivo de registro, pero nunca se imprime ni se le agrega nada. ¿Por qué?

Allison A
fuente
2
Si implementa su programa en Linux, puede simplemente escribir su registro en la salida estándar y luego canalizar la salida a un archivo como: ./program 2> & 1 | tee logs.txt . Debe haber alguna otra forma en otro sistema.
nvcnvn

Respuestas:

165

os.Open() debe haber funcionado de manera diferente en el pasado, pero esto funciona para mí:

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")

Según los documentos de Go, os.Open()no funciona log.SetOutputporque abre el archivo "para leer":

func Open

func Open(name string) (file *File, err error) Openabre el archivo nombrado para su lectura. Si tiene éxito, los métodos del archivo devuelto se pueden usar para leer; el descriptor de archivo asociado tiene modo O_RDONLY. Si hay un error, será de tipo *PathError.

EDITAR

Movido defer f.Close()a después del if err != nilcheque

Allison A
fuente
9
¡No posponga Cerrar antes de verificar err por nulo!
Volker
No es una actividad realmente dañina cerrar en todos los casos iirc. Sin embargo, eso no es cierto para todos los tipos.
Dustin
2
@Dustin fpodría ser nil, lo que resultaría en pánico. Por lo tanto, erres aconsejable verificar antes de aplazar la llamada.
Nemo
@AllisonA se preocupa por explicar por qué Openno funciona log.SetOutput?
nemo
1
Los permisos más seguros son 0644 o incluso 0664 para permitir la lectura / escritura de usuarios, la lectura / escritura de usuarios y grupos, y en ambos casos no permitir que todos escriban.
Jonathan
39

Prefiero la simplicidad y flexibilidad de la recomendación de la aplicación de 12 factores para el registro. Para agregar a un archivo de registro, puede usar la redirección de shell. El registrador predeterminado en Go escribe en stderr (2).

./app 2>> logfile

Véase también: http://12factor.net/logs

Philip Nelson
fuente
no será una buena práctica cuando desee demonizar cosas, especialmente con start-tsop-daemon
Shrey
3
@Shrey Systemd podría encargarse fácilmente del registro, así como de las funciones de inicio y parada.
WarGasm
A pesar de que sea una buena práctica o no, este es el tipo de registro que he estado buscando en Golang. ¡Gracias por compartir esto!
adicto
¿Hay algo similar debajo de las ventanas?
surfmuggle
Era como si $ cd /etc/systemd/system $ sudo vi app.service ExecStart=/bin/bash -c 'sudo go run main.go >> /home/ubuntu/go/src/html_menu_1/logfile' yo no trabajaraUbuntu 18.04.3
Ryosuke Hujisawa
20

Normalmente imprimo los registros en pantalla y también escribo en un archivo. Espero que esto ayude a alguien.

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")
deepakssn
fuente
7

Esto funciona para mi

  1. creó un paquete llamado 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)
    }
    1. importe el paquete donde desee iniciar sesión, por ejemplo, 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()))
      
      }
philip mudenyo
fuente
6

Si ejecuta binario en una máquina Linux, puede usar un script de shell.

sobrescribir en un archivo

./binaryapp > binaryapp.log

agregar en un archivo

./binaryapp >> binaryapp.log

sobrescribir stderr en un archivo

./binaryapp &> binaryapp.error.log

agregar stderr a un archivo

./binaryapp &>> binalyapp.error.log

puede ser más dinámico usando un archivo de script de shell.

Adzimzf
fuente
Es bueno saberlo, ¿cómo anulamos stderr para iniciar sesión?
imposible
5

El registrador predeterminado en Go escribe en stderr (2). redirigir al archivo

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 */

}
Sergey
fuente
5

Declare lo más alto en su global varpara que todos sus procesos puedan acceder si es necesario.

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!!!")
}
openwonk
fuente
Hola @CostaHuang, deja un comentario detallado. Gracias
openwonk
@CostaHuang, acabo de ejecutar mi fragmento de código y funciona.
openwonk
Hola @openwonk, he probado de nuevo y no funcionó en mi computadora. Mi versión es go version go1.10.2 windows/amd64, ¿cuál es la tuya?
Costa Huang
@CostaHuang, acabo de ejecutar un ejemplo con la misma configuración que tú. El ejemplo asume que ya tiene configurada una estructura de carpetas. Hay formas fáciles de verificar esto, sin embargo, mi objetivo con el ejemplo es mostrar lo relativamente simple que es escribir en un archivo de registro. Cambie su código a outfile, _ = os.Create("my.log")y funcionará como se esperaba.
openwonk
Tu código funciona. Estaba usando outfile, _ = os.Create("./path/to/my.log"). De alguna manera tenía la expectativa de que el código crearía las path/tocarpetas y el my.logarchivo, pero aparentemente no funcionó. Le sugiero que modifique su respuesta para que sea outfile, _ = os.Create("./my.log"). De esa manera, sabemos claramente que está creando un registro en la carpeta actual.
Costa Huang
5

Sobre la base de la respuesta de Allison y Deepak, comencé a usar logrus y realmente me gustó:

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)
}

Tengo un aplazamiento f.Close () en la función principal

PeggyScott
fuente
0

Estoy escribiendo registros en los archivos, que se generan a diario (por día se genera un archivo de registro). Este enfoque funciona bien para mí:

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
}

Método "shortenFilePath ()" utilizado para obtener el nombre del archivo de la ruta completa del archivo. y el método "LogServer ()" se utiliza para crear una declaración de registro formateada (contiene: nombre de archivo, número de línea, nivel de registro, declaración de error, etc.)

Hardik Bohra
fuente
0

Para ayudar a otros, creo una función de registro básica para manejar el registro en ambos casos, si desea que la salida sea estándar, luego active la depuración, es sencillo hacer un indicador de cambio para que pueda elegir su salida.

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()
    }
}



Cyberience
fuente
0

tal vez esto le ayude (si el archivo de registro existe, úselo, si no existe, créelo):

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)
    }
}

Para más detalles: https://su9.co/9BAE74B

José G. Mejía
fuente