1/*2Maddy Mail Server - Composable all-in-one email server.3Copyright © 2019-2020 Max Mazurov <fox.cpp@disroot.org>, Maddy Mail Server contributors45This program is free software: you can redistribute it and/or modify6it under the terms of the GNU General Public License as published by7the Free Software Foundation, either version 3 of the License, or8(at your option) any later version.910This program is distributed in the hope that it will be useful,11but WITHOUT ANY WARRANTY; without even the implied warranty of12MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the13GNU General Public License for more details.1415You should have received a copy of the GNU General Public License16along with this program. If not, see <https://www.gnu.org/licenses/>.17*/1819package maddy2021import (22 "errors"23 "fmt"24 "os"25 "path/filepath"2627 "github.com/foxcpp/maddy/framework/config"28 "github.com/foxcpp/maddy/framework/log"29)3031/*32Config matchers for module interfaces.33*/3435// logOut structure wraps log.Output and preserves36// configuration directive it was constructed from, allowing37// dynamic reinitialization for purposes of log file rotation.38type logOut struct {39 args []string40 log.Output41}4243func logOutput(_ *config.Map, node config.Node) (interface{}, error) {44 if len(node.Args) == 0 {45 return nil, config.NodeErr(node, "expected at least 1 argument")46 }47 if len(node.Children) != 0 {48 return nil, config.NodeErr(node, "can't declare block here")49 }5051 return LogOutputOption(node.Args)52}5354func LogOutputOption(args []string) (log.Output, error) {55 outs := make([]log.Output, 0, len(args))56 for i, arg := range args {57 switch arg {58 case "stderr":59 outs = append(outs, log.WriterOutput(os.Stderr, false))60 case "stderr_ts":61 outs = append(outs, log.WriterOutput(os.Stderr, true))62 case "syslog":63 syslogOut, err := log.SyslogOutput()64 if err != nil {65 return nil, fmt.Errorf("failed to connect to syslog daemon: %v", err)66 }67 outs = append(outs, syslogOut)68 case "off":69 if len(args) != 1 {70 return nil, errors.New("'off' can't be combined with other log targets")71 }72 return log.NopOutput{}, nil73 default:74 // Log file paths are converted to absolute to make sure75 // we will be able to recreate them in right location76 // after changing working directory to the state dir.77 absPath, err := filepath.Abs(arg)78 if err != nil {79 return nil, err80 }81 // We change the actual argument, so logOut object will82 // keep the absolute path for reinitialization.83 args[i] = absPath8485 w, err := os.OpenFile(absPath, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0o666)86 if err != nil {87 return nil, fmt.Errorf("failed to create log file: %v", err)88 }8990 outs = append(outs, log.WriteCloserOutput(w, true))91 }92 }9394 if len(outs) == 1 {95 return logOut{args, outs[0]}, nil96 }97 return logOut{args, log.MultiOutput(outs...)}, nil98}99100func defaultLogOutput() (interface{}, error) {101 return log.DefaultLogger.Out, nil102}103104func reinitLogging() {105 out, ok := log.DefaultLogger.Out.(logOut)106 if !ok {107 log.Println("Can't reinitialize logger because it was replaced before, this is a bug")108 return109 }110111 newOut, err := LogOutputOption(out.args)112 if err != nil {113 log.Println("Can't reinitialize logger:", err)114 return115 }116117 out.Close()118119 log.DefaultLogger.Out = newOut120}