github.com/yankunsam/loki/v2@v2.6.3-0.20220817130409-389df5235c27/clients/pkg/promtail/targets/gcplog/push_target_test.go (about) 1 package gcplog_test 2 3 import ( 4 "flag" 5 "fmt" 6 "net" 7 "net/http" 8 "os" 9 "strings" 10 "testing" 11 "time" 12 13 "github.com/go-kit/log" 14 "github.com/prometheus/client_golang/prometheus" 15 "github.com/prometheus/common/model" 16 "github.com/prometheus/prometheus/model/relabel" 17 "github.com/stretchr/testify/require" 18 "github.com/weaveworks/common/server" 19 20 lokiClient "github.com/grafana/loki/clients/pkg/promtail/client" 21 "github.com/grafana/loki/clients/pkg/promtail/client/fake" 22 "github.com/grafana/loki/clients/pkg/promtail/scrapeconfig" 23 "github.com/grafana/loki/clients/pkg/promtail/targets/gcplog" 24 ) 25 26 const localhost = "127.0.0.1" 27 28 const expectedMessageData = `{ 29 "severity": "DEBUG", 30 "textPayload": "Function execution took 1198 ms. Finished with status code: 200", 31 "trace": "projects/wired-height/traces/54aa8a20875253c6b47caa8bd28ad652" 32 }` 33 const testPayload = ` 34 { 35 "message": { 36 "attributes": { 37 "logging.googleapis.com/timestamp": "2022-07-25T22:19:09.903683708Z" 38 }, 39 "data": "ewogICJzZXZlcml0eSI6ICJERUJVRyIsCiAgInRleHRQYXlsb2FkIjogIkZ1bmN0aW9uIGV4ZWN1dGlvbiB0b29rIDExOTggbXMuIEZpbmlzaGVkIHdpdGggc3RhdHVzIGNvZGU6IDIwMCIsCiAgInRyYWNlIjogInByb2plY3RzL3dpcmVkLWhlaWdodC90cmFjZXMvNTRhYThhMjA4NzUyNTNjNmI0N2NhYThiZDI4YWQ2NTIiCn0=", 40 "messageId": "5187581549398349", 41 "message_id": "5187581549398349", 42 "publishTime": "2022-07-25T22:19:15.56Z", 43 "publish_time": "2022-07-25T22:19:15.56Z" 44 }, 45 "subscription": "projects/wired-height-350515/subscriptions/test" 46 }` 47 48 func makeGCPPushRequest(host string, body string) (*http.Request, error) { 49 req, err := http.NewRequest(http.MethodPost, fmt.Sprintf("%s/gcp/api/v1/push", host), strings.NewReader(body)) 50 if err != nil { 51 return nil, err 52 } 53 return req, nil 54 } 55 56 func TestPushTarget(t *testing.T) { 57 w := log.NewSyncWriter(os.Stderr) 58 logger := log.NewLogfmtLogger(w) 59 60 type expectedEntry struct { 61 labels model.LabelSet 62 line string 63 } 64 type args struct { 65 RequestBody string 66 RelabelConfigs []*relabel.Config 67 Labels model.LabelSet 68 } 69 70 cases := map[string]struct { 71 args args 72 expectedEntries []expectedEntry 73 }{ 74 "simplified cloud functions log line": { 75 args: args{ 76 RequestBody: testPayload, 77 Labels: model.LabelSet{ 78 "job": "some_job_name", 79 }, 80 }, 81 expectedEntries: []expectedEntry{ 82 { 83 labels: model.LabelSet{ 84 "job": "some_job_name", 85 }, 86 line: expectedMessageData, 87 }, 88 }, 89 }, 90 "simplified cloud functions log line, with relabeling custom attribute and message id": { 91 args: args{ 92 RequestBody: testPayload, 93 Labels: model.LabelSet{ 94 "job": "some_job_name", 95 }, 96 RelabelConfigs: []*relabel.Config{ 97 { 98 SourceLabels: model.LabelNames{"__gcp_attributes_logging_googleapis_com_timestamp"}, 99 Regex: relabel.MustNewRegexp("(.*)"), 100 Replacement: "$1", 101 TargetLabel: "google_timestamp", 102 Action: relabel.Replace, 103 }, 104 { 105 SourceLabels: model.LabelNames{"__gcp_message_id"}, 106 Regex: relabel.MustNewRegexp("(.*)"), 107 Replacement: "$1", 108 TargetLabel: "message_id", 109 Action: relabel.Replace, 110 }, 111 }, 112 }, 113 expectedEntries: []expectedEntry{ 114 { 115 labels: model.LabelSet{ 116 "job": "some_job_name", 117 "google_timestamp": "2022-07-25T22:19:09.903683708Z", 118 "message_id": "5187581549398349", 119 }, 120 line: expectedMessageData, 121 }, 122 }, 123 }, 124 } 125 for name, tc := range cases { 126 t.Run(name, func(t *testing.T) { 127 // Create fake promtail client 128 eh := fake.New(func() {}) 129 defer eh.Stop() 130 131 serverConfig, port, err := getServerConfigWithAvailablePort() 132 require.NoError(t, err, "error generating server config or finding open port") 133 config := &scrapeconfig.GcplogTargetConfig{ 134 Server: serverConfig, 135 Labels: tc.args.Labels, 136 UseIncomingTimestamp: false, 137 SubscriptionType: "push", 138 } 139 140 prometheus.DefaultRegisterer = prometheus.NewRegistry() 141 metrics := gcplog.NewMetrics(prometheus.DefaultRegisterer) 142 pt, err := gcplog.NewGCPLogTarget(metrics, logger, eh, tc.args.RelabelConfigs, "test_job", config) 143 require.NoError(t, err) 144 defer func() { 145 _ = pt.Stop() 146 }() 147 148 // Clear received lines after test case is ran 149 defer eh.Clear() 150 151 // Send some logs 152 ts := time.Now() 153 154 req, err := makeGCPPushRequest(fmt.Sprintf("http://%s:%d", localhost, port), tc.args.RequestBody) 155 require.NoError(t, err, "expected test drain request to be successfully created") 156 res, err := http.DefaultClient.Do(req) 157 require.NoError(t, err) 158 require.Equal(t, http.StatusNoContent, res.StatusCode, "expected no-content status code") 159 160 waitForMessages(eh) 161 162 // Make sure we didn't timeout 163 require.Equal(t, 1, len(eh.Received())) 164 165 require.Equal(t, len(eh.Received()), len(tc.expectedEntries), "expected to receive equal amount of expected label sets") 166 for i, expectedEntry := range tc.expectedEntries { 167 // TODO: Add assertion over propagated timestamp 168 actualEntry := eh.Received()[i] 169 170 require.Equal(t, expectedEntry.line, actualEntry.Line, "expected line to be equal for %d-th entry", i) 171 172 expectedLS := expectedEntry.labels 173 actualLS := actualEntry.Labels 174 for label, value := range expectedLS { 175 require.Equal(t, expectedLS[label], actualLS[label], "expected label %s to be equal to %s in %d-th entry", label, value, i) 176 } 177 178 // Timestamp is always set in the handler, we expect received timestamps to be slightly higher than the timestamp when we started sending logs. 179 require.GreaterOrEqual(t, actualEntry.Timestamp.Unix(), ts.Unix(), "expected %d-th entry to have a received timestamp greater than publish time", i) 180 } 181 }) 182 } 183 } 184 185 func TestPushTarget_UseIncomingTimestamp(t *testing.T) { 186 w := log.NewSyncWriter(os.Stderr) 187 logger := log.NewLogfmtLogger(w) 188 189 // Create fake promtail client 190 eh := fake.New(func() {}) 191 defer eh.Stop() 192 193 serverConfig, port, err := getServerConfigWithAvailablePort() 194 require.NoError(t, err, "error generating server config or finding open port") 195 config := &scrapeconfig.GcplogTargetConfig{ 196 Server: serverConfig, 197 Labels: nil, 198 UseIncomingTimestamp: true, 199 SubscriptionType: "push", 200 } 201 202 prometheus.DefaultRegisterer = prometheus.NewRegistry() 203 metrics := gcplog.NewMetrics(prometheus.DefaultRegisterer) 204 pt, err := gcplog.NewGCPLogTarget(metrics, logger, eh, nil, "test_job", config) 205 require.NoError(t, err) 206 defer func() { 207 _ = pt.Stop() 208 }() 209 210 // Clear received lines after test case is ran 211 defer eh.Clear() 212 213 req, err := makeGCPPushRequest(fmt.Sprintf("http://%s:%d", localhost, port), testPayload) 214 require.NoError(t, err, "expected test drain request to be successfully created") 215 res, err := http.DefaultClient.Do(req) 216 require.NoError(t, err) 217 require.Equal(t, http.StatusNoContent, res.StatusCode, "expected no-content status code") 218 219 waitForMessages(eh) 220 221 // Make sure we didn't timeout 222 require.Equal(t, 1, len(eh.Received())) 223 224 expectedTs, err := time.Parse(time.RFC3339Nano, "2022-07-25T22:19:15.56Z") 225 require.NoError(t, err, "expected expected timestamp to be parse correctly") 226 require.Equal(t, expectedTs, eh.Received()[0].Timestamp, "expected entry timestamp to be overridden by received one") 227 } 228 229 func TestPushTarget_UseTenantIDHeaderIfPresent(t *testing.T) { 230 w := log.NewSyncWriter(os.Stderr) 231 logger := log.NewLogfmtLogger(w) 232 233 // Create fake promtail client 234 eh := fake.New(func() {}) 235 defer eh.Stop() 236 237 serverConfig, port, err := getServerConfigWithAvailablePort() 238 require.NoError(t, err, "error generating server config or finding open port") 239 config := &scrapeconfig.GcplogTargetConfig{ 240 Server: serverConfig, 241 Labels: nil, 242 UseIncomingTimestamp: true, 243 SubscriptionType: "push", 244 } 245 246 prometheus.DefaultRegisterer = prometheus.NewRegistry() 247 metrics := gcplog.NewMetrics(prometheus.DefaultRegisterer) 248 pt, err := gcplog.NewGCPLogTarget(metrics, logger, eh, nil, "test_job", config) 249 require.NoError(t, err) 250 defer func() { 251 _ = pt.Stop() 252 }() 253 254 // Clear received lines after test case is ran 255 defer eh.Clear() 256 257 req, err := makeGCPPushRequest(fmt.Sprintf("http://%s:%d", localhost, port), testPayload) 258 require.NoError(t, err, "expected test drain request to be successfully created") 259 req.Header.Set("X-Scope-OrgID", "42") 260 res, err := http.DefaultClient.Do(req) 261 require.NoError(t, err) 262 require.Equal(t, http.StatusNoContent, res.StatusCode, "expected no-content status code") 263 264 waitForMessages(eh) 265 266 // Make sure we didn't timeout 267 require.Equal(t, 1, len(eh.Received())) 268 269 require.Equal(t, model.LabelValue("42"), eh.Received()[0].Labels[lokiClient.ReservedLabelTenantID]) 270 } 271 272 func waitForMessages(eh *fake.Client) { 273 countdown := 1000 274 for len(eh.Received()) != 1 && countdown > 0 { 275 time.Sleep(1 * time.Millisecond) 276 countdown-- 277 } 278 } 279 280 func getServerConfigWithAvailablePort() (cfg server.Config, port int, err error) { 281 // Get a randomly available port by open and closing a TCP socket 282 addr, err := net.ResolveTCPAddr("tcp", localhost+":0") 283 if err != nil { 284 return 285 } 286 l, err := net.ListenTCP("tcp", addr) 287 if err != nil { 288 return 289 } 290 port = l.Addr().(*net.TCPAddr).Port 291 err = l.Close() 292 if err != nil { 293 return 294 } 295 296 // Adjust some of the defaults 297 cfg.RegisterFlags(flag.NewFlagSet("empty", flag.ContinueOnError)) 298 cfg.HTTPListenAddress = localhost 299 cfg.HTTPListenPort = port 300 cfg.GRPCListenAddress = localhost 301 cfg.GRPCListenPort = 0 // Not testing GRPC, a random port will be assigned 302 303 return 304 }