Browse Source

Added panicwrap to log un-recovered panics to a new log file

Michael Goldberger 9 years ago
parent
commit
bf048c7ad2
3 changed files with 80 additions and 1 deletions
  1. 72 0
      Server/main.go
  2. 1 1
      Server/make.bash
  3. 7 0
      psiphon/server/config.go

+ 72 - 0
Server/main.go

@@ -20,22 +20,30 @@
 package main
 package main
 
 
 import (
 import (
+	"encoding/json"
 	"flag"
 	"flag"
 	"fmt"
 	"fmt"
+	"io"
 	"io/ioutil"
 	"io/ioutil"
 	"os"
 	"os"
 	"strconv"
 	"strconv"
 	"strings"
 	"strings"
+	"time"
 
 
+	"github.com/Psiphon-Inc/panicwrap"
+	"github.com/Psiphon-Inc/rotate-safe-writer"
 	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common"
 	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common"
 	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/server"
 	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/server"
 )
 )
 
 
+var loadedConfigJSON []byte
+
 func main() {
 func main() {
 
 
 	var generateTrafficRulesFilename string
 	var generateTrafficRulesFilename string
 	var generateServerEntryFilename string
 	var generateServerEntryFilename string
 	var generateLogFilename string
 	var generateLogFilename string
+	var generatePanicLogFilename string
 	var generateServerIPaddress string
 	var generateServerIPaddress string
 	var generateServerNetworkInterface string
 	var generateServerNetworkInterface string
 	var generateWebServerPort int
 	var generateWebServerPort int
@@ -60,6 +68,12 @@ func main() {
 		"",
 		"",
 		"set application log file name and path; blank for stderr")
 		"set application log file name and path; blank for stderr")
 
 
+	flag.StringVar(
+		&generatePanicLogFilename,
+		"panicLogFilename",
+		"",
+		"set application log file name and path for recording un-recovered panics; blank for stderr")
+
 	flag.StringVar(
 	flag.StringVar(
 		&generateServerIPaddress,
 		&generateServerIPaddress,
 		"ipaddress",
 		"ipaddress",
@@ -135,6 +149,7 @@ func main() {
 			server.GenerateConfig(
 			server.GenerateConfig(
 				&server.GenerateConfigParams{
 				&server.GenerateConfigParams{
 					LogFilename:          generateLogFilename,
 					LogFilename:          generateLogFilename,
+					PanicLogFilename:     generatePanicLogFilename,
 					ServerIPAddress:      serverIPaddress,
 					ServerIPAddress:      serverIPaddress,
 					EnableSSHAPIRequests: true,
 					EnableSSHAPIRequests: true,
 					WebServerPort:        generateWebServerPort,
 					WebServerPort:        generateWebServerPort,
@@ -172,6 +187,22 @@ func main() {
 			os.Exit(1)
 			os.Exit(1)
 		}
 		}
 
 
+		loadedConfigJSON = configJSON
+
+		// Unhandled panic wrapper. Logs it, then re-executes the current executable
+		exitStatus, exitErr := panicwrap.BasicWrap(panicHandler)
+		if exitErr != nil {
+			fmt.Printf("failed to set up the panic wrapper: %s\n", err)
+			os.Exit(1)
+		}
+
+		// If exitStatus >= 0, then we're the parent process and the panicwrap
+		// re-executed ourselves and completed. Just exit with the proper status.
+		if exitStatus >= 0 {
+			os.Exit(exitStatus)
+		}
+		// Otherwise, exitStatus < 0 means we're the child. Continue executing as normal
+
 		err = server.RunServices(configJSON)
 		err = server.RunServices(configJSON)
 		if err != nil {
 		if err != nil {
 			fmt.Printf("run failed: %s\n", err)
 			fmt.Printf("run failed: %s\n", err)
@@ -190,3 +221,44 @@ func (list *stringListFlag) Set(flagValue string) error {
 	*list = append(*list, flagValue)
 	*list = append(*list, flagValue)
 	return nil
 	return nil
 }
 }
+
+func panicHandler(output string) {
+	if len(loadedConfigJSON) > 0 {
+		config, err := server.LoadConfig([]byte(loadedConfigJSON))
+		if err != nil {
+			fmt.Errorf("error parsing configuration file: %s", err)
+			os.Exit(1)
+		}
+
+		logEvent := make(map[string]string)
+		logEvent["host_id"] = config.HostID
+		logEvent["build_rev"] = common.GetBuildInfo().BuildRev
+		logEvent["timestamp"] = time.Now().Format(time.RFC3339)
+		logEvent["event_name"] = "panic"
+		logEvent["panic"] = output
+
+		var jsonWriter io.Writer
+		if config.PanicLogFilename != "" {
+			panicLog, err := rotate.NewRotatableFileWriter(config.PanicLogFilename, 0666)
+			if err != nil {
+				fmt.Printf("unable to set panic log output: %s\n", err)
+				os.Exit(1)
+			}
+			jsonWriter = panicLog
+		} else {
+			jsonWriter = os.Stderr
+		}
+
+		enc := json.NewEncoder(jsonWriter)
+		err = enc.Encode(logEvent)
+		if err != nil {
+			fmt.Printf("unable to serialize panic message to JSON: %s\n", err)
+			os.Exit(1)
+		}
+	} else {
+		fmt.Errorf("no configuration JSON was loaded, cannot continue")
+		os.Exit(1)
+	}
+
+	os.Exit(1)
+}

+ 1 - 1
Server/make.bash

@@ -65,7 +65,7 @@ build_for_linux () {
   chmod 555 psiphond
   chmod 555 psiphond
 
 
   if [ "$1" == "generate" ]; then
   if [ "$1" == "generate" ]; then
-    ./psiphond --ipaddress 0.0.0.0 --web 3000 --protocol SSH:3001 --protocol OSSH:3002 --logFilename /var/log/psiphond/psiphond.log generate
+    ./psiphond --ipaddress 0.0.0.0 --web 3000 --protocol SSH:3001 --protocol OSSH:3002 --logFilename /var/log/psiphond/psiphond.log --panicLogFilename /var/log/psiphond/psiphond-panics.log generate
 
 
     chmod 666 psiphond.config
     chmod 666 psiphond.config
     chmod 666 psiphond-traffic-rules.config
     chmod 666 psiphond-traffic-rules.config

+ 7 - 0
psiphon/server/config.go

@@ -63,6 +63,11 @@ type Config struct {
 	// to. When blank, logs are written to stderr.
 	// to. When blank, logs are written to stderr.
 	LogFilename string
 	LogFilename string
 
 
+	// PanicLogFilename specifies the path of the file to
+	// log unrecovered panics to. When blank, logs are
+	// written to stderr
+	PanicLogFilename string
+
 	// DiscoveryValueHMACKey is the network-wide secret value
 	// DiscoveryValueHMACKey is the network-wide secret value
 	// used to determine a unique discovery strategy.
 	// used to determine a unique discovery strategy.
 	DiscoveryValueHMACKey string
 	DiscoveryValueHMACKey string
@@ -339,6 +344,7 @@ func validateNetworkAddress(address string, requireIPaddress bool) error {
 // a generated server config.
 // a generated server config.
 type GenerateConfigParams struct {
 type GenerateConfigParams struct {
 	LogFilename          string
 	LogFilename          string
+	PanicLogFilename     string
 	LogLevel             string
 	LogLevel             string
 	ServerIPAddress      string
 	ServerIPAddress      string
 	WebServerPort        int
 	WebServerPort        int
@@ -495,6 +501,7 @@ func GenerateConfig(params *GenerateConfigParams) ([]byte, []byte, []byte, error
 	config := &Config{
 	config := &Config{
 		LogLevel:                       logLevel,
 		LogLevel:                       logLevel,
 		LogFilename:                    params.LogFilename,
 		LogFilename:                    params.LogFilename,
+		PanicLogFilename:               params.PanicLogFilename,
 		GeoIPDatabaseFilenames:         nil,
 		GeoIPDatabaseFilenames:         nil,
 		HostID:                         "example-host-id",
 		HostID:                         "example-host-id",
 		ServerIPAddress:                params.ServerIPAddress,
 		ServerIPAddress:                params.ServerIPAddress,