-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathshim.go
154 lines (131 loc) · 2.81 KB
/
shim.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
package main
import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"log"
"os"
"os/user"
"time"
)
type LogEntry struct {
Time string `json:"time"`
UserID string `json:"uid"`
Username string `json:"username,omitempty"`
Arguments []string `json:"arguments"`
Body string `json:"body"`
}
type LogError struct {
Err error
Tag string
}
// Returns (uid, username)
type UsernameFunc func() (string, string)
func GetUsername() (uid string, username string) {
// get calling user ID and name
u, err := user.Current()
if err == nil {
return u.Uid, u.Username
}
// just return the user ID
return fmt.Sprintf("%d", os.Getuid()), ""
}
type TimeFunc func() string
func GetTime() string {
return time.Now().UTC().Format(time.RFC3339)
}
func OpenLogFile(path string) (*os.File, *LogError) {
f, err := os.OpenFile(path, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0644)
if err != nil {
return nil, &LogError{
fmt.Errorf("couldn't open log file: %v", err),
"open-log-file",
}
}
return f, nil
}
type EmailLogger struct {
Args []string
Body io.Reader
User UsernameFunc
Time TimeFunc
Writer io.Writer
LogPath string // used if there's no Writer
}
func (l *EmailLogger) Populate(e *LogEntry) *LogError {
// set the time
e.Time = l.Time()
// populate the uid and username
uid, username := l.User()
e.UserID = uid
e.Username = username
// just use the full arguments list minus the program name
e.Arguments = l.Args
// read stdin
body, err := ioutil.ReadAll(l.Body)
if err != nil {
return &LogError{
fmt.Errorf("couldn't read stdin: %v", err),
"stdin-failed",
}
}
e.Body = string(body)
return nil
}
func (l *EmailLogger) EncodeJSON(e LogEntry) *LogError {
j := json.NewEncoder(l.Writer)
err := j.Encode(e)
if err != nil {
return &LogError{
fmt.Errorf("couldn't encode JSON: %v", err),
"json-encoding",
}
}
return nil
}
func (l *EmailLogger) Emit() *LogError {
if l.Writer == nil {
// open the log file if there's no writer
f, err := OpenLogFile(l.LogPath)
if err != nil {
return err
}
l.Writer = f
defer func() {
l.Writer = nil
f.Close()
}()
}
// build the log entry
entry := LogEntry{}
err := l.Populate(&entry)
if err != nil {
return err
}
// write out JSON
err = l.EncodeJSON(entry)
if err != nil {
return err
}
// emit success metrics here if you want!
//
// metrics.Increment("sendmail-shim.success", 1, map[string]string{"uid": entry.UserID})
return nil
}
func main() {
l := EmailLogger{
LogPath: "/var/log/sendmail-shim.log.json",
Args: os.Args[1:],
Body: os.Stdin,
User: GetUsername,
Time: GetTime,
}
err := l.Emit()
if err != nil {
// emit failure metrics here if you want!
//
// metrics.Increment("sendmail-shim.error", 1, map[string]string{"reason": err.Tag})
log.Fatal(err.Err)
}
}