19
19
import io .micrometer .core .instrument .*;
20
20
import io .micrometer .core .instrument .config .validate .ValidationException ;
21
21
import io .micrometer .core .ipc .http .HttpSender ;
22
+ import io .micrometer .core .util .internal .logging .LogEvent ;
22
23
import io .micrometer .core .util .internal .logging .MockLogger ;
23
24
import io .micrometer .core .util .internal .logging .MockLoggerFactory ;
24
25
import io .micrometer .dynatrace .DynatraceApiVersion ;
43
44
import static org .assertj .core .api .Assertions .assertThat ;
44
45
import static org .assertj .core .api .Assertions .assertThatThrownBy ;
45
46
import static org .mockito .ArgumentMatchers .isA ;
46
- import static org .mockito .Mockito .mock ;
47
- import static org .mockito .Mockito .when ;
47
+ import static org .mockito .Mockito .*;
48
48
49
49
/**
50
50
* Tests for {@link DynatraceExporterV1}.
@@ -266,16 +266,21 @@ void whenAllTsTooLargeEmptyMessageListReturned() {
266
266
267
267
@ Test
268
268
void splitsWhenExactlyExceedingMaxByComma () {
269
+ // @formatter:off
269
270
// comma needs to be considered when there is more than one time series
270
- List <DynatraceBatchedPayload > messages = exporter .createPostMessages ("my.type" , "my.group" ,
271
+ List <DynatraceBatchedPayload > messages = exporter .createPostMessages (
272
+ "my.type" ,
273
+ "my.group" ,
271
274
// Max bytes: 15330 (excluding header/footer, 15360 with header/footer)
272
- Arrays .asList (createTimeSeriesWithDimensions ( 750 ), // 14861 bytes
273
- createTimeSeriesWithDimensions (23 , "asdfg" ), // 469 bytes
274
- // (overflows due to
275
- // comma)
275
+ Arrays .asList (
276
+ createTimeSeriesWithDimensions (750 ), // 14861 bytes
277
+ // 469 bytes (overflows due to comma)
278
+ createTimeSeriesWithDimensions ( 23 , "asdfg" ),
276
279
createTimeSeriesWithDimensions (750 ), // 14861 bytes
277
280
createTimeSeriesWithDimensions (22 , "asd" ) // 468 bytes + comma
278
- ));
281
+ )
282
+ );
283
+ // @formatter:on
279
284
assertThat (messages ).hasSize (3 );
280
285
assertThat (messages .get (0 ).metricCount ).isEqualTo (1 );
281
286
assertThat (messages .get (1 ).metricCount ).isEqualTo (1 );
@@ -286,14 +291,19 @@ void splitsWhenExactlyExceedingMaxByComma() {
286
291
287
292
@ Test
288
293
void countsPreviousAndNextComma () {
289
- List <DynatraceBatchedPayload > messages = exporter .createPostMessages ("my.type" , null ,
294
+ // @formatter:off
295
+ List <DynatraceBatchedPayload > messages = exporter .createPostMessages (
296
+ "my.type" ,
297
+ null ,
290
298
// Max bytes: 15330 (excluding header/footer, 15360 with header/footer)
291
- Arrays .asList (createTimeSeriesWithDimensions (750 ), // 14861 bytes
299
+ Arrays .asList (
300
+ createTimeSeriesWithDimensions (750 ), // 14861 bytes
292
301
createTimeSeriesWithDimensions (10 , "asdf" ), // 234 bytes + comma
293
- createTimeSeriesWithDimensions (10 , "asdf" ) // 234 bytes + comma =
294
- // 15331 bytes
295
- // (overflow)
296
- ));
302
+ // 234 bytes + comma = 15331 bytes (overflow)
303
+ createTimeSeriesWithDimensions (10 , "asdf" )
304
+ )
305
+ );
306
+ // @formatter:on
297
307
assertThat (messages ).hasSize (2 );
298
308
assertThat (messages .get (0 ).metricCount ).isEqualTo (2 );
299
309
assertThat (messages .get (1 ).metricCount ).isEqualTo (1 );
@@ -386,6 +396,128 @@ void failOnPostShouldHaveProperLogging() throws Throwable {
386
396
assertThat (LOGGER .getLogEvents ().get (1 ).getCause ()).isNull ();
387
397
}
388
398
399
+ @ Test
400
+ void testTokenShouldBeRedactedInPutFailure () {
401
+ HttpSender httpClient = spy (HttpSender .class );
402
+
403
+ String invalidUrl = "http://localhost###" ;
404
+ String apiToken = "this.is.a.fake.apiToken" ;
405
+
406
+ DynatraceExporterV1 exporter = getDynatraceExporterV1 (httpClient , invalidUrl , apiToken );
407
+
408
+ meterRegistry .gauge ("my.gauge" , GAUGE_VALUE );
409
+ Gauge gauge = meterRegistry .find ("my.gauge" ).gauge ();
410
+
411
+ exporter .export (Collections .singletonList (gauge ));
412
+
413
+ assertThat (LOGGER .getLogEvents ())
414
+ // map to only keep the message strings
415
+ .extracting (LogEvent ::getMessage )
416
+ .containsExactly (String .format (
417
+ "failed to build request: Illegal character in fragment at index 17: %s/api/v1/timeseries/custom:my.gauge?api-token=<redacted>" ,
418
+ invalidUrl ));
419
+ }
420
+
421
+ @ Test
422
+ void testTokenShouldBeRedactedInPostFailure () throws Throwable {
423
+ HttpSender httpClient = spy (HttpSender .class );
424
+
425
+ String invalidUrl = "http://localhost###" ;
426
+ String apiToken = "this.is.a.fake.apiToken" ;
427
+
428
+ HttpSender .Request .Builder builder = HttpSender .Request .build ("http://localhost" , httpClient );
429
+ // mock the PUT call, so we can even run the post call
430
+ doReturn (builder ).when (httpClient ).put (anyString ());
431
+ doReturn (new HttpSender .Response (200 , "" )).when (httpClient ).send (any (HttpSender .Request .class ));
432
+
433
+ DynatraceExporterV1 exporter = getDynatraceExporterV1 (httpClient , invalidUrl , apiToken );
434
+
435
+ meterRegistry .gauge ("my.gauge" , GAUGE_VALUE );
436
+ Gauge gauge = meterRegistry .find ("my.gauge" ).gauge ();
437
+
438
+ exporter .export (Collections .singletonList (gauge ));
439
+
440
+ assertThat (LOGGER .getLogEvents ())
441
+ // map to only keep the message strings
442
+ .extracting (LogEvent ::getMessage ).containsExactly (
443
+ // the custom metric was created, meaning the PUT call succeeded
444
+ "created custom:my.gauge as custom metric in Dynatrace" ,
445
+ // the POST call throws an exception and the token is redacted
446
+ String .format (
447
+ "failed to build request: Illegal character in fragment at index 17: %s/api/v1/entity/infrastructure/custom/?api-token=<redacted>" ,
448
+ invalidUrl ));
449
+ }
450
+
451
+ @ Test
452
+ void trySendHttpRequestSuccess () throws Throwable {
453
+ HttpSender httpClient = mock (HttpSender .class );
454
+ DynatraceExporterV1 exporter = FACTORY .injectLogger (() -> createExporter (httpClient ));
455
+ HttpSender .Request .Builder reqBuilder = mock (HttpSender .Request .Builder .class );
456
+
457
+ // simulate a success response
458
+ when (reqBuilder .send ()).thenReturn (new HttpSender .Response (200 , "" ));
459
+
460
+ // test that everything works and no error is logged
461
+ exporter .trySendHttpRequest (reqBuilder );
462
+ verify (reqBuilder ).send ();
463
+ assertThat (LOGGER .getLogEvents ()).isEmpty ();
464
+ }
465
+
466
+ @ Test
467
+ void trySendHttpRequestErrorCode () throws Throwable {
468
+ HttpSender httpClient = mock (HttpSender .class );
469
+ DynatraceExporterV1 exporter = FACTORY .injectLogger (() -> createExporter (httpClient ));
470
+ HttpSender .Request .Builder reqBuilder = mock (HttpSender .Request .Builder .class );
471
+
472
+ // simulate a failure response, errors are handled elsewhere
473
+ when (reqBuilder .send ()).thenReturn (new HttpSender .Response (400 , "" ));
474
+
475
+ // test that everything works and no error is logged
476
+ exporter .trySendHttpRequest (reqBuilder );
477
+ verify (reqBuilder ).send ();
478
+ assertThat (LOGGER .getLogEvents ()).isEmpty ();
479
+ }
480
+
481
+ @ Test
482
+ void trySendHttpRequestThrowsAndRedacts () throws Throwable {
483
+ HttpSender httpClient = mock (HttpSender .class );
484
+ String apiToken = "this.is.a.fake.apiToken" ;
485
+ DynatraceExporterV1 exporter = getDynatraceExporterV1 (httpClient , "http://localhost" , apiToken );
486
+
487
+ HttpSender .Request .Builder reqBuilder = mock (HttpSender .Request .Builder .class );
488
+
489
+ // Simulate that the request builder throws an exception.
490
+ // Should not happen if the endpoint is invalid, the URI is validated elsewhere.
491
+ String exceptionMessageTemplate = "Exception with the token: %s" ;
492
+ when (reqBuilder .send ()).thenThrow (new Throwable (String .format (exceptionMessageTemplate , apiToken )));
493
+
494
+ exporter .trySendHttpRequest (reqBuilder );
495
+ verify (reqBuilder ).send ();
496
+ // assert that an error is logged
497
+ assertThat (LOGGER .getLogEvents ()).hasSize (1 ).extracting (x -> x .getMessage ()).containsExactlyInAnyOrder (
498
+ "failed to send metrics to Dynatrace: " + String .format (exceptionMessageTemplate , "<redacted>" ));
499
+ }
500
+
501
+ private DynatraceExporterV1 getDynatraceExporterV1 (HttpSender httpClient , String url , String apiToken ) {
502
+ DynatraceExporterV1 exporter = FACTORY .injectLogger (() -> new DynatraceExporterV1 (new DynatraceConfig () {
503
+ @ Override
504
+ public String get (String key ) {
505
+ return null ;
506
+ }
507
+
508
+ @ Override
509
+ public String apiToken () {
510
+ return apiToken ;
511
+ }
512
+
513
+ @ Override
514
+ public String uri () {
515
+ return url ;
516
+ }
517
+ }, clock , httpClient ));
518
+ return exporter ;
519
+ }
520
+
389
521
private DynatraceExporterV1 createExporter (HttpSender httpClient ) {
390
522
return new DynatraceExporterV1 (config , clock , httpClient );
391
523
}
0 commit comments