Gopher2600/logger/log.go
2023-07-10 16:20:43 +01:00

191 lines
3.8 KiB
Go

// This file is part of Gopher2600.
//
// Gopher2600 is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Gopher2600 is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Gopher2600. If not, see <https://www.gnu.org/licenses/>.
package logger
import (
"fmt"
"io"
"strings"
"sync"
"time"
)
// Entry represents a single line/entry in the log.
type Entry struct {
Time time.Time
Tag string
Detail string
Repeated int
}
func (e *Entry) String() string {
s := strings.Builder{}
s.WriteString(fmt.Sprintf("%s: %s", e.Tag, e.Detail))
if e.Repeated > 0 {
s.WriteString(fmt.Sprintf(" (repeat x%d)", e.Repeated+1))
}
return s.String()
}
// not exposing logger to outside of the package. the package level functions
// can be used to log to the central logger.
type logger struct {
crit sync.Mutex
entries []Entry
maxEntries int
// the index of newest entry not seen by the writeRecent() function
recentStart int
// if echo is not nil than write new entry to the io.Writer
echo io.Writer
}
func newLogger(maxEntries int) *logger {
return &logger{
entries: make([]Entry, 0, maxEntries),
maxEntries: maxEntries,
}
}
func (l *logger) log(tag, detail string) {
l.crit.Lock()
defer l.crit.Unlock()
// remove first part of the details string if it's the same as the tag
p := strings.SplitN(detail, ": ", 3)
if len(p) > 1 && p[0] == tag {
detail = strings.Join(p[1:], ": ")
}
last := Entry{}
if len(l.entries) > 0 {
last = l.entries[len(l.entries)-1]
}
// split multi-line log entries and log each separetely
for _, d := range strings.Split(detail, "\n") {
d = strings.TrimSpace(d)
if len(d) == 0 {
continue
}
e := Entry{
Time: time.Now(),
Tag: tag,
Detail: d,
}
if last.Tag == e.Tag && last.Detail == e.Detail {
l.entries[len(l.entries)-1].Repeated++
return
} else {
l.entries = append(l.entries, e)
}
if len(l.entries) > l.maxEntries {
l.recentStart -= l.maxEntries - len(l.entries)
if l.recentStart < 0 {
l.recentStart = 0
}
}
if l.echo != nil {
l.echo.Write([]byte(e.String()))
l.echo.Write([]byte("\n"))
}
}
}
func (l *logger) logf(tag, detail string, args ...interface{}) {
l.log(tag, fmt.Sprintf(detail, args...))
}
func (l *logger) clear() {
l.crit.Lock()
defer l.crit.Unlock()
l.entries = l.entries[:0]
l.recentStart = 0
}
func (l *logger) write(output io.Writer) {
if output == nil {
return
}
l.crit.Lock()
defer l.crit.Unlock()
for _, e := range l.entries {
io.WriteString(output, e.String())
io.WriteString(output, "\n")
}
}
func (l *logger) writeRecent(output io.Writer) {
l.crit.Lock()
defer l.crit.Unlock()
if output != nil {
for _, e := range l.entries[l.recentStart:] {
io.WriteString(output, e.String())
io.WriteString(output, "\n")
}
}
l.recentStart = len(l.entries)
}
func (l *logger) tail(output io.Writer, number int) {
if output == nil {
return
}
l.crit.Lock()
defer l.crit.Unlock()
var n int
n = len(l.entries) - number
if n < 0 {
n = 0
}
for _, e := range l.entries[n:] {
io.WriteString(output, e.String())
io.WriteString(output, "\n")
}
}
func (l *logger) setEcho(output io.Writer, writeRecent bool) {
l.crit.Lock()
l.echo = output
l.crit.Unlock()
if writeRecent {
l.writeRecent(output)
}
}
func (l *logger) borrowLog(f func([]Entry)) {
l.crit.Lock()
defer l.crit.Unlock()
f(l.entries)
}