github.com/yankunsam/loki/v2@v2.6.3-0.20220817130409-389df5235c27/clients/pkg/promtail/targets/gelf/gelftarget_test.go (about) 1 package gelf 2 3 import ( 4 "crypto/rand" 5 "fmt" 6 "io" 7 "net" 8 "strings" 9 "testing" 10 "time" 11 12 "github.com/go-kit/log" 13 "github.com/grafana/go-gelf/v2/gelf" 14 "github.com/prometheus/common/model" 15 "github.com/prometheus/prometheus/model/relabel" 16 "github.com/stretchr/testify/require" 17 18 "github.com/grafana/loki/clients/pkg/promtail/client/fake" 19 "github.com/grafana/loki/clients/pkg/promtail/scrapeconfig" 20 ) 21 22 func Test_Gelf(t *testing.T) { 23 client := fake.New(func() {}) 24 tm, err := NewTargetManager(NewMetrics(nil), log.NewNopLogger(), client, []scrapeconfig.Config{ 25 { 26 JobName: "gelf", 27 GelfConfig: &scrapeconfig.GelfTargetConfig{ 28 ListenAddress: ":0", 29 UseIncomingTimestamp: true, 30 Labels: model.LabelSet{"cfg": "true"}, 31 }, 32 RelabelConfigs: []*relabel.Config{ 33 { 34 SourceLabels: model.LabelNames{"__gelf_message_level"}, 35 TargetLabel: "level", 36 Replacement: "$1", 37 Action: relabel.Replace, 38 Regex: relabel.MustNewRegexp("(.*)"), 39 }, 40 { 41 SourceLabels: model.LabelNames{"__gelf_message_host"}, 42 TargetLabel: "hostname", 43 Replacement: "$1", 44 Action: relabel.Replace, 45 Regex: relabel.MustNewRegexp("(.*)"), 46 }, 47 { 48 SourceLabels: model.LabelNames{"__gelf_message_facility"}, 49 TargetLabel: "facility", 50 Replacement: "$1", 51 Action: relabel.Replace, 52 Regex: relabel.MustNewRegexp("(.*)"), 53 }, 54 }, 55 }, 56 }) 57 require.NoError(t, err) 58 defer tm.Stop() 59 target := tm.targets["gelf"] 60 require.NotNil(t, target) 61 w, err := gelf.NewUDPWriter(target.gelfReader.Addr()) 62 require.NoError(t, err) 63 defer func() { 64 require.NoError(t, w.Close()) 65 }() 66 baseTs := float64(time.Unix(10, 0).Unix()) + 0.250 67 ts := baseTs 68 69 for i := 0; i < 10; i++ { 70 require.NoError(t, w.WriteMessage(&gelf.Message{ 71 Short: "short", 72 Full: "full", 73 Version: "2.2", 74 Host: "thishost", 75 TimeUnix: ts, 76 Level: gelf.LOG_ERR, 77 Facility: "gelftest", 78 Extra: map[string]interface{}{ 79 "_foo": "bar", 80 }, 81 })) 82 ts += 0.250 83 } 84 85 require.Eventually(t, func() bool { 86 return len(client.Received()) == 10 87 }, 1*time.Second, 20*time.Millisecond) 88 89 for i, actual := range client.Received() { 90 require.Equal(t, "error", string(actual.Labels["level"])) 91 require.Equal(t, "true", string(actual.Labels["cfg"])) 92 require.Equal(t, "thishost", string(actual.Labels["hostname"])) 93 require.Equal(t, "gelftest", string(actual.Labels["facility"])) 94 95 require.Equal(t, secondsToUnixTimestamp(baseTs+float64(i)*0.250), actual.Timestamp) 96 97 var gelfMsg gelf.Message 98 99 require.NoError(t, gelfMsg.UnmarshalJSON([]byte(actual.Line))) 100 101 require.Equal(t, "short", gelfMsg.Short) 102 require.Equal(t, "full", gelfMsg.Full) 103 require.Equal(t, "2.2", gelfMsg.Version) 104 require.Equal(t, "thishost", gelfMsg.Host) 105 require.Equal(t, "bar", gelfMsg.Extra["_foo"]) 106 require.Equal(t, gelf.LOG_ERR, int(gelfMsg.Level)) 107 require.Equal(t, "gelftest", gelfMsg.Facility) 108 109 } 110 } 111 112 func TestConvertTime(t *testing.T) { 113 require.Equal(t, time.Unix(0, int64(time.Second+(time.Duration(250)*time.Millisecond))), secondsToUnixTimestamp(float64(time.Unix(1, 0).Unix())+0.250)) 114 } 115 116 func Test_GelfChunksUnordered(t *testing.T) { 117 client := fake.New(func() {}) 118 119 tm, err := NewTargetManager(NewMetrics(nil), log.NewNopLogger(), client, []scrapeconfig.Config{ 120 { 121 JobName: "gelf", 122 GelfConfig: &scrapeconfig.GelfTargetConfig{ 123 ListenAddress: ":0", 124 }, 125 }, 126 }) 127 require.NoError(t, err) 128 defer tm.Stop() 129 130 target := tm.targets["gelf"] 131 require.NotNil(t, target) 132 connection, err := net.Dial("udp", target.gelfReader.Addr()) 133 require.NoError(t, err) 134 defer func() { 135 require.NoError(t, connection.Close()) 136 }() 137 138 chunksA := createChunks(t, "a") 139 chunksB := createChunks(t, "b") 140 // send messages(a, b) chunks in order: chunk-0a, chunk-0b, chunk-1a, chunk-1b 141 for i := 0; i < len(chunksB); i++ { 142 writeA, err := connection.Write(chunksA[i]) 143 require.NoError(t, err) 144 require.Equal(t, len(chunksA[i]), writeA) 145 146 writeB, err := connection.Write(chunksB[i]) 147 require.NoError(t, err) 148 require.Equal(t, len(chunksB[i]), writeB) 149 } 150 151 require.Eventually(t, func() bool { 152 return len(client.Received()) == 2 153 }, 2*time.Second, 100*time.Millisecond, "expected 2 messages to be received") 154 } 155 156 func createChunks(t *testing.T, char string) [][]byte { 157 chunksA, err := splitToChunks([]byte(fmt.Sprintf("{\"short_message\":\"%v\"}", strings.Repeat(char, gelf.ChunkSize*2)))) 158 require.NoError(t, err) 159 return chunksA 160 } 161 162 // static value that indicated that GELF message is chunked 163 var magicChunked = []byte{0x1e, 0x0f} 164 165 const ( 166 chunkedHeaderLen = 12 167 chunkedDataLen = gelf.ChunkSize - chunkedHeaderLen 168 ) 169 170 func splitToChunks(messageBytes []byte) ([][]byte, error) { 171 chunksCount := uint8(len(messageBytes)/chunkedDataLen + 1) 172 messageID := make([]byte, 8) 173 n, err := io.ReadFull(rand.Reader, messageID) 174 if err != nil || n != 8 { 175 return nil, fmt.Errorf("rand.Reader: %d/%s", n, err) 176 } 177 chunks := make([][]byte, 0, chunksCount) 178 bytesLeft := len(messageBytes) 179 for i := uint8(0); i < chunksCount; i++ { 180 buf := make([]byte, 0, gelf.ChunkSize) 181 buf = append(buf, magicChunked...) 182 buf = append(buf, messageID...) 183 buf = append(buf, i) 184 buf = append(buf, chunksCount) 185 chunkLen := chunkedDataLen 186 if chunkLen > bytesLeft { 187 chunkLen = bytesLeft 188 } 189 off := int(i) * chunkedDataLen 190 chunkData := messageBytes[off : off+chunkLen] 191 buf = append(buf, chunkData...) 192 193 chunks = append(chunks, buf) 194 bytesLeft -= chunkLen 195 } 196 197 if bytesLeft != 0 { 198 return nil, fmt.Errorf("error: %d bytes left after splitting", bytesLeft) 199 } 200 return chunks, nil 201 }