Created
April 19, 2021 17:41
-
-
Save alexedwards/3e158aba800a3044f31bda3d32c72c56 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package jsonlog | |
import ( | |
"encoding/json" | |
"io" | |
"os" | |
"runtime/debug" | |
"sync" | |
"time" | |
) | |
// Define a Level type to represent the severity level for a log entry. | |
type Level int8 | |
// Initialize constants which represent a specific severity level. We use the iota | |
// keyword as a shortcut to assign successive integer values to the constants. | |
const ( | |
LevelInfo Level = iota // Has the value 0. | |
LevelError // Has the value 1. | |
LevelFatal // Has the value 2. | |
LevelOff // Has the value 3. | |
) | |
// Return a human-friendly string for the severity level. | |
func (l Level) String() string { | |
switch l { | |
case LevelInfo: | |
return "INFO" | |
case LevelError: | |
return "ERROR" | |
case LevelFatal: | |
return "FATAL" | |
default: | |
return "" | |
} | |
} | |
// Define a custom Logger type. This holds the output destination that the log messages | |
// should written to, the minimum severity level that log messages should be written | |
// for, plus a mutex for coordinating the writes. | |
type Logger struct { | |
out io.Writer | |
minLevel Level | |
mu sync.Mutex | |
} | |
// Return a new Logger instance which writes log entries at or above a minimum severity | |
// level to a specific output destination. | |
func New(out io.Writer, minLevel Level) *Logger { | |
return &Logger{ | |
out: out, | |
minLevel: minLevel, | |
} | |
} | |
// Declare some helper methods for writing log entries at the different levels. Notice | |
// that these all accept a map as the second parameter which can contain any arbitrary | |
// 'properties' that you want to appear in the log entry. | |
func (l *Logger) PrintInfo(message string, properties map[string]string) { | |
l.print(LevelInfo, message, properties) | |
} | |
func (l *Logger) PrintError(err error, properties map[string]string) { | |
l.print(LevelError, err.Error(), properties) | |
} | |
func (l *Logger) PrintFatal(err error, properties map[string]string) { | |
l.print(LevelFatal, err.Error(), properties) | |
os.Exit(1) // For entries at the FATAL level, we also terminate the application. | |
} | |
// Print is an internal method for writing the log entry. | |
func (l *Logger) print(level Level, message string, properties map[string]string) (int, error) { | |
// If the severity level of the log entry is below the minimum severity for the | |
// logger, then return with no further action. | |
if level < l.minLevel { | |
return 0, nil | |
} | |
// Declare an anonymous struct holding the data for the log entry. | |
aux := struct { | |
Level string `json:"level"` | |
Time string `json:"time"` | |
Message string `json:"message"` | |
Properties map[string]string `json:"properties,omitempty"` | |
Trace string `json:"trace,omitempty"` | |
}{ | |
Level: level.String(), | |
Time: time.Now().UTC().Format(time.RFC3339), | |
Message: message, | |
Properties: properties, | |
} | |
// Include a stack trace for entries at the ERROR and FATAL levels. | |
if level >= LevelError { | |
aux.Trace = string(debug.Stack()) | |
} | |
// Declare a line variable for holding the actual log entry text. | |
var line []byte | |
// Marshal the anonymous struct to JSON and store it in the line variable. If there | |
// was a problem creating the JSON, set the contents of the log entry to be that | |
// plain-text error message instead. | |
line, err := json.Marshal(aux) | |
if err != nil { | |
line = []byte(LevelError.String() + ": unable to marshal log message:" + err.Error()) | |
} | |
// Lock the mutex so that no two writes to the output destination cannot happen | |
// concurrently. If we don't do this, it's possible that the text for two or more | |
// log entries will be intermingled in the output. | |
l.mu.Lock() | |
defer l.mu.Unlock() | |
// Write the log entry followed by a newline. | |
return l.out.Write(append(line, '\n')) | |
} | |
// We also implement a Write() method so that our Logger satisfies the io.Writer | |
// interface. This writes a log entry at the ERROR level with no additional properties. | |
// Note that we have to return 0 bytes written and a nil error here, because | |
// this information is not surfaced by the underlying log.Logger when we write out a | |
// log entry. | |
func (l *Logger) Write(message []byte) (n int, err error) { | |
return l.print(LevelError, string(message), nil) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment