├── .gitignore ├── Makefile ├── README.md ├── loader.go ├── logs └── .gitignore ├── remove_logs.sh └── test.go /.gitignore: -------------------------------------------------------------------------------- 1 | loader 2 | test -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: 2 | go build loader.go 3 | go build test.go 4 | .PHONY: all 5 | .DEFAULT: all -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Go Spawn & Output Logs From Process 2 | This is a small project to demonstrate how to spawn multiple processes from a Go program and have the Go program write their `stdout` and `stderr` lines to a log file. 3 | 4 | In this example, we have `test` and `loader` Go programs. 5 | 6 | The `test` program outputs a random string from the `messages` variable each second to `stdout`. This is just used as a demo program. 7 | 8 | The `loader` program runs the `test` program five times and outputs their `stdout` and `stderr` pipes to a log file in the `logs/` directory (e.g. `logs/.log`). 9 | 10 | ## Motives 11 | I'm working on a private project which spawns processes from a Go program. However, I wanted to write the `stdout` and `stderr` pipes from these spawned processes to a file so I knew what was going on. Since the project utilized Docker which extended build/test time, I decided to write a separate open source program to achieve this goal since I could easily test things. 12 | 13 | ## Building 14 | You may use `make` via Makefile to build everything easily. Otherwise, you may use `go build loader.go` and `go build test.go` to build the Go programs. 15 | 16 | ## Running 17 | Simply run the `loader` executable to test. 18 | 19 | ```bash 20 | ./loader 21 | ``` 22 | 23 | **Note** - The `remove_logs.sh` file is ran on each loader start. This Bash script simply removes all log files in the `logs/` directory so we start off from a clean slate and the loader ignores any errors from executing the file. 24 | 25 | ## Credits 26 | * [Christian Deacon](https://github.com/gamemann) -------------------------------------------------------------------------------- /loader.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "os" 7 | "os/exec" 8 | "os/signal" 9 | "syscall" 10 | ) 11 | 12 | type TestProcess struct { 13 | Process *exec.Cmd 14 | Stdout *bufio.Writer 15 | Stderr *bufio.Writer 16 | } 17 | 18 | const RMLOGS_EXEC = "./remove_logs.sh" 19 | const EXEC = "./test" 20 | const NUM_PROCESSES = 5 21 | 22 | func main() { 23 | // Remove logs by using shell script. 24 | rmLogs := exec.Command(RMLOGS_EXEC) 25 | 26 | err := rmLogs.Run() 27 | 28 | if err != nil { 29 | fmt.Println("Failed to remove logs via shell script.") 30 | fmt.Println(err) 31 | } 32 | 33 | var processes []*TestProcess 34 | 35 | for i := 0; i < NUM_PROCESSES; i++ { 36 | process := exec.Command(EXEC) 37 | 38 | stdoutPipe, _ := process.StdoutPipe() 39 | stderrPipe, _ := process.StderrPipe() 40 | 41 | err := process.Start() 42 | 43 | if err != nil { 44 | fmt.Printf("[%d] Error creating process.\n", i) 45 | fmt.Println(err) 46 | 47 | continue 48 | } 49 | 50 | if process.Process == nil { 51 | fmt.Printf("[%d] Could not find process.\n", process.Process.Pid) 52 | 53 | continue 54 | } 55 | 56 | // Create log file. 57 | fileName := fmt.Sprintf("logs/%d.log", process.Process.Pid) 58 | logFile, err := os.Create(fileName) 59 | 60 | if err != nil { 61 | fmt.Printf("[%d] Error creating log file :: %s.\n", process.Process.Pid, fileName) 62 | 63 | continue 64 | } 65 | 66 | stdoutWriter := bufio.NewWriter(logFile) 67 | stderrWriter := bufio.NewWriter(logFile) 68 | 69 | // Create goroutines to capture stdout and stderr output and write them our log files. 70 | go func() { 71 | scanner := bufio.NewScanner(stdoutPipe) 72 | for scanner.Scan() { 73 | line := scanner.Text() 74 | stdoutWriter.WriteString(line + "\n") 75 | stdoutWriter.Flush() 76 | } 77 | }() 78 | 79 | go func() { 80 | scanner := bufio.NewScanner(stderrPipe) 81 | for scanner.Scan() { 82 | line := scanner.Text() 83 | stderrWriter.WriteString(line + "\n") 84 | stderrWriter.Flush() 85 | } 86 | }() 87 | 88 | processes = append(processes, &TestProcess{Process: process, Stdout: stdoutWriter, Stderr: stderrWriter}) 89 | } 90 | 91 | fmt.Println("Started loader. Check logs/ directory for outputs.") 92 | 93 | sigc := make(chan os.Signal, 1) 94 | signal.Notify(sigc, syscall.SIGINT, syscall.SIGTERM) 95 | 96 | <-sigc 97 | 98 | os.Exit(0) 99 | } 100 | -------------------------------------------------------------------------------- /logs/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore -------------------------------------------------------------------------------- /remove_logs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | rm -f logs/*.log -------------------------------------------------------------------------------- /test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "math/rand" 6 | "os" 7 | "os/signal" 8 | "syscall" 9 | "time" 10 | ) 11 | 12 | var messages = []string{ 13 | "This is a test message", 14 | "Another test message.", 15 | "Or ANOTHER test message.", 16 | "Or yet ANOTHER test message!!!!", 17 | } 18 | 19 | func Repeat() { 20 | for range time.Tick(time.Second * 1) { 21 | fmt.Println(messages[rand.Intn(len(messages))]) 22 | } 23 | } 24 | 25 | func main() { 26 | // Repeat. 27 | go Repeat() 28 | 29 | sigc := make(chan os.Signal, 1) 30 | signal.Notify(sigc, syscall.SIGINT, syscall.SIGTERM) 31 | 32 | <-sigc 33 | 34 | os.Exit(0) 35 | } 36 | --------------------------------------------------------------------------------