Skip to content

Commit f09cc45

Browse files
committed
Pull request 83: AGDNS-3219-agdcslog-syslog-tests
Merge in GO/adguarddnsclient from AGDNS-3219-agdcslog-syslog-tests to master Squashed commit of the following: commit aea29b1 Author: Stanislav Chzhen <s.chzhen@adguard.com> Date: Tue Sep 23 15:47:19 2025 +0300 agdcslog: imp docs commit 5b76c10 Author: Stanislav Chzhen <s.chzhen@adguard.com> Date: Mon Sep 22 17:29:28 2025 +0300 agdcslog: imp tests commit 6f75811 Merge: 2a0ac83 5a0279e Author: Stanislav Chzhen <s.chzhen@adguard.com> Date: Fri Sep 19 14:53:04 2025 +0300 Merge branch 'master' into AGDNS-3219-agdcslog-syslog-tests commit 2a0ac83 Author: Stanislav Chzhen <s.chzhen@adguard.com> Date: Fri Sep 19 14:52:55 2025 +0300 agdcslog: imp docs, tests commit f6eaee7 Author: Stanislav Chzhen <s.chzhen@adguard.com> Date: Thu Sep 18 15:36:39 2025 +0300 agdcslog: fix tests commit a440082 Author: Stanislav Chzhen <s.chzhen@adguard.com> Date: Thu Sep 18 14:49:59 2025 +0300 agdcslog: add syslog tests
1 parent 5a0279e commit f09cc45

File tree

7 files changed

+315
-22
lines changed

7 files changed

+315
-22
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package agdcslog
2+
3+
import "time"
4+
5+
// testTimeout is the common timeout for tests.
6+
const testTimeout = 1 * time.Second

