Commit 1ab28a50 authored by Sebastian Stark's avatar Sebastian Stark

log through a ring buffered proxy io.Writer

  - This enables us to access the last n lines of log output, for
  instance for sending them via email.
parent 9e2eb387
......@@ -252,7 +252,9 @@ func subcmdList(cl clock) {
}
func mainExitCode() int {
logger = log.New(os.Stderr, "", log.Ldate|log.Ltime|log.Lshortfile)
rio := newRingIO(os.Stderr, 5, 100)
logger = log.New(rio, "", log.Ldate|log.Ltime|log.Lshortfile)
log.SetOutput(rio)
var err error
if config, err = loadConfig(); err != nil || config == nil {
if err == flag.ErrHelp {
......
/* See the file "LICENSE.txt" for the full license governing this code. */
package main
import (
"bytes"
"io"
"sync"
)
type RingIO struct {
out io.Writer // the io we are proxying
maxLen int // max number of lines
maxElem int // max length of a line
mu *sync.Mutex
buf map[int][]byte // a map holding the lines
p int // points to the current item in the map
}
// newRingIO instantiates a new RingIO list, which satisfies the io.Writer
// out is an io.Writer that will write the output to the final destination.
// maxLen will be the maximum number of elements kept in the ring buffer. If
// this number is reached, for each Write() the first element will be removed
// before the new element is added.
// maxElem is the maximum size in bytes of an individual element of the list.
func newRingIO(out io.Writer, maxLen int, maxElem int) *RingIO {
return &RingIO{
out: out,
maxLen: maxLen,
maxElem: maxElem,
mu: new(sync.Mutex),
buf: make(map[int][]byte),
p: 0,
}
}
func (r *RingIO) Write(s []byte) (int, error) {
r.mu.Lock()
defer r.mu.Unlock()
var e []byte
// we need to copy the slice because the caller may be reusing it
c := make([]byte, len(s))
copy(c, s)
// write to the io.Writer we are proxying
r.out.Write(c)
ls := len(c)
// if needed, truncate the new entry to maxElem bytes and append a newline
if ls > r.maxElem {
e = append(c[0:r.maxElem], byte('\n'))
} else {
e = c
}
r.buf[r.p] = e
// reset the pointer if maxLen is reached
if r.p < r.maxLen-1 {
r.p += 1
} else {
r.p = 0
}
return len(c), nil
}
// GetAll returns all elements of the ring buffer as a slice of byte slices
func (r *RingIO) GetAll() [][]byte {
r.mu.Lock()
defer r.mu.Unlock()
var ret [][]byte
// return buf, but starting from where the pointer currently points to
for i := r.p; i < r.maxLen; i += 1 {
ret = append(ret, r.buf[i])
}
for i := 0; i < r.p; i += 1 {
ret = append(ret, r.buf[i])
}
return ret
}
// GetAsText concatenates all buffered lines into one byte slice
func (r *RingIO) GetAsText() []byte {
var b bytes.Buffer
for _, l := range r.GetAll() {
b.Write(l)
}
return b.Bytes()
}
/* See the file "LICENSE.txt" for the full license governing this code. */
package main
import (
"bytes"
"reflect"
"testing"
)
type rioTestPair struct {
params [2]int
in [][]byte
out []byte
}
func TestRingIO(t *testing.T) {
tests := []rioTestPair{
{
[2]int{3, 12},
[][]byte{
[]byte("a string\n"),
[]byte("another string\n"),
[]byte("something\n"),
[]byte("else\n"),
},
[]byte("another stri\nsomething\nelse\n"),
},
{
[2]int{2, 10},
[][]byte{
[]byte("a string\n"),
[]byte("another string\n"),
[]byte("something\n"),
[]byte("else\n"),
},
[]byte("something\nelse\n"),
},
{
[2]int{2, 4},
[][]byte{
[]byte("a string"),
[]byte("test1"),
[]byte("test2"),
},
[]byte("test\ntest\n"),
},
{
[2]int{100, 100},
[][]byte{
[]byte("a string"),
[]byte("test1"),
[]byte("test2"),
},
[]byte("a stringtest1test2"),
},
}
var buf bytes.Buffer
for _, tp := range tests {
rio := newRingIO(&buf, tp.params[0], tp.params[1])
for _, l := range tp.in {
rio.Write(l)
}
got := rio.GetAsText()
wanted := tp.out
if !reflect.DeepEqual(got, wanted) {
t.Errorf("wanted:\n>>>\n%s\n<<<\ngot:\n>>>\n%s\n<<<", wanted, got)
}
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment