@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 226 lines 5.93 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 "fmt"
19 "io"
20 "net"
21 "os"
22 "sync"
23 "time"
24
25 "github.com/caddyserver/caddy/v2"
26 "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
27 )
28
29 func init() {
30 caddy.RegisterModule(NetWriter{})
31 }
32
33 // NetWriter implements a log writer that outputs to a network socket. If
34 // the socket goes down, it will dump logs to stderr while it attempts to
35 // reconnect.
36 type NetWriter struct {
37 // The address of the network socket to which to connect.
38 Address string `json:"address,omitempty"`
39
40 // The timeout to wait while connecting to the socket.
41 DialTimeout caddy.Duration `json:"dial_timeout,omitempty"`
42
43 // If enabled, allow connections errors when first opening the
44 // writer. The error and subsequent log entries will be reported
45 // to stderr instead until a connection can be re-established.
46 SoftStart bool `json:"soft_start,omitempty"`
47
48 addr caddy.NetworkAddress
49 }
50
51 // CaddyModule returns the Caddy module information.
52 func (NetWriter) CaddyModule() caddy.ModuleInfo {
53 return caddy.ModuleInfo{
54 ID: "caddy.logging.writers.net",
55 New: func() caddy.Module { return new(NetWriter) },
56 }
57 }
58
59 // Provision sets up the module.
60 func (nw *NetWriter) Provision(ctx caddy.Context) error {
61 repl := caddy.NewReplacer()
62 address, err := repl.ReplaceOrErr(nw.Address, true, true)
63 if err != nil {
64 return fmt.Errorf("invalid host in address: %v", err)
65 }
66
67 nw.addr, err = caddy.ParseNetworkAddress(address)
68 if err != nil {
69 return fmt.Errorf("parsing network address '%s': %v", address, err)
70 }
71
72 if nw.addr.PortRangeSize() != 1 {
73 return fmt.Errorf("multiple ports not supported")
74 }
75
76 if nw.DialTimeout < 0 {
77 return fmt.Errorf("timeout cannot be less than 0")
78 }
79
80 return nil
81 }
82
83 func (nw NetWriter) String() string {
84 return nw.addr.String()
85 }
86
87 // WriterKey returns a unique key representing this nw.
88 func (nw NetWriter) WriterKey() string {
89 return nw.addr.String()
90 }
91
92 // OpenWriter opens a new network connection.
93 func (nw NetWriter) OpenWriter() (io.WriteCloser, error) {
94 reconn := &redialerConn{
95 nw: nw,
96 timeout: time.Duration(nw.DialTimeout),
97 }
98 conn, err := reconn.dial()
99 if err != nil {
100 if !nw.SoftStart {
101 return nil, err
102 }
103 // don't block config load if remote is down or some other external problem;
104 // we can dump logs to stderr for now (see issue #5520)
105 fmt.Fprintf(os.Stderr, "[ERROR] net log writer failed to connect: %v (will retry connection and print errors here in the meantime)\n", err)
106 }
107 reconn.connMu.Lock()
108 reconn.Conn = conn
109 reconn.connMu.Unlock()
110 return reconn, nil
111 }
112
113 // UnmarshalCaddyfile sets up the handler from Caddyfile tokens. Syntax:
114 //
115 // net <address> {
116 // dial_timeout <duration>
117 // soft_start
118 // }
119 func (nw *NetWriter) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
120 d.Next() // consume writer name
121 if !d.NextArg() {
122 return d.ArgErr()
123 }
124 nw.Address = d.Val()
125 if d.NextArg() {
126 return d.ArgErr()
127 }
128 for d.NextBlock(0) {
129 switch d.Val() {
130 case "dial_timeout":
131 if !d.NextArg() {
132 return d.ArgErr()
133 }
134 timeout, err := caddy.ParseDuration(d.Val())
135 if err != nil {
136 return d.Errf("invalid duration: %s", d.Val())
137 }
138 if d.NextArg() {
139 return d.ArgErr()
140 }
141 nw.DialTimeout = caddy.Duration(timeout)
142
143 case "soft_start":
144 if d.NextArg() {
145 return d.ArgErr()
146 }
147 nw.SoftStart = true
148
149 default:
150 return d.Errf("unrecognized subdirective '%s'", d.Val())
151 }
152 }
153 return nil
154 }
155
156 // redialerConn wraps an underlying Conn so that if any
157 // writes fail, the connection is redialed and the write
158 // is retried.
159 type redialerConn struct {
160 net.Conn
161 connMu sync.RWMutex
162 nw NetWriter
163 timeout time.Duration
164 lastRedial time.Time
165 }
166
167 // Write wraps the underlying Conn.Write method, but if that fails,
168 // it will re-dial the connection anew and try writing again.
169 func (reconn *redialerConn) Write(b []byte) (n int, err error) {
170 reconn.connMu.RLock()
171 conn := reconn.Conn
172 reconn.connMu.RUnlock()
173 if conn != nil {
174 if n, err = conn.Write(b); err == nil {
175 return n, err
176 }
177 }
178
179 // problem with the connection - lock it and try to fix it
180 reconn.connMu.Lock()
181 defer reconn.connMu.Unlock()
182
183 // if multiple concurrent writes failed on the same broken conn, then
184 // one of them might have already re-dialed by now; try writing again
185 if reconn.Conn != nil {
186 if n, err = reconn.Conn.Write(b); err == nil {
187 return n, err
188 }
189 }
190
191 // there's still a problem, so try to re-attempt dialing the socket
192 // if some time has passed in which the issue could have potentially
193 // been resolved - we don't want to block at every single log
194 // emission (!) - see discussion in #4111
195 if time.Since(reconn.lastRedial) > 10*time.Second {
196 reconn.lastRedial = time.Now()
197 conn2, err2 := reconn.dial()
198 if err2 != nil {
199 // logger socket still offline; instead of discarding the log, dump it to stderr
200 os.Stderr.Write(b)
201 return n, err
202 }
203 if n, err = conn2.Write(b); err == nil {
204 if reconn.Conn != nil {
205 reconn.Conn.Close()
206 }
207 reconn.Conn = conn2
208 }
209 } else {
210 // last redial attempt was too recent; just dump to stderr for now
211 os.Stderr.Write(b)
212 }
213
214 return n, err
215 }
216
217 func (reconn *redialerConn) dial() (net.Conn, error) {
218 return net.DialTimeout(reconn.nw.addr.Network, reconn.nw.addr.JoinHostPort(0), reconn.timeout)
219 }
220
221 // Interface guards
222 var (
223 _ caddy.Provisioner = (*NetWriter)(nil)
224 _ caddy.WriterOpener = (*NetWriter)(nil)
225 _ caddyfile.Unmarshaler = (*NetWriter)(nil)
226 )