web-dev-qa-db-fra.com

Capturer la panique () en golang

Nous avons une application golang de grande taille qui utilise le consignateur (en fait, un consignateur personnalisé) pour écrire la sortie dans un fichier journal soumis à une rotation périodique.

Cependant, lorsqu'une application se bloque ou que panique () survient, ces messages passent en erreur standard.

Existe-t-il un moyen de remplacer la fonctionnalité de panique pour utiliser notre enregistreur?

14
David Frascone

Autant que je sache, vous ne pouvez pas rediriger la sortie de la panique vers une erreur standard ou vers votre enregistreur. La meilleure chose à faire est de rediriger l'erreur standard vers un fichier que vous pouvez créer en externe ou à l'intérieur de votre programme.

Pour mon rclone programme, j'ai redirigé l'erreur standard afin de tout capturer dans un fichier avec une option qui, malheureusement, n'est pas particulièrement facile à faire de manière multiplateforme. Voici comment je l'ai fait (voir les fichiers de redirection * .go)

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

et pour les fenêtres

// 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
}
11
Nick Craig-Wood

Vous pouvez utiliser recover() pour récupérer les paniques du même goroutine. Lorsque vous appelez recover() dans une méthode différée (rappelez-vous que les méthodes différées seront toujours appelées, même lorsque panic()ing), il renverra tout ce qui a été passé dans le dernier appel panic() en argument (ou nil lorsque le programme ne panique pas).

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

Notez cependant , que vous ne pouvez pas récupérer des paniques déclenchés dans un autre goroutine (merci à JimB pour le conseil). Utiliser un seul recover() pour sortir de la panique n’est pas possible.

7
helmbert

Développer la réponse de @ nick-craig-wood: Si vous êtes sous Linux, vous pouvez créer une instance logger (1) et y rediriger stderr. De cette façon, vous obtenez la trace complète dans syslog. C'est ce que gocryptfs fait:

// 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()
}
0
Jakob