@marcel91200 / caddy Mirror · read-only
Fork 0

Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS

Mirrored from https://github.com/Slazee/caddy.git

Synced Nov 11, 01:00 UTC

master
go 218 lines 5.85 KB
Raw
1 // Copyright 2015 Matthew Holt and The Caddy 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 package logging
16
17 import (
18 "encoding/json"
19 "fmt"
20 "os"
21
22 "go.uber.org/zap/buffer"
23 "go.uber.org/zap/zapcore"
24 "golang.org/x/term"
25
26 "github.com/caddyserver/caddy/v2"
27 "github.com/caddyserver/caddy/v2/caddyconfig"
28 "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
29 )
30
31 func init() {
32 caddy.RegisterModule(JournaldEncoder{})
33 }
34
35 // JournaldEncoder wraps another encoder and prepends a systemd/journald
36 // priority prefix to each emitted log line. This lets journald classify
37 // stdout/stderr log lines by severity while leaving the underlying log
38 // structure to the wrapped encoder.
39 //
40 // This encoder does not write directly to journald; it only changes the
41 // encoded output by adding the priority marker that journald understands.
42 // The wrapped encoder still controls the actual log format, such as JSON
43 // or console output.
44 type JournaldEncoder struct {
45 zapcore.Encoder `json:"-"`
46
47 // The underlying encoder that actually encodes the log entries.
48 // If not specified, defaults to "json", unless the output is a
49 // terminal, in which case it defaults to "console".
50 WrappedRaw json.RawMessage `json:"wrap,omitempty" caddy:"namespace=caddy.logging.encoders inline_key=format"`
51
52 wrappedIsDefault bool
53 ctx caddy.Context
54 }
55
56 // CaddyModule returns the Caddy module information.
57 func (JournaldEncoder) CaddyModule() caddy.ModuleInfo {
58 return caddy.ModuleInfo{
59 ID: "caddy.logging.encoders.journald",
60 New: func() caddy.Module { return new(JournaldEncoder) },
61 }
62 }
63
64 // Provision sets up the encoder.
65 func (je *JournaldEncoder) Provision(ctx caddy.Context) error {
66 je.ctx = ctx
67
68 if je.WrappedRaw == nil {
69 je.Encoder = &JSONEncoder{}
70 if p, ok := je.Encoder.(caddy.Provisioner); ok {
71 if err := p.Provision(ctx); err != nil {
72 return fmt.Errorf("provisioning fallback encoder module: %v", err)
73 }
74 }
75 je.wrappedIsDefault = true
76 } else {
77 val, err := ctx.LoadModule(je, "WrappedRaw")
78 if err != nil {
79 return fmt.Errorf("loading wrapped encoder module: %v", err)
80 }
81 je.Encoder = val.(zapcore.Encoder)
82 }
83
84 suppressConsoleEncoderTimestamp(je.Encoder)
85
86 return nil
87 }
88
89 // ConfigureDefaultFormat will set the default wrapped format to "console"
90 // if the writer is a terminal. If already configured, it passes through
91 // the writer so a deeply nested encoder can configure its own default format.
92 func (je *JournaldEncoder) ConfigureDefaultFormat(wo caddy.WriterOpener) error {
93 if !je.wrappedIsDefault {
94 if cfd, ok := je.Encoder.(caddy.ConfiguresFormatterDefault); ok {
95 return cfd.ConfigureDefaultFormat(wo)
96 }
97 return nil
98 }
99
100 if caddy.IsWriterStandardStream(wo) && term.IsTerminal(int(os.Stderr.Fd())) {
101 je.Encoder = &ConsoleEncoder{}
102 if p, ok := je.Encoder.(caddy.Provisioner); ok {
103 if err := p.Provision(je.ctx); err != nil {
104 return fmt.Errorf("provisioning fallback encoder module: %v", err)
105 }
106 }
107 }
108
109 suppressConsoleEncoderTimestamp(je.Encoder)
110
111 return nil
112 }
113
114 // UnmarshalCaddyfile sets up the module from Caddyfile tokens. Syntax:
115 //
116 // journald {
117 // wrap <another encoder>
118 // }
119 //
120 // Example:
121 //
122 // log {
123 // format journald {
124 // wrap json
125 // }
126 // }
127 func (je *JournaldEncoder) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
128 d.Next() // consume encoder name
129 if d.NextArg() {
130 return d.ArgErr()
131 }
132
133 for d.NextBlock(0) {
134 if d.Val() != "wrap" {
135 return d.Errf("unrecognized subdirective %s", d.Val())
136 }
137 if !d.NextArg() {
138 return d.ArgErr()
139 }
140 moduleName := d.Val()
141 moduleID := "caddy.logging.encoders." + moduleName
142 unm, err := caddyfile.UnmarshalModule(d, moduleID)
143 if err != nil {
144 return err
145 }
146 enc, ok := unm.(zapcore.Encoder)
147 if !ok {
148 return d.Errf("module %s (%T) is not a zapcore.Encoder", moduleID, unm)
149 }
150 je.WrappedRaw = caddyconfig.JSONModuleObject(enc, "format", moduleName, nil)
151 }
152
153 return nil
154 }
155
156 // Clone implements zapcore.Encoder.
157 func (je JournaldEncoder) Clone() zapcore.Encoder {
158 return JournaldEncoder{
159 Encoder: je.Encoder.Clone(),
160 }
161 }
162
163 // EncodeEntry implements zapcore.Encoder.
164 func (je JournaldEncoder) EncodeEntry(ent zapcore.Entry, fields []zapcore.Field) (*buffer.Buffer, error) {
165 encoded, err := je.Encoder.Clone().EncodeEntry(ent, fields)
166 if err != nil {
167 return nil, err
168 }
169
170 out := bufferpool.Get()
171 out.AppendString(journaldPriorityPrefix(ent.Level))
172 out.AppendBytes(encoded.Bytes())
173 encoded.Free()
174
175 return out, nil
176 }
177
178 func journaldPriorityPrefix(level zapcore.Level) string {
179 switch level {
180 case zapcore.InvalidLevel:
181 return "<6>"
182 case zapcore.DebugLevel:
183 return "<7>"
184 case zapcore.InfoLevel:
185 return "<6>"
186 case zapcore.WarnLevel:
187 return "<4>"
188 case zapcore.ErrorLevel:
189 return "<3>"
190 case zapcore.DPanicLevel, zapcore.PanicLevel, zapcore.FatalLevel:
191 return "<2>"
192 default:
193 return "<6>"
194 }
195 }
196
197 func suppressConsoleEncoderTimestamp(enc zapcore.Encoder) {
198 empty := ""
199
200 switch e := enc.(type) {
201 case *ConsoleEncoder:
202 e.TimeKey = &empty
203 _ = e.Provision(caddy.Context{})
204 case *AppendEncoder:
205 suppressConsoleEncoderTimestamp(e.wrapped)
206 case *FilterEncoder:
207 suppressConsoleEncoderTimestamp(e.wrapped)
208 case *JournaldEncoder:
209 suppressConsoleEncoderTimestamp(e.Encoder)
210 }
211 }
212
213 // Interface guards
214 var (
215 _ zapcore.Encoder = (*JournaldEncoder)(nil)
216 _ caddyfile.Unmarshaler = (*JournaldEncoder)(nil)
217 _ caddy.ConfiguresFormatterDefault = (*JournaldEncoder)(nil)
218 )