Skip to content

Commit

Permalink
Better Logs Management
Browse files Browse the repository at this point in the history
  • Loading branch information
F1bonacc1 committed May 30, 2022
1 parent 2389a4a commit 2f9880b
Show file tree
Hide file tree
Showing 10 changed files with 243 additions and 50 deletions.
16 changes: 12 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
## Process Compose

[![made-with-Go](https://img.shields.io/badge/Made%20with-Go-1f425f.svg)](https://go.dev/) [![Maintenance](https://img.shields.io/badge/Maintained%3F-yes-green.svg)](https://GitHub.com/Naereen/StrapDown.js/graphs/commit-activity) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](http://makeapullrequest.com) ![Go Report](https://goreportcard.com/badge/github.com/F1bonacc1/process-compose)
[![made-with-Go](https://img.shields.io/badge/Made%20with-Go-1f425f.svg)](https://go.dev/) [![Maintenance](https://img.shields.io/badge/Maintained%3F-yes-green.svg)](https://GitHub.com/Naereen/StrapDown.js/graphs/commit-activity) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](http://makeapullrequest.com) ![Go Report](https://goreportcard.com/badge/github.com/F1bonacc1/process-compose)[![Releases](https://img.shields.io/github/downloads/F1bonacc1/process-compose/total.svg)]()

**What?** Process Compose is like [docker-compose](https://github.com/docker/compose), but for orchestrating a suite of processes, not containers.
Process Compose is like [docker-compose](https://github.com/docker/compose), but for orchestrating a suite of processes, not containers.

**Why?** Because sometimes you just don't want to deal with docker files, volume definitions, networks and docker registries.

**How?** Declare all the system processes dependencies in a single YAML (don't judge) file, monitor the execution and output with a built-in UI.

Main use cases would be:

* Processes execution (in parallel or serially)
Expand Down Expand Up @@ -192,7 +190,17 @@ TUI is the default run mode, but it's possible to disable it:
./process-compose -t=false
```

Control the UI log buffer size:

```yaml
log_level: info
log_length: 1200 #default: 1000
processes:
process2:
command: "ls -R /"
```

**Note**: Using a too large buffer will put a significant penalty on your CPU.

#### ✅ <u>Logger</u>

Expand Down
1 change: 1 addition & 0 deletions process-compose.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
version: "0.5"
log_level: debug
log_length: 3000
processes:
process0:
command: "ls ddd"
Expand Down
1 change: 1 addition & 0 deletions src/app/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ type Project struct {
Version string `yaml:"version"`
LogLocation string `yaml:"log_location,omitempty"`
LogLevel string `yaml:"log_level,omitempty"`
LogLength int `yaml:"log_length,omitempty"`
Processes Processes `yaml:"processes"`
Environment []string `yaml:"environment,omitempty"`

Expand Down
53 changes: 49 additions & 4 deletions src/app/project.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,25 @@ import (
"strings"

"github.com/f1bonacc1/process-compose/src/pclog"

"github.com/joho/godotenv"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"gopkg.in/yaml.v2"
)

const (
DEFAULT_LOG_LENGTH = 1000
)

var PROJ *Project

func (p *Project) Run() {
func (p *Project) init() {
p.initProcessStates()
p.initProcessLogs()
}

func (p *Project) Run() {
p.runningProcesses = make(map[string]*Process)
runOrder := []ProcessConfig{}
p.WithProcesses([]string{}, func(process ProcessConfig) error {
Expand Down Expand Up @@ -51,7 +59,9 @@ func (p *Project) runProcess(proc ProcessConfig) {
}
procLog, err := p.getProcessLog(proc.Name)
if err != nil {
procLog = pclog.NewLogBuffer(1000)
// we shouldn't get here
log.Error().Msgf("Error: Can't get log: %s using empty buffer", err.Error())
procLog = pclog.NewLogBuffer(0)
}
process := NewProcess(p.Environment, procLogger, proc, p.GetProcessState(proc.Name), procLog, 1)
p.addRunningProcess(process)
Expand Down Expand Up @@ -109,7 +119,7 @@ func (p *Project) initProcessStates() {
func (p *Project) initProcessLogs() {
p.processLogs = make(map[string]*pclog.ProcessLogBuffer)
for key := range p.Processes {
p.processLogs[key] = pclog.NewLogBuffer(1000)
p.processLogs[key] = pclog.NewLogBuffer(p.LogLength)
}
}

Expand Down Expand Up @@ -189,7 +199,40 @@ func (p *Project) GetProcessLog(name string, offsetFromEnd, limit int) ([]string
if err != nil {
return nil, err
}
return logs.GetLog(offsetFromEnd, limit), nil
return logs.GetLogRange(offsetFromEnd, limit), nil
}

func (p *Project) GetProcessLogLine(name string, lineIndex int) (string, error) {
logs, err := p.getProcessLog(name)
if err != nil {
return "", err
}
return logs.GetLogLine(lineIndex), nil
}

func (p *Project) GetProcessLogLength(name string) int {
logs, err := p.getProcessLog(name)
if err != nil {
return 0
}
return logs.GetLogLength()
}

func (p *Project) GetLogsAndSubscribe(name string, observer pclog.PcLogObserver) {

logs, err := p.getProcessLog(name)
if err != nil {
return
}
logs.GetLogsAndSubscribe(observer)
}

func (p *Project) UnSubscribeLogger(name string) {
logs, err := p.getProcessLog(name)
if err != nil {
return
}
logs.UnSubscribe()
}

func (p *Project) getProcesses(names ...string) ([]ProcessConfig, error) {
Expand Down Expand Up @@ -287,6 +330,7 @@ func CreateProject(inputFile string) *Project {
yamlFile = []byte(os.ExpandEnv(string(yamlFile)))

var project Project
project.LogLength = DEFAULT_LOG_LENGTH
err = yaml.Unmarshal(yamlFile, &project)
if err != nil {
log.Fatal().Msg(err.Error())
Expand All @@ -302,6 +346,7 @@ func CreateProject(inputFile string) *Project {

}
PROJ = &project
project.init()
return &project
}

Expand Down
2 changes: 1 addition & 1 deletion src/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ func main() {
if isTui {
defer quiet()()
go project.Run()
tui.SetupTui(version)
tui.SetupTui(version, project.LogLength)
} else {
project.Run()
}
Expand Down
File renamed without changes.
56 changes: 51 additions & 5 deletions src/pclog/process_log_buffer.go
Original file line number Diff line number Diff line change
@@ -1,29 +1,42 @@
package pclog

import (
"sync"
)

const (
slack = 100
)

type ProcessLogBuffer struct {
buffer []string
size int
buffer []string
size int
observer PcLogObserver
mx sync.Mutex
}

func NewLogBuffer(size int) *ProcessLogBuffer {
return &ProcessLogBuffer{
size: size,
buffer: make([]string, 0, size),
size: size,
buffer: make([]string, 0, size+slack),
observer: nil,
}
}

func (b *ProcessLogBuffer) Write(message string) {
b.mx.Lock()
defer b.mx.Unlock()
b.buffer = append(b.buffer, message)
if len(b.buffer) > b.size+slack {
b.buffer = b.buffer[slack:]
}
if b.observer != nil {
b.observer.AddLine(message)
}

}

func (b ProcessLogBuffer) GetLog(offsetFromEnd, limit int) []string {
func (b *ProcessLogBuffer) GetLogRange(offsetFromEnd, limit int) []string {
if len(b.buffer) == 0 {
return []string{}
}
Expand All @@ -48,3 +61,36 @@ func (b ProcessLogBuffer) GetLog(offsetFromEnd, limit int) []string {
}
return b.buffer[len(b.buffer)-offsetFromEnd : offsetFromEnd+limit]
}

func (b *ProcessLogBuffer) GetLogLine(lineIndex int) string {
if len(b.buffer) == 0 {
return ""
}

if lineIndex >= len(b.buffer) {
lineIndex = len(b.buffer) - 1
}

if lineIndex < 0 {
lineIndex = 0
}

return b.buffer[lineIndex]
}

func (b *ProcessLogBuffer) GetLogLength() int {
return len(b.buffer)
}

func (b *ProcessLogBuffer) GetLogsAndSubscribe(observer PcLogObserver) {
b.mx.Lock()
defer b.mx.Unlock()
observer.SetLines(b.buffer)
b.observer = observer
}

func (b *ProcessLogBuffer) UnSubscribe() {
b.mx.Lock()
defer b.mx.Unlock()
b.observer = nil
}
63 changes: 63 additions & 0 deletions src/tui/log_viewer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package tui

import (
"fmt"
"strings"
"sync"

"github.com/rivo/tview"
)

type LogView struct {
tview.TextView
isWrapOn bool
buffer *strings.Builder
mx sync.Mutex
}

func NewLogView(maxLines int) *LogView {
l := &LogView{
isWrapOn: true,
TextView: *tview.NewTextView().SetDynamicColors(true).SetScrollable(true).SetMaxLines(maxLines),
buffer: &strings.Builder{},
}
l.SetBorder(true)
return l
}

func (l *LogView) AddLine(line string) {
l.mx.Lock()
defer l.mx.Unlock()
if strings.Contains(strings.ToLower(line), "error") {
fmt.Fprintf(l.buffer, "[deeppink]%s[-:-:-]\n", tview.Escape(line))
} else {
fmt.Fprintf(l.buffer, "%s\n", tview.Escape(line))
}
}

func (l *LogView) AddLines(lines []string) {
for _, line := range lines {
l.AddLine(line)
}
}

func (l *LogView) SetLines(lines []string) {
l.Clear()
l.AddLines(lines)
}

func (l *LogView) ToggleWrap() {
l.isWrapOn = !l.isWrapOn
l.SetWrap(l.isWrapOn)
}

func (l *LogView) IsWrapOn() bool {
return l.isWrapOn
}

func (l *LogView) Flush() {
l.mx.Lock()
defer l.mx.Unlock()
l.Write([]byte(l.buffer.String()))
l.buffer.Reset()
}
Loading

0 comments on commit 2f9880b

Please sign in to comment.