193 lines
5.1 KiB
Go
193 lines
5.1 KiB
Go
// Copyright 2014 ISRG. All rights reserved
|
|
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
package boulder
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"log"
|
|
"net"
|
|
"bytes"
|
|
"sync"
|
|
)
|
|
|
|
// This package transmits JSON data over IP. It is designed to follow
|
|
// general conventions for syslog, but uses JSON encoding instead of
|
|
// the RFC 5424 strings.
|
|
//
|
|
// The JSON encoding is suitable for import by Logstash's "json_lines" CODEC
|
|
// module.
|
|
//
|
|
// NOTE: In TCP mode, this package attempts to retransmit in the event of a
|
|
// channel failure. If the retransmission fails, it aborts and expects to
|
|
// be restarted by the process controller.
|
|
|
|
|
|
// Use the same Severity levels as RFC 5424.
|
|
// Note: RFC 5424 Facility is not used.
|
|
const (
|
|
EMERGENCY = 0
|
|
ALERT = 1
|
|
CRITICAL = 2
|
|
ERROR = 3
|
|
WARNING = 4
|
|
NOTICE = 5
|
|
INFO = 6
|
|
DEBUG = 7
|
|
)
|
|
|
|
// JSON Schema for the Log Messages on the wire.
|
|
type LogMessage struct {
|
|
// User-readable descriptive message; may be null.
|
|
Message string `json:"message"`
|
|
// Sub-object; must be JSON-formattable.
|
|
Payload interface{} `json:"payload"`
|
|
// Logger identifier
|
|
Program string `json:"program"`
|
|
// RFC 5424 severity level
|
|
Severity int `json:"severity"`
|
|
}
|
|
|
|
// Structure to hold logger details.
|
|
type JsonLogger struct {
|
|
stdout bool // True if logging to stdout (independent of network)
|
|
online bool // True if logging to network
|
|
scheme string // Golang net URI scheme (tcp/udp)
|
|
host string // "address:port"
|
|
level int // Maximum-transmitted log level
|
|
conn net.Conn // Socket representation
|
|
mu sync.Mutex // guards conn
|
|
program string // Defines the 'program' field in JSON
|
|
}
|
|
|
|
func NewJsonLogger(programName string) (*JsonLogger) {
|
|
return &JsonLogger{
|
|
program: programName,
|
|
level: 7, // Default to all
|
|
}
|
|
}
|
|
|
|
func (jl *JsonLogger) EnableStdOut(stdout bool) {
|
|
jl.stdout = stdout
|
|
}
|
|
|
|
func (jl *JsonLogger) SetLevel(level int) {
|
|
jl.level = level
|
|
}
|
|
|
|
func (jl *JsonLogger) SetEndpoint(scheme string, host string) {
|
|
jl.scheme = scheme
|
|
jl.host = host
|
|
jl.online = true
|
|
}
|
|
|
|
func (jl *JsonLogger) Connect() (error) {
|
|
conn, err := net.Dial(jl.scheme, jl.host)
|
|
if err == nil {
|
|
jl.conn = conn
|
|
}
|
|
return err
|
|
}
|
|
|
|
// Log at the Critical severity level.
|
|
func (jl *JsonLogger) Critical(messageStr string, payloadObj interface{}) {
|
|
jl.Write(CRITICAL, messageStr, payloadObj)
|
|
}
|
|
|
|
// Log at the Alert severity level.
|
|
func (jl *JsonLogger) Alert(messageStr string, payloadObj interface{}) {
|
|
jl.Write(ALERT, messageStr, payloadObj)
|
|
}
|
|
|
|
// Log at the Emergency severity level.
|
|
func (jl *JsonLogger) Emergency(messageStr string, payloadObj interface{}) {
|
|
jl.Write(EMERGENCY, messageStr, payloadObj)
|
|
}
|
|
|
|
// Log at the Error severity level.
|
|
func (jl *JsonLogger) Error(messageStr string, payloadObj interface{}) {
|
|
jl.Write(ERROR, messageStr, payloadObj)
|
|
}
|
|
|
|
// Log at the Warning severity level.
|
|
func (jl *JsonLogger) Warning(messageStr string, payloadObj interface{}) {
|
|
jl.Write(WARNING, messageStr, payloadObj)
|
|
}
|
|
|
|
// Log at the Notice severity level.
|
|
func (jl *JsonLogger) Notice(messageStr string, payloadObj interface{}) {
|
|
jl.Write(NOTICE, messageStr, payloadObj)
|
|
}
|
|
|
|
// Log at the Info severity level.
|
|
func (jl *JsonLogger) Info(messageStr string, payloadObj interface{}) {
|
|
jl.Write(INFO, messageStr, payloadObj)
|
|
}
|
|
|
|
// Log at the Debug severity level.
|
|
func (jl *JsonLogger) Debug(messageStr string, payloadObj interface{}) {
|
|
jl.Write(DEBUG, messageStr, payloadObj)
|
|
}
|
|
|
|
|
|
// Combines a message, payload, and severity to a LogMessage struct and
|
|
// serializes it to the wire. If the send via WriteAndRetry() fails, this method
|
|
// calls log.Fatalf() which will abort the program, leaving the system to restart
|
|
// the process.
|
|
func (jl *JsonLogger) Write(severity int, messageStr string, payloadObj interface{}) {
|
|
if severity > jl.level {
|
|
return
|
|
}
|
|
|
|
data := LogMessage{
|
|
Program: jl.program,
|
|
Payload: payloadObj,
|
|
Message: messageStr,
|
|
Severity: severity}
|
|
|
|
encoded, err := json.Marshal(data)
|
|
|
|
if err != nil {
|
|
log.Printf("Could not marshal log message: %s\n", err)
|
|
return
|
|
}
|
|
|
|
buf := bytes.NewBuffer(encoded)
|
|
buf.WriteByte('\n') // Append a newline
|
|
|
|
if jl.stdout {
|
|
log.Println(fmt.Sprintf("<%d> %s", severity, buf.String()))
|
|
}
|
|
|
|
if jl.online {
|
|
// If we've been told to be connected, write to the socket.
|
|
_, err = jl.WriteAndRetry(buf.Bytes())
|
|
if err != nil {
|
|
log.Fatalf("Failed to send log message, even with retry, exiting: %s\n", buf.String())
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
// Send the provided data on the connection; if there is an error,
|
|
// it will retry to connect and transmit again, once. If that fails,
|
|
// it returns an error.
|
|
func (jl *JsonLogger) WriteAndRetry(data []byte) (int, error) {
|
|
jl.mu.Lock()
|
|
defer jl.mu.Unlock()
|
|
|
|
if jl.conn != nil {
|
|
if n, err := jl.conn.Write(data); err == nil {
|
|
return n, err
|
|
}
|
|
}
|
|
if err := jl.Connect(); err != nil {
|
|
return 0, err
|
|
}
|
|
return jl.conn.Write(data)
|
|
}
|
|
|
|
|