// Copyright 2020 Tymoteusz Blazejczyk
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package logger
import (
"bytes"
)
// A Buffer represents a log handler object for logging messages using buffer
// object.
type Buffer struct {
buffer bytes.Buffer
stream *Stream
}
// NewBuffer creates a new buffer log handler object.
func NewBuffer() *Buffer {
b := &Buffer{
stream: NewStream(),
}
b.stream.writer = &b.buffer
return b
}
// SetStreamHandler sets custom stream handler.
func (b *Buffer) SetStreamHandler(handler StreamHandler) *Buffer {
b.stream.SetStreamHandler(handler)
return b
}
// GetBuffer returns internal buffer object.
func (b *Buffer) GetBuffer() *bytes.Buffer {
b.stream.RLock()
defer b.stream.RUnlock()
return &b.buffer
}
// Length returns the number of bytes of the unread portion of the buffer.
func (b *Buffer) Length() int {
b.stream.RLock()
defer b.stream.RUnlock()
return b.buffer.Len()
}
// String returns the contents of the unread portion of the buffer as a string.
func (b *Buffer) String() string {
b.stream.RLock()
defer b.stream.RUnlock()
return b.buffer.String()
}
// Bytes returns a slice of length b.Length() holding the unread portion of
// the buffer. The slice is valid for use only until the next buffer
// modification.
func (b *Buffer) Bytes() []byte {
b.stream.RLock()
defer b.stream.RUnlock()
return b.buffer.Bytes()
}
// Reset resets the buffer to be empty, but it retains the underlying storage for use by future writes.
func (b *Buffer) Reset() {
b.stream.Lock()
defer b.stream.Unlock()
b.buffer.Reset()
}
// Enable enables log handler.
func (b *Buffer) Enable() Handler {
return b.stream.Enable()
}
// Disable disabled log handler.
func (b *Buffer) Disable() Handler {
return b.stream.Disable()
}
// IsEnabled returns if log handler is enabled.
func (b *Buffer) IsEnabled() bool {
return b.stream.IsEnabled()
}
// SetFormatter sets log formatter.
func (b *Buffer) SetFormatter(formatter *Formatter) Handler {
return b.stream.SetFormatter(formatter)
}
// GetFormatter returns log formatter.
func (b *Buffer) GetFormatter() *Formatter {
return b.stream.GetFormatter()
}
// SetLevel sets log level.
func (b *Buffer) SetLevel(level int) Handler {
return b.stream.SetLevel(level)
}
// SetMinimumLevel sets minimum log level.
func (b *Buffer) SetMinimumLevel(level int) Handler {
return b.stream.SetMinimumLevel(level)
}
// GetMinimumLevel returns minimum log level.
func (b *Buffer) GetMinimumLevel() int {
return b.stream.GetMinimumLevel()
}
// SetMaximumLevel sets maximum log level.
func (b *Buffer) SetMaximumLevel(level int) Handler {
return b.stream.SetMaximumLevel(level)
}
// GetMaximumLevel returns maximum log level.
func (b *Buffer) GetMaximumLevel() int {
return b.stream.GetMaximumLevel()
}
// SetLevelRange sets minimum and maximum log level values.
func (b *Buffer) SetLevelRange(min, max int) Handler {
return b.stream.SetLevelRange(min, max)
}
// GetLevelRange returns minimum and maximum log level values.
func (b *Buffer) GetLevelRange() (min, max int) {
return b.stream.GetLevelRange()
}
// Emit logs messages from logger using buffer.
func (b *Buffer) Emit(record *Record) error {
return b.stream.Emit(record)
}
// Close closes buffer.
func (b *Buffer) Close() error {
return b.stream.Close()
}
// Copyright 2020 Tymoteusz Blazejczyk
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package logger
import (
"io"
"os"
)
// These constants define default values for File log handler.
const (
DefaultFileName = "log"
DefaultFileMode = 0644
DefaultFileFlags = os.O_CREATE | os.O_APPEND | os.O_WRONLY
)
// A File represents a log handler object for logging messages to file.
type File struct {
name string
stream *Stream
flags int
mode os.FileMode
}
// NewFile creates a new File log handler object.
func NewFile() *File {
f := &File{
name: DefaultFileName,
mode: DefaultFileMode,
flags: DefaultFileFlags,
stream: NewStream(),
}
f.stream.SetOpener(f)
return f
}
// SetStreamHandler sets custom stream handler.
func (f *File) SetStreamHandler(handler StreamHandler) *File {
f.stream.SetStreamHandler(handler)
return f
}
// Open file.
func (f *File) Open() (io.WriteCloser, error) {
return os.OpenFile(f.name, f.flags, f.mode)
}
// Enable enables log handler.
func (f *File) Enable() Handler {
return f.stream.Enable()
}
// Disable disabled log handler.
func (f *File) Disable() Handler {
return f.stream.Disable()
}
// IsEnabled returns if log handler is enabled.
func (f *File) IsEnabled() bool {
return f.stream.IsEnabled()
}
// SetFormatter sets Formatter.
func (f *File) SetFormatter(formatter *Formatter) Handler {
return f.stream.SetFormatter(formatter)
}
// GetFormatter returns Formatter.
func (f *File) GetFormatter() *Formatter {
return f.stream.GetFormatter()
}
// SetLevel sets log level.
func (f *File) SetLevel(level int) Handler {
return f.stream.SetLevel(level)
}
// SetMinimumLevel sets minimum log level.
func (f *File) SetMinimumLevel(level int) Handler {
return f.stream.SetMinimumLevel(level)
}
// GetMinimumLevel returns minimum log level.
func (f *File) GetMinimumLevel() int {
return f.stream.GetMinimumLevel()
}
// SetMaximumLevel sets maximum log level.
func (f *File) SetMaximumLevel(level int) Handler {
return f.stream.SetMaximumLevel(level)
}
// GetMaximumLevel returns maximum log level.
func (f *File) GetMaximumLevel() int {
return f.stream.GetMaximumLevel()
}
// SetLevelRange sets minimum and maximum log level values.
func (f *File) SetLevelRange(min, max int) Handler {
return f.stream.SetLevelRange(min, max)
}
// GetLevelRange returns minimum and maximum log level values.
func (f *File) GetLevelRange() (min, max int) {
return f.stream.GetLevelRange()
}
// SetName sets file name used for log messages.
func (f *File) SetName(name string) *File {
f.stream.Lock()
defer f.stream.Unlock()
if f.name != name {
f.name = name
f.stream.Reopen()
}
return f
}
// GetName sets file name used for log messages.
func (f *File) GetName() string {
f.stream.RLock()
defer f.stream.RUnlock()
return f.name
}
// SetFlags sets file flags from os package.
func (f *File) SetFlags(flags int) *File {
f.stream.Lock()
defer f.stream.Unlock()
if f.flags != flags {
f.flags = flags
f.stream.Reopen()
}
return f
}
// GetFlags returns file flags.
func (f *File) GetFlags() int {
f.stream.RLock()
defer f.stream.RUnlock()
return f.flags
}
// SetMode sets file mode/permissions.
func (f *File) SetMode(mode os.FileMode) *File {
f.stream.Lock()
defer f.stream.Unlock()
if f.mode != mode {
f.mode = mode
f.stream.Reopen()
}
return f
}
// GetMode returns file mode/permissions.
func (f *File) GetMode() os.FileMode {
f.stream.RLock()
defer f.stream.RUnlock()
return f.mode
}
// Emit logs messages from Logger to file.
func (f *File) Emit(record *Record) error {
return f.stream.Emit(record)
}
// Close closes opened file.
func (f *File) Close() error {
return f.stream.Close()
}
// Copyright 2020 Tymoteusz Blazejczyk
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package logger
import (
"bytes"
"fmt"
"os"
"path/filepath"
"reflect"
"strconv"
"strings"
"sync"
"text/template"
"time"
)
// These constants define default values for Formatter.
const (
DefaultDateFormat = "{year}-{month}-{day} {hour}:{minute}:{second},{millisecond}"
DefaultFormat = "{date} - {Level | printf \"%-8s\"} - {file}:{line}:{function}(): {message}"
DefaultPlaceholder = "p"
kilo = 1e3
mega = 1e6
percentage = 100
)
// FormatterFuncs defines map of template functions.
type FormatterFuncs map[string]interface{}
// A Formatter represents a formatter object used by log handler to format log
// message.
type Formatter struct {
format string
dateFormat string
template *template.Template
placeholder string
timeBuffer *bytes.Buffer
formatBuffer *bytes.Buffer
messageBuffer *bytes.Buffer
mutex sync.RWMutex
usedArguments map[int]bool
}
// NewFormatter creates a new Formatter object with default format settings.
func NewFormatter() *Formatter {
f := &Formatter{
format: DefaultFormat,
dateFormat: DefaultDateFormat,
template: template.New("").Delims("{", "}"),
placeholder: DefaultPlaceholder,
timeBuffer: new(bytes.Buffer),
formatBuffer: new(bytes.Buffer),
messageBuffer: new(bytes.Buffer),
}
return f
}
// Reset resets Formatter.
func (f *Formatter) Reset() *Formatter {
f.mutex.Lock()
defer f.mutex.Unlock()
f.format = DefaultFormat
f.dateFormat = DefaultDateFormat
f.placeholder = DefaultPlaceholder
return f
}
// SetPlaceholder sets placeholder string prefix used for automatic and
// positional placeholders to format log message.
func (f *Formatter) SetPlaceholder(placeholder string) *Formatter {
f.mutex.Lock()
defer f.mutex.Unlock()
f.placeholder = placeholder
return f
}
// GetPlaceholder returns placeholder string prefix used for automatic and
// positional placeholders to format log message.
func (f *Formatter) GetPlaceholder() string {
f.mutex.RLock()
defer f.mutex.RUnlock()
return f.placeholder
}
// AddFuncs adds template functions to format log message.
func (f *Formatter) AddFuncs(funcs FormatterFuncs) *Formatter {
f.mutex.Lock()
defer f.mutex.Unlock()
f.template.Funcs(template.FuncMap(funcs))
return f
}
// SetFormat sets format string used for formatting log message.
func (f *Formatter) SetFormat(format string) *Formatter {
f.mutex.Lock()
defer f.mutex.Unlock()
f.format = format
return f
}
// GetFormat returns format string used for formatting log message.
func (f *Formatter) GetFormat() string {
f.mutex.RLock()
defer f.mutex.RUnlock()
return f.format
}
// SetDateFormat sets format string used for formatting date in log message.
func (f *Formatter) SetDateFormat(dateFormat string) *Formatter {
f.mutex.Lock()
defer f.mutex.Unlock()
f.dateFormat = dateFormat
return f
}
// GetDateFormat returns format string used for formatting date in log message.
func (f *Formatter) GetDateFormat() string {
f.mutex.RLock()
defer f.mutex.RUnlock()
return f.format
}
// Format returns formatted log message string based on provided log record
// object.
func (f *Formatter) Format(record *Record) (string, error) {
f.mutex.Lock()
defer f.mutex.Unlock()
f.template.Funcs(f.getRecordFuncs(record))
message, err := f.formatString(f.template, f.formatBuffer, f.format, nil)
if err != nil {
return "", NewRuntimeError("cannot format record", err)
}
return message, nil
}
// FormatTime returns formatted date string based on provided log record object.
func (f *Formatter) FormatTime(record *Record) (string, error) {
f.mutex.Lock()
defer f.mutex.Unlock()
f.template.Funcs(f.getRecordFuncs(record))
message, err := f.formatString(f.template, f.timeBuffer, f.dateFormat, nil)
if err != nil {
return "", NewRuntimeError("cannot format time", err)
}
return message, nil
}
// FormatMessage returns formatted user message string based on provided log
// record object.
func (f *Formatter) FormatMessage(record *Record) (string, error) {
f.mutex.Lock()
defer f.mutex.Unlock()
message, err := f.formatMessageRecord(record)
if err != nil {
return "", NewRuntimeError("cannot format message", err)
}
return message, nil
}
// formatMessageUnsafe returns formatted user message string based on provided log
// record object.
func (f *Formatter) formatMessageRecord(record *Record) (string, error) {
if len(record.Arguments) == 0 {
return record.Message, nil
}
var err error
var object interface{}
message := record.Message
f.usedArguments = make(map[int]bool)
funcMap := make(template.FuncMap)
funcMap[f.placeholder] = f.argumentAutomatic(record)
for position, argument := range record.Arguments {
placeholder := f.placeholder + strconv.Itoa(position)
funcMap[placeholder] = f.argumentValue(position, argument)
valueOf := reflect.ValueOf(argument)
switch valueOf.Kind() {
case reflect.Map:
if reflect.TypeOf(argument).Key().Kind() == reflect.String {
for _, key := range valueOf.MapKeys() {
funcMap[key.String()] = f.argumentValue(position, valueOf.MapIndex(key).Interface())
}
}
case reflect.Struct:
object = argument
}
}
if message, err = f.formatString(
template.New("").Delims("{", "}").Funcs(f.getRecordFuncs(record)).Funcs(funcMap),
f.messageBuffer,
message,
object,
); err != nil {
return "", err
}
if len(f.usedArguments) >= len(record.Arguments) {
return message, nil
}
for position, argument := range record.Arguments {
if !f.isArgumentUsed(position, argument) {
if message != "" {
message += " "
}
message += fmt.Sprint(argument)
}
}
return message, nil
}
func (f *Formatter) isArgumentUsed(position int, argument interface{}) bool {
valueOf := reflect.ValueOf(argument)
switch valueOf.Kind() {
case reflect.Map:
if reflect.TypeOf(argument).Key().Kind() == reflect.String {
return true
}
case reflect.Struct:
return true
}
return f.usedArguments[position]
}
// argumentValue returns closure that returns log argument used in log message.
func (f *Formatter) argumentValue(position int, argument interface{}) func() interface{} {
return func() interface{} {
f.usedArguments[position] = true
return argument
}
}
// argumentAutomatic returns closure that returns log argument from automatic
// placeholder used in log message.
func (f *Formatter) argumentAutomatic(record *Record) func() interface{} {
position := 0
arguments := len(record.Arguments)
return func() interface{} {
var argument interface{}
if position < arguments {
f.usedArguments[position] = true
argument = record.Arguments[position]
position++
}
return argument
}
}
// formatString returns formatted string.
func (*Formatter) formatString(templ *template.Template, buffer *bytes.Buffer, format string, object interface{}) (string, error) {
var message string
if format != "" {
var err error
templ, err = templ.Parse(format)
if err != nil {
return "", NewRuntimeError("cannot parse text template", err)
}
buffer.Reset()
err = templ.Execute(buffer, object)
if err != nil {
return "", NewRuntimeError("cannot execute text template", err)
}
message = buffer.String()
}
return message, nil
}
// getRecordFuncs sets default template functions used to formatting log message.
func (f *Formatter) getRecordFuncs(record *Record) template.FuncMap {
return template.FuncMap{
"uid": os.Getuid,
"gid": os.Getgid,
"pid": os.Getpid,
"egid": os.Getegid,
"euid": os.Geteuid,
"ppid": os.Getppid,
"getEnv": os.Getenv,
"expandEnv": os.ExpandEnv,
"executable": func() string {
return filepath.Base(os.Args[0])
},
"date": func() string {
date, err := f.formatString(f.template, f.timeBuffer, f.dateFormat, nil)
if err != nil {
printError(NewRuntimeError("cannot format date", err))
}
return date
},
"message": func() string {
message, err := f.formatMessageRecord(record)
if err != nil {
printError(NewRuntimeError("cannot format message", err))
}
return message
},
"levelValue": func() int {
return record.Level.Value
},
"level": func() string {
return strings.ToLower(record.Level.Name)
},
"Level": func() string {
return strings.Title(strings.ToLower(record.Level.Name))
},
"LEVEL": func() string {
return strings.ToUpper(record.Level.Name)
},
"iso8601": func() string {
return record.Time.Format(time.RFC3339)
},
"id": func() interface{} {
return record.ID
},
"name": func() string {
return record.Name
},
"host": func() string {
return record.Address
},
"hostname": func() string {
return record.Hostname
},
"address": func() string {
return record.Address
},
"nanosecond": func() string {
return fmt.Sprintf("%09d", record.Time.Nanosecond())
},
"microsecond": func() string {
return fmt.Sprintf("%06d", record.Time.Nanosecond()/kilo)
},
"millisecond": func() string {
return fmt.Sprintf("%03d", record.Time.Nanosecond()/mega)
},
"second": func() string {
return fmt.Sprintf("%02d", record.Time.Second())
},
"minute": func() string {
return fmt.Sprintf("%02d", record.Time.Minute())
},
"hour": func() string {
return fmt.Sprintf("%02d", record.Time.Hour())
},
"day": func() string {
return fmt.Sprintf("%02d", record.Time.Day())
},
"month": func() string {
return fmt.Sprintf("%02d", record.Time.Month())
},
"YEAR": func() string {
return fmt.Sprintf("%02d", record.Time.Year()%percentage)
},
"year": func() int {
return record.Time.Year()
},
"file": func() string {
return record.File.Name
},
"line": func() int {
return record.File.Line
},
"function": func() string {
return record.File.Function
},
}
}
// Copyright 2020 Tymoteusz Blazejczyk
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package logger
import (
"os"
"sync"
)
var gOnce sync.Once // nolint:gochecknoglobals
var gInstance *Logger // nolint:gochecknoglobals
// Get returns global logger instance.
func Get() *Logger {
gOnce.Do(func() {
gInstance = New()
})
return gInstance
}
// Enable enables all added log handlers.
func Enable() *Logger {
return Get().Enable()
}
// Disable disabled all added log handlers.
func Disable() *Logger {
return Get().Disable()
}
// IsEnabled returns true if at least one of added log handlers is enabled.
func IsEnabled() bool {
return Get().IsEnabled()
}
// SetLevel sets log level to all added log handlers.
func SetLevel(level int) *Logger {
return Get().SetLevel(level)
}
// SetMinimumLevel sets minimum log level to all added log handlers.
func SetMinimumLevel(level int) *Logger {
return Get().SetMinimumLevel(level)
}
// SetMaximumLevel sets maximum log level to all added log handlers.
func SetMaximumLevel(level int) *Logger {
return Get().SetMaximumLevel(level)
}
// SetLevelRange sets minimum and maximum log level values to all added log handlers.
func SetLevelRange(min, max int) *Logger {
return Get().SetLevelRange(min, max)
}
// SetFormatter sets provided formatter to all added log handlers.
func SetFormatter(formatter *Formatter) *Logger {
return Get().SetFormatter(formatter)
}
// SetFormat sets provided format string to all added log handlers.
func SetFormat(format string) *Logger {
return Get().SetFormat(format)
}
// SetDateFormat sets provided date format string to all added log handlers.
func SetDateFormat(format string) *Logger {
return Get().SetDateFormat(format)
}
// SetPlaceholder sets provided placeholder string to all added log handlers.
func SetPlaceholder(placeholder string) *Logger {
return Get().SetPlaceholder(placeholder)
}
// AddFuncs adds template functions to format log message to all added log handlers.
func AddFuncs(funcs FormatterFuncs) *Logger {
return Get().AddFuncs(funcs)
}
// ResetFormatters resets all formatters from added log handlers.
func ResetFormatters() *Logger {
return Get().ResetFormatters()
}
// SetErrorCode sets error code that is returned during Fatal call.
// On default it is 1.
func SetErrorCode(errorCode int) *Logger {
return Get().SetErrorCode(errorCode)
}
// GetErrorCode returns error code.
func GetErrorCode() int {
return Get().GetErrorCode()
}
// SetName sets logger name.
func SetName(name string) *Logger {
return Get().SetName(name)
}
// GetName returns logger name.
func GetName() string {
return Get().GetName()
}
// AddHandler sets log handler under provided identifier name.
func AddHandler(name string, handler Handler) *Logger {
return Get().AddHandler(name, handler)
}
// SetHandler sets a single log handler for logger. It is equivalent to
// logger.RemoveHandlers().SetHandlers(logger.Handlers{name: handler}).
func SetHandler(name string, handler Handler) *Logger {
return Get().SetHandler(name, handler)
}
// SetHandlers sets log handlers for logger.
func SetHandlers(handlers Handlers) *Logger {
return Get().SetHandlers(handlers)
}
// GetHandler returns added log handler by provided name.
func GetHandler(name string) (Handler, error) {
return Get().GetHandler(name)
}
// GetHandlers returns all added log handlers.
func GetHandlers() Handlers {
return Get().GetHandlers()
}
// RemoveHandler removes added log handler by provided name.
func RemoveHandler(name string) *Logger {
return Get().RemoveHandler(name)
}
// RemoveHandlers removes all added log handlers.
func RemoveHandlers() *Logger {
return Get().RemoveHandlers()
}
// ResetHandlers sets logger default log handlers.
func ResetHandlers() *Logger {
return Get().ResetHandlers()
}
// Reset resets logger to default state and default log handlers.
func Reset() *Logger {
return Get().ResetHandlers()
}
// SetIDGenerator sets ID generator function that is called by logger to
// generate ID for created log messages.
func SetIDGenerator(idGenerator IDGenerator) *Logger {
return Get().SetIDGenerator(idGenerator)
}
// GetIDGenerator returns ID generator function that is called by logger to
// generate ID for created log messages.
func GetIDGenerator() IDGenerator {
return Get().GetIDGenerator()
}
// Trace logs finer-grained informational messages than the Debug. It creates
// and sends lightweight not formatted log messages to separate running logger
// thread for further formatting and I/O handling from different added log
// handlers.
func Trace(message string, arguments ...interface{}) {
Get().LogMessage(TraceLevel, TraceName, message, arguments...)
}
// Debug logs debugging messages. It creates and sends lightweight not formatted
// log messages to separate running logger thread for further formatting and
// I/O handling from different added log handlers.
func Debug(message string, arguments ...interface{}) {
Get().LogMessage(DebugLevel, DebugName, message, arguments...)
}
// Info logs informational messages. It creates and sends lightweight not
// formatted log messages to separate running logger thread for further
// formatting and I/O handling from different added log handlers.
func Info(message string, arguments ...interface{}) {
Get().LogMessage(InfoLevel, InfoName, message, arguments...)
}
// Notice logs messages for significant conditions. It creates and sends
// lightweight not formatted log messages to separate running logger thread for
// further formatting and I/O handling from different added log handlers.
func Notice(message string, arguments ...interface{}) {
Get().LogMessage(NoticeLevel, NoticeName, message, arguments...)
}
// Warning logs messages for warning conditions that can be potentially harmful.
// It creates and sends lightweight not formatted log messages to separate
// running logger thread for further formatting and I/O handling from different
// added log handlers.
func Warning(message string, arguments ...interface{}) {
Get().LogMessage(WarningLevel, WarningName, message, arguments...)
}
// Error logs messages for error conditions. It creates and sends lightweight
// not formatted log messages to separate running logger thread for further
// formatting and I/O handling from different added log handlers.
func Error(message string, arguments ...interface{}) {
Get().LogMessage(ErrorLevel, ErrorName, message, arguments...)
}
// Critical logs messages for critical conditions. It creates and sends
// lightweight not formatted log messages to separate running logger thread for
// further formatting and I/O handling from different added log handlers.
func Critical(message string, arguments ...interface{}) {
Get().LogMessage(CriticalLevel, CriticalName, message, arguments...)
}
// Alert logs messages for alert conditions. It creates and sends lightweight
// not formatted log messages to separate running logger thread for further
// formatting and I/O handling from different added log handlers.
func Alert(message string, arguments ...interface{}) {
Get().LogMessage(AlertLevel, AlertName, message, arguments...)
}
// Fatal logs messages for fatal conditions. It stops logger worker thread and
// it exists the application with an error code. It creates and sends
// lightweight not formatted log messages to separate running logger thread for
// further formatting and I/O handling from different added log handlers.
func Fatal(message string, arguments ...interface{}) {
Get().LogMessage(FatalLevel, FatalName, message, arguments...)
Close()
os.Exit(Get().GetErrorCode()) // revive:disable-line
}
// Panic logs messages for fatal conditions. It stops logger worker thread and
// it exists the application with a panic. It creates and sends lightweight not
// formatted log messages to separate running logger thread for further
// formatting and I/O handling from different added log handlers.
func Panic(message string, arguments ...interface{}) {
Get().LogMessage(PanicLevel, PanicName, message, arguments...)
Close()
panic(NewRuntimeError("Panic error"))
}
// Log logs messages with user defined log level value and name. It creates and
// sends lightweight not formatted log messages to separate running logger
// thread for further formatting and I/O handling from different added log
// handlers.
func Log(level int, levelName, message string, arguments ...interface{}) {
Get().LogMessage(level, levelName, message, arguments...)
}
// Emit emits provided log record to logger worker thread for further
// formatting and I/O handling from different addded log handlers.
func Emit(record *Record) *Logger {
return Get().Emit(record)
}
// Flush flushes all log messages.
func Flush() *Logger {
return Get().Flush()
}
// Close closes all added log handlers.
func Close() {
err := Get().Close()
if err != nil {
printError(NewRuntimeError("cannot close logger"))
}
}
// Copyright 2020 Tymoteusz Blazejczyk
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package logger
import (
"os"
"runtime"
"sync"
"time"
)
// These constants define log level values and names used by various logger
// functions like for example Debug or Info. It defines also default logger
// values.
const (
OffsetLevel = 10
TraceLevel = 0
DebugLevel = OffsetLevel + TraceLevel
InfoLevel = OffsetLevel + DebugLevel
NoticeLevel = OffsetLevel + InfoLevel
WarningLevel = OffsetLevel + NoticeLevel
ErrorLevel = OffsetLevel + WarningLevel
CriticalLevel = OffsetLevel + ErrorLevel
AlertLevel = OffsetLevel + CriticalLevel
FatalLevel = OffsetLevel + AlertLevel
PanicLevel = OffsetLevel + FatalLevel
MinimumLevel = TraceLevel
MaximumLevel = PanicLevel
TraceName = "trace"
DebugName = "debug"
InfoName = "info"
NoticeName = "notice"
WarningName = "warning"
ErrorName = "error"
CriticalName = "critical"
AlertName = "alert"
FatalName = "fatal"
PanicName = "panic"
DefaultTypeName = "log"
DefaultErrorCode = 1
loggerSkipCall = 2
)
// A Logger represents an active logging object that generates log messages for
// different added log handlers. Each logging operations creates and sends
// lightweight not formatted log message to separate worker thread. It offloads
// main code from unnecessary resource consuming formatting and I/O operations.
type Logger struct {
name string
handlers Handlers
idGenerator IDGenerator
errorCode int
mutex sync.RWMutex
}
// New creates new logger instance with default handlers.
func New() *Logger {
return &Logger{
handlers: Handlers{
"stdout": NewStdout(),
"stderr": NewStderr(),
},
errorCode: DefaultErrorCode,
idGenerator: NewUUID4(),
}
}
// Enable enables all added log handlers.
func (l *Logger) Enable() *Logger {
l.mutex.Lock()
defer l.mutex.Unlock()
for _, handler := range l.handlers {
handler.Enable()
}
return l
}
// Disable disabled all added log handlers.
func (l *Logger) Disable() *Logger {
l.mutex.Lock()
defer l.mutex.Unlock()
for _, handler := range l.handlers {
handler.Disable()
}
return l
}
// IsEnabled returns true if at least one of added log handlers is enabled.
func (l *Logger) IsEnabled() bool {
l.mutex.RLock()
defer l.mutex.RUnlock()
for _, handler := range l.handlers {
if handler.IsEnabled() {
return true
}
}
return false
}
// SetLevel sets log level to all added log handlers.
func (l *Logger) SetLevel(level int) *Logger {
l.mutex.Lock()
defer l.mutex.Unlock()
for _, handler := range l.handlers {
handler.SetLevel(level)
}
return l
}
// SetMinimumLevel sets minimum log level to all added log handlers.
func (l *Logger) SetMinimumLevel(level int) *Logger {
l.mutex.Lock()
defer l.mutex.Unlock()
for _, handler := range l.handlers {
handler.SetMinimumLevel(level)
}
return l
}
// SetMaximumLevel sets maximum log level to all added log handlers.
func (l *Logger) SetMaximumLevel(level int) *Logger {
l.mutex.Lock()
defer l.mutex.Unlock()
for _, handler := range l.handlers {
handler.SetMaximumLevel(level)
}
return l
}
// SetLevelRange sets minimum and maximum log level values to all added log handlers.
func (l *Logger) SetLevelRange(min, max int) *Logger {
l.mutex.Lock()
defer l.mutex.Unlock()
for _, handler := range l.handlers {
handler.SetLevelRange(min, max)
}
return l
}
// SetFormatter sets provided formatter to all added log handlers.
func (l *Logger) SetFormatter(formatter *Formatter) *Logger {
l.mutex.Lock()
defer l.mutex.Unlock()
for _, handler := range l.handlers {
handler.SetFormatter(formatter)
}
return l
}
// SetFormat sets provided format string to all added log handlers.
func (l *Logger) SetFormat(format string) *Logger {
l.mutex.Lock()
defer l.mutex.Unlock()
for _, handler := range l.handlers {
handler.GetFormatter().SetFormat(format)
}
return l
}
// SetDateFormat sets provided date format string to all added log handlers.
func (l *Logger) SetDateFormat(format string) *Logger {
l.mutex.Lock()
defer l.mutex.Unlock()
for _, handler := range l.handlers {
handler.GetFormatter().SetDateFormat(format)
}
return l
}
// SetPlaceholder sets provided placeholder string to all added log handlers.
func (l *Logger) SetPlaceholder(placeholder string) *Logger {
l.mutex.Lock()
defer l.mutex.Unlock()
for _, handler := range l.handlers {
handler.GetFormatter().SetPlaceholder(placeholder)
}
return l
}
// AddFuncs adds template functions to format log message to all added log handlers.
func (l *Logger) AddFuncs(funcs FormatterFuncs) *Logger {
l.mutex.Lock()
defer l.mutex.Unlock()
for _, handler := range l.handlers {
handler.GetFormatter().AddFuncs(funcs)
}
return l
}
// ResetFormatters resets all formatters from added log handlers.
func (l *Logger) ResetFormatters() *Logger {
l.mutex.Lock()
defer l.mutex.Unlock()
for _, handler := range l.handlers {
handler.GetFormatter().Reset()
}
return l
}
// SetErrorCode sets error code that is returned during Fatal call.
// On default it is 1.
func (l *Logger) SetErrorCode(errorCode int) *Logger {
l.mutex.Lock()
defer l.mutex.Unlock()
l.errorCode = errorCode
return l
}
// GetErrorCode returns error code.
func (l *Logger) GetErrorCode() int {
l.mutex.RLock()
defer l.mutex.RUnlock()
return l.errorCode
}
// SetName sets logger name.
func (l *Logger) SetName(name string) *Logger {
l.mutex.Lock()
defer l.mutex.Unlock()
l.name = name
return l
}
// GetName returns logger name.
func (l *Logger) GetName() string {
l.mutex.RLock()
defer l.mutex.RUnlock()
return l.name
}
// AddHandler sets log handler under provided identifier name.
func (l *Logger) AddHandler(name string, handler Handler) *Logger {
l.mutex.Lock()
defer l.mutex.Unlock()
l.handlers[name] = handler
return l
}
// SetHandler sets a single log handler for logger. It is equivalent to
// logger.RemoveHandlers().SetHandlers(logger.Handlers{name: handler}).
func (l *Logger) SetHandler(name string, handler Handler) *Logger {
l.mutex.Lock()
defer l.mutex.Unlock()
l.handlers = Handlers{name: handler}
return l
}
// SetHandlers sets log handlers for logger.
func (l *Logger) SetHandlers(handlers Handlers) *Logger {
l.mutex.Lock()
defer l.mutex.Unlock()
l.handlers = handlers
return l
}
// GetHandler returns added log handler by provided name.
func (l *Logger) GetHandler(name string) (Handler, error) {
l.mutex.RLock()
defer l.mutex.RUnlock()
handler, ok := l.handlers[name]
if !ok {
return nil, NewRuntimeError("cannot get handler", name)
}
return handler, nil
}
// GetHandlers returns all added log handlers.
func (l *Logger) GetHandlers() Handlers {
l.mutex.RLock()
defer l.mutex.RUnlock()
return l.handlers
}
// RemoveHandler removes added log handler by provided name.
func (l *Logger) RemoveHandler(name string) *Logger {
l.mutex.Lock()
defer l.mutex.Unlock()
delete(l.handlers, name)
return l
}
// RemoveHandlers removes all added log handlers.
func (l *Logger) RemoveHandlers() *Logger {
l.mutex.Lock()
defer l.mutex.Unlock()
l.handlers = make(Handlers)
return l
}
// ResetHandlers sets logger default log handlers.
func (l *Logger) ResetHandlers() *Logger {
l.mutex.Lock()
defer l.mutex.Unlock()
l.handlers = Handlers{
"stdout": NewStdout(),
"stderr": NewStderr(),
}
return l
}
// Reset resets logger to default state and default log handlers.
func (l *Logger) Reset() *Logger {
l.mutex.Lock()
defer l.mutex.Unlock()
l.idGenerator = NewUUID4()
l.errorCode = DefaultErrorCode
l.handlers = Handlers{
"stdout": NewStdout(),
"stderr": NewStderr(),
}
return l
}
// SetIDGenerator sets ID generator function that is called by logger to
// generate ID for created log messages.
func (l *Logger) SetIDGenerator(idGenerator IDGenerator) *Logger {
l.mutex.Lock()
defer l.mutex.Unlock()
l.idGenerator = idGenerator
return l
}
// GetIDGenerator returns ID generator function that is called by logger to
// generate ID for created log messages.
func (l *Logger) GetIDGenerator() IDGenerator {
l.mutex.RLock()
defer l.mutex.RUnlock()
return l.idGenerator
}
// Trace logs finer-grained informational messages than the Debug. It creates
// and sends lightweight not formatted log messages to separate running logger
// thread for further formatting and I/O handling from different added log
// handlers.
func (l *Logger) Trace(message string, arguments ...interface{}) {
l.LogMessage(TraceLevel, TraceName, message, arguments...)
}
// Debug logs debugging messages. It creates and sends lightweight not formatted
// log messages to separate running logger thread for further formatting and
// I/O handling from different added log handlers.
func (l *Logger) Debug(message string, arguments ...interface{}) {
l.LogMessage(DebugLevel, DebugName, message, arguments...)
}
// Info logs informational messages. It creates and sends lightweight not
// formatted log messages to separate running logger thread for further
// formatting and I/O handling from different added log handlers.
func (l *Logger) Info(message string, arguments ...interface{}) {
l.LogMessage(InfoLevel, InfoName, message, arguments...)
}
// Notice logs messages for significant conditions. It creates and sends
// lightweight not formatted log messages to separate running logger thread for
// further formatting and I/O handling from different added log handlers.
func (l *Logger) Notice(message string, arguments ...interface{}) {
l.LogMessage(NoticeLevel, NoticeName, message, arguments...)
}
// Warning logs messages for warning conditions that can be potentially harmful.
// It creates and sends lightweight not formatted log messages to separate
// running logger thread for further formatting and I/O handling from different
// added log handlers.
func (l *Logger) Warning(message string, arguments ...interface{}) {
l.LogMessage(WarningLevel, WarningName, message, arguments...)
}
// Error logs messages for error conditions. It creates and sends lightweight
// not formatted log messages to separate running logger thread for further
// formatting and I/O handling from different log handlers.
func (l *Logger) Error(message string, arguments ...interface{}) {
l.LogMessage(ErrorLevel, ErrorName, message, arguments...)
}
// Critical logs messages for critical conditions. It creates and sends
// lightweight not formatted log messages to separate running logger thread for
// further formatting and I/O handling from different added log handlers.
func (l *Logger) Critical(message string, arguments ...interface{}) {
l.LogMessage(CriticalLevel, CriticalName, message, arguments...)
}
// Alert logs messages for alert conditions. It creates and sends lightweight
// not formatted log messages to separate running logger thread for further
// formatting and I/O handling from different added log handlers.
func (l *Logger) Alert(message string, arguments ...interface{}) {
l.LogMessage(AlertLevel, AlertName, message, arguments...)
}
// Fatal logs messages for fatal conditions. It stops logger worker thread and
// it exists the application with an error code. It creates and sends
// lightweight not formatted log messages to separate running logger thread for
// further formatting and I/O handling from different added log handlers.
func (l *Logger) Fatal(message string, arguments ...interface{}) {
l.LogMessage(FatalLevel, FatalName, message, arguments...)
Close()
os.Exit(l.errorCode) // revive:disable-line
}
// Panic logs messages for fatal conditions. It stops logger worker thread and
// it exists the application with a panic. It creates and sends lightweight not
// formatted log messages to separate running logger thread for further
// formatting and I/O handling from different added log handlers.
func (l *Logger) Panic(message string, arguments ...interface{}) {
l.LogMessage(PanicLevel, PanicName, message, arguments...)
Close()
panic(NewRuntimeError("Panic error"))
}
// Log logs messages with user defined log level value and name. It creates and
// sends lightweight not formatted log messages to separate running logger
// thread for further formatting and I/O handling from different added log
// handlers.
func (l *Logger) Log(level int, levelName, message string, arguments ...interface{}) {
l.LogMessage(level, levelName, message, arguments...)
}
// Flush flushes all log messages.
func (l *Logger) Flush() *Logger {
GetWorker().Flush()
return l
}
// Close closes all added log handlers.
func (l *Logger) Close() error {
GetWorker().Flush()
l.mutex.Lock()
defer l.mutex.Unlock()
var err error
for _, handler := range l.handlers {
handlerError := handler.Close()
if handlerError != nil {
err = NewRuntimeError("cannot close log handler", handlerError)
printError(err)
}
}
return err
}
// CloseDefer is a small helper function that invokes the .Close() method
// and it does an error checking with logging. Useful when using with
// the defer keyword to avoid creating an anonymous function wrapper only
// to check for an error manually and passing the errcheck linter.
func (l *Logger) CloseDefer() {
if err := l.Close(); err != nil {
printError(NewRuntimeError("cannot close logger", err))
}
}
// LogMessage logs message with defined log level value and name. It creates and
// sends lightweight not formatted log messages to separate running logger
// thread for further formatting and I/O handling from different added log
// handlers. Use this method in custom log wrapper methods.
func (l *Logger) LogMessage(level int, levelName, message string, arguments ...interface{}) {
now := time.Now()
pc, path, line, _ := runtime.Caller(loggerSkipCall)
GetWorker().records <- &Record{
Time: now,
Message: message,
Arguments: arguments,
Level: Level{
Name: levelName,
Value: level,
},
File: Source{
Line: line,
Path: path,
Function: runtime.FuncForPC(pc).Name(),
},
logger: l,
}
}
// Emit emits provided log record to logger worker thread for further
// formatting and I/O handling from different addded log handlers.
func (l *Logger) Emit(record *Record) *Logger {
record.logger = l
GetWorker().records <- record
return l
}
// Copyright 2020 Tymoteusz Blazejczyk
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package logger
import (
"encoding/json"
"time"
)
// Record defines log record fields created by Logger and it is used by
// Formatter to format log message based on these fields.
type Record struct {
ID string `json:"id"`
Type string `json:"type"`
Name string `json:"name"`
Time time.Time `json:"-"`
Level Level `json:"level"`
Address string `json:"address"`
Hostname string `json:"hostname"`
Message string `json:"message"`
File Source `json:"file"`
Arguments Arguments `json:"arguments"`
Timestamp Timestamp `json:"timestamp"`
logger *Logger
}
// ToJSON packs data to JSON.
func (r *Record) ToJSON() ([]byte, error) {
return json.Marshal(r)
}
// FromJSON unpacks data from JSON.
func (r *Record) FromJSON(data []byte) error {
return json.Unmarshal(data, r)
}
// GetMessage returns formatted message.
func (r *Record) GetMessage() (string, error) {
message, err := NewFormatter().FormatMessage(r)
if err != nil {
return "", NewRuntimeError("cannot get formatted message", err)
}
return message, nil
}
// Copyright 2020 Tymoteusz Blazejczyk
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package logger
import (
"fmt"
"path/filepath"
"runtime"
)
// These constants are used for the RuntimeError.
const (
RuntimeErrorSkipCall = 1
)
// RuntimeError defines runtime error with returned error message, file name,
// file line number and function name.
type RuntimeError struct {
line int
file string
message string
function string
arguments []interface{}
}
// NewRuntimeError creates new RuntimeError object.
func NewRuntimeError(message string, arguments ...interface{}) *RuntimeError {
return NewRuntimeErrorBase(RuntimeErrorSkipCall, message, arguments...)
}
// NewRuntimeErrorBase creates new RuntimeError object using custom skip call value.
func NewRuntimeErrorBase(skipCall int, message string, arguments ...interface{}) *RuntimeError {
pc, path, line, _ := runtime.Caller(skipCall + 1)
return &RuntimeError{
line: line,
file: filepath.Base(path),
message: message,
function: filepath.Base(runtime.FuncForPC(pc).Name()),
arguments: arguments,
}
}
// Error returns formatted error string with message, file name, file line
// number and function name.
func (r *RuntimeError) Error() string {
var formatted string
var err error
record := &Record{
Message: r.message,
Arguments: r.arguments,
}
if formatted, err = NewFormatter().FormatMessage(record); err != nil {
formatted = r.message
}
return fmt.Sprintf("%s:%d:%s(): %s",
r.file,
r.line,
r.function,
formatted,
)
}
// Unwrap wrapped error.
func (r *RuntimeError) Unwrap() error {
for _, argument := range r.arguments {
if err, ok := argument.(error); ok {
return err
}
}
return nil
}
// Copyright 2020 Tymoteusz Blazejczyk
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package logger
import (
"os"
)
// NewStderr created a new Stderr log handler object.
func NewStderr() *Stream {
stream := NewStream()
stream.writer = os.Stderr
stream.minimumLevel = ErrorLevel
return stream
}
// Copyright 2020 Tymoteusz Blazejczyk
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package logger
import (
"os"
)
// NewStdout created a new Stdout log handler object.
func NewStdout() *Stream {
stream := NewStream()
stream.writer = os.Stdout
stream.maximumLevel = ErrorLevel - 1
return stream
}
// Copyright 2020 Tymoteusz Blazejczyk
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package logger
import (
"fmt"
"io"
"sync"
)
// StreamHandler defines a custom stream handler for writing log records with writer.
type StreamHandler func(writer io.Writer, record *Record, formatter *Formatter) error
// Opener implements Open method.
type Opener interface {
Open() (io.WriteCloser, error)
}
// A Stream represents a log handler object for logging messages using stream
// object.
type Stream struct {
writer io.Writer
closer io.Closer
formatter *Formatter
mutex sync.RWMutex
opener Opener
minimumLevel int
maximumLevel int
reopen bool
isDisabled bool
handler StreamHandler
}
// NewStream creates a new Stream log handler object.
func NewStream() *Stream {
return &Stream{
formatter: NewFormatter(),
minimumLevel: MinimumLevel,
maximumLevel: MaximumLevel,
handler: StreamHandlerDefault,
}
}
// Lock locks stream.
func (s *Stream) Lock() {
s.mutex.Lock()
}
// Unlock locks stream.
func (s *Stream) Unlock() {
s.mutex.Unlock()
}
// RLock locks stream.
func (s *Stream) RLock() {
s.mutex.RLock()
}
// RUnlock locks stream.
func (s *Stream) RUnlock() {
s.mutex.RUnlock()
}
// SetStreamHandler sets custom stream handler.
func (s *Stream) SetStreamHandler(handler StreamHandler) *Stream {
s.mutex.Lock()
defer s.mutex.Unlock()
if handler != nil {
s.handler = handler
}
return s
}
// SetWriter sets new writer to stream.
func (s *Stream) SetWriter(writer io.Writer) error {
s.mutex.Lock()
defer s.mutex.Unlock()
if s.writer == writer {
return nil
}
if s.closer != nil {
err := s.closer.Close()
if err != nil {
return NewRuntimeError("cannot close stream", err)
}
}
s.writer = writer
s.closer = nil
return nil
}
// SetWriteCloser sets new writer and closer to stream.
func (s *Stream) SetWriteCloser(writeCloser io.WriteCloser) error {
s.mutex.Lock()
defer s.mutex.Unlock()
if (s.writer == writeCloser) && (s.closer == writeCloser) {
return nil
}
if s.closer != nil {
err := s.closer.Close()
if err != nil {
return NewRuntimeError("cannot close stream", err)
}
}
s.writer = writeCloser
s.closer = writeCloser
return nil
}
// SetOpener sets opener interface.
func (s *Stream) SetOpener(opener Opener) *Stream {
s.mutex.Lock()
defer s.mutex.Unlock()
s.opener = opener
return s
}
// Reopen reopens stream.
func (s *Stream) Reopen() *Stream {
s.reopen = true
return s
}
// Enable enables log handler.
func (s *Stream) Enable() Handler {
s.mutex.Lock()
defer s.mutex.Unlock()
s.isDisabled = false
return s
}
// Disable disabled log handler.
func (s *Stream) Disable() Handler {
s.mutex.Lock()
defer s.mutex.Unlock()
s.isDisabled = true
return s
}
// IsEnabled returns if log handler is enabled.
func (s *Stream) IsEnabled() bool {
s.mutex.RLock()
defer s.mutex.RUnlock()
return !s.isDisabled
}
// SetFormatter sets Formatter.
func (s *Stream) SetFormatter(formatter *Formatter) Handler {
s.mutex.Lock()
defer s.mutex.Unlock()
s.formatter = formatter
return s
}
// GetFormatter returns Formatter.
func (s *Stream) GetFormatter() *Formatter {
s.mutex.RLock()
defer s.mutex.RUnlock()
return s.formatter
}
// SetLevel sets log level.
func (s *Stream) SetLevel(level int) Handler {
s.mutex.Lock()
defer s.mutex.Unlock()
s.minimumLevel = level
s.maximumLevel = level
return s
}
// SetMinimumLevel sets minimum log level.
func (s *Stream) SetMinimumLevel(level int) Handler {
s.mutex.Lock()
defer s.mutex.Unlock()
s.minimumLevel = level
return s
}
// GetMinimumLevel returns minimum log level.
func (s *Stream) GetMinimumLevel() int {
s.mutex.RLock()
defer s.mutex.RUnlock()
return s.minimumLevel
}
// SetMaximumLevel sets maximum log level.
func (s *Stream) SetMaximumLevel(level int) Handler {
s.mutex.Lock()
defer s.mutex.Unlock()
s.maximumLevel = level
return s
}
// GetMaximumLevel returns maximum log level.
func (s *Stream) GetMaximumLevel() int {
s.mutex.RLock()
defer s.mutex.RUnlock()
return s.maximumLevel
}
// SetLevelRange sets minimum and maximum log level values.
func (s *Stream) SetLevelRange(min, max int) Handler {
s.mutex.Lock()
defer s.mutex.Unlock()
s.minimumLevel = min
s.maximumLevel = max
return s
}
// GetLevelRange returns minimum and maximum log level values.
func (s *Stream) GetLevelRange() (min, max int) {
s.mutex.RLock()
defer s.mutex.RUnlock()
return s.minimumLevel, s.maximumLevel
}
// Emit logs messages from logger using I/O stream.
func (s *Stream) Emit(record *Record) error {
s.mutex.Lock()
defer s.mutex.Unlock()
if s.reopen {
if s.closer != nil {
err := s.closer.Close()
if err != nil {
return NewRuntimeError("cannot close stream", err)
}
s.writer = nil
s.closer = nil
}
s.reopen = false
}
if (s.writer == nil) && (s.closer == nil) && (s.opener != nil) {
writer, err := s.opener.Open()
if err != nil {
return NewRuntimeError("cannot open stream", err)
}
s.writer = writer
s.closer = writer
}
if s.writer != nil {
if err := s.handler(s.writer, record, s.formatter); err != nil {
return NewRuntimeError("cannot write to stream", err)
}
}
return nil
}
// Close closes I/O stream.
func (s *Stream) Close() error {
s.mutex.Lock()
defer s.mutex.Unlock()
if s.closer != nil {
err := s.closer.Close()
if err != nil {
return NewRuntimeError("cannot close stream", err)
}
s.writer = nil
s.closer = nil
}
return nil
}
// StreamHandlerDefault is a default stream handler for writing log records to stream.
func StreamHandlerDefault(writer io.Writer, record *Record, formatter *Formatter) error {
message, err := formatter.Format(record)
if err != nil {
return NewRuntimeError("cannot format record", err)
}
if _, err := fmt.Fprintln(writer, message); err != nil {
return NewRuntimeError("cannot write to stream", err)
}
return nil
}
// StreamHandlerNDJSON handles writing log records in the NDJSON format.
func StreamHandlerNDJSON(writer io.Writer, record *Record, _ *Formatter) error {
bytes, err := record.ToJSON()
if err != nil {
return NewRuntimeError("cannot format record", err)
}
if _, err := fmt.Fprintln(writer, string(bytes)); err != nil {
return NewRuntimeError("cannot write to stream", err)
}
return nil
}
// Copyright 2020 Tymoteusz Blazejczyk
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package logger
import (
"io"
"net"
"strconv"
"time"
)
// These constants define default values for syslog.
const (
DefaultSyslogPort = 514
DefaultSyslogVersion = 1
DefaultSyslogNetwork = "udp"
DefaultSyslogAddress = "localhost"
DefaultSyslogTimeout = 100 * time.Millisecond
DefaultSyslogFormat = "<{syslogPriority}>{syslogVersion} {iso8601} {address} {name} {pid} {id} - " +
"{file}:{line}:{function}(): {message}"
DefaultSyslogFacility = 1
)
// A Syslog represents a log handler object for logging messages to running
// Syslog server.
type Syslog struct {
port int
version int
network string
address string
facility int
timeout time.Duration
stream *Stream
}
// NewSyslog creates a new Syslog log handler object.
func NewSyslog() *Syslog {
s := &Syslog{
port: DefaultSyslogPort,
version: DefaultSyslogVersion,
network: DefaultSyslogNetwork,
address: DefaultSyslogAddress,
facility: DefaultSyslogFacility,
timeout: DefaultSyslogTimeout,
stream: NewStream(),
}
s.stream.GetFormatter().SetFormat(DefaultSyslogFormat)
s.stream.SetOpener(s)
return s
}
// Open opens new connection.
func (s *Syslog) Open() (io.WriteCloser, error) {
address := s.address + ":" + strconv.Itoa(s.port)
if s.timeout != 0 {
return net.DialTimeout(s.network, address, s.timeout)
}
return net.Dial(s.network, address)
}
// Enable enables log handler.
func (s *Syslog) Enable() Handler {
return s.stream.Enable()
}
// Disable disabled log handler.
func (s *Syslog) Disable() Handler {
return s.stream.Disable()
}
// IsEnabled returns if log handler is enabled.
func (s *Syslog) IsEnabled() bool {
return s.stream.IsEnabled()
}
// SetFormatter sets Formatter.
func (s *Syslog) SetFormatter(formatter *Formatter) Handler {
return s.stream.SetFormatter(formatter)
}
// GetFormatter returns Formatter.
func (s *Syslog) GetFormatter() *Formatter {
return s.stream.GetFormatter()
}
// SetLevel sets log level.
func (s *Syslog) SetLevel(level int) Handler {
return s.stream.SetLevel(level)
}
// SetMinimumLevel sets minimum log level.
func (s *Syslog) SetMinimumLevel(level int) Handler {
return s.stream.SetMinimumLevel(level)
}
// GetMinimumLevel returns minimum log level.
func (s *Syslog) GetMinimumLevel() int {
return s.stream.GetMinimumLevel()
}
// SetMaximumLevel sets maximum log level.
func (s *Syslog) SetMaximumLevel(level int) Handler {
return s.stream.SetMaximumLevel(level)
}
// GetMaximumLevel returns maximum log level.
func (s *Syslog) GetMaximumLevel() int {
return s.stream.GetMaximumLevel()
}
// SetLevelRange sets minimum and maximum log level values.
func (s *Syslog) SetLevelRange(min, max int) Handler {
return s.stream.SetLevelRange(min, max)
}
// GetLevelRange returns minimum and maximum log level values.
func (s *Syslog) GetLevelRange() (min, max int) {
return s.stream.GetLevelRange()
}
// SetPort sets port number that is used to communicate with Syslog server.
func (s *Syslog) SetPort(port int) *Syslog {
s.stream.Lock()
defer s.stream.Unlock()
if port <= 0 {
port = DefaultSyslogPort
}
if s.port != port {
s.port = port
s.stream.Reopen()
}
return s
}
// GetPort returns port number that is used to communicate with Syslog server.
func (s *Syslog) GetPort() int {
s.stream.RLock()
defer s.stream.RUnlock()
return s.port
}
// SetTimeout sets connection timeout.
func (s *Syslog) SetTimeout(timeout time.Duration) *Syslog {
s.stream.Lock()
defer s.stream.Unlock()
if s.timeout != timeout {
s.timeout = timeout
s.stream.Reopen()
}
return s
}
// GetTimeout returns connection timeout.
func (s *Syslog) GetTimeout() time.Duration {
s.stream.RLock()
defer s.stream.RUnlock()
return s.timeout
}
// SetNetwork sets network type like "udp" or "tcp" that is used to communicate
// with Syslog server.
func (s *Syslog) SetNetwork(network string) *Syslog {
s.stream.Lock()
defer s.stream.Unlock()
if network == "" {
network = DefaultSyslogNetwork
}
if s.network != network {
s.network = network
s.stream.Reopen()
}
return s
}
// GetNetwork returns network type like "udp" or "tcp" that is used to
// communicate with Syslog server.
func (s *Syslog) GetNetwork() string {
s.stream.RLock()
defer s.stream.RUnlock()
return s.network
}
// SetAddress sets IP address or hostname that is used to communicate with
// Syslog server.
func (s *Syslog) SetAddress(address string) *Syslog {
s.stream.Lock()
defer s.stream.Unlock()
if address == "" {
address = DefaultSyslogAddress
}
if s.address != address {
s.address = address
s.stream.Reopen()
}
return s
}
// GetAddress returns IP address or hostname that is used to communicate with
// Syslog server.
func (s *Syslog) GetAddress() string {
s.stream.RLock()
defer s.stream.RUnlock()
return s.network
}
// Emit logs messages from Logger to Syslog server.
func (s *Syslog) Emit(record *Record) error {
s.stream.GetFormatter().AddFuncs(s.getRecordFuncs(record))
return s.stream.Emit(record)
}
// Close closes communication to Syslog server.
func (s *Syslog) Close() error {
return s.stream.Close()
}
// setFormatterFuncs sets template functions that are specific for Syslog log
// messages.
func (s *Syslog) getRecordFuncs(record *Record) FormatterFuncs {
return FormatterFuncs{
"syslogVersion": func() int {
return s.version
},
"syslogPriority": func() int {
severities := [8]int{
FatalLevel,
AlertLevel,
CriticalLevel,
ErrorLevel,
WarningLevel,
NoticeLevel,
InfoLevel,
DebugLevel,
}
severity := 0
for i, level := range severities {
if level <= record.Level.Value {
severity = i
break
}
}
return ((0x1F & s.facility) << 3) | (0x07 & severity)
},
}
}
// Copyright 2020 Tymoteusz Blazejczyk
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package logger
import (
"fmt"
"net"
"os"
)
// Named is used as named string placeholders for logger functions.
type Named map[string]interface{}
// getHostname returns local hostname.
func getHostname() (string, error) {
hostname, err := os.Hostname()
if err != nil {
return "localhost", NewRuntimeError("cannot get hostname", err)
}
return hostname, nil
}
// getAddress returns local IP address.
func getAddress() (string, error) {
connection, err := net.Dial("udp", "8.8.8.8:80")
if err != nil {
return "127.0.0.1", NewRuntimeError("cannot connect to primary Google DNS", err)
}
defer func() {
err := connection.Close()
if err != nil {
printError(NewRuntimeError("cannot close UDP connection", err))
}
}()
return connection.LocalAddr().(*net.UDPAddr).IP.String(), nil
}
// printError prints error to error output.
func printError(err error) {
fmt.Fprintln(os.Stderr, "Logger error:", err)
}
// Copyright 2020 Tymoteusz Blazejczyk
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package logger
import (
"crypto/rand"
"encoding/hex"
"io"
)
// An UUID4 represents uui4 generator.
type UUID4 struct{}
// NewUUID4 create a new UUID4 object.
func NewUUID4() *UUID4 {
return &UUID4{}
}
// Generate generates new UUID4.
func (u *UUID4) Generate() (id string, err error) {
var uuid [16]byte
var buffer [36]byte
if _, err := io.ReadFull(rand.Reader, uuid[:]); err != nil {
return "", NewRuntimeError("cannot generate UUID4", err)
}
uuid[6] = (uuid[6] & 0x0f) | 0x40 // Version 4
uuid[8] = (uuid[8] & 0x3f) | 0x80 // Variant is 10
u.encodeHex(uuid, buffer[:])
return string(buffer[:]), nil
}
// encodeHex encodes uuid to UUID format.
func (*UUID4) encodeHex(uuid [16]byte, buffer []byte) {
hex.Encode(buffer, uuid[:4])
buffer[8] = '-'
hex.Encode(buffer[9:13], uuid[4:6])
buffer[13] = '-'
hex.Encode(buffer[14:18], uuid[6:8])
buffer[18] = '-'
hex.Encode(buffer[19:23], uuid[8:10])
buffer[23] = '-'
hex.Encode(buffer[24:], uuid[10:])
}
// Copyright 2020 Tymoteusz Blazejczyk
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package logger
import (
"os"
"path/filepath"
"sync"
"time"
)
// These constants define default values for Worker.
const (
DefaultQueueLength = 4096
)
// A Worker represents an active logger worker thread. It handles formatting
// received log messages and I/O operations.
type Worker struct {
flush chan *sync.WaitGroup
records chan *Record
mutex sync.RWMutex
}
var gWorkerOnce sync.Once // nolint:gochecknoglobals
var gWorkerInstance *Worker // nolint:gochecknoglobals
// NewWorker creates a new Worker object.
func NewWorker() *Worker {
worker := &Worker{
flush: make(chan *sync.WaitGroup, 1),
records: make(chan *Record, DefaultQueueLength),
}
go worker.run()
return worker
}
// GetWorker returns logger worker instance. First call to it creates and
// starts logger worker thread.
func GetWorker() *Worker {
gWorkerOnce.Do(func() {
gWorkerInstance = NewWorker()
})
return gWorkerInstance
}
// SetQueueLength sets logger worker thread queue length for log messages.
func (w *Worker) SetQueueLength(length int) *Worker {
w.mutex.Lock()
defer w.mutex.Unlock()
if length <= 0 {
length = DefaultQueueLength
}
if cap(w.records) != length {
w.records = make(chan *Record, length)
}
return w
}
// Flush flushes all log messages.
func (w *Worker) Flush() *Worker {
flush := new(sync.WaitGroup)
flush.Add(1)
w.flush <- flush
flush.Wait()
return w
}
// Run processes all incoming log messages from loggers. It emits received log
// records to all added log handlers for specific logger.
func (w *Worker) run() {
for {
select {
case flush := <-w.flush:
for records := len(w.records); records > 0; records-- {
record := <-w.records
if record != nil {
w.emit(record.logger, record)
}
}
if flush != nil {
flush.Done()
}
case record := <-w.records:
if record != nil {
w.emit(record.logger, record)
}
}
}
}
// emit prepares provided log record and it dispatches to all added log
// handlers for further formatting and specific I/O implementation operations.
func (*Worker) emit(logger *Logger, record *Record) {
var err error
record.Type = DefaultTypeName
record.File.Name = filepath.Base(record.File.Path)
record.File.Function = filepath.Base(record.File.Function)
record.Timestamp.Created = record.Time.Format(time.RFC3339)
record.Address, err = getAddress()
if err != nil {
printError(NewRuntimeError("cannot get local IP address", err))
}
record.Hostname, err = getHostname()
if err != nil {
printError(NewRuntimeError("cannot get local hostname", err))
}
logger.mutex.RLock()
defer logger.mutex.RUnlock()
record.Name = logger.name
record.ID, err = logger.idGenerator.Generate()
if err != nil {
printError(NewRuntimeError("cannot generate ID", err))
}
if record.Name == "" {
record.Name = filepath.Base(os.Args[0])
}
for _, handler := range logger.handlers {
min, max := handler.GetLevelRange()
if handler.IsEnabled() && (record.Level.Value >= min) && (record.Level.Value <= max) {
err = handler.Emit(record)
if err != nil {
printError(NewRuntimeError("cannot emit record", err))
}
}
}
}