source: code/trunk/logger.go@ 318

Last change on this file since 318 was 250, checked in by contact, 5 years ago

Parse timestamp from message tags in messageLogger.Append

File size: 3.3 KB
Line 
1package soju
2
3import (
4 "fmt"
5 "os"
6 "path/filepath"
7 "strings"
8 "time"
9
10 "gopkg.in/irc.v3"
11)
12
13type messageLogger struct {
14 network *network
15 entity string
16
17 path string
18 file *os.File
19}
20
21func newMessageLogger(network *network, entity string) *messageLogger {
22 return &messageLogger{
23 network: network,
24 entity: entity,
25 }
26}
27
28func logPath(network *network, entity string, t time.Time) string {
29 user := network.user
30 srv := user.srv
31
32 // TODO: handle/forbid network/entity names with illegal path characters
33 year, month, day := t.Date()
34 filename := fmt.Sprintf("%04d-%02d-%02d.log", year, month, day)
35 return filepath.Join(srv.LogPath, user.Username, network.GetName(), entity, filename)
36}
37
38func (ml *messageLogger) Append(msg *irc.Message) error {
39 s := formatMessage(msg)
40 if s == "" {
41 return nil
42 }
43
44 var t time.Time
45 if tag, ok := msg.Tags["time"]; ok {
46 var err error
47 t, err = time.Parse(serverTimeLayout, string(tag))
48 if err != nil {
49 return fmt.Errorf("failed to parse message time tag: %v", err)
50 }
51 t = t.In(time.Local)
52 } else {
53 t = time.Now()
54 }
55
56 // TODO: enforce maximum open file handles (LRU cache of file handles)
57 // TODO: handle non-monotonic clock behaviour
58 path := logPath(ml.network, ml.entity, t)
59 if ml.path != path {
60 if ml.file != nil {
61 ml.file.Close()
62 }
63
64 dir := filepath.Dir(path)
65 if err := os.MkdirAll(dir, 0700); err != nil {
66 return fmt.Errorf("failed to create logs directory %q: %v", dir, err)
67 }
68
69 f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0600)
70 if err != nil {
71 return fmt.Errorf("failed to open log file %q: %v", path, err)
72 }
73
74 ml.path = path
75 ml.file = f
76 }
77
78 _, err := fmt.Fprintf(ml.file, "[%02d:%02d:%02d] %s\n", t.Hour(), t.Minute(), t.Second(), s)
79 if err != nil {
80 return fmt.Errorf("failed to log message to %q: %v", ml.path, err)
81 }
82 return nil
83}
84
85func (ml *messageLogger) Close() error {
86 if ml.file == nil {
87 return nil
88 }
89 return ml.file.Close()
90}
91
92// formatMessage formats a message log line. It assumes a well-formed IRC
93// message.
94func formatMessage(msg *irc.Message) string {
95 switch strings.ToUpper(msg.Command) {
96 case "NICK":
97 return fmt.Sprintf("*** %s is now known as %s", msg.Prefix.Name, msg.Params[0])
98 case "JOIN":
99 return fmt.Sprintf("*** Joins: %s (%s@%s)", msg.Prefix.Name, msg.Prefix.User, msg.Prefix.Host)
100 case "PART":
101 var reason string
102 if len(msg.Params) > 1 {
103 reason = msg.Params[1]
104 }
105 return fmt.Sprintf("*** Parts: %s (%s@%s) (%s)", msg.Prefix.Name, msg.Prefix.User, msg.Prefix.Host, reason)
106 case "KICK":
107 nick := msg.Params[1]
108 var reason string
109 if len(msg.Params) > 2 {
110 reason = msg.Params[2]
111 }
112 return fmt.Sprintf("*** %s was kicked by %s (%s)", nick, msg.Prefix.Name, reason)
113 case "QUIT":
114 var reason string
115 if len(msg.Params) > 0 {
116 reason = msg.Params[0]
117 }
118 return fmt.Sprintf("*** Quits: %s (%s@%s) (%s)", msg.Prefix.Name, msg.Prefix.User, msg.Prefix.Host, reason)
119 case "TOPIC":
120 var topic string
121 if len(msg.Params) > 1 {
122 topic = msg.Params[1]
123 }
124 return fmt.Sprintf("*** %s changes topic to '%s'", msg.Prefix.Name, topic)
125 case "MODE":
126 return fmt.Sprintf("*** %s sets mode: %s", msg.Prefix.Name, strings.Join(msg.Params[1:], " "))
127 case "NOTICE":
128 return fmt.Sprintf("-%s- %s", msg.Prefix.Name, msg.Params[1])
129 case "PRIVMSG":
130 return fmt.Sprintf("<%s> %s", msg.Prefix.Name, msg.Params[1])
131 default:
132 return ""
133 }
134}
Note: See TracBrowser for help on using the repository browser.