github.com/webonyx/up@v0.7.4-0.20180808230834-91b94e551323/internal/logs/text/text.go (about)

     1  // Package text implements a development-friendly textual handler.
     2  package text
     3  
     4  import (
     5  	"bytes"
     6  	"fmt"
     7  	"io"
     8  	"sync"
     9  	"time"
    10  
    11  	"github.com/apex/log"
    12  	"github.com/dustin/go-humanize"
    13  
    14  	"github.com/apex/up/internal/colors"
    15  	"github.com/apex/up/internal/util"
    16  )
    17  
    18  var (
    19  	spacerPlaceholderBytes = []byte("{{spacer}}")
    20  	spacerBytes            = []byte(colors.Gray(":"))
    21  	newlineBytes           = []byte("\n")
    22  	emptyBytes             = []byte("")
    23  )
    24  
    25  // color function.
    26  type colorFunc func(string) string
    27  
    28  // omit fields.
    29  var omit = map[string]bool{
    30  	"app":     true,
    31  	"stage":   true,
    32  	"region":  true,
    33  	"plugin":  true,
    34  	"commit":  true,
    35  	"version": true,
    36  }
    37  
    38  // Colors mapping.
    39  var Colors = [...]colorFunc{
    40  	log.DebugLevel: colors.Gray,
    41  	log.InfoLevel:  colors.Purple,
    42  	log.WarnLevel:  colors.Yellow,
    43  	log.ErrorLevel: colors.Red,
    44  	log.FatalLevel: colors.Red,
    45  }
    46  
    47  // Strings mapping.
    48  var Strings = [...]string{
    49  	log.DebugLevel: "DEBU",
    50  	log.InfoLevel:  "INFO",
    51  	log.WarnLevel:  "WARN",
    52  	log.ErrorLevel: "ERRO",
    53  	log.FatalLevel: "FATA",
    54  }
    55  
    56  // Handler implementation.
    57  type Handler struct {
    58  	mu     sync.Mutex
    59  	Writer io.Writer
    60  	expand bool
    61  	layout string
    62  }
    63  
    64  // New handler.
    65  func New(w io.Writer) *Handler {
    66  	return &Handler{
    67  		Writer: w,
    68  	}
    69  }
    70  
    71  // WithExpandedFields sets the expanded field state.
    72  func (h *Handler) WithExpandedFields(v bool) *Handler {
    73  	h.expand = v
    74  	return h
    75  }
    76  
    77  // HandleLog implements log.Handler.
    78  func (h *Handler) HandleLog(e *log.Entry) error {
    79  	switch {
    80  	case h.expand:
    81  		return h.handleExpanded(e)
    82  	default:
    83  		return h.handleInline(e)
    84  	}
    85  }
    86  
    87  // handleExpanded fields.
    88  func (h *Handler) handleExpanded(e *log.Entry) error {
    89  	color := Colors[e.Level]
    90  	level := Strings[e.Level]
    91  	names := e.Fields.Names()
    92  
    93  	h.mu.Lock()
    94  	defer h.mu.Unlock()
    95  
    96  	ts := formatDate(e.Timestamp.Local())
    97  	fmt.Fprintf(h.Writer, "  %s %s %s\n", colors.Gray(ts), bold(color(level)), colors.Purple(e.Message))
    98  
    99  	for _, name := range names {
   100  		v := e.Fields.Get(name)
   101  
   102  		if v == "" {
   103  			continue
   104  		}
   105  
   106  		fmt.Fprintf(h.Writer, "    %s%s%v\n", color(name), colors.Gray(": "), value(name, v))
   107  	}
   108  
   109  	if len(names) > 0 {
   110  		fmt.Fprintf(h.Writer, "\n")
   111  	}
   112  
   113  	return nil
   114  }
   115  
   116  // handleInline fields.
   117  func (h *Handler) handleInline(e *log.Entry) error {
   118  	var buf bytes.Buffer
   119  	var fields int
   120  
   121  	color := Colors[e.Level]
   122  	level := Strings[e.Level]
   123  	names := e.Fields.Names()
   124  	ts := formatDate(e.Timestamp.Local())
   125  
   126  	if stage, ok := e.Fields.Get("stage").(string); ok && stage != "" {
   127  		fmt.Fprintf(&buf, "  %s %s %s %s %s{{spacer}}", colors.Gray(ts), bold(color(level)), colors.Gray(stage), colors.Gray(version(e)), colors.Purple(e.Message))
   128  	} else {
   129  		fmt.Fprintf(&buf, "  %s %s %s{{spacer}}", colors.Gray(ts), bold(color(level)), colors.Purple(e.Message))
   130  	}
   131  
   132  	for _, name := range names {
   133  		if omit[name] {
   134  			continue
   135  		}
   136  
   137  		v := e.Fields.Get(name)
   138  
   139  		if v == "" {
   140  			continue
   141  		}
   142  
   143  		fields++
   144  		fmt.Fprintf(&buf, " %s%s%v", color(name), colors.Gray("="), value(name, v))
   145  	}
   146  
   147  	b := buf.Bytes()
   148  
   149  	if fields > 0 {
   150  		b = bytes.Replace(b, spacerPlaceholderBytes, spacerBytes, 1)
   151  	} else {
   152  		b = bytes.Replace(b, spacerPlaceholderBytes, emptyBytes, 1)
   153  	}
   154  
   155  	h.mu.Lock()
   156  	h.Writer.Write(b)
   157  	h.Writer.Write(newlineBytes)
   158  	h.mu.Unlock()
   159  
   160  	return nil
   161  }
   162  
   163  // value returns the formatted value.
   164  func value(name string, v interface{}) interface{} {
   165  	switch name {
   166  	case "size":
   167  		return humanize.Bytes(uint64(util.ToFloat(v)))
   168  	case "duration":
   169  		return time.Millisecond * time.Duration(util.ToFloat(v))
   170  	default:
   171  		return v
   172  	}
   173  }
   174  
   175  // day duration.
   176  var day = time.Hour * 24
   177  
   178  // formatDate formats t relative to now.
   179  func formatDate(t time.Time) string {
   180  	return t.Format(`Jan 2` + util.DateSuffix(t) + ` 03:04:05pm`)
   181  }
   182  
   183  // version returns the entry version via GIT commit or lambda version.
   184  func version(e *log.Entry) string {
   185  	if s, ok := e.Fields.Get("commit").(string); ok && s != "" {
   186  		return s
   187  	}
   188  
   189  	if s, ok := e.Fields.Get("version").(string); ok && s != "" {
   190  		return s
   191  	}
   192  
   193  	return ""
   194  }
   195  
   196  // bold string.
   197  func bold(s string) string {
   198  	return fmt.Sprintf("\033[1m%s\033[0m", s)
   199  }