internal/agdcslog/syslog.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import (
99
//
1010
// TODO(e.burkov): Consider moving to golibs.
1111
//
12-
// TODO(s.chzhen): Add platform-specific tests.
12+
// TODO(s.chzhen): Add platform-specific integration tests.
1313
type SystemLogger interface {
1414
// Debug logs a message at debug level.
1515
Debug(msg string) (err error)

internal/agdcslog/syslog_darwin.go

Lines changed: 37 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,10 @@ const defaultOutLimit datasize.ByteSize = 1 * datasize.KB
3636
//
3737
// TODO(e.burkov): Get rid of it when golang/go#59229 is resolved.
3838
type systemLogger struct {
39-
debug *process
40-
info *process
41-
warning *process
42-
error *process
39+
debug *osProcess
40+
info *osProcess
41+
warning *osProcess
42+
error *osProcess
4343

4444
// tag is the prefix for all log messages.
4545
//
@@ -50,6 +50,25 @@ type systemLogger struct {
5050

5151
// newSystemLogger returns a macOS-specific system logger.
5252
func newSystemLogger(ctx context.Context, tag string) (l SystemLogger, err error) {
53+
cmdCons := executil.SystemCommandConstructor{}
54+
sysl, err := newSystemLoggerWithCommandConstructor(ctx, cmdCons, tag)
55+
if err != nil {
56+
// Don't wrap the error because it's informative enough as is.
57+
return nil, err
58+
}
59+
60+
return sysl, nil
61+
}
62+
63+
// newSystemLoggerWithCommandConstructor returns a macOS-specific system logger
64+
// using the provided command constructor.
65+
//
66+
// TODO(s.chzhen): Use this as the actual constructor.
67+
func newSystemLoggerWithCommandConstructor(
68+
ctx context.Context,
69+
cmdCons executil.CommandConstructor,
70+
tag string,
71+
) (l SystemLogger, err error) {
5372
sysl := &systemLogger{
5473
tag: tag,
5574
}
@@ -60,24 +79,22 @@ func newSystemLogger(ctx context.Context, tag string) (l SystemLogger, err error
6079
// first argument and an error as the second.
6180
const msgFmt = "creating %s logger process: %w"
6281

63-
cmdCons := executil.SystemCommandConstructor{}
64-
65-
sysl.debug, err = newProcess(ctx, cmdCons, sevDebug)
82+
sysl.debug, err = newOSProcess(ctx, cmdCons, sevDebug)
6683
if err != nil {
6784
errs = append(errs, fmt.Errorf(msgFmt, sevDebug, err))
6885
}
6986

70-
sysl.info, err = newProcess(ctx, cmdCons, sevInfo)
87+
sysl.info, err = newOSProcess(ctx, cmdCons, sevInfo)
7188
if err != nil {
7289
errs = append(errs, fmt.Errorf(msgFmt, sevInfo, err))
7390
}
7491

75-
sysl.warning, err = newProcess(ctx, cmdCons, sevWarning)
92+
sysl.warning, err = newOSProcess(ctx, cmdCons, sevWarning)
7693
if err != nil {
7794
errs = append(errs, fmt.Errorf(msgFmt, sevWarning, err))
7895
}
7996

80-
sysl.error, err = newProcess(ctx, cmdCons, sevError)
97+
sysl.error, err = newOSProcess(ctx, cmdCons, sevError)
8198
if err != nil {
8299
errs = append(errs, fmt.Errorf(msgFmt, sevError, err))
83100
}
@@ -121,7 +138,7 @@ func (l *systemLogger) Close() (err error) {
121138
defer func() { err = errors.Annotate(err, "closing logger processes: %w") }()
122139

123140
var errs []error
124-
procs := []*process{
141+
procs := []*osProcess{
125142
l.debug,
126143
l.info,
127144
l.warning,
@@ -143,8 +160,8 @@ func (l *systemLogger) Close() (err error) {
143160
return errors.Join(errs...)
144161
}
145162

146-
// process is an instance of the logger process with a particular severity.
147-
type process struct {
163+
// osProcess is an instance of the logger process with a particular severity.
164+
type osProcess struct {
148165
// mu synchronizes writes to stdin between each other and with the process
149166
// closing.
150167
mu *sync.Mutex
@@ -166,12 +183,12 @@ type process struct {
166183
severity severity
167184
}
168185

169-
// newProcess creates a new process with a particular severity.
170-
func newProcess(
186+
// newOSProcess creates a new process with a particular severity.
187+
func newOSProcess(
171188
ctx context.Context,
172189
cmdCons executil.CommandConstructor,
173190
sev severity,
174-
) (p *process, err error) {
191+
) (p *osProcess, err error) {
175192
const (
176193
binPath = "/usr/bin/logger"
177194
optionPriority = "-p"
@@ -204,7 +221,7 @@ func newProcess(
204221
return nil, fmt.Errorf("starting command: %w", err)
205222
}
206223

207-
return &process{
224+
return &osProcess{
208225
mu: &sync.Mutex{},
209226
cmd: cmd,
210227
stdin: stdinWriter,
@@ -215,7 +232,7 @@ func newProcess(
215232
}
216233

217234
// write writes the message to the logger.
218-
func (p *process) write(tag, msg string) (err error) {
235+
func (p *osProcess) write(tag, msg string) (err error) {
219236
defer func() { err = errors.Annotate(err, "writing %s message", p.severity) }()
220237

221238
msg = strings.TrimSuffix(msg, "\n")
@@ -228,8 +245,8 @@ func (p *process) write(tag, msg string) (err error) {
228245
return err
229246
}
230247

231-
// close closes the process' pipes and waits for the command to exit.
232-
func (p *process) close() (err error) {
248+
// close closes the process's pipes and waits for the command to exit.
249+
func (p *osProcess) close() (err error) {
233250
defer func() { err = errors.Annotate(err, "closing %s logger: %w", p.severity) }()
234251

235252
var errs []error
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
//go:build darwin
2+
3+
package agdcslog
4+
5+
import (
6+
"bytes"
7+
"context"
8+
"io"
9+
"sync"
10+
"testing"
11+
12+
"github.com/AdguardTeam/golibs/osutil/executil"
13+
"github.com/AdguardTeam/golibs/testutil"
14+
"github.com/AdguardTeam/golibs/testutil/fakeos/fakeexec"
15+
"github.com/stretchr/testify/assert"
16+
"github.com/stretchr/testify/require"
17+
)
18+
19+
// lockedWriter protects concurrent writes to an underlying writer.
20+
type lockedWriter struct {
21+
mu *sync.Mutex
22+
w io.Writer
23+
}
24+
25+
// type check
26+
var _ io.Writer = (*lockedWriter)(nil)
27+
28+
// Write implements the [io.Writer] interface for *lockedWriter.
29+
func (lw *lockedWriter) Write(b []byte) (n int, err error) {
30+
lw.mu.Lock()
31+
defer lw.mu.Unlock()
32+
33+
return lw.w.Write(b)
34+
}
35+
36+
func TestSystemLogger(t *testing.T) {
37+
t.Parallel()
38+
39+
const (
40+
testTag = "tag"
41+
42+
debugMsg = "debug_msg"
43+
infoMsg = "info_msg"
44+
warnMsg = "warn_msg"
45+
errMsg = "err_msg"
46+
)
47+
48+
stdoutBuf := bytes.Buffer{}
49+
lw := &lockedWriter{
50+
mu: &sync.Mutex{},
51+
w: &stdoutBuf,
52+
}
53+
54+
onNew := func(_ context.Context, conf *executil.CommandConfig) (c executil.Command, err error) {
55+
cmd := fakeexec.NewCommand()
56+
57+
done := make(chan struct{})
58+
cmd.OnStart = func(_ context.Context) (err error) {
59+
go func(r io.Reader) {
60+
_, err = io.Copy(lw, r)
61+
require.NoError(testutil.PanicT{}, err)
62+
63+
close(done)
64+
}(conf.Stdin)
65+
66+
return nil
67+
}
68+
69+
cmd.OnWait = func(_ context.Context) (err error) {
70+
<-done
71+
72+
return nil
73+
}
74+
75+
return cmd, nil
76+
}
77+
78+
cmdCons := &fakeexec.CommandConstructor{
79+
OnNew: onNew,
80+
}
81+
82+
ctx := testutil.ContextWithTimeout(t, testTimeout)
83+
l, err := newSystemLoggerWithCommandConstructor(ctx, cmdCons, testTag)
84+
require.NoError(t, err)
85+
86+
require.NoError(t, l.Debug(debugMsg))
87+
require.NoError(t, l.Info(infoMsg))
88+
require.NoError(t, l.Warning(warnMsg))
89+
require.NoError(t, l.Error(errMsg))
90+
require.NoError(t, l.Close())
91+
92+
out := stdoutBuf.String()
93+
assert.Contains(t, out, testTag+": "+debugMsg+"\n")
94+
assert.Contains(t, out, testTag+": "+infoMsg+"\n")
95+
assert.Contains(t, out, testTag+": "+warnMsg+"\n")
96+
assert.Contains(t, out, testTag+": "+errMsg+"\n")
97+
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
//go:build linux
2+
3+
package agdcslog
4+
5+
import (
6+
"io"
7+
"log/syslog"
8+
"net"
9+
"net/netip"
10+
"testing"
11+
"time"
12+
13+
"github.com/AdguardTeam/golibs/netutil"
14+
"github.com/AdguardTeam/golibs/testutil"
15+
"github.com/stretchr/testify/assert"
16+
"github.com/stretchr/testify/require"
17+
)
18+
19+
func TestSystemLogger(t *testing.T) {
20+
t.Parallel()
21+
22+
const (
23+
network = "tcp"
24+
25+
testTag = "tag"
26+
27+
debugMsg = "debug_msg"
28+
infoMsg = "info_msg"
29+
warnMsg = "warn_msg"
30+
errMsg = "err_msg"
31+
)
32+
33+
localhostAnyPort := netip.AddrPortFrom(netutil.IPv4Localhost(), 0)
34+
addr := net.TCPAddrFromAddrPort(localhostAnyPort)
35+
ln, err := net.ListenTCP(network, addr)
36+
require.NoError(t, err)
37+
testutil.CleanupAndRequireSuccess(t, ln.Close)
38+
39+
w, err := syslog.Dial(network, ln.Addr().String(), syslog.LOG_LOCAL0, testTag)
40+
require.NoError(t, err)
41+
42+
l := &systemLogger{writer: w}
43+
require.NoError(t, l.Debug(debugMsg))
44+
require.NoError(t, l.Info(infoMsg))
45+
require.NoError(t, l.Warning(warnMsg))
46+
require.NoError(t, l.Error(errMsg))
47+
48+
// Close the syslog writer to signal EOF to the reader.
49+
require.NoError(t, l.Close())
50+
51+
conn, err := ln.Accept()
52+
require.NoError(t, err)
53+
testutil.CleanupAndRequireSuccess(t, conn.Close)
54+
55+
err = conn.SetReadDeadline(time.Now().Add(testTimeout))
56+
require.NoError(t, err)
57+
58+
data, err := io.ReadAll(conn)
59+
require.NoError(t, err)
60+
61+
out := string(data)
62+
assert.Contains(t, out, debugMsg)
63+
assert.Contains(t, out, infoMsg)
64+
assert.Contains(t, out, warnMsg)
65+
assert.Contains(t, out, errMsg)
66+
}

internal/agdcslog/syslog_windows.go

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,26 @@ const (
2424
debugEventID = 4
2525
)
2626

27+
// eventlogWriter abstracts the Windows event log.
28+
type eventlogWriter interface {
29+
// Info logs at the info level. The same method is also used for debug
30+
// messages with a different event ID.
31+
Info(id uint32, msg string) (err error)
32+
33+
// Warning logs a message at the warning level.
34+
Warning(id uint32, msg string) (err error)
35+
36+
// Error logs a message at the error level.
37+
Error(id uint32, msg string) (err error)
38+
39+
// Close closes the system log writer.
40+
Close() (err error)
41+
}
42+
2743
// systemLogger is the implementation of the [SystemLogger] interface for
2844
// Windows.
2945
type systemLogger struct {
30-
writer *eventlog.Log
46+
writer eventlogWriter
3147
}
3248

3349
// newSystemLogger returns a Windows-specific system logger.

0 commit comments

Comments
 (0)