Skip to content

Commit

Permalink
services/serve: add prefixes to verbose logs (#129)
Browse files Browse the repository at this point in the history
* services/serve: add prefixes to verbose logs

* added new pkg/lineprefixer.
* added new pkg/prefixgen.
* added options builder to pkg/cmdrunner/step.
* cosmetics.

* fix tests & add missing file

* add STARPORT prefix to native logs

* add buildLog log type.
* reorganize verbose logs.

* fix linter

* fix verbose logs
  • Loading branch information
ilgooz authored Aug 7, 2020
1 parent 4fcbaae commit 9c311df
Show file tree
Hide file tree
Showing 9 changed files with 505 additions and 118 deletions.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -29,5 +29,5 @@ require (
golang.org/x/mod v0.3.0
golang.org/x/net v0.0.0-20200625001655-4c5254603344 // indirect
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208
golang.org/x/sys v0.0.0-20200806060901-a37d78b92225 // indirect
golang.org/x/sys v0.0.0-20200806125547-5acd03effb82 // indirect
)
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -316,8 +316,8 @@ golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200806060901-a37d78b92225 h1:a5kp7Ohh+lqGCGHUBQdPwGHTJXKNhVVWp34F+ncDC9M=
golang.org/x/sys v0.0.0-20200806060901-a37d78b92225/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200806125547-5acd03effb82 h1:6cBnXxYO+CiRVrChvCosSv7magqTPbyAgz1M8iOv5wM=
golang.org/x/sys v0.0.0-20200806125547-5acd03effb82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
Expand Down
11 changes: 11 additions & 0 deletions starport/pkg/cmdrunner/step/step.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,17 @@ type Step struct {

type Option func(*Step)

type Options []Option

func NewOptions() Options {
return Options{}
}

func (o Options) Add(options ...Option) Options {
o = append(o, options...)
return o
}

func New(options ...Option) *Step {
s := &Step{
PreExec: func() error { return nil },
Expand Down
47 changes: 47 additions & 0 deletions starport/pkg/lineprefixer/lineprefixer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// Package lineprefixer is a helpers to add prefixes to new lines.
package lineprefixer

import (
"bytes"
"io"
)

// Writer is a prefixed line writer.
type Writer struct {
prefix []byte
w io.Writer
shouldPrefix bool
}

// NewWriter returns a new Writer that adds prefixes to each line
// written. It then writes prefixed data stream into w.
func NewWriter(w io.Writer, prefix string) *Writer {
return &Writer{
w: w,
prefix: []byte(prefix),
shouldPrefix: true,
}
}

// Write implements io.Writer.
func (p *Writer) Write(b []byte) (n int, err error) {
var (
blen = len(b)
lastChar = b[blen-1]
newLine = byte('\n')
snewLine = []byte{newLine}
replaceCount = bytes.Count(b, snewLine)
)
if lastChar == newLine {
replaceCount--
}
b = bytes.Replace(b, snewLine, append(snewLine, p.prefix...), replaceCount)
if p.shouldPrefix {
b = append(p.prefix, b...)
}
p.shouldPrefix = lastChar == newLine
if _, err := p.w.Write(b); err != nil {
return 0, err
}
return blen, nil
}
27 changes: 27 additions & 0 deletions starport/pkg/lineprefixer/lineprefixer_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package lineprefixer

import (
"bytes"
"io"
"strings"
"testing"

"github.com/stretchr/testify/require"
)

func TestWriter(t *testing.T) {
logs := `hello,
this
is
Starport!`
buf := bytes.Buffer{}
w := NewWriter(&buf, "[TENDERMINT] ")
_, err := io.Copy(w, strings.NewReader(logs))
require.NoError(t, err)
require.Equal(t, `[TENDERMINT] hello,
[TENDERMINT] this
[TENDERMINT] is
[TENDERMINT] Starport!`,
buf.String(),
)
}
88 changes: 88 additions & 0 deletions starport/pkg/prefixgen/prefixgen.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
// Package prefixgen is a prefix generation helper for log messages
// and any other kind.
package prefixgen

import (
"fmt"
"strings"

"github.com/gookit/color"
)

// Prefixer generates prefixes.
type Prefixer struct {
format string
color uint8
left, right string
convertUppercase bool
}

// Option configures Prefixer.
type Option func(p *Prefixer)

// Color sets color to the prefix.
func Color(color uint8) Option {
return func(p *Prefixer) {
p.color = color
}
}

// SquareBrackets adds square brackets to the prefix.
func SquareBrackets() Option {
return func(p *Prefixer) {
p.left = "["
p.right = "]"
}
}

// SpaceRight adds rights space to the prefix.
func SpaceRight() Option {
return func(p *Prefixer) {
p.right += " "
}
}

// Uppercase formats the prefix to uppercase.
func Uppercase() Option {
return func(p *Prefixer) {
p.convertUppercase = true
}
}

// Common holds some common prefix options and extends those
// options by given options.
func Common(options ...Option) []Option {
return append([]Option{
SquareBrackets(),
SpaceRight(),
Uppercase(),
}, options...)
}

// New creates a new Prefixer with format and options.
// Format is an fmt.Sprintf() like format to dynamically create prefix texts
// as needed.
func New(format string, options ...Option) *Prefixer {
p := &Prefixer{
format: format,
}
for _, o := range options {
o(p)
}
return p
}

// Gen generates a new prefix by applying s to format given during New().
func (p *Prefixer) Gen(s ...interface{}) string {
format := p.format
format = p.left + format
format = format + p.right
prefix := fmt.Sprintf(format, s...)
if p.convertUppercase {
prefix = strings.ToUpper(prefix)
}
if p.color != 0 {
return color.C256(p.color).Sprint(prefix)
}
return prefix
}
23 changes: 23 additions & 0 deletions starport/pkg/prefixgen/prefixgen_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package prefixgen

import (
"testing"

"github.com/stretchr/testify/require"
)

func TestGen(t *testing.T) {
cases := []struct {
expected string
given string
}{
{"[TENDERMINT] ", New("Tendermint", Common()...).Gen()},
{"Tendermint", New("Tendermint").Gen()},
{"appd", New("%sd").Gen("app")},
}
for _, tt := range cases {
t.Run(tt.expected, func(t *testing.T) {
require.Equal(t, tt.expected, tt.given)
})
}
}
75 changes: 75 additions & 0 deletions starport/services/serve/log.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package starportserve

import (
"io"
"os"
"strings"

"github.com/tendermint/starport/starport/pkg/cmdrunner/step"
"github.com/tendermint/starport/starport/pkg/lineprefixer"
"github.com/tendermint/starport/starport/pkg/prefixgen"
)

// prefixes holds prefix configuration for logs messages.
var prefixes = map[logType]struct {
Name string
Color uint8
}{
logStarport: {"starport", 202},
logBuild: {"build", 203},
logAppd: {"%sd", 204},
logAppcli: {"%scli", 205},
}

// logType represents the different types of logs.
type logType int

const (
logStarport logType = iota
logBuild
logAppd
logAppcli
)

// std returns the cmdrunner steps to configure stdout and stderr to output logs by logType.
func (s *starportServe) stdSteps(logType logType) []step.Option {
std := s.stdLog(logType)
return []step.Option{
step.Stdout(std.out),
step.Stderr(std.err),
}
}

type std struct {
out, err io.Writer
}

// std returns the stdout and stderr to output logs by logType.
func (s *starportServe) stdLog(logType logType) std {
prefixed := func(w io.Writer) *lineprefixer.Writer {
var (
prefix = prefixes[logType]
prefixStr string
options = prefixgen.Common(prefixgen.Color(prefix.Color))
gen = prefixgen.New(prefix.Name, options...)
)
if strings.Count(prefix.Name, "%s") > 0 {
prefixStr = gen.Gen(s.app.Name)
} else {
prefixStr = gen.Gen()
}
return lineprefixer.NewWriter(w, prefixStr)
}
var (
stdout io.Writer = prefixed(s.stdout)
stderr io.Writer = prefixed(s.stderr)
)
if logType == logStarport && !s.verbose {
stdout = os.Stdout
stderr = os.Stderr
}
return std{
out: stdout,
err: stderr,
}
}
Loading

0 comments on commit 9c311df

Please sign in to comment.