~emersion/soju

8f29ae41a2ce599fc13a3865330b2bc2a1409731 — Simon Ser 3 months ago 4dae0da
Introduce internal message IDs

For now, these can be used as cursors in the logs. Future patches will
introduce functions that perform log queries with message IDs.

The IDs are state-less tokens containing all the required information to
refer to an on-disk log line: network name, entity name, date and byte
offset. The byte offset doesn't need to point to the first byte of the
line, any byte will do (note, this makes it so message IDs aren't
necessarily unique, we may want to change that in the future).

These internal message IDs are not exposed to clients because we don't
support upstream message IDs yet.
2 files changed, 53 insertions(+), 9 deletions(-)

M logger.go
M upstream.go
M logger.go => logger.go +52 -8
@@ 3,6 3,7 @@ package soju
import (
	"bufio"
	"fmt"
	"io"
	"os"
	"path/filepath"
	"strings"


@@ 39,10 40,48 @@ func logPath(network *network, entity string, t time.Time) string {
	return filepath.Join(srv.LogPath, escapeFilename.Replace(user.Username), escapeFilename.Replace(network.GetName()), escapeFilename.Replace(entity), filename)
}

func (ml *messageLogger) Append(msg *irc.Message) error {
func parseMsgID(s string) (network, entity string, t time.Time, offset int64, err error) {
	var year, month, day int
	_, err = fmt.Sscanf(s, "%s %s %04d-%02d-%02d %d", &network, &entity, &year, &month, &day, &offset)
	if err != nil {
		return "", "", time.Time{}, 0, fmt.Errorf("invalid message ID: %v", err)
	}
	t = time.Date(year, time.Month(month), day, 0, 0, 0, 0, time.Local)
	return network, entity, t, offset, nil
}

func formatMsgID(network, entity string, t time.Time, offset int64) string {
	year, month, day := t.Date()
	return fmt.Sprintf("%s %s %04d-%02d-%02d %d", network, entity, year, month, day, offset)
}

// nextMsgID queries the message ID for the next message to be written to f.
func nextMsgID(network *network, entity string, t time.Time, f *os.File) (string, error) {
	offset, err := f.Seek(0, io.SeekEnd)
	if err != nil {
		return "", err
	}
	return formatMsgID(network.GetName(), entity, t, offset), nil
}

// lastMsgID queries the last message ID for the given network, entity and
// date. The message ID returned may not refer to a valid message, but can be
// used in history queries.
func lastMsgID(network *network, entity string, t time.Time) (string, error) {
	p := logPath(network, entity, t)
	fi, err := os.Stat(p)
	if os.IsNotExist(err) {
		return formatMsgID(network.GetName(), entity, t, -1), nil
	} else if err != nil {
		return "", err
	}
	return formatMsgID(network.GetName(), entity, t, fi.Size()-1), nil
}

func (ml *messageLogger) Append(msg *irc.Message) (string, error) {
	s := formatMessage(msg)
	if s == "" {
		return nil
		return "", nil
	}

	var t time.Time


@@ 50,7 89,7 @@ func (ml *messageLogger) Append(msg *irc.Message) error {
		var err error
		t, err = time.Parse(serverTimeLayout, string(tag))
		if err != nil {
			return fmt.Errorf("failed to parse message time tag: %v", err)
			return "", fmt.Errorf("failed to parse message time tag: %v", err)
		}
		t = t.In(time.Local)
	} else {


@@ 67,23 106,28 @@ func (ml *messageLogger) Append(msg *irc.Message) error {

		dir := filepath.Dir(path)
		if err := os.MkdirAll(dir, 0700); err != nil {
			return fmt.Errorf("failed to create logs directory %q: %v", dir, err)
			return "", fmt.Errorf("failed to create logs directory %q: %v", dir, err)
		}

		f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0600)
		if err != nil {
			return fmt.Errorf("failed to open log file %q: %v", path, err)
			return "", fmt.Errorf("failed to open log file %q: %v", path, err)
		}

		ml.path = path
		ml.file = f
	}

	_, err := fmt.Fprintf(ml.file, "[%02d:%02d:%02d] %s\n", t.Hour(), t.Minute(), t.Second(), s)
	msgID, err := nextMsgID(ml.network, ml.entity, t, ml.file)
	if err != nil {
		return "", fmt.Errorf("failed to generate message ID: %v", err)
	}

	_, err = fmt.Fprintf(ml.file, "[%02d:%02d:%02d] %s\n", t.Hour(), t.Minute(), t.Second(), s)
	if err != nil {
		return fmt.Errorf("failed to log message to %q: %v", ml.path, err)
		return "", fmt.Errorf("failed to log message to %q: %v", ml.path, err)
	}
	return nil
	return msgID, nil
}

func (ml *messageLogger) Close() error {

M upstream.go => upstream.go +1 -1
@@ 1623,7 1623,7 @@ func (uc *upstreamConn) appendLog(entity string, msg *irc.Message) {
		uc.messageLoggers[entity] = ml
	}

	if err := ml.Append(msg); err != nil {
	if _, err := ml.Append(msg); err != nil {
		uc.logger.Printf("failed to log message: %v", err)
	}
}