Skip to content
Permalink
Branch: master
Find file Copy path
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
238 lines (207 sloc) 5.45 KB
// 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
}
You can’t perform that action at this time.