Customization of the original library will continue from here to make it leaner and more suited for general use in MimirTech projects.
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
|
|
}
|