Skip to content

Commit f82742b

Browse files
committed
Implement net.Listener hand-over during config reload
Log library refactored a little to make it easier to enable debug logging.
1 parent 3ce6ebf commit f82742b

File tree

19 files changed

+394
-44
lines changed

19 files changed

+394
-44
lines changed

framework/log/log.go

Lines changed: 39 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ import (
4242
// No serialization is provided by Logger, its log.Output responsibility to
4343
// ensure goroutine-safety if necessary.
4444
type Logger struct {
45+
Parent *Logger
46+
4547
Out Output
4648
Name string
4749
Debug bool
@@ -51,30 +53,37 @@ type Logger struct {
5153
Fields map[string]interface{}
5254
}
5355

54-
func (l Logger) Zap() *zap.Logger {
56+
func (l *Logger) Zap() *zap.Logger {
5557
// TODO: Migrate to using zap natively.
5658
return zap.New(zapLogger{L: l})
5759
}
5860

59-
func (l Logger) Debugf(format string, val ...interface{}) {
60-
if !l.Debug {
61+
func (l *Logger) IsDebug() bool {
62+
if l.Parent == nil {
63+
return l.Debug
64+
}
65+
return l.Debug || l.Parent.IsDebug()
66+
}
67+
68+
func (l *Logger) Debugf(format string, val ...interface{}) {
69+
if !l.IsDebug() {
6170
return
6271
}
6372
l.log(true, l.formatMsg(fmt.Sprintf(format, val...), nil))
6473
}
6574

66-
func (l Logger) Debugln(val ...interface{}) {
67-
if !l.Debug {
75+
func (l *Logger) Debugln(val ...interface{}) {
76+
if !l.IsDebug() {
6877
return
6978
}
7079
l.log(true, l.formatMsg(strings.TrimRight(fmt.Sprintln(val...), "\n"), nil))
7180
}
7281

73-
func (l Logger) Printf(format string, val ...interface{}) {
82+
func (l *Logger) Printf(format string, val ...interface{}) {
7483
l.log(false, l.formatMsg(fmt.Sprintf(format, val...), nil))
7584
}
7685

77-
func (l Logger) Println(val ...interface{}) {
86+
func (l *Logger) Println(val ...interface{}) {
7887
l.log(false, l.formatMsg(strings.TrimRight(fmt.Sprintln(val...), "\n"), nil))
7988
}
8089

@@ -87,13 +96,13 @@ func (l Logger) Println(val ...interface{}) {
8796
// followed by corresponding values. That is, for example, []interface{"key",
8897
// "value", "key2", "value2"}.
8998
//
90-
// If value in fields implements LogFormatter, it will be represented by the
99+
// If value in fields implements Formatter, it will be represented by the
91100
// string returned by FormatLog method. Same goes for fmt.Stringer and error
92101
// interfaces.
93102
//
94103
// Additionally, time.Time is written as a string in ISO 8601 format.
95104
// time.Duration follows fmt.Stringer rule above.
96-
func (l Logger) Msg(msg string, fields ...interface{}) {
105+
func (l *Logger) Msg(msg string, fields ...interface{}) {
97106
m := make(map[string]interface{}, len(fields)/2)
98107
fieldsToMap(fields, m)
99108
l.log(false, l.formatMsg(msg, m))
@@ -112,7 +121,7 @@ func (l Logger) Msg(msg string, fields ...interface{}) {
112121
// In the context of Error method, "msg" typically indicates the top-level
113122
// context in which the error is *handled*. For example, if error leads to
114123
// rejection of SMTP DATA command, msg will probably be "DATA error".
115-
func (l Logger) Error(msg string, err error, fields ...interface{}) {
124+
func (l *Logger) Error(msg string, err error, fields ...interface{}) {
116125
if err == nil {
117126
return
118127
}
@@ -133,8 +142,8 @@ func (l Logger) Error(msg string, err error, fields ...interface{}) {
133142
l.log(false, l.formatMsg(msg, allFields))
134143
}
135144

136-
func (l Logger) DebugMsg(kind string, fields ...interface{}) {
137-
if !l.Debug {
145+
func (l *Logger) DebugMsg(kind string, fields ...interface{}) {
146+
if !l.IsDebug() {
138147
return
139148
}
140149
m := make(map[string]interface{}, len(fields)/2)
@@ -162,7 +171,7 @@ func fieldsToMap(fields []interface{}, out map[string]interface{}) {
162171
}
163172
}
164173

165-
func (l Logger) formatMsg(msg string, fields map[string]interface{}) string {
174+
func (l *Logger) formatMsg(msg string, fields map[string]interface{}) string {
166175
formatted := strings.Builder{}
167176

168177
formatted.WriteString(msg)
@@ -184,30 +193,31 @@ func (l Logger) formatMsg(msg string, fields map[string]interface{}) string {
184193
return formatted.String()
185194
}
186195

187-
type LogFormatter interface {
196+
type Formatter interface {
188197
FormatLog() string
189198
}
190199

191200
// Write implements io.Writer, all bytes sent
192201
// to it will be written as a separate log messages.
193202
// No line-buffering is done.
194-
func (l Logger) Write(s []byte) (int, error) {
203+
func (l *Logger) Write(s []byte) (int, error) {
204+
if !l.IsDebug() {
205+
return len(s), nil
206+
}
195207
l.log(false, strings.TrimRight(string(s), "\n"))
196208
return len(s), nil
197209
}
198210

199211
// DebugWriter returns a writer that will act like Logger.Write
200212
// but will use debug flag on messages. If Logger.Debug is false,
201213
// Write method of returned object will be no-op.
202-
func (l Logger) DebugWriter() io.Writer {
203-
if !l.Debug {
204-
return io.Discard
205-
}
206-
l.Debug = true
207-
return &l
214+
func (l *Logger) DebugWriter() io.Writer {
215+
l2 := l.Sublogger("")
216+
l2.Debug = true
217+
return l2
208218
}
209219

210-
func (l Logger) log(debug bool, s string) {
220+
func (l *Logger) log(debug bool, s string) {
211221
if l.Name != "" {
212222
s = l.Name + ": " + s
213223
}
@@ -224,14 +234,15 @@ func (l Logger) log(debug bool, s string) {
224234
// Logging is disabled - do nothing.
225235
}
226236

227-
func (l Logger) Sublogger(name string) Logger {
228-
if l.Name != "" {
237+
func (l *Logger) Sublogger(name string) *Logger {
238+
if l.Name != "" && name != "" {
229239
name = l.Name + "/" + name
230240
}
231-
return Logger{
232-
Out: l.Out,
233-
Name: name,
234-
Debug: l.Debug,
241+
return &Logger{
242+
Parent: l,
243+
Out: l.Out,
244+
Name: name,
245+
Debug: l.Debug,
235246
}
236247
}
237248

framework/log/orderedjson.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ func marshalOrderedJSON(output *strings.Builder, m map[string]interface{}) error
5858
val = casted.Format("2006-01-02T15:04:05.000")
5959
case time.Duration:
6060
val = casted.String()
61-
case LogFormatter:
61+
case Formatter:
6262
val = casted.FormatLog()
6363
case fmt.Stringer:
6464
val = casted.String()

framework/log/zap.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import (
77
// TODO: Migrate to using actual zapcore to improve logging performance
88

99
type zapLogger struct {
10-
L Logger
10+
L *Logger
1111
}
1212

1313
func (l zapLogger) Enabled(level zapcore.Level) bool {

framework/module/lifetime.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ type ReloadModule interface {
3838
}
3939

4040
type LifetimeTracker struct {
41-
logger log.Logger
41+
logger *log.Logger
4242
instances []*struct {
4343
mod LifetimeModule
4444
started bool
@@ -114,7 +114,7 @@ func (lt *LifetimeTracker) StopAll() error {
114114
return nil
115115
}
116116

117-
func NewLifetime(log log.Logger) *LifetimeTracker {
117+
func NewLifetime(log *log.Logger) *LifetimeTracker {
118118
return &LifetimeTracker{
119119
logger: log,
120120
}

framework/module/registry.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,14 +35,14 @@ type registryEntry struct {
3535
}
3636

3737
type Registry struct {
38-
logger log.Logger
38+
logger *log.Logger
3939
instances map[string]registryEntry
4040
initialized map[string]struct{}
4141
started map[string]struct{}
4242
aliases map[string]string
4343
}
4444

45-
func NewRegistry(log log.Logger) *Registry {
45+
func NewRegistry(log *log.Logger) *Registry {
4646
return &Registry{
4747
logger: log,
4848
instances: make(map[string]registryEntry),

framework/resource/netresource/dup.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package netresource
2+
3+
import "net"
4+
5+
func dupTCPListener(l *net.TCPListener) (*net.TCPListener, error) {
6+
f, err := l.File()
7+
if err != nil {
8+
return nil, err
9+
}
10+
l2, err := net.FileListener(f)
11+
if err != nil {
12+
return nil, err
13+
}
14+
return l2.(*net.TCPListener), nil
15+
}
16+
17+
func dupUnixListener(l *net.UnixListener) (*net.UnixListener, error) {
18+
f, err := l.File()
19+
if err != nil {
20+
return nil, err
21+
}
22+
l2, err := net.FileListener(f)
23+
if err != nil {
24+
return nil, err
25+
}
26+
return l2.(*net.UnixListener), nil
27+
}

framework/resource/netresource/fd.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package netresource
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
"net"
7+
"os"
8+
"strconv"
9+
"strings"
10+
)
11+
12+
func ListenFD(fd uint) (net.Listener, error) {
13+
file := os.NewFile(uintptr(fd), strconv.FormatUint(uint64(fd), 10))
14+
defer file.Close()
15+
return net.FileListener(file)
16+
}
17+
18+
func ListenFDName(name string) (net.Listener, error) {
19+
listenPDStr := os.Getenv("LISTEN_PID")
20+
if listenPDStr == "" {
21+
return nil, errors.New("$LISTEN_PID is not set")
22+
}
23+
listenPid, err := strconv.Atoi(listenPDStr)
24+
if err != nil {
25+
return nil, errors.New("$LISTEN_PID is not integer")
26+
}
27+
if listenPid != os.Getpid() {
28+
return nil, fmt.Errorf("$LISTEN_PID (%d) is not our PID (%d)", listenPid, os.Getpid())
29+
}
30+
31+
names := strings.Split(os.Getenv("LISTEN_FDNAMES"), ":")
32+
fd := uintptr(0)
33+
for i, fdName := range names {
34+
if fdName == name {
35+
fd = uintptr(3 + i)
36+
break
37+
}
38+
}
39+
40+
if fd == 0 {
41+
return nil, fmt.Errorf("name %s not found in $LISTEN_FDNAMES", name)
42+
}
43+
44+
file := os.NewFile(3+fd, name)
45+
defer file.Close()
46+
return net.FileListener(file)
47+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package netresource
2+
3+
import (
4+
"fmt"
5+
"net"
6+
"strconv"
7+
8+
"github.com/foxcpp/maddy/framework/log"
9+
)
10+
11+
var (
12+
tracker = NewListenerTracker(log.DefaultLogger.Sublogger("netresource"))
13+
)
14+
15+
func CloseUnusedListeners() error {
16+
return tracker.Close()
17+
}
18+
19+
func ResetListenersUsage() {
20+
tracker.ResetUsage()
21+
}
22+
23+
func Listen(network, addr string) (net.Listener, error) {
24+
switch network {
25+
case "fd":
26+
fd, err := strconv.ParseUint(addr, 10, strconv.IntSize)
27+
if err != nil {
28+
return nil, fmt.Errorf("invalid FD number: %v", addr)
29+
}
30+
return ListenFD(uint(fd))
31+
case "fdname":
32+
return ListenFDName(addr)
33+
case "tcp", "tcp4", "tcp6", "unix":
34+
return tracker.Get(network, addr)
35+
default:
36+
return nil, fmt.Errorf("unsupported network: %v", network)
37+
}
38+
}

0 commit comments

Comments
 (0)