Introduce error log.

This commit is contained in:
Solderpunk 2020-06-28 18:34:50 +02:00
parent 31161cf21c
commit 77691d6983
6 changed files with 49 additions and 25 deletions

View File

@ -89,7 +89,10 @@ The following options can be set in `/etc/molly.conf`:
actual home directories like you may expect based on experience with actual home directories like you may expect based on experience with
other server software. Of course, you can symlink other server software. Of course, you can symlink
`/var/gemini/users/gus/` to `/home/gus/public_gemini/` if you want. `/var/gemini/users/gus/` to `/home/gus/public_gemini/` if you want.
* `LogPath`: Path to log file (default value `molly.log`). Note that * `AccessLog`: Path to access log file (default value `access.log`, i.e. in the current wrorking directory). Note that
all intermediate directories must exist, Molly Brown won't create
them for you.
* `ErrorLog`: Path to error log file (default value `error.log`, i.e. in the current wrorking directory). Note that
all intermediate directories must exist, Molly Brown won't create all intermediate directories must exist, Molly Brown won't create
them for you. them for you.
* `MimeOverrides`: A map from path regexs to MIME types. If the path of a file which is about to be served matches the regex, the specified MIME type will be used instead of one inferred from the filename extension. * `MimeOverrides`: A map from path regexs to MIME types. If the path of a file which is about to be served matches the regex, the specified MIME type will be used instead of one inferred from the filename extension.

View File

