blob: fcd39038107cbabe3c2bca51d05d4e02af3d987b [file] [log] [blame]
Takahiro Suzuki241c10e2020-12-17 20:17:57 +09001// Copyright 2018 The etcd Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15// +build !windows
16
17package logutil
18
19import (
20 "bytes"
21 "encoding/json"
22 "fmt"
23 "io"
24 "os"
25 "path/filepath"
26
27 "go.etcd.io/etcd/pkg/systemd"
28
29 "github.com/coreos/go-systemd/journal"
30 "go.uber.org/zap/zapcore"
31)
32
33// NewJournalWriter wraps "io.Writer" to redirect log output
34// to the local systemd journal. If journald send fails, it fails
35// back to writing to the original writer.
36// The decode overhead is only <30µs per write.
37// Reference: https://github.com/coreos/pkg/blob/master/capnslog/journald_formatter.go
38func NewJournalWriter(wr io.Writer) (io.Writer, error) {
39 return &journalWriter{Writer: wr}, systemd.DialJournal()
40}
41
42type journalWriter struct {
43 io.Writer
44}
45
46// WARN: assume that etcd uses default field names in zap encoder config
47// make sure to keep this up-to-date!
48type logLine struct {
49 Level string `json:"level"`
50 Caller string `json:"caller"`
51}
52
53func (w *journalWriter) Write(p []byte) (int, error) {
54 line := &logLine{}
55 if err := json.NewDecoder(bytes.NewReader(p)).Decode(line); err != nil {
56 return 0, err
57 }
58
59 var pri journal.Priority
60 switch line.Level {
61 case zapcore.DebugLevel.String():
62 pri = journal.PriDebug
63 case zapcore.InfoLevel.String():
64 pri = journal.PriInfo
65
66 case zapcore.WarnLevel.String():
67 pri = journal.PriWarning
68 case zapcore.ErrorLevel.String():
69 pri = journal.PriErr
70
71 case zapcore.DPanicLevel.String():
72 pri = journal.PriCrit
73 case zapcore.PanicLevel.String():
74 pri = journal.PriCrit
75 case zapcore.FatalLevel.String():
76 pri = journal.PriCrit
77
78 default:
79 panic(fmt.Errorf("unknown log level: %q", line.Level))
80 }
81
82 err := journal.Send(string(p), pri, map[string]string{
83 "PACKAGE": filepath.Dir(line.Caller),
84 "SYSLOG_IDENTIFIER": filepath.Base(os.Args[0]),
85 })
86 if err != nil {
87 // "journal" also falls back to stderr
88 // "fmt.Fprintln(os.Stderr, s)"
89 return w.Writer.Write(p)
90 }
91 return 0, nil
92}