pkg/logutil: add "NewJournaldWriter"
Signed-off-by: Gyuho Lee <gyuhox@gmail.com>
This commit is contained in:
@ -15,8 +15,6 @@
|
|||||||
package logutil
|
package logutil
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/coreos/etcd/raft"
|
|
||||||
|
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
"go.uber.org/zap/zapcore"
|
"go.uber.org/zap/zapcore"
|
||||||
"google.golang.org/grpc/grpclog"
|
"google.golang.org/grpc/grpclog"
|
||||||
@ -102,65 +100,3 @@ func (zl *zapGRPCLogger) V(l int) bool {
|
|||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewRaftLogger converts "*zap.Logger" to "raft.Logger".
|
|
||||||
func NewRaftLogger(lcfg zap.Config) (raft.Logger, error) {
|
|
||||||
lg, err := lcfg.Build(zap.AddCallerSkip(1)) // to annotate caller outside of "logutil"
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &zapRaftLogger{lg: lg, sugar: lg.Sugar()}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type zapRaftLogger struct {
|
|
||||||
lg *zap.Logger
|
|
||||||
sugar *zap.SugaredLogger
|
|
||||||
}
|
|
||||||
|
|
||||||
func (zl *zapRaftLogger) Debug(args ...interface{}) {
|
|
||||||
zl.sugar.Debug(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (zl *zapRaftLogger) Debugf(format string, args ...interface{}) {
|
|
||||||
zl.sugar.Debugf(format, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (zl *zapRaftLogger) Error(args ...interface{}) {
|
|
||||||
zl.sugar.Error(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (zl *zapRaftLogger) Errorf(format string, args ...interface{}) {
|
|
||||||
zl.sugar.Errorf(format, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (zl *zapRaftLogger) Info(args ...interface{}) {
|
|
||||||
zl.sugar.Info(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (zl *zapRaftLogger) Infof(format string, args ...interface{}) {
|
|
||||||
zl.sugar.Infof(format, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (zl *zapRaftLogger) Warning(args ...interface{}) {
|
|
||||||
zl.sugar.Warn(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (zl *zapRaftLogger) Warningf(format string, args ...interface{}) {
|
|
||||||
zl.sugar.Warnf(format, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (zl *zapRaftLogger) Fatal(args ...interface{}) {
|
|
||||||
zl.sugar.Fatal(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (zl *zapRaftLogger) Fatalf(format string, args ...interface{}) {
|
|
||||||
zl.sugar.Fatalf(format, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (zl *zapRaftLogger) Panic(args ...interface{}) {
|
|
||||||
zl.sugar.Panic(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (zl *zapRaftLogger) Panicf(format string, args ...interface{}) {
|
|
||||||
zl.sugar.Panicf(format, args...)
|
|
||||||
}
|
|
@ -66,50 +66,7 @@ func TestNewGRPCLoggerV2(t *testing.T) {
|
|||||||
if !bytes.Contains(data, []byte("etcd-logutil-2")) {
|
if !bytes.Contains(data, []byte("etcd-logutil-2")) {
|
||||||
t.Fatalf("can't find data in log %q", string(data))
|
t.Fatalf("can't find data in log %q", string(data))
|
||||||
}
|
}
|
||||||
if !bytes.Contains(data, []byte("logutil/zap_test.go:")) {
|
if !bytes.Contains(data, []byte("logutil/zap_grpc_test.go:")) {
|
||||||
t.Fatalf("unexpected caller; %q", string(data))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNewRaftLogger(t *testing.T) {
|
|
||||||
logPath := filepath.Join(os.TempDir(), fmt.Sprintf("test-log-%d", time.Now().UnixNano()))
|
|
||||||
defer os.RemoveAll(logPath)
|
|
||||||
|
|
||||||
lcfg := zap.Config{
|
|
||||||
Level: zap.NewAtomicLevelAt(zap.DebugLevel),
|
|
||||||
Development: false,
|
|
||||||
Sampling: &zap.SamplingConfig{
|
|
||||||
Initial: 100,
|
|
||||||
Thereafter: 100,
|
|
||||||
},
|
|
||||||
Encoding: "json",
|
|
||||||
EncoderConfig: zap.NewProductionEncoderConfig(),
|
|
||||||
OutputPaths: []string{logPath},
|
|
||||||
ErrorOutputPaths: []string{logPath},
|
|
||||||
}
|
|
||||||
gl, err := NewRaftLogger(lcfg)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
gl.Info("etcd-logutil-1")
|
|
||||||
data, err := ioutil.ReadFile(logPath)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if !bytes.Contains(data, []byte("etcd-logutil-1")) {
|
|
||||||
t.Fatalf("can't find data in log %q", string(data))
|
|
||||||
}
|
|
||||||
|
|
||||||
gl.Warning("etcd-logutil-2")
|
|
||||||
data, err = ioutil.ReadFile(logPath)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if !bytes.Contains(data, []byte("etcd-logutil-2")) {
|
|
||||||
t.Fatalf("can't find data in log %q", string(data))
|
|
||||||
}
|
|
||||||
if !bytes.Contains(data, []byte("logutil/zap_test.go:")) {
|
|
||||||
t.Fatalf("unexpected caller; %q", string(data))
|
t.Fatalf("unexpected caller; %q", string(data))
|
||||||
}
|
}
|
||||||
}
|
}
|
85
pkg/logutil/zap_journald.go
Normal file
85
pkg/logutil/zap_journald.go
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
// Copyright 2018 The etcd Authors
|
||||||
|
//
|
||||||
|
// 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 logutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/coreos/go-systemd/journal"
|
||||||
|
"go.uber.org/zap/zapcore"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewJournaldWriter wraps "io.Writer" to redirect log output
|
||||||
|
// to the local systemd journal. If journald send fails, it fails
|
||||||
|
// back to writing to the original writer.
|
||||||
|
// The decode overhead is only <30µs per write.
|
||||||
|
// Reference: https://github.com/coreos/pkg/blob/master/capnslog/journald_formatter.go
|
||||||
|
func NewJournaldWriter(wr io.Writer) io.Writer {
|
||||||
|
return &journaldWriter{Writer: wr}
|
||||||
|
}
|
||||||
|
|
||||||
|
type journaldWriter struct {
|
||||||
|
io.Writer
|
||||||
|
}
|
||||||
|
|
||||||
|
type logLine struct {
|
||||||
|
Level string `json:"level"`
|
||||||
|
Caller string `json:"caller"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *journaldWriter) Write(p []byte) (int, error) {
|
||||||
|
line := &logLine{}
|
||||||
|
if err := json.NewDecoder(bytes.NewReader(p)).Decode(line); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var pri journal.Priority
|
||||||
|
switch line.Level {
|
||||||
|
case zapcore.DebugLevel.String():
|
||||||
|
pri = journal.PriDebug
|
||||||
|
case zapcore.InfoLevel.String():
|
||||||
|
pri = journal.PriInfo
|
||||||
|
|
||||||
|
case zapcore.WarnLevel.String():
|
||||||
|
pri = journal.PriWarning
|
||||||
|
case zapcore.ErrorLevel.String():
|
||||||
|
pri = journal.PriErr
|
||||||
|
|
||||||
|
case zapcore.DPanicLevel.String():
|
||||||
|
pri = journal.PriCrit
|
||||||
|
case zapcore.PanicLevel.String():
|
||||||
|
pri = journal.PriCrit
|
||||||
|
case zapcore.FatalLevel.String():
|
||||||
|
pri = journal.PriCrit
|
||||||
|
|
||||||
|
default:
|
||||||
|
panic(fmt.Errorf("unknown log level: %q", line.Level))
|
||||||
|
}
|
||||||
|
|
||||||
|
err := journal.Send(string(p), pri, map[string]string{
|
||||||
|
"PACKAGE": filepath.Dir(line.Caller),
|
||||||
|
"SYSLOG_IDENTIFIER": filepath.Base(os.Args[0]),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("FAILED TO WRITE TO JOURNALD", err, string(p))
|
||||||
|
return w.Writer.Write(p)
|
||||||
|
}
|
||||||
|
return 0, nil
|
||||||
|
}
|
42
pkg/logutil/zap_journald_test.go
Normal file
42
pkg/logutil/zap_journald_test.go
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
// Copyright 2018 The etcd Authors
|
||||||
|
//
|
||||||
|
// 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 logutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"go.uber.org/zap/zapcore"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNewJournaldWriter(t *testing.T) {
|
||||||
|
buf := bytes.NewBuffer(nil)
|
||||||
|
syncer := zapcore.AddSync(NewJournaldWriter(buf))
|
||||||
|
cr := zapcore.NewCore(
|
||||||
|
zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig()),
|
||||||
|
syncer,
|
||||||
|
zap.NewAtomicLevelAt(zap.InfoLevel),
|
||||||
|
)
|
||||||
|
|
||||||
|
lg := zap.New(cr, zap.AddCaller(), zap.ErrorOutput(syncer))
|
||||||
|
defer lg.Sync()
|
||||||
|
|
||||||
|
lg.Info("TestNewJournaldWriter")
|
||||||
|
if buf.String() == "" {
|
||||||
|
// check with "journalctl -f"
|
||||||
|
t.Log("sent logs successfully to journald")
|
||||||
|
}
|
||||||
|
}
|
68
pkg/logutil/zap_raft.go
Normal file
68
pkg/logutil/zap_raft.go
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
package logutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/coreos/etcd/raft"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewRaftLogger converts "*zap.Logger" to "raft.Logger".
|
||||||
|
func NewRaftLogger(lcfg zap.Config) (raft.Logger, error) {
|
||||||
|
lg, err := lcfg.Build(zap.AddCallerSkip(1)) // to annotate caller outside of "logutil"
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &zapRaftLogger{lg: lg, sugar: lg.Sugar()}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type zapRaftLogger struct {
|
||||||
|
lg *zap.Logger
|
||||||
|
sugar *zap.SugaredLogger
|
||||||
|
}
|
||||||
|
|
||||||
|
func (zl *zapRaftLogger) Debug(args ...interface{}) {
|
||||||
|
zl.sugar.Debug(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (zl *zapRaftLogger) Debugf(format string, args ...interface{}) {
|
||||||
|
zl.sugar.Debugf(format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (zl *zapRaftLogger) Error(args ...interface{}) {
|
||||||
|
zl.sugar.Error(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (zl *zapRaftLogger) Errorf(format string, args ...interface{}) {
|
||||||
|
zl.sugar.Errorf(format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (zl *zapRaftLogger) Info(args ...interface{}) {
|
||||||
|
zl.sugar.Info(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (zl *zapRaftLogger) Infof(format string, args ...interface{}) {
|
||||||
|
zl.sugar.Infof(format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (zl *zapRaftLogger) Warning(args ...interface{}) {
|
||||||
|
zl.sugar.Warn(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (zl *zapRaftLogger) Warningf(format string, args ...interface{}) {
|
||||||
|
zl.sugar.Warnf(format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (zl *zapRaftLogger) Fatal(args ...interface{}) {
|
||||||
|
zl.sugar.Fatal(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (zl *zapRaftLogger) Fatalf(format string, args ...interface{}) {
|
||||||
|
zl.sugar.Fatalf(format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (zl *zapRaftLogger) Panic(args ...interface{}) {
|
||||||
|
zl.sugar.Panic(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (zl *zapRaftLogger) Panicf(format string, args ...interface{}) {
|
||||||
|
zl.sugar.Panicf(format, args...)
|
||||||
|
}
|
70
pkg/logutil/zap_raft_test.go
Normal file
70
pkg/logutil/zap_raft_test.go
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
// Copyright 2018 The etcd Authors
|
||||||
|
//
|
||||||
|
// 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 logutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNewRaftLogger(t *testing.T) {
|
||||||
|
logPath := filepath.Join(os.TempDir(), fmt.Sprintf("test-log-%d", time.Now().UnixNano()))
|
||||||
|
defer os.RemoveAll(logPath)
|
||||||
|
|
||||||
|
lcfg := zap.Config{
|
||||||
|
Level: zap.NewAtomicLevelAt(zap.DebugLevel),
|
||||||
|
Development: false,
|
||||||
|
Sampling: &zap.SamplingConfig{
|
||||||
|
Initial: 100,
|
||||||
|
Thereafter: 100,
|
||||||
|
},
|
||||||
|
Encoding: "json",
|
||||||
|
EncoderConfig: zap.NewProductionEncoderConfig(),
|
||||||
|
OutputPaths: []string{logPath},
|
||||||
|
ErrorOutputPaths: []string{logPath},
|
||||||
|
}
|
||||||
|
gl, err := NewRaftLogger(lcfg)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
gl.Info("etcd-logutil-1")
|
||||||
|
data, err := ioutil.ReadFile(logPath)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !bytes.Contains(data, []byte("etcd-logutil-1")) {
|
||||||
|
t.Fatalf("can't find data in log %q", string(data))
|
||||||
|
}
|
||||||
|
|
||||||
|
gl.Warning("etcd-logutil-2")
|
||||||
|
data, err = ioutil.ReadFile(logPath)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !bytes.Contains(data, []byte("etcd-logutil-2")) {
|
||||||
|
t.Fatalf("can't find data in log %q", string(data))
|
||||||
|
}
|
||||||
|
if !bytes.Contains(data, []byte("logutil/zap_raft_test.go:")) {
|
||||||
|
t.Fatalf("unexpected caller; %q", string(data))
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user