@ -14,7 +14,8 @@ type Config struct {
HomeDocBase string HomeDocBase string
GeminiExt string GeminiExt string
DefaultLang string DefaultLang string
LogPath string AccessLog string
ErrorLog string
TempRedirects map[string]string TempRedirects map[string]string
PermRedirects map[string]string PermRedirects map[string]string
MimeOverrides map[string]string MimeOverrides map[string]string
@ -47,7 +48,8 @@ func getConfig(filename string) (Config, error) {
config.HomeDocBase = "users" config.HomeDocBase = "users"
config.GeminiExt = "gmi" config.GeminiExt = "gmi"
config.DefaultLang = "" config.DefaultLang = ""
config.LogPath = "molly.log" config.AccessLog = "access.log"
config.ErrorLog = "error.log"
config.TempRedirects = make(map[string]string) config.TempRedirects = make(map[string]string)
config.PermRedirects = make(map[string]string) config.PermRedirects = make(map[string]string)
config.CGIPaths = make([]string, 0) config.CGIPaths = make([]string, 0)

View File

@ -16,7 +16,7 @@ import (
"time" "time"
) )
func handleCGI(config Config, path string, URL *url.URL, log *LogEntry, conn net.Conn) { func handleCGI(config Config, path string, URL *url.URL, log *LogEntry, errorLog chan string, conn net.Conn) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel() defer cancel()
cmd := exec.CommandContext(ctx, path) cmd := exec.CommandContext(ctx, path)
@ -27,14 +27,15 @@ func handleCGI(config Config, path string, URL *url.URL, log *LogEntry, conn net
for key, value := range vars { for key, value := range vars {
cmd.Env = append(cmd.Env, key+"="+value) cmd.Env = append(cmd.Env, key+"="+value)
} }
response, err := cmd.Output() response, err := cmd.Output()
if ctx.Err() == context.DeadlineExceeded { if ctx.Err() == context.DeadlineExceeded {
errorLog <- "Terminating CGI process " + path + " due to exceeding 10 second runtime limit."
conn.Write([]byte("42 CGI process timed out!\r\n")) conn.Write([]byte("42 CGI process timed out!\r\n"))
log.Status = 42 log.Status = 42
return return
} }
if err != nil { if err != nil {
errorLog <- "Error starting CGI executable " + path + ": " + err.Error()
conn.Write([]byte("42 CGI error!\r\n")) conn.Write([]byte("42 CGI error!\r\n"))
log.Status = 42 log.Status = 42
return return
@ -43,6 +44,7 @@ func handleCGI(config Config, path string, URL *url.URL, log *LogEntry, conn net
header, _, err := bufio.NewReader(strings.NewReader(string(response))).ReadLine() header, _, err := bufio.NewReader(strings.NewReader(string(response))).ReadLine()
status, err2 := strconv.Atoi(strings.Fields(string(header))[0]) status, err2 := strconv.Atoi(strings.Fields(string(header))[0])
if err != nil || err2 != nil { if err != nil || err2 != nil {
errorLog <- "Unable to parse first line of output from CGI process " + path + " as valid Gemini response header."
conn.Write([]byte("42 CGI error!\r\n")) conn.Write([]byte("42 CGI error!\r\n"))
log.Status = 42 log.Status = 42
return return

View File

@ -5,7 +5,8 @@
#DocBase = "/var/gemini/" #DocBase = "/var/gemini/"
#HomeDocBase = "users" #HomeDocBase = "users"
#GeminiExt = "gmi" #GeminiExt = "gmi"
#LogPath = "molly.log" #AccessLog = "/var/log/molly/access.log"
#ErrorLog = "/var/log/molly/error.log"
#CGIPaths = [ "^/var/gemini/cgi-bin/" ] #CGIPaths = [ "^/var/gemini/cgi-bin/" ]
#[SCGIPaths] #[SCGIPaths]
#"/scgi/" = "/var/run/scgi.sock" #"/scgi/" = "/var/run/scgi.sock"

View File

@ -19,7 +19,7 @@ import (
"time" "time"
) )
func handleGeminiRequest(conn net.Conn, config Config, logEntries chan LogEntry) { func handleGeminiRequest(conn net.Conn, config Config, accessLogEntries chan LogEntry, errorLogEntries chan string) {
defer conn.Close() defer conn.Close()
var tlsConn (*tls.Conn) = conn.(*tls.Conn) var tlsConn (*tls.Conn) = conn.(*tls.Conn)
var log LogEntry var log LogEntry
@ -27,10 +27,10 @@ func handleGeminiRequest(conn net.Conn, config Config, logEntries chan LogEntry)
log.RemoteAddr = conn.RemoteAddr() log.RemoteAddr = conn.RemoteAddr()
log.RequestURL = "-" log.RequestURL = "-"
log.Status = 0 log.Status = 0
defer func() { logEntries <- log }() defer func() { accessLogEntries <- log }()
// Read request // Read request
URL, err := readRequest(conn, &log) URL, err := readRequest(conn, &log, errorLogEntries)
if err != nil { if err != nil {
return return
} }
@ -149,7 +149,7 @@ func handleGeminiRequest(conn net.Conn, config Config, logEntries chan LogEntry)
// Paranoid security measure: // Paranoid security measure:
// Fail if the URL has mapped to our TLS files or the log // Fail if the URL has mapped to our TLS files or the log
if path == config.CertPath || path == config.KeyPath || path == config.LogPath { if path == config.CertPath || path == config.KeyPath || path == config.AccessLog || path == config.ErrorLog {
conn.Write([]byte("51 Not found!\r\n")) conn.Write([]byte("51 Not found!\r\n"))
log.Status = 51 log.Status = 51
return return
@ -163,7 +163,7 @@ func handleGeminiRequest(conn net.Conn, config Config, logEntries chan LogEntry)
} }
// Read Molly files // Read Molly files
parseMollyFiles(path, info, &config) parseMollyFiles(path, info, &config, errorLogEntries)
// Handle directories // Handle directories
if info.IsDir() { if info.IsDir() {
@ -178,7 +178,7 @@ func handleGeminiRequest(conn net.Conn, config Config, logEntries chan LogEntry)
index_path := filepath.Join(path, "index."+config.GeminiExt) index_path := filepath.Join(path, "index."+config.GeminiExt)
index_info, err := os.Stat(index_path) index_info, err := os.Stat(index_path)
if err == nil && uint64(index_info.Mode().Perm())&0444 == 0444 { if err == nil && uint64(index_info.Mode().Perm())&0444 == 0444 {
serveFile(index_path, &log, conn, config) serveFile(index_path, &log, conn, config, errorLogEntries)
// Serve a generated listing // Serve a generated listing
} else { } else {
conn.Write([]byte("20 text/gemini\r\n")) conn.Write([]byte("20 text/gemini\r\n"))
@ -193,19 +193,19 @@ func handleGeminiRequest(conn net.Conn, config Config, logEntries chan LogEntry)
for _, cgiPath := range config.CGIPaths { for _, cgiPath := range config.CGIPaths {
inCGIPath, err := regexp.Match(cgiPath, []byte(path)) inCGIPath, err := regexp.Match(cgiPath, []byte(path))
if err == nil && inCGIPath { if err == nil && inCGIPath {
handleCGI(config, path, URL, &log, conn) handleCGI(config, path, URL, &log, errorLogEntries, conn)
return return
} }
} }
} }
// Otherwise, serve the file contents // Otherwise, serve the file contents
serveFile(path, &log, conn, config) serveFile(path, &log, conn, config, errorLogEntries)
return return
} }
func readRequest(conn net.Conn, log *LogEntry) (*url.URL, error) { func readRequest(conn net.Conn, log *LogEntry, errorLog chan string) (*url.URL, error) {
reader := bufio.NewReaderSize(conn, 1024) reader := bufio.NewReaderSize(conn, 1024)
request, overflow, err := reader.ReadLine() request, overflow, err := reader.ReadLine()
if overflow { if overflow {
@ -213,6 +213,7 @@ func readRequest(conn net.Conn, log *LogEntry) (*url.URL, error) {
log.Status = 59 log.Status = 59
return nil, errors.New("Request too long") return nil, errors.New("Request too long")
} else if err != nil { } else if err != nil {
errorLog <- "Error reading request: " + err.Error()
conn.Write([]byte("40 Unknown error reading request!\r\n")) conn.Write([]byte("40 Unknown error reading request!\r\n"))
log.Status = 40 log.Status = 40
return nil, errors.New("Error reading request") return nil, errors.New("Error reading request")
@ -221,6 +222,7 @@ func readRequest(conn net.Conn, log *LogEntry) (*url.URL, error) {
// Parse request as URL // Parse request as URL
URL, err := url.Parse(string(request)) URL, err := url.Parse(string(request))
if err != nil { if err != nil {
errorLog <- "Error parsing request URL " + string(request) + ": " + err.Error()
conn.Write([]byte("59 Error parsing URL!\r\n")) conn.Write([]byte("59 Error parsing URL!\r\n"))
log.Status = 59 log.Status = 59
return nil, errors.New("Bad URL in request") return nil, errors.New("Bad URL in request")
@ -254,7 +256,7 @@ func resolvePath(path string, config Config) (string, os.FileInfo, error) {
return path, info, nil return path, info, nil
} }
func parseMollyFiles(path string, info os.FileInfo, config *Config) { func parseMollyFiles(path string, info os.FileInfo, config *Config, errorLogEntries chan string) {
// Build list of directories to check // Build list of directories to check
dirs := make([]string, 16) dirs := make([]string, 16)
if !info.IsDir() { if !info.IsDir() {
@ -286,6 +288,7 @@ func parseMollyFiles(path string, info os.FileInfo, config *Config) {
} }
_, err = toml.DecodeFile(mollyPath, &mollyFile) _, err = toml.DecodeFile(mollyPath, &mollyFile)
if err != nil { if err != nil {
errorLogEntries <- "Error parsing .molly file " + mollyPath + ": " + err.Error()
continue continue
} }
// Overwrite main Config using MollyFile // Overwrite main Config using MollyFile
@ -399,7 +402,7 @@ func readHeading(path string, info os.FileInfo) string {
return info.Name() return info.Name()
} }
func serveFile(path string, log *LogEntry, conn net.Conn, config Config) { func serveFile(path string, log *LogEntry, conn net.Conn, config Config, errorLog chan string) {
// Get MIME type of files // Get MIME type of files
ext := filepath.Ext(path) ext := filepath.Ext(path)
var mimeType string var mimeType string

29
main.go
View File

@ -6,6 +6,7 @@ import (
"log" "log"
"os" "os"
"strconv" "strconv"
"time"
) )
func main() { func main() {
@ -25,12 +26,17 @@ func main() {
log.Fatal(err) log.Fatal(err)
} }
// Open logfile // Open log files
logfile, err := os.OpenFile(config.LogPath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) errorLogFile, err := os.OpenFile(config.ErrorLog, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
defer logfile.Close() defer errorLogFile.Close()
accessLogFile, err := os.OpenFile(config.AccessLog, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
log.Fatal(err)
}
defer accessLogFile.Close()
// Read TLS files, create TLS config // Read TLS files, create TLS config
cert, err := tls.LoadX509KeyPair(config.CertPath, config.KeyPath) cert, err := tls.LoadX509KeyPair(config.CertPath, config.KeyPath)
@ -50,12 +56,19 @@ func main() {
} }
defer listener.Close() defer listener.Close()
// Start log handling routine // Start log handling routines
logEntries := make(chan LogEntry, 10) accessLogEntries := make(chan LogEntry, 10)
go func() { go func() {
for { for {
entry := <-logEntries entry := <- accessLogEntries
writeLogEntry(logfile, entry) writeLogEntry(accessLogFile, entry)
}
}()
errorLogEntries := make(chan string, 10)
go func() {
for {
message := <- errorLogEntries
errorLogFile.WriteString( time.Now().Format(time.RFC3339) + " " + message + "\n")
} }
}() }()
@ -65,7 +78,7 @@ func main() {
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
go handleGeminiRequest(conn, config, logEntries) go handleGeminiRequest(conn, config, accessLogEntries, errorLogEntries)
} }
} }