mirror of
https://github.com/foxcpp/maddy.git
synced 2025-04-03 05:07:38 +03:00
124 lines
3.2 KiB
Go
124 lines
3.2 KiB
Go
/*
|
|
Maddy Mail Server - Composable all-in-one email server.
|
|
Copyright © 2019-2020 Max Mazurov <fox.cpp@disroot.org>, Maddy Mail Server contributors
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
// Package parser provides utilities for parsing of structured log messsages
|
|
// generated by maddy.
|
|
package parser
|
|
|
|
import (
|
|
"encoding/json"
|
|
"strings"
|
|
"time"
|
|
"unicode"
|
|
)
|
|
|
|
type (
|
|
Msg struct {
|
|
Stamp time.Time
|
|
Debug bool
|
|
Module string
|
|
Message string
|
|
Context map[string]interface{}
|
|
}
|
|
|
|
MalformedMsg struct {
|
|
Desc string
|
|
Err error
|
|
}
|
|
)
|
|
|
|
const (
|
|
ISO8601_UTC = "2006-01-02T15:04:05.000Z"
|
|
)
|
|
|
|
func (m MalformedMsg) Error() string {
|
|
if m.Err != nil {
|
|
return "parse: " + m.Desc + ": " + m.Err.Error()
|
|
}
|
|
return "parse: " + m.Desc
|
|
}
|
|
|
|
// Parse parses the message from the maddy log file.
|
|
//
|
|
// It assumes standard file output, including the [debug] tag and
|
|
// ISO 8601 timestamp at the start of each line. Timestamp is assumed to be in
|
|
// the UTC, as it is enforced by maddy.
|
|
//
|
|
// JSON context values are unmarshalled without any additional processing,
|
|
// notably that means that all numbers are represented as float64.
|
|
func Parse(line string) (Msg, error) {
|
|
parts := strings.Split(line, "\t")
|
|
if len(parts) != 2 {
|
|
// All messages even without a Context have a trailing \t,
|
|
// so this one is obviously malformed.
|
|
return Msg{}, MalformedMsg{Desc: "missing a tab separator"}
|
|
}
|
|
|
|
m := Msg{
|
|
Context: map[string]interface{}{},
|
|
}
|
|
|
|
// After that, the second part is the context. It can be empty, so don't fail
|
|
// if there is none.
|
|
if len(parts[1]) != 0 {
|
|
if err := json.Unmarshal([]byte(parts[1]), &m.Context); err != nil {
|
|
return Msg{}, MalformedMsg{Desc: "context unmarshal", Err: err}
|
|
}
|
|
}
|
|
|
|
// Okay, the first one might contain the timestamp at start.
|
|
// Cut it away.
|
|
msgParts := strings.SplitN(parts[0], " ", 2)
|
|
if len(msgParts) == 1 {
|
|
return Msg{}, MalformedMsg{Desc: "missing a timestamp"}
|
|
}
|
|
|
|
var err error
|
|
m.Stamp, err = time.ParseInLocation(ISO8601_UTC, msgParts[0], time.UTC)
|
|
if err != nil {
|
|
return Msg{}, MalformedMsg{Desc: "timestamp parse", Err: err}
|
|
}
|
|
|
|
msgText := msgParts[1]
|
|
if strings.HasPrefix(msgText, "[debug] ") {
|
|
msgText = strings.TrimPrefix(msgText, "[debug] ")
|
|
m.Debug = true
|
|
}
|
|
|
|
moduleText := strings.SplitN(msgText, ": ", 2)
|
|
if len(moduleText) == 1 {
|
|
// No module prefix, that's fine.
|
|
m.Message = msgText
|
|
return m, nil
|
|
}
|
|
|
|
for _, ch := range moduleText[0] {
|
|
switch {
|
|
case unicode.IsDigit(ch), unicode.IsLetter(ch), ch == '/':
|
|
default:
|
|
// This is not a module prefix, don't treat it as such.
|
|
m.Message = msgText
|
|
return m, nil
|
|
}
|
|
}
|
|
|
|
m.Module = moduleText[0]
|
|
m.Message = moduleText[1]
|
|
|
|
return m, nil
|
|
}
|