Browse Source

feat: Use log/slog for logging to file or stdout

Switched to Go1.21 to use the log/slog package for strutctured logging.

TODO: Log messages that are stringifying objects can now use strutctured
output.

TODO: Customise log levels for different messages.

Fix tests
Ananth Bhaskararaman 1 year ago
parent
commit
f93a4d12cf

+ 38 - 40
SETUP_DEV_OSX.md

@@ -1,90 +1,88 @@
 # How to setup Browsh's build system for Mac
-If you just want to try Browsh, you can use [Homebrew](https://brew.sh/) (check out the [installation page](https://www.brow.sh/docs/installation/) at the [official site](https://www.brow.sh/)).
+
+If you want to try Browsh, you can use [Homebrew](https://brew.sh/).
+Check out the [installation page](https://www.brow.sh/docs/installation/) at the
+[official site](https://www.brow.sh/)).
 
 ## Installations
+
 You need Go, Firefox and Node.js to run Browsh.
 
 ### Install Go
-Follow the [installation guide](https://golang.org/doc/install) (you can use an installer).
-
-#### Ensure your GOPATH is set
-
-```sh
-$ echo $GOPATH
-/Users/uesr_name/go
-$ # anywhere is ok, but make sure it's not none
-```
-
-#### Ensure you have `$GOPATH/src` and `$GOPATH/bin` folders
-If you're not sure if you have these folders, run:
 
-```sh
-$ mkdir "$GOPATH/src"
-$ mkdir "$GOPATH/bin"
-```
+Follow the [installation guide](https://golang.org/doc/install) (you can use an installer).
 
 ### Install Firefox
-Follow the official [guide](https://support.mozilla.org/en-US/kb/how-download-and-install-firefox-mac) to install Firefox.
+
+Follow the official [guide](https://support.mozilla.org/en-US/kb/how-download-and-install-firefox-mac)
+to install Firefox.
 
 #### Include Firefox to your PATH
-The `firefox` executable is probably at `/Applications/Firefox.app/Contents/MacOS`. You need to add it to your `PATH` so that Browsh can create new instances of Firefox.
+
+The `firefox` executable is probably at `/Applications/Firefox.app/Contents/MacOS`.
+You need to add it to your `PATH` so that Browsh can create new instances of Firefox.
 
 ### Install Node.js
+
 Follow the [official downloading page](https://nodejs.org/en/download/).
 
-> v8.11.4. is currently recommended for working with Browsh (?)
+Use Nodejs > v8.11.4 with Browsh.
 
 #### Install web-ext globally
-It's a Mozilla's handy tool for working with Firefox web extensions:
 
-```sh
-$ npm install -g web-ext
+It's a Mozilla tool for working with Firefox web extensions:
+
+```shell
+npm install -g web-ext
 ```
 
 ## Setting up your Browsh
 
 ### Clone Browsh
-Fork Browsh to your Github account. Clone it to `$GOPATH/src`.
+
+Fork Browsh to your Github account.
+Clone it to a directory of your choice.
+We will refer to this directory as `$browsh` for the rest of the guide.
 
 ### Install NPM packages
 
 ```shell
-$ cd "$GOPATH/src/browsh/webext"
-$ npm install
+cd "$browsh/webext"
+npm install
 ```
 
 ### Run the build script
 
 ```sh
-$ cd "$GOPATH/src/browsh"
-$ # install several required package"
-$ ./interfacer/contrib/build_browsh.sh
+cd "$browsh"
+# install required package"
+./interfacer/contrib/build_browsh.sh
 ```
 
 ## Running Browsh from source
-Now that you have all of the required dependencies installed, we can run Browsh. Open three terminals and do the follows:
+
+Now that you have the required dependencies installed, we can run Browsh.
+Open three terminals and do the following:
 
 ### Terminal 1 (builds JavaScript)
 
 ```sh
-$ cd "$GOPATH/src/browsh/webext"
-$ # create a dist folder inside the webext folder.
-$ npx webpack --watch
+cd "$browsh/webext
+# create a dist folder inside the webext folder.
+npx webpack --watch
 ```
 
 ### Terminal 2 (handles Firefox web extension)
 
 ```sh
-$ # the dist folder is created in the first terminal
-$ cd "$GOPATH/src/browsh/webext/dist"
-$ # create a dist folder inside the webext folder.
-$ npx webpack --watch
+mkdir "$browsh/webext/dist"
+cd "$browsh/webext/dist"
+npx webpack --watch
 ```
 
 ### Terminal 3 (Displays Browsh)
 
 ```sh
-$ cd "$GOPATH/src/browsh/interfacer"
-$ go run ./cmd/browsh/main.go --firefox.use-existing --debug
+cd "$browsh/interfacer"
+go run ./cmd/browsh/main.go --firefox.use-existing --debug
 ```
-

+ 1 - 1
interfacer/go.mod

@@ -1,6 +1,6 @@
 module github.com/browsh-org/browsh/interfacer
 
-go 1.18
+go 1.21
 
 require (
 	github.com/NYTimes/gziphandler v1.1.1

+ 8 - 0
interfacer/go.sum

@@ -58,6 +58,7 @@ github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5y
 github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
 github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
 github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE=
+github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps=
 github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
 github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
 github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI=
@@ -98,6 +99,7 @@ github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QD
 github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
 github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
 github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
+github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
 github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
 github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
 github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
@@ -110,6 +112,7 @@ github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
 github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
+github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
 github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
 github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
 github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
@@ -143,9 +146,11 @@ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+o
 github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
 github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
 github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
+github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
 github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
 github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
+github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
 github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
 github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
 github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
@@ -164,6 +169,7 @@ github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108
 github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
 github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
 github.com/onsi/ginkgo/v2 v2.1.3 h1:e/3Cwtogj0HA+25nMP1jCMDIf8RtRYbGwGGuBIFztkc=
+github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c=
 github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
 github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
 github.com/onsi/gomega v1.19.0 h1:4ieX6qQjPP/BfC3mpsAtIGGlxTWPeA3Inl/7DtXw1tw=
@@ -182,6 +188,7 @@ github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
 github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
 github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
 github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k=
+github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
 github.com/shibukawa/configdir v0.0.0-20170330084843-e180dbdc8da0 h1:Xuk8ma/ibJ1fOy4Ee11vHhUFHQNpHhrBneOCNHVXS5w=
 github.com/shibukawa/configdir v0.0.0-20170330084843-e180dbdc8da0/go.mod h1:7AwjWCpdPhkSmNAgUv5C7EJ4AbmjEB3r047r3DXWu3Y=
 github.com/spf13/afero v1.9.0 h1:sFSLUHgxdnN32Qy38hK3QkYBFXZj9DKjVjCUCtD7juY=
@@ -506,6 +513,7 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD
 google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
 google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
 google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
+google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

+ 24 - 40
interfacer/src/browsh/browsh.go

@@ -3,7 +3,7 @@ package browsh
 import (
 	"encoding/base64"
 	"fmt"
-	"io/ioutil"
+	"log/slog"
 	"net/url"
 	"os"
 	"os/exec"
@@ -44,40 +44,18 @@ var (
 )
 
 func setupLogging() {
-	dir, err := os.Getwd()
-	if err != nil {
-		Shutdown(err)
-	}
-	logfile = fmt.Sprintf(filepath.Join(dir, "debug.log"))
-	fmt.Println("Logging to: " + logfile)
-	if _, err := os.Stat(logfile); err == nil {
-		os.Truncate(logfile, 0)
-	}
-	if err != nil {
-		Shutdown(err)
-	}
-}
-
-// Log for general purpose logging
-// TODO: accept generic types
-func Log(msg string) {
-	if !*isDebug {
-		return
-	}
-	if viper.GetBool("http-server-mode") && !IsTesting {
-		fmt.Println(msg)
-	} else {
-		f, oErr := os.OpenFile(logfile, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0600)
-		if oErr != nil {
-			Shutdown(oErr)
+	out := os.Stderr
+	if *isDebug {
+		dir, err := os.Getwd()
+		if err != nil {
+			Shutdown(err)
 		}
-		defer f.Close()
-
-		msg = msg + "\n"
-		if _, wErr := f.WriteString(msg); wErr != nil {
-			Shutdown(wErr)
+		logfile = fmt.Sprintf(filepath.Join(dir, "debug.log"))
+		if out, err = os.OpenFile(logfile, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0o644); err != nil {
+			Shutdown(err)
 		}
 	}
+	slog.SetDefault(slog.New(slog.NewTextHandler(out, nil)))
 }
 
 // Initialise browsh
@@ -85,9 +63,7 @@ func Initialise() {
 	if IsTesting {
 		*isDebug = true
 	}
-	if *isDebug {
-		setupLogging()
-	}
+	setupLogging()
 	loadConfig()
 }
 
@@ -95,9 +71,9 @@ func Initialise() {
 func Shutdown(err error) {
 	if *isDebug {
 		if e, ok := err.(*errors.Error); ok {
-			Log(fmt.Sprintf(e.ErrorStack()))
+			slog.Error(e.ErrorStack())
 		} else {
-			Log(err.Error())
+			slog.Error(err.Error())
 		}
 	}
 	exitCode := 0
@@ -111,12 +87,15 @@ func Shutdown(err error) {
 	os.Exit(exitCode)
 }
 
+func Log(message string) {
+}
+
 func saveScreenshot(base64String string) {
 	dec, err := base64.StdEncoding.DecodeString(base64String)
 	if err != nil {
 		Shutdown(err)
 	}
-	file, err := ioutil.TempFile(os.TempDir(), "browsh-screenshot")
+	file, err := os.CreateTemp("", "browsh-screenshot")
 	if err != nil {
 		Shutdown(err)
 	}
@@ -154,9 +133,14 @@ func TTYStart(injectedScreen tcell.Screen) {
 	screen = injectedScreen
 	setupTcell()
 	writeString(1, 0, logo, tcell.StyleDefault)
-	writeString(0, 15, "Starting Browsh v"+browshVersion+", the modern text-based web browser.", tcell.StyleDefault)
+	writeString(
+		0,
+		15,
+		"Starting Browsh v"+browshVersion+", the modern text-based web browser.",
+		tcell.StyleDefault,
+	)
 	StartFirefox()
-	Log("Starting Browsh CLI client")
+	slog.Info("Starting Browsh CLI client")
 	go readStdin()
 	startWebSocketServer()
 }

+ 15 - 13
interfacer/src/browsh/comms.go

@@ -3,6 +3,7 @@ package browsh
 import (
 	"encoding/json"
 	"fmt"
+	"log/slog"
 	"net/http"
 	"strings"
 
@@ -30,7 +31,7 @@ func startWebSocketServer() {
 	serverMux := http.NewServeMux()
 	serverMux.HandleFunc("/", webSocketServer)
 	port := viper.GetString("browsh.websocket-port")
-	Log("Starting websocket server...")
+	slog.Info("Starting websocket server...")
 	if netErr := http.ListenAndServe(":"+port, serverMux); netErr != nil {
 		Shutdown(errors.New(fmt.Errorf("Error starting websocket server: %v", netErr)))
 	}
@@ -38,7 +39,7 @@ func startWebSocketServer() {
 
 func sendMessageToWebExtension(message string) {
 	if !IsConnectedToWebExtension {
-		Log("Webextension not connected. Message not sent: " + message)
+		slog.Info("Webextension not connected. Message not sent: " + message)
 		return
 	}
 	stdinChannel <- message
@@ -53,12 +54,12 @@ func webSocketReader(ws *websocket.Conn) {
 		handleWebextensionCommand(message)
 		if err != nil {
 			if websocket.IsCloseError(err, websocket.CloseGoingAway) {
-				Log("Socket reader detected that the browser closed the websocket")
+				slog.Info("Socket reader detected that the browser closed the websocket")
 				triggerSocketWriterClose()
 				return
 			}
 			if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway) {
-				Log("Socket reader detected that the connection unexpectedly dissapeared")
+				slog.Info("Socket reader detected that the connection unexpectedly dissapeared")
 				triggerSocketWriterClose()
 				return
 			}
@@ -89,7 +90,7 @@ func handleWebextensionCommand(message []byte) {
 	case "/screenshot":
 		saveScreenshot(parts[1])
 	default:
-		Log("WEBEXT: " + string(message))
+		slog.Info("WEBEXT: " + string(message))
 	}
 }
 
@@ -102,13 +103,13 @@ func handleRawFrameTextCommands(parts []string) {
 			Shutdown(err)
 		}
 		if incoming.RequestID != "" {
-			Log("Raw text for " + incoming.RequestID)
+			slog.Info("Raw text for " + incoming.RequestID)
 			rawTextRequests.store(incoming.RequestID, incoming.RawJSON)
 		} else {
-			Log("Raw text but no associated request ID")
+			slog.Info("Raw text but no associated request ID")
 		}
 	} else {
-		Log("WEBEXT: " + strings.Join(parts[0:], ","))
+		slog.Info("WEBEXT: " + strings.Join(parts[0:], ","))
 	}
 }
 
@@ -117,7 +118,8 @@ func handleRawFrameTextCommands(parts []string) {
 // automatically notice until it actually needs to send something. So we force that
 // by sending this NOOP text.
 // TODO: There's a potential race condition because new connections share the same
-//       Go channel. So we need to setup a new channel for every connection.
+//
+//	Go channel. So we need to setup a new channel for every connection.
 func triggerSocketWriterClose() {
 	stdinChannel <- "BROWSH CLIENT FORCING CLOSE OF WEBSOCKET WRITER"
 }
@@ -128,12 +130,12 @@ func webSocketWriter(ws *websocket.Conn) {
 	defer ws.Close()
 	for {
 		message = <-stdinChannel
-		Log(fmt.Sprintf("TTY sending: %s", message))
+		slog.Info(fmt.Sprintf("TTY sending: %s", message))
 		if err := ws.WriteMessage(websocket.TextMessage, []byte(message)); err != nil {
 			if err == websocket.ErrCloseSent {
-				Log("Socket writer detected that the browser closed the websocket")
+				slog.Info("Socket writer detected that the browser closed the websocket")
 			} else {
-				Log("Socket writer detected unexpected closure of websocket")
+				slog.Info("Socket writer detected unexpected closure of websocket")
 			}
 			return
 		}
@@ -141,7 +143,7 @@ func webSocketWriter(ws *websocket.Conn) {
 }
 
 func webSocketServer(w http.ResponseWriter, r *http.Request) {
-	Log("Incoming web request from browser")
+	slog.Info("Incoming web request from browser")
 	ws, err := upgrader.Upgrade(w, r, nil)
 	if err != nil {
 		Shutdown(err)

+ 5 - 6
interfacer/src/browsh/config.go

@@ -3,6 +3,7 @@ package browsh
 import (
 	"bytes"
 	"fmt"
+	"log/slog"
 	"os"
 	"path/filepath"
 	"strings"
@@ -15,7 +16,7 @@ import (
 var (
 	configFilename = "config.toml"
 
-	isDebug   = pflag.Bool("debug", false, "Log to ./debug.log")
+	isDebug   = pflag.Bool("debug", false, "slog.Info to ./debug.log")
 	timeLimit = pflag.Int("time-limit", 0, "Kill Browsh after the specified number of seconds")
 	_         = pflag.Bool("http-server-mode", false, "Run as an HTTP service")
 
@@ -79,20 +80,18 @@ func setDefaults() {
 func loadConfig() {
 	dir := getConfigDir()
 	fullPath := filepath.Join(dir, configFilename)
-	Log("Looking in " + fullPath + " for config.")
+	slog.Info("Looking in " + fullPath + " for config.")
 	viper.SetConfigType("toml")
 	viper.SetConfigName(strings.Trim(configFilename, ".toml"))
 	viper.AddConfigPath(dir)
 	viper.AddConfigPath(".")
 	setDefaults()
 	// First load the sample config in case the user hasn't updated any new fields
-	err := viper.ReadConfig(bytes.NewBuffer([]byte(configSample)))
-	if err != nil {
+	if err := viper.ReadConfig(bytes.NewBuffer([]byte(configSample))); err != nil {
 		panic(fmt.Errorf("Config file error: %s \n", err))
 	}
 	// Then load the users own config file, overwriting the sample config
-	err = viper.MergeInConfig()
-	if err != nil {
+	if err := viper.MergeInConfig(); err != nil {
 		panic(fmt.Errorf("Config file error: %s \n", err))
 	}
 	viper.BindPFlags(pflag.CommandLine)

+ 14 - 13
interfacer/src/browsh/firefox.go

@@ -6,6 +6,7 @@ import (
 	"encoding/json"
 	"fmt"
 	"io/ioutil"
+	"log/slog"
 	"net"
 	"os"
 	"os/exec"
@@ -59,7 +60,7 @@ var (
 )
 
 func startHeadlessFirefox() {
-	Log("Starting Firefox in headless mode")
+	slog.Info("Starting Firefox in headless mode")
 	checkIfFirefoxIsAlreadyRunning()
 	firefoxPath := ensureFirefoxBinary()
 	ensureFirefoxVersion(firefoxPath)
@@ -69,11 +70,11 @@ func startHeadlessFirefox() {
 	}
 	profile := viper.GetString("firefox.profile")
 	if profile != "browsh-default" {
-		Log("Using profile: " + profile)
+		slog.Info("Using profile: " + profile)
 		args = append(args, "-P", profile)
 	} else {
 		profilePath := getFirefoxProfilePath()
-		Log("Using default profile at: " + profilePath)
+		slog.Info("Using default profile at: " + profilePath)
 		args = append(args, "--profile", profilePath)
 	}
 	firefoxProcess := exec.Command(firefoxPath, args...)
@@ -87,7 +88,7 @@ func startHeadlessFirefox() {
 	}
 	in := bufio.NewScanner(stdout)
 	for in.Scan() {
-		Log("FF-CONSOLE: " + in.Text())
+		slog.Info("FF-CONSOLE: " + in.Text())
 	}
 }
 
@@ -114,7 +115,7 @@ func ensureFirefoxBinary() string {
 			path = getFirefoxPath()
 		}
 	}
-	Log("Using Firefox at: " + path)
+	slog.Info("Using Firefox at: " + path)
 	if _, err := os.Stat(path); os.IsNotExist(err) {
 		Shutdown(errors.New("Firefox binary not found: " + path))
 	}
@@ -155,12 +156,12 @@ func versionOrdinal(version string) string {
 // because I haven't been able to recreate the way `web-ext` injects an unsigned
 // extension.
 func startWERFirefox() {
-	Log("Attempting to start headless Firefox with `web-ext`")
+	slog.Info("Attempting to start headless Firefox with `web-ext`")
 	if IsConnectedToWebExtension {
 		Shutdown(errors.New("There appears to already be an existing Web Extension connection"))
 	}
 	checkIfFirefoxIsAlreadyRunning()
-	var rootDir = Shell("git rev-parse --show-toplevel")
+	rootDir := Shell("git rev-parse --show-toplevel")
 	args := []string{
 		"run",
 		"--firefox=" + rootDir + "/webext/contrib/firefoxheadless.sh",
@@ -185,9 +186,9 @@ func startWERFirefox() {
 			strings.Contains(in.Text(), "dbus") {
 			continue
 		}
-		Log("FF-CONSOLE: " + in.Text())
+		slog.Info("FF-CONSOLE: " + in.Text())
 	}
-	Log("WER Firefox unexpectedly closed")
+	slog.Info("WER Firefox unexpectedly closed")
 }
 
 // Connect to Firefox's Marionette service.
@@ -206,7 +207,7 @@ func firefoxMarionette() {
 		conn net.Conn
 	)
 	connected := false
-	Log("Attempting to connect to Firefox Marionette")
+	slog.Info("Attempting to connect to Firefox Marionette")
 	start := time.Now()
 	for time.Since(start) < 30*time.Second {
 		conn, err = net.Dial("tcp", "127.0.0.1:2828")
@@ -262,14 +263,14 @@ func readMarionette() {
 	buffer := make([]byte, 4096)
 	count, err := marionette.Read(buffer)
 	if err != nil {
-		Log("Error reading from Marionette connection")
+		slog.Info("Error reading from Marionette connection")
 		return
 	}
-	Log("FF-MRNT: " + string(buffer[:count]))
+	slog.Info("FF-MRNT: " + string(buffer[:count]))
 }
 
 func sendFirefoxCommand(command string, args map[string]interface{}) {
-	Log("Sending `" + command + "` to Firefox Marionette")
+	slog.Info("Sending `" + command + "` to Firefox Marionette")
 	fullCommand := []interface{}{0, ffCommandCount, command, args}
 	marshalled, _ := json.Marshal(fullCommand)
 	message := fmt.Sprintf("%d:%s", len(marshalled), marshalled)

+ 3 - 2
interfacer/src/browsh/firefox_windows.go

@@ -4,6 +4,7 @@ package browsh
 
 import (
 	"fmt"
+	"log/slog"
 	"strings"
 
 	"github.com/go-errors/errors"
@@ -46,13 +47,13 @@ func getWindowsFirefoxVersionString() string {
 		Shutdown(errors.New("Error reading Windows registry: " + fmt.Sprintf("%s", err)))
 	}
 
-	Log("Windows registry Firefox version: " + versionString)
+	slog.Info("Windows registry Firefox version: " + versionString)
 
 	return versionString
 }
 
 func getFirefoxFlavor() string {
-	var flavor = "null"
+	flavor := "null"
 	k, err := registry.OpenKey(
 		registry.LOCAL_MACHINE,
 		`Software\Mozilla\Mozilla Firefox`,

+ 9 - 4
interfacer/src/browsh/frame_builder.go

@@ -3,6 +3,7 @@ package browsh
 import (
 	"encoding/json"
 	"fmt"
+	"log/slog"
 	"unicode"
 
 	"github.com/gdamore/tcell"
@@ -78,7 +79,9 @@ func parseJSONFrameText(jsonString string) {
 		Shutdown(err)
 	}
 	if !isTabPresent(incoming.Meta.TabID) {
-		Log(fmt.Sprintf("Not building frame for non-existent tab ID: %d", incoming.Meta.TabID))
+		slog.Info(
+			fmt.Sprintf("Not building frame for non-existent tab ID: %d", incoming.Meta.TabID),
+		)
 		return
 	}
 	Tabs[incoming.Meta.TabID].frame.buildFrameText(incoming)
@@ -100,7 +103,9 @@ func parseJSONFramePixels(jsonString string) {
 		Shutdown(err)
 	}
 	if !isTabPresent(incoming.Meta.TabID) {
-		Log(fmt.Sprintf("Not building frame for non-existent tab ID: %d", incoming.Meta.TabID))
+		slog.Info(
+			fmt.Sprintf("Not building frame for non-existent tab ID: %d", incoming.Meta.TabID),
+		)
 		return
 	}
 	if len(Tabs[incoming.Meta.TabID].frame.text) == 0 {
@@ -139,7 +144,7 @@ func (f *frame) resetCells() {
 
 func (f *frame) isIncomingFrameTextValid(incoming incomingFrameText) bool {
 	if len(incoming.Text) == 0 {
-		Log("Not parsing zero-size text frame")
+		slog.Info("Not parsing zero-size text frame")
 		return false
 	}
 	return true
@@ -224,7 +229,7 @@ func (f *frame) populateFramePixels(incoming incomingFramePixels) {
 
 func (f *frame) isIncomingFramePixelsValid(incoming incomingFramePixels) bool {
 	if len(incoming.Colours) == 0 {
-		Log("Not parsing zero-size text frame")
+		slog.Info("Not parsing zero-size text frame")
 		return false
 	}
 	return true

+ 5 - 4
interfacer/src/browsh/raw_text_server.go

@@ -5,6 +5,7 @@ import (
 	"encoding/json"
 	"fmt"
 	"io"
+	"log/slog"
 	"net/http"
 	"net/url"
 	"regexp"
@@ -69,7 +70,7 @@ func HTTPServerStart() {
 	IsHTTPServerMode = true
 	StartFirefox()
 	go startWebSocketServer()
-	Log("Starting Browsh HTTP server")
+	slog.Info("Starting Browsh HTTP server")
 	bind := viper.GetString("http-server.bind")
 	port := viper.GetString("http-server.port")
 	serverMux := http.NewServeMux()
@@ -120,7 +121,7 @@ func (h *slashFix) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 func handleHTTPServerRequest(w http.ResponseWriter, r *http.Request) {
 	var message string
 	var isErrored bool
-	var start = time.Now().Format(time.RFC3339)
+	start := time.Now().Format(time.RFC3339)
 	urlForBrowsh, _ := url.PathUnescape(strings.TrimPrefix(r.URL.Path, "/"))
 	urlForBrowsh, isErrored = deRecurseURL(urlForBrowsh)
 	if isErrored {
@@ -147,7 +148,7 @@ func handleHTTPServerRequest(w http.ResponseWriter, r *http.Request) {
 			return
 		}
 	}
-	Log(r.Header.Get("User-Agent"))
+	slog.Info(r.Header.Get("User-Agent"))
 	if isKubeReadinessProbe(r.Header.Get("User-Agent")) {
 		io.WriteString(w, "healthy")
 		return
@@ -228,7 +229,7 @@ func isProductionHTTP(r *http.Request) bool {
 // 'HTML' mode returns some basic HTML tags for things like anchor links.
 // 'DOM' mode returns a simple dump of the DOM.
 func getRawTextMode(r *http.Request) string {
-	var mode = "HTML"
+	mode := "HTML"
 	if strings.Contains(r.Host, "text.") {
 		mode = "PLAIN"
 	}

+ 11 - 11
interfacer/src/browsh/ui.go

@@ -1,20 +1,20 @@
 package browsh
 
 import (
+	"log/slog"
+
 	"github.com/gdamore/tcell"
 	"github.com/spf13/viper"
 )
 
-var (
-	urlInputBox = inputBox{
-		X:        0,
-		Y:        1,
-		Height:   1,
-		text:     nil,
-		FgColour: [3]int32{255, 255, 255},
-		bgColour: [3]int32{-1, -1, -1},
-	}
-)
+var urlInputBox = inputBox{
+	X:        0,
+	Y:        1,
+	Height:   1,
+	text:     nil,
+	FgColour: [3]int32{255, 255, 255},
+	bgColour: [3]int32{-1, -1, -1},
+}
 
 // Render tabs, URL bar, status messages, etc
 func renderUI() {
@@ -28,7 +28,7 @@ func renderUI() {
 func writeString(x, y int, str string, style tcell.Style) {
 	xOriginal := x
 	if viper.GetBool("http-server-mode") {
-		Log(str)
+		slog.Info(str)
 		return
 	}
 	for _, c := range str {

+ 11 - 9
interfacer/test/http-server/setup.go

@@ -2,18 +2,20 @@ package test
 
 import (
 	"fmt"
-	"io/ioutil"
+	"io"
+	"log/slog"
 	"net/http"
 	"time"
 
-	ginkgo "github.com/onsi/ginkgo"
-
 	"github.com/browsh-org/browsh/interfacer/src/browsh"
+	ginkgo "github.com/onsi/ginkgo"
 	"github.com/spf13/viper"
 )
 
-var staticFileServerPort = "4444"
-var rootDir = browsh.Shell("git rev-parse --show-toplevel")
+var (
+	staticFileServerPort = "4444"
+	rootDir              = browsh.Shell("git rev-parse --show-toplevel")
+)
 
 func startStaticFileServer() {
 	serverMux := http.NewServeMux()
@@ -59,7 +61,7 @@ func getPath(path string, mode string) string {
 		panic(fmt.Sprintf("%s", err))
 	} else {
 		defer response.Body.Close()
-		contents, err := ioutil.ReadAll(response.Body)
+		contents, err := io.ReadAll(response.Body)
 		if err != nil {
 			fmt.Printf("%s", err)
 			panic(fmt.Sprintf("%s", err))
@@ -78,9 +80,9 @@ var _ = ginkgo.BeforeEach(func() {
 	browsh.ResetTabs()
 	waitUntilConnectedToWebExtension(15 * time.Second)
 	browsh.IsMonochromeMode = false
-	browsh.Log("\n---------")
-	browsh.Log(ginkgo.CurrentGinkgoTestDescription().FullTestText)
-	browsh.Log("---------")
+	slog.Info("\n---------")
+	slog.Info(ginkgo.CurrentGinkgoTestDescription().FullTestText)
+	slog.Info("---------")
 })
 
 var _ = ginkgo.BeforeSuite(func() {

+ 40 - 36
interfacer/test/tty/setup.go

@@ -2,6 +2,7 @@ package test
 
 import (
 	"fmt"
+	"log/slog"
 	"net/http"
 	"os"
 	"path/filepath"
@@ -9,24 +10,41 @@ import (
 	"time"
 	"unicode/utf8"
 
+	"github.com/browsh-org/browsh/interfacer/src/browsh"
 	"github.com/gdamore/tcell"
 	"github.com/gdamore/tcell/terminfo"
 	ginkgo "github.com/onsi/ginkgo"
 	gomega "github.com/onsi/gomega"
-
-	"github.com/browsh-org/browsh/interfacer/src/browsh"
 	"github.com/spf13/viper"
 )
 
-var staticFileServerPort = "4444"
-var simScreen tcell.SimulationScreen
-var startupWait = 60 * time.Second
-var perTestTimeout = 2000 * time.Millisecond
-var rootDir = browsh.Shell("git rev-parse --show-toplevel")
-var testSiteURL = "http://localhost:" + staticFileServerPort
-var ti *terminfo.Terminfo
-var dir, _ = os.Getwd()
-var framesLogFile = fmt.Sprintf(filepath.Join(dir, "frames.log"))
+var (
+	staticFileServerPort = "4444"
+	simScreen            tcell.SimulationScreen
+	startupWait          = 60 * time.Second
+	perTestTimeout       = 2000 * time.Millisecond
+	rootDir              = browsh.Shell("git rev-parse --show-toplevel")
+	testSiteURL          = "http://localhost:" + staticFileServerPort
+	ti                   *terminfo.Terminfo
+	framesLogFileName    string
+	frameLogger          *slog.Logger
+)
+
+func init() {
+	dir, err := os.Getwd()
+	if err != nil {
+		panic(err)
+	}
+	framesLogFileName = fmt.Sprintf(filepath.Join(dir, "frames.log"))
+	framesLogFile, err := os.OpenFile(framesLogFileName,
+		os.O_CREATE|os.O_TRUNC|os.O_WRONLY,
+		0o644,
+	)
+	if err != nil {
+		panic(err)
+	}
+	frameLogger = slog.New(slog.NewTextHandler(framesLogFile, nil))
+}
 
 func initTerm() {
 	// The tests check for true colour RGB values. The only downside to forcing true colour
@@ -39,8 +57,8 @@ func initTerm() {
 // GetFrame returns the current Browsh frame's text
 func GetFrame() string {
 	var frame, log string
-	var line = 0
-	var styleDefault = ti.TParm(ti.SetFgBg, int(tcell.ColorWhite), int(tcell.ColorBlack))
+	line := 0
+	styleDefault := ti.TParm(ti.SetFgBg, int(tcell.ColorWhite), int(tcell.ColorBlack))
 	width, _ := simScreen.Size()
 	cells, _, _ := simScreen.GetContents()
 	for _, element := range cells {
@@ -53,25 +71,12 @@ func GetFrame() string {
 			line = 0
 		}
 	}
-	writeFrameLog("================================================")
-	writeFrameLog(ginkgo.CurrentGinkgoTestDescription().FullTestText)
-	writeFrameLog("================================================\n")
-	log = "\n" + log + styleDefault
-	writeFrameLog(log)
+	frameLogger.Info("================================================")
+	frameLogger.Info(ginkgo.CurrentGinkgoTestDescription().FullTestText)
+	frameLogger.Info("================================================\n")
 	return frame
 }
 
-func writeFrameLog(log string) {
-	f, err := os.OpenFile(framesLogFile, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0600)
-	if err != nil {
-		panic(err)
-	}
-	defer f.Close()
-	if _, err = f.WriteString(log); err != nil {
-		panic(err)
-	}
-}
-
 // Trigger the key definition specified by name
 func triggerUserKeyFor(name string) {
 	key := viper.GetStringSlice(name)
@@ -228,11 +233,10 @@ func initBrowsh() {
 	browsh.IsTesting = true
 	simScreen = tcell.NewSimulationScreen("UTF-8")
 	browsh.Initialise()
-
 }
 
 func stopFirefox() {
-	browsh.Log("Attempting to kill all firefox processes")
+	slog.Info("Attempting to kill all firefox processes")
 	browsh.IsConnectedToWebExtension = false
 	browsh.Shell(rootDir + "/webext/contrib/firefoxheadless.sh kill")
 	time.Sleep(500 * time.Millisecond)
@@ -243,19 +247,19 @@ func runeCount(text string) int {
 }
 
 var _ = ginkgo.BeforeEach(func() {
-	browsh.Log("Attempting to restart WER Firefox...")
+	slog.Info("Attempting to restart WER Firefox...")
 	stopFirefox()
 	browsh.ResetTabs()
 	browsh.StartFirefox()
 	sleepUntilPageLoad(startupWait)
 	browsh.IsMonochromeMode = false
-	browsh.Log("\n---------")
-	browsh.Log(ginkgo.CurrentGinkgoTestDescription().FullTestText)
-	browsh.Log("---------")
+	slog.Info("\n---------")
+	slog.Info(ginkgo.CurrentGinkgoTestDescription().FullTestText)
+	slog.Info("---------")
 })
 
 var _ = ginkgo.BeforeSuite(func() {
-	os.Truncate(framesLogFile, 0)
+	os.Truncate(framesLogFileName, 0)
 	initTerm()
 	initBrowsh()
 	stopFirefox()