cannon_test.go

  1package log
  2
  3import (
  4	"bytes"
  5	"encoding/json"
  6	"log"
  7	"log/slog"
  8	"testing"
  9	"testing/slogtest"
 10
 11	"github.com/stretchr/testify/assert"
 12)
 13
 14// Tests conformity with a slog handler for normal logging operation
 15func TestHandler(t *testing.T) {
 16	var buf bytes.Buffer
 17	h := slog.NewJSONHandler(&buf, nil)
 18	c := wrap(h)
 19
 20	results := func() []map[string]any {
 21		return getJSONLines(buf.Bytes(), t)
 22	}
 23	err := slogtest.TestHandler(c, results)
 24	if err != nil {
 25		log.Fatal(err)
 26	}
 27}
 28
 29func getJSONLines(b []byte, t *testing.T) []map[string]any {
 30	var ms []map[string]any
 31	for _, line := range bytes.Split(b, []byte{'\n'}) {
 32		if len(line) == 0 {
 33			continue
 34		}
 35		var m map[string]any
 36		if err := json.Unmarshal(line, &m); err != nil {
 37			t.Fatal(err)
 38		}
 39		ms = append(ms, m)
 40	}
 41	return ms
 42}
 43
 44func getJSON(b []byte, t *testing.T) map[string]any {
 45	var m map[string]any
 46	if err := json.Unmarshal(b, &m); err != nil {
 47		t.Fatal(err)
 48	}
 49	return m
 50}
 51
 52// log 2 lines then emit the canonical log line, should have all prior attributes
 53func TestAttrs(t *testing.T) {
 54	var buf bytes.Buffer
 55	h := wrap(slog.NewJSONHandler(&buf, nil))
 56	log := New(h)
 57
 58	log.Info("test 1", "a", "b", "c", "d")
 59	result := getJSON(buf.Bytes(), t)
 60	assert.Equal(t, result["a"], "b")
 61	assert.Equal(t, result["c"], "d")
 62	buf.Reset()
 63
 64	log.Info("test 2", "e", "z", "f", "x")
 65	result = getJSON(buf.Bytes(), t)
 66	assert.Equal(t, result["e"], "z")
 67	assert.Equal(t, result["f"], "x")
 68	buf.Reset()
 69
 70	assert.NoError(t, h.(*cannon).emit())
 71	result = getJSON(buf.Bytes(), t)
 72	assert.Equal(t, result["a"], "b")
 73	assert.Equal(t, result["c"], "d")
 74	assert.Equal(t, result["e"], "z")
 75	assert.Equal(t, result["f"], "x")
 76	assert.Equal(t, result["msg"], "canonical_log_line")
 77	assert.Equal(t, result["level"], "INFO")
 78}
 79
 80func TestGroup(t *testing.T) {
 81	var buf bytes.Buffer
 82	h := wrap(slog.NewJSONHandler(&buf, nil))
 83	log := New(h)
 84
 85	log.WithGroup("test").Info("test 1", "a", "b", "c", "d")
 86	result := getJSON(buf.Bytes(), t)
 87	assert.Equal(t, result["test"], map[string]any{"a": "b", "c": "d"})
 88	buf.Reset()
 89
 90	log.WithGroup("test2").Info("test 2", "e", "z", "f", "x")
 91	result = getJSON(buf.Bytes(), t)
 92	assert.Equal(t, result["test2"], map[string]any{"e": "z", "f": "x"})
 93	buf.Reset()
 94
 95	assert.NoError(t, Emit(log))
 96	result = getJSON(buf.Bytes(), t)
 97	assert.Equal(t, result["test"], map[string]any{"a": "b", "c": "d"})
 98	assert.Equal(t, result["test2"], map[string]any{"e": "z", "f": "x"})
 99	assert.Equal(t, result["msg"], "canonical_log_line")
100	assert.Equal(t, result["level"], "INFO")
101}
102
103func TestWithAttrs(t *testing.T) {
104	var buf bytes.Buffer
105	h := wrap(slog.NewJSONHandler(&buf, nil))
106	log := New(h)
107
108	log.With("n", "m").Info("test 1", "a", "b", "c", "d")
109	result := getJSON(buf.Bytes(), t)
110	assert.Equal(t, result["a"], "b")
111	assert.Equal(t, result["c"], "d")
112	assert.Equal(t, result["n"], "m")
113	buf.Reset()
114
115	assert.NoError(t, Emit(log))
116	result = getJSON(buf.Bytes(), t)
117	assert.Equal(t, result["a"], "b")
118	assert.Equal(t, result["c"], "d")
119	assert.Equal(t, result["n"], "m")
120	assert.Equal(t, result["msg"], "canonical_log_line")
121	assert.Equal(t, result["level"], "INFO")
122}
123
124func TestWithAttrsNoLogCall(t *testing.T) {
125	var buf bytes.Buffer
126	h := wrap(slog.NewJSONHandler(&buf, nil))
127	log := New(h)
128
129	log.With("n", "m")
130
131	assert.NoError(t, Emit(log))
132	result := getJSON(buf.Bytes(), t)
133	assert.Equal(t, result["n"], "m")
134	assert.Equal(t, result["msg"], "canonical_log_line")
135	assert.Equal(t, result["level"], "INFO")
136}
137
138func TestEmit(t *testing.T) {
139	var buf bytes.Buffer
140	h := wrap(slog.NewJSONHandler(&buf, nil))
141	log := New(h)
142
143	assert.NoError(t, Emit(log, "a", "b", "c", "d", "n", "m"))
144	result := getJSON(buf.Bytes(), t)
145	assert.Equal(t, result["a"], "b")
146	assert.Equal(t, result["c"], "d")
147	assert.Equal(t, result["n"], "m")
148	assert.Equal(t, result["msg"], "canonical_log_line")
149	assert.Equal(t, result["level"], "INFO")
150}
151
152// tests that values that cannot be merged are last write wins
153func TestLWW(t *testing.T) {
154	var buf bytes.Buffer
155	h := wrap(slog.NewJSONHandler(&buf, nil))
156	log := New(h)
157
158	log.Info("test 1", "a", "b", "c", "d")
159	result := getJSON(buf.Bytes(), t)
160	assert.Equal(t, result["a"], "b")
161	assert.Equal(t, result["c"], "d")
162	buf.Reset()
163
164	// attribute c clobbers previous value when merging is not possible
165	log.Info("test 2", "c", "e")
166	result = getJSON(buf.Bytes(), t)
167	assert.Equal(t, result["c"], "e")
168	buf.Reset()
169
170	assert.NoError(t, h.(*cannon).emit())
171	result = getJSON(buf.Bytes(), t)
172	assert.Equal(t, result["a"], "b")
173	assert.Equal(t, result["c"], "e")
174	assert.Equal(t, result["msg"], "canonical_log_line")
175	assert.Equal(t, result["level"], "INFO")
176}
177
178func TestMergeGroup(t *testing.T) {
179	var buf bytes.Buffer
180	h := wrap(slog.NewJSONHandler(&buf, nil))
181	log := New(h)
182
183	log.WithGroup("test").Info("test 1", "a", "b", "c", "d")
184	result := getJSON(buf.Bytes(), t)
185	assert.Equal(t, result["test"], map[string]any{"a": "b", "c": "d"})
186	buf.Reset()
187
188	// another group with same key, should merge with first group
189	log.WithGroup("test").Info("test 2", "e", "f", "g", "h")
190	result = getJSON(buf.Bytes(), t)
191	assert.Equal(t, result["test"], map[string]any{"e": "f", "g": "h"})
192	buf.Reset()
193
194	assert.NoError(t, Emit(log))
195	result = getJSON(buf.Bytes(), t)
196	assert.Equal(t, result["test"], map[string]any{"a": "b", "c": "d", "e": "f", "g": "h"})
197	assert.Equal(t, result["msg"], "canonical_log_line")
198	assert.Equal(t, result["level"], "INFO")
199}
200
201func TestMergeGroupLWW(t *testing.T) {
202	var buf bytes.Buffer
203	h := wrap(slog.NewJSONHandler(&buf, nil))
204	log := New(h)
205
206	log.WithGroup("test").Info("test 1", "a", "b", "c", "d")
207	result := getJSON(buf.Bytes(), t)
208	assert.Equal(t, result["test"], map[string]any{"a": "b", "c": "d"})
209	buf.Reset()
210
211	// another group with same key, should merge with first group. Key "a" is overwritten, so LWW.
212	log.WithGroup("test").Info("test 2", "a", "z", "g", "h")
213	result = getJSON(buf.Bytes(), t)
214	assert.Equal(t, result["test"], map[string]any{"a": "z", "g": "h"})
215	buf.Reset()
216
217	assert.NoError(t, Emit(log))
218	result = getJSON(buf.Bytes(), t)
219	assert.Equal(t, result["test"], map[string]any{"a": "z", "c": "d", "g": "h"})
220	assert.Equal(t, result["msg"], "canonical_log_line")
221	assert.Equal(t, result["level"], "INFO")
222}
223
224type user string
225
226func (a user) Merge(b slog.Value) slog.Value {
227	previous := b.String()
228	return slog.StringValue(previous + "->" + string(a))
229}
230
231func TestMergeUserDefined(t *testing.T) {
232	var buf bytes.Buffer
233	h := wrap(slog.NewJSONHandler(&buf, nil))
234	log := New(h)
235
236	log.Info("test 1", "a", user("a"))
237	result := getJSON(buf.Bytes(), t)
238	assert.Equal(t, result["a"], "a")
239	buf.Reset()
240
241	// attribute c clobbers previous value when merging is not possible
242	log.Info("test 2", "a", user("b"))
243	result = getJSON(buf.Bytes(), t)
244	assert.Equal(t, result["a"], "b")
245	buf.Reset()
246
247	assert.NoError(t, h.(*cannon).emit())
248	result = getJSON(buf.Bytes(), t)
249	assert.Equal(t, result["a"], "a->b")
250	assert.Equal(t, result["msg"], "canonical_log_line")
251	assert.Equal(t, result["level"], "INFO")
252
253}