13 "github.com/stretchr/testify/require"
16 func TestDaemonLogFormatsLevelsAndPrefixes(t *testing.T) {
17 testCases := []struct {
24 name: "error names the failed part",
25 level: slog.LevelError,
30 name: "warning names the retried part",
31 level: slog.LevelWarn,
36 name: "info names the connected part",
37 level: slog.LevelInfo,
42 name: "debug has its own priority",
43 level: slog.LevelDebug,
49 for _, tc := range testCases {
50 t.Run(tc.name, func(t *testing.T) {
51 for _, journal := range []bool{false, true} {
52 var out strings.Builder
54 handler := &DaemonLogHandler{
61 record := slog.NewRecord(time.Date(2026, 1, 2, 3, 4, 5, 0, time.UTC), tc.level, "ready", 0)
62 require.True(t, handler.Enabled(t.Context(), tc.level))
63 require.NoError(t, handler.Handle(t.Context(), record))
65 expected := "2026/01/02 03:04:05 " + tc.level.String() + " " + tc.prefix + "ready\n"
67 expected = "<" + tc.priority + ">" + tc.prefix + "ready\n"
70 require.Equal(t, expected, out.String())
76 func TestDaemonLogKeepsAttributesInOrderAndCauseLast(t *testing.T) {
77 var out strings.Builder
79 handler := &DaemonLogHandler{
86 logger := slog.New(handler).With("first", "two words", slog.Attr{})
87 logger.Warn("receive failed", "err", errors.New("502 Bad Gateway"), "retry", 2040*time.Millisecond, "count", 3, "short", 1500*time.Microsecond)
88 require.Equal(t, "<4>telegram: receive failed first=two words retry=2s count=3 short=2ms: 502 Bad Gateway\n", out.String())
91 func TestDaemonLogOmitsEmptyAttributesAndNilCause(t *testing.T) {
92 var out strings.Builder
94 handler := &DaemonLogHandler{
101 slog.New(handler).Info("ready", slog.Attr{}, slog.Group("empty"), "err", nil)
102 require.Equal(t, "<6>onechat: ready\n", out.String())
105 func TestDaemonLogGroupsDoNotChangeOtherLoggers(t *testing.T) {
106 var out strings.Builder
108 handler := &DaemonLogHandler{
111 mutex: &sync.Mutex{},
114 logger := slog.New(handler)
115 grouped := logger.With("before", 1).WithGroup("outer").With("saved", 2).WithGroup("inner")
116 grouped.Info("done", slog.Group("", "count", 3))
118 require.Equal(t, "<6>done before=1 outer.saved=2 outer.inner.count=3\n<6>ready\n", out.String())
121 func TestDaemonLogReturnsWriteFailure(t *testing.T) {
122 handler := &DaemonLogHandler{
123 out: DaemonLogBrokenWriter{},
124 mutex: &sync.Mutex{},
127 err := handler.Handle(context.Background(), slog.NewRecord(time.Now(), slog.LevelInfo, "ready", 0))
128 require.ErrorIs(t, err, io.ErrClosedPipe)
129 require.ErrorContains(t, err, "write daemon log")
132 type DaemonLogBrokenWriter struct{}
134 func (w DaemonLogBrokenWriter) Write(_ []byte) (int, error) {
135 return 0, io.ErrClosedPipe