Marvin Preuss
1d4ae27878
All checks were successful
continuous-integration/drone/push Build is passing
238 lines
5.5 KiB
Go
238 lines
5.5 KiB
Go
// Copyright 2013, Örjan Persson. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
// +build !appengine
|
|
|
|
package logging
|
|
|
|
import (
|
|
"sync"
|
|
"sync/atomic"
|
|
"time"
|
|
"unsafe"
|
|
)
|
|
|
|
// TODO pick one of the memory backends and stick with it or share interface.
|
|
|
|
// InitForTesting is a convenient method when using logging in a test. Once
|
|
// called, the time will be frozen to January 1, 1970 UTC.
|
|
func InitForTesting(level Level) *MemoryBackend {
|
|
Reset()
|
|
|
|
memoryBackend := NewMemoryBackend(10240)
|
|
|
|
leveledBackend := AddModuleLevel(memoryBackend)
|
|
leveledBackend.SetLevel(level, "")
|
|
SetBackend(leveledBackend)
|
|
|
|
timeNow = func() time.Time {
|
|
return time.Unix(0, 0).UTC()
|
|
}
|
|
return memoryBackend
|
|
}
|
|
|
|
// Node is a record node pointing to an optional next node.
|
|
type node struct {
|
|
next *node
|
|
Record *Record
|
|
}
|
|
|
|
// Next returns the next record node. If there's no node available, it will
|
|
// return nil.
|
|
func (n *node) Next() *node {
|
|
return n.next
|
|
}
|
|
|
|
// MemoryBackend is a simple memory based logging backend that will not produce
|
|
// any output but merly keep records, up to the given size, in memory.
|
|
type MemoryBackend struct {
|
|
size int32
|
|
maxSize int32
|
|
head, tail unsafe.Pointer
|
|
}
|
|
|
|
// NewMemoryBackend creates a simple in-memory logging backend.
|
|
func NewMemoryBackend(size int) *MemoryBackend {
|
|
return &MemoryBackend{maxSize: int32(size)}
|
|
}
|
|
|
|
// Log implements the Log method required by Backend.
|
|
func (b *MemoryBackend) Log(level Level, calldepth int, rec *Record) error {
|
|
var size int32
|
|
|
|
n := &node{Record: rec}
|
|
np := unsafe.Pointer(n)
|
|
|
|
// Add the record to the tail. If there's no records available, tail and
|
|
// head will both be nil. When we successfully set the tail and the previous
|
|
// value was nil, it's safe to set the head to the current value too.
|
|
for {
|
|
tailp := b.tail
|
|
swapped := atomic.CompareAndSwapPointer(
|
|
&b.tail,
|
|
tailp,
|
|
np,
|
|
)
|
|
if swapped == true {
|
|
if tailp == nil {
|
|
b.head = np
|
|
} else {
|
|
(*node)(tailp).next = n
|
|
}
|
|
size = atomic.AddInt32(&b.size, 1)
|
|
break
|
|
}
|
|
}
|
|
|
|
// Since one record was added, we might have overflowed the list. Remove
|
|
// a record if that is the case. The size will fluctate a bit, but
|
|
// eventual consistent.
|
|
if b.maxSize > 0 && size > b.maxSize {
|
|
for {
|
|
headp := b.head
|
|
head := (*node)(b.head)
|
|
if head.next == nil {
|
|
break
|
|
}
|
|
swapped := atomic.CompareAndSwapPointer(
|
|
&b.head,
|
|
headp,
|
|
unsafe.Pointer(head.next),
|
|
)
|
|
if swapped == true {
|
|
atomic.AddInt32(&b.size, -1)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Head returns the oldest record node kept in memory. It can be used to
|
|
// iterate over records, one by one, up to the last record.
|
|
//
|
|
// Note: new records can get added while iterating. Hence the number of records
|
|
// iterated over might be larger than the maximum size.
|
|
func (b *MemoryBackend) Head() *node {
|
|
return (*node)(b.head)
|
|
}
|
|
|
|
type event int
|
|
|
|
const (
|
|
eventFlush event = iota
|
|
eventStop
|
|
)
|
|
|
|
// ChannelMemoryBackend is very similar to the MemoryBackend, except that it
|
|
// internally utilizes a channel.
|
|
type ChannelMemoryBackend struct {
|
|
maxSize int
|
|
size int
|
|
incoming chan *Record
|
|
events chan event
|
|
mu sync.Mutex
|
|
running bool
|
|
flushWg sync.WaitGroup
|
|
stopWg sync.WaitGroup
|
|
head, tail *node
|
|
}
|
|
|
|
// NewChannelMemoryBackend creates a simple in-memory logging backend which
|
|
// utilizes a go channel for communication.
|
|
//
|
|
// Start will automatically be called by this function.
|
|
func NewChannelMemoryBackend(size int) *ChannelMemoryBackend {
|
|
backend := &ChannelMemoryBackend{
|
|
maxSize: size,
|
|
incoming: make(chan *Record, 1024),
|
|
events: make(chan event),
|
|
}
|
|
backend.Start()
|
|
return backend
|
|
}
|
|
|
|
// Start launches the internal goroutine which starts processing data from the
|
|
// input channel.
|
|
func (b *ChannelMemoryBackend) Start() {
|
|
b.mu.Lock()
|
|
defer b.mu.Unlock()
|
|
|
|
// Launch the goroutine unless it's already running.
|
|
if b.running != true {
|
|
b.running = true
|
|
b.stopWg.Add(1)
|
|
go b.process()
|
|
}
|
|
}
|
|
|
|
func (b *ChannelMemoryBackend) process() {
|
|
defer b.stopWg.Done()
|
|
for {
|
|
select {
|
|
case rec := <-b.incoming:
|
|
b.insertRecord(rec)
|
|
case e := <-b.events:
|
|
switch e {
|
|
case eventStop:
|
|
return
|
|
case eventFlush:
|
|
for len(b.incoming) > 0 {
|
|
b.insertRecord(<-b.incoming)
|
|
}
|
|
b.flushWg.Done()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (b *ChannelMemoryBackend) insertRecord(rec *Record) {
|
|
prev := b.tail
|
|
b.tail = &node{Record: rec}
|
|
if prev == nil {
|
|
b.head = b.tail
|
|
} else {
|
|
prev.next = b.tail
|
|
}
|
|
|
|
if b.maxSize > 0 && b.size >= b.maxSize {
|
|
b.head = b.head.next
|
|
} else {
|
|
b.size++
|
|
}
|
|
}
|
|
|
|
// Flush waits until all records in the buffered channel have been processed.
|
|
func (b *ChannelMemoryBackend) Flush() {
|
|
b.flushWg.Add(1)
|
|
b.events <- eventFlush
|
|
b.flushWg.Wait()
|
|
}
|
|
|
|
// Stop signals the internal goroutine to exit and waits until it have.
|
|
func (b *ChannelMemoryBackend) Stop() {
|
|
b.mu.Lock()
|
|
if b.running == true {
|
|
b.running = false
|
|
b.events <- eventStop
|
|
}
|
|
b.mu.Unlock()
|
|
b.stopWg.Wait()
|
|
}
|
|
|
|
// Log implements the Log method required by Backend.
|
|
func (b *ChannelMemoryBackend) Log(level Level, calldepth int, rec *Record) error {
|
|
b.incoming <- rec
|
|
return nil
|
|
}
|
|
|
|
// Head returns the oldest record node kept in memory. It can be used to
|
|
// iterate over records, one by one, up to the last record.
|
|
//
|
|
// Note: new records can get added while iterating. Hence the number of records
|
|
// iterated over might be larger than the maximum size.
|
|
func (b *ChannelMemoryBackend) Head() *node {
|
|
return b.head
|
|
}
|