2016-01-13 14 views
11

Abbiamo un'applicazione golan di grandi dimensioni che utilizza il registratore (in realtà, un registratore personalizzato) per scrivere l'output in un file di registro che viene periodicamente ruotato.Cattura del panico() in golang

Tuttavia, quando un'applicazione si arresta in modo anomalo o panico(), tali messaggi vengono visualizzati come errore standard.

C'è un modo per ignorare la funzionalità di panico per utilizzare il nostro registratore?

+0

Penso che 'recover' sia meglio di reindirizzare l'output. 'debug.PrintStack' può stampare i log di cui hai bisogno. Alcuni altri registri, ad esempio il messaggio GC, possono ancora rimanere nello stderr. – Bryce

risposta

9

Per quanto ne so, non è possibile reindirizzare l'uscita dal panico dall'errore standard o dal proprio registratore. La cosa migliore che puoi fare è redirigere l'errore standard in un file che puoi eseguire esternamente o all'interno del tuo programma.

Per il mio programma rclone ho reindirizzato l'errore standard per acquisire tutto su un file su un'opzione che sfortunatamente non è particolarmente facile da fare in modo multipiattaforma. Ecco come ho fatto (vedi il redirect * .Vai file)

per Linux/Unix

// Log the panic under unix to the log file 

//+build unix 

package main 

import (
    "log" 
    "os" 
    "syscall" 
) 

// redirectStderr to the file passed in 
func redirectStderr(f *os.File) { 
    err := syscall.Dup2(int(f.Fd()), int(os.Stderr.Fd())) 
    if err != nil { 
     log.Fatalf("Failed to redirect stderr to file: %v", err) 
    } 
} 

e per le finestre

// Log the panic under windows to the log file 
// 
// Code from minix, via 
// 
// http://play.golang.org/p/kLtct7lSUg 

//+build windows 

package main 

import (
    "log" 
    "os" 
    "syscall" 
) 

var (
    kernel32   = syscall.MustLoadDLL("kernel32.dll") 
    procSetStdHandle = kernel32.MustFindProc("SetStdHandle") 
) 

func setStdHandle(stdhandle int32, handle syscall.Handle) error { 
    r0, _, e1 := syscall.Syscall(procSetStdHandle.Addr(), 2, uintptr(stdhandle), uintptr(handle), 0) 
    if r0 == 0 { 
     if e1 != 0 { 
      return error(e1) 
     } 
     return syscall.EINVAL 
    } 
    return nil 
} 

// redirectStderr to the file passed in 
func redirectStderr(f *os.File) { 
    err := setStdHandle(syscall.STD_ERROR_HANDLE, syscall.Handle(f.Fd())) 
    if err != nil { 
     log.Fatalf("Failed to redirect stderr to file: %v", err) 
    } 
    // SetStdHandle does not affect prior references to stderr 
    os.Stderr = f 
} 
+0

Questo ha funzionato per me - il panico che va al mio fd invece di stderr ha risolto il mio problema. –

+0

Ho provato questo e il lato unix ha funzionato per me, ma il lato Windows non ha ... qualche intuizione? - https://play.golang.org/p/jQaN1bNypx –

+0

Ho fatto altri esperimenti e sulla finestra devi anche fare 'os.Stderr = f' dato che' SetStdHandle' non influenza il riferimento precedente a stderr. Su Unix non è necessario poiché il 'Dup2' influisce sul riferimento precedente a stderr. https://play.golang.org/p/mXjw6lOrG9 –

5

È possibile utilizzare recover() per recuperare il panico dalla stessa goroutine. Quando si chiama recover() in un metodo posticipato (si ricorda che i metodi differiti saranno ancora chiamati, anche quando panic() ing), restituirà ciò che è stato passato nell'ultima chiamata panic() come argomento (o nil quando il programma non è in panico).

defer func() { 
    if x := recover(); x != nil { 
     // recovering from a panic; x contains whatever was passed to panic() 
     log.Printf("run time panic: %v", x) 

     // if you just want to log the panic, panic again 
     panic(x) 
    } 
}() 

panic("foo"); 

Nota tuttavia, che non si può recuperare da panico che sono stati attivati ​​in un goroutine diversa (grazie a JimB per il suggerimento). Non è possibile utilizzare un singolo recover() per ripristinare il panico da qualsiasi goroutine.

+3

Vorrei notare che puoi recuperare solo dalla tua goroutine. Penso che l'OP stia cercando un "recupero" globale, che non puoi fare. – JimB

+0

Vero, davvero. Lo modificherò, grazie per il suggerimento. – helmbert

+0

Beh, non ho bisogno di fermare il panico, voglio solo che il backtrace compaia nel mio log, non su stderr. Suppongo che potrei sostituire os.Stderr con il mio logger? No: appena controllato, anche se os.Stderr è il mio gestore del file di log, il panico continua a passare allo stderr. –

0

espansione @ risposta di nick-craig-legno: Se sei su Linux puoi generare un'istanza logger (1) e reindirizzare lo stderr su di esso. In questo modo si ottiene il backtrace completo in syslog. Questo è ciò che gocryptfs fa:

// redirectStdFds redirects stderr and stdout to syslog; stdin to /dev/null 
func redirectStdFds() { 
    // stderr and stdout 
    pr, pw, err := os.Pipe() 
    if err != nil { 
     tlog.Warn.Printf("redirectStdFds: could not create pipe: %v\n", err) 
     return 
    } 
    tag := fmt.Sprintf("gocryptfs-%d-logger", os.Getpid()) 
    cmd := exec.Command("logger", "-t", tag) 
    cmd.Stdout = os.Stdout 
    cmd.Stderr = os.Stderr 
    cmd.Stdin = pr 
    err = cmd.Start() 
    if err != nil { 
     tlog.Warn.Printf("redirectStdFds: could not start logger: %v\n", err) 
    } 
    pr.Close() 
    err = syscall.Dup2(int(pw.Fd()), 1) 
    if err != nil { 
     tlog.Warn.Printf("redirectStdFds: stdout dup error: %v\n", err) 
    } 
    syscall.Dup2(int(pw.Fd()), 2) 
    if err != nil { 
     tlog.Warn.Printf("redirectStdFds: stderr dup error: %v\n", err) 
    } 
    pw.Close() 

    // stdin 
    nullFd, err := os.Open("/dev/null") 
    if err != nil { 
     tlog.Warn.Printf("redirectStdFds: could not open /dev/null: %v\n", err) 
     return 
    } 
    err = syscall.Dup2(int(nullFd.Fd()), 0) 
    if err != nil { 
     tlog.Warn.Printf("redirectStdFds: stdin dup error: %v\n", err) 
    } 
    nullFd.Close() 
}