// Copyright 2015 Matthew Holt and The Caddy 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 logging import ( "errors" "fmt" "io" "log" "net" "os" "sync" "time" "github.com/caddyserver/caddy/v2" "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" "github.com/rosedblabs/wal" ) func init() { caddy.RegisterModule(NetWriter{}) } // NetWriter implements a log writer that outputs to a network socket. If // the socket goes down, it will dump logs to stderr while it attempts to // reconnect. type NetWriter struct { // The address of the network socket to which to connect. Address string `json:"address,omitempty"` // The timeout to wait while connecting to the socket. DialTimeout caddy.Duration `json:"dial_timeout,omitempty"` // If enabled, allow connections errors when first opening the // writer. The error and subsequent log entries will be reported // to stderr instead until a connection can be re-established. SoftStart bool `json:"soft_start,omitempty"` addr caddy.NetworkAddress w *wal.WAL wr *wal.Reader } // CaddyModule returns the Caddy module information. func (NetWriter) CaddyModule() caddy.ModuleInfo { return caddy.ModuleInfo{ ID: "caddy.logging.writers.net", New: func() caddy.Module { return new(NetWriter) }, } } // Provision sets up the module. func (nw *NetWriter) Provision(ctx caddy.Context) error { repl := caddy.NewReplacer() address, err := repl.ReplaceOrErr(nw.Address, true, true) if err != nil { return fmt.Errorf("invalid host in address: %v", err) } nw.addr, err = caddy.ParseNetworkAddress(address) if err != nil { return fmt.Errorf("parsing network address '%s': %v", address, err) } if nw.addr.PortRangeSize() != 1 { return fmt.Errorf("multiple ports not supported") } if nw.DialTimeout < 0 { return fmt.Errorf("timeout cannot be less than 0") } return nil } func (nw NetWriter) String() string { return nw.addr.String() } // WriterKey returns a unique key representing this nw. func (nw NetWriter) WriterKey() string { return nw.addr.String() } func getPool() []byte { return make([]byte, 1024) } // OpenWriter opens a new network connection. func (nw *NetWriter) OpenWriter() (io.WriteCloser, error) { if err := os.MkdirAll(caddy.AppDataDir()+"/wal", 0o755); err != nil { return nil, err } opts := wal.DefaultOptions opts.DirPath = caddy.AppDataDir() + "/wal" opts.Sync = true opts.SegmentSize = wal.KB opts.BlockCache = wal.KB / 2 w, err := wal.Open(opts) if err != nil { return nil, err } nw.w, nw.wr = w, w.NewReader() reconn := &redialerConn{ nw: nw, timeout: time.Duration(nw.DialTimeout), } conn, err := reconn.dial() if err != nil { if !nw.SoftStart { return nil, err } // don't block config load if remote is down or some other external problem; // we can dump logs to stderr for now (see issue #5520) fmt.Fprintf(os.Stderr, "[ERROR] net log writer failed to connect: %v (will retry connection and print errors here in the meantime)\n", err) } reconn.connMu.Lock() reconn.Conn = conn reconn.connMu.Unlock() go reconn.readWal() return reconn, nil } func (rc *redialerConn) readWal() { for { data, _, err := rc.nw.wr.Next() if err == io.EOF { continue } if err == wal.ErrClosed { log.Printf("wal closed") return } if err != nil { log.Printf("error reading from wal: %v", err) continue } log.Printf("trying to write") log.Printf("data is: %s", string(data)) for _, err := rc.write(data); err != nil; _, err = rc.write(data) { log.Printf("failed to write: %s", err) time.Sleep(time.Second) } } } // UnmarshalCaddyfile sets up the handler from Caddyfile tokens. Syntax: // // net
{ // dial_timeout