mlog: Make default message handler human readable
The JSON message handler is left as a separate implementation, for any that want to use it.
This commit is contained in:
parent
07f3889a70
commit
47c8c5b850
155
mlog/message_handler.go
Normal file
155
mlog/message_handler.go
Normal file
@ -0,0 +1,155 @@
|
|||||||
|
package mlog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"path"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/mediocregopher/mediocre-go-lib/v2/mctx"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MessageHandler is a type which can process Messages in some way.
|
||||||
|
//
|
||||||
|
// NOTE that Logger does not handle thread-safety, that must be done inside the
|
||||||
|
// MessageHandler if necessary.
|
||||||
|
type MessageHandler interface {
|
||||||
|
Handle(FullMessage) error
|
||||||
|
|
||||||
|
// Sync flushes any buffered data to the handler's output, e.g. a file or
|
||||||
|
// network connection. If the handler doesn't buffer data then this will be
|
||||||
|
// a no-op.
|
||||||
|
Sync() error
|
||||||
|
}
|
||||||
|
|
||||||
|
func maybeSyncWriter(w io.Writer) error {
|
||||||
|
if s, ok := w.(interface{ Sync() error }); ok {
|
||||||
|
return s.Sync()
|
||||||
|
} else if f, ok := w.(interface{ Flush() error }); ok {
|
||||||
|
return f.Flush()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
type msgHandler struct {
|
||||||
|
l sync.Mutex
|
||||||
|
out io.Writer
|
||||||
|
aa mctx.Annotations
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMessageHandler initializes and returns a MessageHandler which will write
|
||||||
|
// all messages to the given io.Writer in a human-readable format.
|
||||||
|
//
|
||||||
|
// If the io.Writer also implements a Sync or Flush method then that will be
|
||||||
|
// called when Sync is called on the returned MessageHandler.
|
||||||
|
func NewMessageHandler(out io.Writer) MessageHandler {
|
||||||
|
return &msgHandler{
|
||||||
|
out: out,
|
||||||
|
aa: mctx.Annotations{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *msgHandler) Sync() error {
|
||||||
|
h.l.Lock()
|
||||||
|
defer h.l.Unlock()
|
||||||
|
return maybeSyncWriter(h.out)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *msgHandler) Handle(msg FullMessage) error {
|
||||||
|
h.l.Lock()
|
||||||
|
defer h.l.Unlock()
|
||||||
|
|
||||||
|
var namespaceStr string
|
||||||
|
|
||||||
|
if len(msg.Namespace) > 0 {
|
||||||
|
namespaceStr = "[" + path.Join(msg.Namespace...) + "] "
|
||||||
|
}
|
||||||
|
|
||||||
|
var annotationsStr string
|
||||||
|
|
||||||
|
if ss := mctx.EvaluateAnnotations(msg.Context, h.aa).StringSlice(true); len(ss) > 0 {
|
||||||
|
for i := range ss {
|
||||||
|
annotationsStr += fmt.Sprintf(" %q=%q", ss[i][0], ss[i][1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Fprintf(
|
||||||
|
h.out, "%s %s%s%s\n",
|
||||||
|
msg.Level.String(),
|
||||||
|
namespaceStr,
|
||||||
|
msg.Description,
|
||||||
|
annotationsStr,
|
||||||
|
)
|
||||||
|
|
||||||
|
for k := range h.aa {
|
||||||
|
delete(h.aa, k)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
type jsonMsgHandler struct {
|
||||||
|
l sync.Mutex
|
||||||
|
out io.Writer
|
||||||
|
enc *json.Encoder
|
||||||
|
aa mctx.Annotations
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewJSONMessageHandler initializes and returns a MessageHandler which will
|
||||||
|
// write all messages to the given io.Writer as JSON objects.
|
||||||
|
//
|
||||||
|
// If the io.Writer also implements a Sync or Flush method then that will be
|
||||||
|
// called when Sync is called on the returned MessageHandler.
|
||||||
|
func NewJSONMessageHandler(out io.Writer) MessageHandler {
|
||||||
|
return &jsonMsgHandler{
|
||||||
|
out: out,
|
||||||
|
enc: json.NewEncoder(out),
|
||||||
|
aa: mctx.Annotations{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type messageJSON struct {
|
||||||
|
TimeDate string `json:"td"`
|
||||||
|
Timestamp int64 `json:"ts"`
|
||||||
|
Level string `json:"level"`
|
||||||
|
Namespace []string `json:"ns,omitempty"`
|
||||||
|
Description string `json:"descr"`
|
||||||
|
LevelInt int `json:"level_int"`
|
||||||
|
|
||||||
|
// key -> value
|
||||||
|
Annotations map[string]string `json:"annotations,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
const msgTimeFormat = "06/01/02 15:04:05.000000"
|
||||||
|
|
||||||
|
func (h *jsonMsgHandler) Handle(msg FullMessage) error {
|
||||||
|
h.l.Lock()
|
||||||
|
defer h.l.Unlock()
|
||||||
|
|
||||||
|
msgJSON := messageJSON{
|
||||||
|
TimeDate: msg.Time.UTC().Format(msgTimeFormat),
|
||||||
|
Timestamp: msg.Time.UnixNano(),
|
||||||
|
Level: msg.Level.String(),
|
||||||
|
LevelInt: msg.Level.Int(),
|
||||||
|
Namespace: msg.Namespace,
|
||||||
|
Description: msg.Description,
|
||||||
|
Annotations: mctx.EvaluateAnnotations(msg.Context, h.aa).StringMap(),
|
||||||
|
}
|
||||||
|
|
||||||
|
for k := range h.aa {
|
||||||
|
delete(h.aa, k)
|
||||||
|
}
|
||||||
|
|
||||||
|
return h.enc.Encode(msgJSON)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *jsonMsgHandler) Sync() error {
|
||||||
|
h.l.Lock()
|
||||||
|
defer h.l.Unlock()
|
||||||
|
return maybeSyncWriter(h.out)
|
||||||
|
}
|
81
mlog/mlog.go
81
mlog/mlog.go
@ -4,14 +4,11 @@
|
|||||||
// The log methods take in a message string and a Context. The Context can be
|
// The log methods take in a message string and a Context. The Context can be
|
||||||
// loaded with additional annotations which will be included in the log entry as
|
// loaded with additional annotations which will be included in the log entry as
|
||||||
// well (see mctx package).
|
// well (see mctx package).
|
||||||
//
|
|
||||||
package mlog
|
package mlog
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
"errors"
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
@ -112,84 +109,6 @@ type FullMessage struct {
|
|||||||
Namespace []string
|
Namespace []string
|
||||||
}
|
}
|
||||||
|
|
||||||
// MessageHandler is a type which can process Messages in some way.
|
|
||||||
//
|
|
||||||
// NOTE that Logger does not handle thread-safety, that must be done inside the
|
|
||||||
// MessageHandler if necessary.
|
|
||||||
type MessageHandler interface {
|
|
||||||
Handle(FullMessage) error
|
|
||||||
|
|
||||||
// Sync flushes any buffered data to the handler's output, e.g. a file or
|
|
||||||
// network connection. If the handler doesn't buffer data then this will be
|
|
||||||
// a no-op.
|
|
||||||
Sync() error
|
|
||||||
}
|
|
||||||
|
|
||||||
type messageHandler struct {
|
|
||||||
l sync.Mutex
|
|
||||||
out io.Writer
|
|
||||||
enc *json.Encoder
|
|
||||||
aa mctx.Annotations
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewMessageHandler initializes and returns a MessageHandler which will write
|
|
||||||
// all messages to the given io.Writer in a thread-safe way. If the io.Writer
|
|
||||||
// also implements a Sync or Flush method then that will be called when Sync is
|
|
||||||
// called on the returned MessageHandler.
|
|
||||||
func NewMessageHandler(out io.Writer) MessageHandler {
|
|
||||||
return &messageHandler{
|
|
||||||
out: out,
|
|
||||||
enc: json.NewEncoder(out),
|
|
||||||
aa: mctx.Annotations{},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type messageJSON struct {
|
|
||||||
TimeDate string `json:"td"`
|
|
||||||
Timestamp int64 `json:"ts"`
|
|
||||||
Level string `json:"level"`
|
|
||||||
Namespace []string `json:"ns,omitempty"`
|
|
||||||
Description string `json:"descr"`
|
|
||||||
LevelInt int `json:"level_int"`
|
|
||||||
|
|
||||||
// key -> value
|
|
||||||
Annotations map[string]string `json:"annotations,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
const msgTimeFormat = "06/01/02 15:04:05.000000"
|
|
||||||
|
|
||||||
func (h *messageHandler) Handle(msg FullMessage) error {
|
|
||||||
h.l.Lock()
|
|
||||||
defer h.l.Unlock()
|
|
||||||
|
|
||||||
msgJSON := messageJSON{
|
|
||||||
TimeDate: msg.Time.UTC().Format(msgTimeFormat),
|
|
||||||
Timestamp: msg.Time.UnixNano(),
|
|
||||||
Level: msg.Level.String(),
|
|
||||||
LevelInt: msg.Level.Int(),
|
|
||||||
Namespace: msg.Namespace,
|
|
||||||
Description: msg.Description,
|
|
||||||
Annotations: mctx.EvaluateAnnotations(msg.Context, h.aa).StringMap(),
|
|
||||||
}
|
|
||||||
|
|
||||||
for k := range h.aa {
|
|
||||||
delete(h.aa, k)
|
|
||||||
}
|
|
||||||
|
|
||||||
return h.enc.Encode(msgJSON)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *messageHandler) Sync() error {
|
|
||||||
h.l.Lock()
|
|
||||||
defer h.l.Unlock()
|
|
||||||
if s, ok := h.out.(interface{ Sync() error }); ok {
|
|
||||||
return s.Sync()
|
|
||||||
} else if f, ok := h.out.(interface{ Flush() error }); ok {
|
|
||||||
return f.Flush()
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// LoggerOpts are optional parameters to NewLogger. All fields are optional. A
|
// LoggerOpts are optional parameters to NewLogger. All fields are optional. A
|
||||||
// nil value of LoggerOpts is equivalent to an empty one.
|
// nil value of LoggerOpts is equivalent to an empty one.
|
||||||
type LoggerOpts struct {
|
type LoggerOpts struct {
|
||||||
|
Loading…
Reference in New Issue
Block a user