/
Observation.java
1254 lines (1129 loc) · 44.8 KB
/
Observation.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*
* Copyright 2022 VMware, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.micrometer.observation;
import io.micrometer.common.KeyValue;
import io.micrometer.common.KeyValues;
import io.micrometer.common.lang.NonNull;
import io.micrometer.common.lang.Nullable;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;
/**
* An act of viewing or noticing a fact or an occurrence for some scientific or other
* special purpose (According to dictionary.com).
*
* You can wrap an operation within your code in an {@link Observation} so that actions
* can take place within the lifecycle of that observation via the
* {@link ObservationHandler}.
*
* According to what is configured the actions can be e.g. taking measurements via
* {@code Timer}, creating spans for distributed tracing, correlating logs or just logging
* out additional information. You instrument your code once with an {@link Observation}
* but you can get as many benefits out of it as many {@link ObservationHandler} you have.
*
* @author Jonatan Ivanov
* @author Tommy Ludwig
* @author Marcin Grzejszczak
* @since 1.10.0
*/
public interface Observation extends ObservationView {
/**
* No-op observation.
*/
Observation NOOP = NoopObservation.INSTANCE;
/**
* Creates and starts an {@link Observation}. When no registry is passed or
* observation is not applicable will return a no-op observation.
* @param name name of the observation
* @param registry observation registry
* @return started observation
*/
static Observation start(String name, ObservationRegistry registry) {
return start(name, Context::new, registry);
}
/**
* Creates and starts an {@link Observation}. When the {@link ObservationRegistry} is
* null or the no-op registry, this fast returns a no-op {@link Observation} and skips
* the creation of the {@link Observation.Context}. This check avoids unnecessary
* {@link Observation.Context} creation, which is why it takes a {@link Supplier} for
* the context rather than the context directly. If the observation is not enabled
* (see
* {@link ObservationRegistry.ObservationConfig#observationPredicate(ObservationPredicate)
* ObservationConfig#observationPredicate}), a no-op observation will also be
* returned.
* @param name name of the observation
* @param contextSupplier mutable context supplier
* @param registry observation registry
* @return started observation
*/
static <T extends Context> Observation start(String name, Supplier<T> contextSupplier,
ObservationRegistry registry) {
return createNotStarted(name, contextSupplier, registry).start();
}
/**
* Creates but <b>does not start</b> an {@link Observation}. Remember to call
* {@link Observation#start()} when you want the measurements to start. When no
* registry is passed or observation is not applicable will return a no-op
* observation.
* @param name name of the observation
* @param registry observation registry
* @return created but not started observation
*/
static Observation createNotStarted(String name, ObservationRegistry registry) {
return createNotStarted(name, Context::new, registry);
}
/**
* Creates but <b>does not start</b> an {@link Observation}. Remember to call
* {@link Observation#start()} when you want the measurements to start. When the
* {@link ObservationRegistry} is null or the no-op registry, this fast returns a
* no-op {@link Observation} and skips the creation of the
* {@link Observation.Context}. This check avoids unnecessary
* {@link Observation.Context} creation, which is why it takes a {@link Supplier} for
* the context rather than the context directly. If the observation is not enabled
* (see
* {@link ObservationRegistry.ObservationConfig#observationPredicate(ObservationPredicate)
* ObservationConfig#observationPredicate}), a no-op observation will also be
* returned.
* @param name name of the observation
* @param contextSupplier supplier for mutable context
* @param registry observation registry
* @return created but not started observation
*/
static <T extends Context> Observation createNotStarted(String name, Supplier<T> contextSupplier,
ObservationRegistry registry) {
if (registry == null || registry.isNoop()) {
return NOOP;
}
Context context = contextSupplier.get();
if (!registry.observationConfig().isObservationEnabled(name, context)) {
return NOOP;
}
return new SimpleObservation(name, registry, context == null ? new Context() : context);
}
/**
* Creates but <b>does not start</b> an {@link Observation}. Remember to call
* {@link Observation#start()} when you want the measurements to start. When the
* {@link ObservationRegistry} is null or the no-op registry, this fast returns a
* no-op {@link Observation} and skips the creation of the
* {@link Observation.Context}. This check avoids unnecessary
* {@link Observation.Context} creation, which is why it takes a {@link Supplier} for
* the context rather than the context directly. If the observation is not enabled
* (see
* {@link ObservationRegistry.ObservationConfig#observationPredicate(ObservationPredicate)
* ObservationConfig#observationPredicate}), a no-op observation will also be
* returned. Allows to set a custom {@link ObservationConvention} and requires to
* provide a default one if neither a custom nor a pre-configured one (via
* {@link ObservationRegistry.ObservationConfig#getObservationConvention(Context, ObservationConvention)})
* was found. The {@link ObservationConvention} implementation can override
* {@link Observation} names (i.e. name and contextual name) and key values.
* @param <T> type of context
* @param customConvention custom convention. If {@code null}, the default one will be
* picked.
* @param defaultConvention default convention when no custom convention was passed,
* nor a configured one was found
* @param contextSupplier supplier for the observation context
* @param registry observation registry
* @return created but not started observation
*/
static <T extends Context> Observation createNotStarted(@Nullable ObservationConvention<T> customConvention,
ObservationConvention<T> defaultConvention, Supplier<T> contextSupplier, ObservationRegistry registry) {
if (registry.isNoop()) {
return Observation.NOOP;
}
ObservationConvention<T> convention;
T context = contextSupplier.get();
if (customConvention != null) {
convention = customConvention;
}
else {
convention = registry.observationConfig().getObservationConvention(context, defaultConvention);
}
if (!registry.observationConfig().isObservationEnabled(convention.getName(), context)) {
return NOOP;
}
return new SimpleObservation(convention, registry, context == null ? new Context() : context);
}
/**
* Creates and starts an {@link Observation}. When no registry is passed or
* observation is not applicable will return a no-op observation.
* @param observationConvention observation convention
* @param registry observation registry
* @return started observation
*/
static Observation start(ObservationConvention<Context> observationConvention, ObservationRegistry registry) {
return start(observationConvention, Context::new, registry);
}
/**
* Creates and starts an {@link Observation}. When the {@link ObservationRegistry} is
* null or the no-op registry, this fast returns a no-op {@link Observation} and skips
* the creation of the {@link Observation.Context}. This check avoids unnecessary
* {@link Observation.Context} creation, which is why it takes a {@link Supplier} for
* the context rather than the context directly. If the observation is not enabled
* (see
* {@link ObservationRegistry.ObservationConfig#observationPredicate(ObservationPredicate)
* ObservationConfig#observationPredicate}), a no-op observation will also be
* returned.
* @param <T> type of context
* @param observationConvention observation convention
* @param contextSupplier mutable context supplier
* @param registry observation registry
* @return started observation
*/
static <T extends Context> Observation start(ObservationConvention<T> observationConvention,
Supplier<T> contextSupplier, ObservationRegistry registry) {
return createNotStarted(observationConvention, contextSupplier, registry).start();
}
/**
* Creates and starts an {@link Observation}. When the {@link ObservationRegistry} is
* null or the no-op registry, this fast returns a no-op {@link Observation} and skips
* the creation of the {@link Observation.Context}. This check avoids unnecessary
* {@link Observation.Context} creation, which is why it takes a {@link Supplier} for
* the context rather than the context directly. If the observation is not enabled
* (see
* {@link ObservationRegistry.ObservationConfig#observationPredicate(ObservationPredicate)
* ObservationConfig#observationPredicate}), a no-op observation will also be
* returned. Allows to set a custom {@link ObservationConvention} and requires to
* provide a default one if neither a custom nor a pre-configured one (via
* {@link ObservationRegistry.ObservationConfig#getObservationConvention(Context, ObservationConvention)})
* was found.
* @param <T> type of context
* @param registry observation registry
* @param contextSupplier the observation context supplier
* @param customConvention custom convention. If {@code null}, the default one will be
* picked.
* @param defaultConvention default convention when no custom convention was passed,
* nor a configured one was found
* @return started observation
*/
static <T extends Context> Observation start(@Nullable ObservationConvention<T> customConvention,
ObservationConvention<T> defaultConvention, Supplier<T> contextSupplier, ObservationRegistry registry) {
return createNotStarted(customConvention, defaultConvention, contextSupplier, registry).start();
}
/**
* Creates but <b>does not start</b> an {@link Observation}. Remember to call
* {@link Observation#start()} when you want the measurements to start. When no
* registry is passed or observation is not applicable will return a no-op
* observation.
* @param observationConvention observation convention
* @param registry observation registry
* @return created but not started observation
*/
static Observation createNotStarted(ObservationConvention<Context> observationConvention,
ObservationRegistry registry) {
return createNotStarted(observationConvention, Context::new, registry);
}
/**
* Creates but <b>does not start</b> an {@link Observation}. Remember to call
* {@link Observation#start()} when you want the measurements to start. When the
* {@link ObservationRegistry} is null or the no-op registry, this fast returns a
* no-op {@link Observation} and skips the creation of the
* {@link Observation.Context}. This check avoids unnecessary
* {@link Observation.Context} creation, which is why it takes a {@link Supplier} for
* the context rather than the context directly. If the observation is not enabled
* (see
* {@link ObservationRegistry.ObservationConfig#observationPredicate(ObservationPredicate)
* ObservationConfig#observationPredicate}), a no-op observation will also be
* returned.
* <p>
* <b>Important!</b> If you're using the
* {@link ObservationConvention#getContextualName(Context)} method to override the
* contextual name <b>you MUST use a non {@code null} context</b> (i.e. the
* {@code context} parameter of this method MUST NOT be {@code null}). The
* {@link ObservationConvention#getContextualName(Context)} requires a concrete type
* of {@link Context} to be passed and if you're not providing one we won't be able to
* initialize it ourselves.
* </p>
* @param <T> type of context
* @param observationConvention observation convention
* @param contextSupplier mutable context supplier
* @param registry observation registry
* @return created but not started observation
*/
static <T extends Context> Observation createNotStarted(ObservationConvention<T> observationConvention,
Supplier<T> contextSupplier, ObservationRegistry registry) {
if (registry == null || registry.isNoop() || observationConvention == NoopObservationConvention.INSTANCE) {
return NOOP;
}
T context = contextSupplier.get();
if (!registry.observationConfig().isObservationEnabled(observationConvention.getName(), context)) {
return NOOP;
}
return new SimpleObservation(observationConvention, registry, context == null ? new Context() : context);
}
/**
* Sets the name that can be defined from the contents of the context. E.g. a span
* name should not be the default observation name but one coming from an HTTP
* request.
* @param contextualName contextual name
* @return this
*/
Observation contextualName(@Nullable String contextualName);
/**
* If you have access to a previously created {@link Observation} you can manually set
* the parent {@link Observation} using this method - that way you won't need to open
* scopes just to create a child observation.
*
* If you're using the {@link #openScope()} method then the parent observation will be
* automatically set, and you don't have to call this method.
* @param parentObservation parent observation to set
* @return this
*/
Observation parentObservation(Observation parentObservation);
/**
* Adds a low cardinality key value. Low cardinality means that this key value will
* have a bounded number of possible values. A templated HTTP URL is a good example of
* such a key value (e.g. /foo/{userId}).
* @param keyValue key value
* @return this
*/
Observation lowCardinalityKeyValue(KeyValue keyValue);
/**
* Adds a low cardinality key value. Low cardinality means that this key value will
* have a bounded number of possible values. A templated HTTP URL is a good example of
* such a key value (e.g. /foo/{userId}).
* @param key key
* @param value value
* @return this
*/
default Observation lowCardinalityKeyValue(String key, String value) {
return lowCardinalityKeyValue(KeyValue.of(key, value));
}
/**
* Adds multiple low cardinality key value instances. Low cardinality means that the
* key value will have a bounded number of possible values. A templated HTTP URL is a
* good example of such a key value (e.g. /foo/{userId}).
* @param keyValues key value instances
* @return this
*/
default Observation lowCardinalityKeyValues(KeyValues keyValues) {
keyValues.stream().forEach(this::lowCardinalityKeyValue);
return this;
}
/**
* Adds a high cardinality key value. High cardinality means that this key value will
* have an unbounded number of possible values. An HTTP URL is a good example of such
* a key value (e.g. /foo/bar, /foo/baz etc.).
* @param keyValue key value
* @return this
*/
Observation highCardinalityKeyValue(KeyValue keyValue);
/**
* Adds a high cardinality key value. High cardinality means that this key value will
* have an unbounded number of possible values. An HTTP URL is a good example of such
* a key value (e.g. /foo/bar, /foo/baz etc.).
* @param key key
* @param value value
* @return this
*/
default Observation highCardinalityKeyValue(String key, String value) {
return highCardinalityKeyValue(KeyValue.of(key, value));
}
/**
* Adds multiple high cardinality key value instances. High cardinality means that the
* key value will have an unbounded number of possible values. An HTTP URL is a good
* example of such a key value (e.g. /foo/bar, /foo/baz etc.).
* @param keyValues key value instances
* @return this
*/
default Observation highCardinalityKeyValues(KeyValues keyValues) {
keyValues.stream().forEach(this::highCardinalityKeyValue);
return this;
}
/**
* Checks whether this {@link Observation} is no-op.
* @return {@code true} when this is a no-op observation
*/
default boolean isNoop() {
return this == NOOP;
}
/**
* Adds an observation convention that can be used to attach key values to the
* observation. WARNING: You must add ObservationConvention instances to the
* Observation before it is started.
* @param observationConvention observation convention
* @return this
*/
Observation observationConvention(ObservationConvention<?> observationConvention);
/**
* Signals an error.
* @param error error
* @return this
*/
Observation error(Throwable error);
/**
* Signals an arbitrary {@link Event}.
* @param event event
* @return this
*/
Observation event(Event event);
/**
* Starts the observation. Remember to call this method, otherwise timing calculations
* will not take place.
* @return this
*/
Observation start();
/**
* Returns the context attached to this observation.
* @return corresponding context
*/
Context getContext();
/**
* Returns the context attached to this observation as a read only view.
* @return corresponding context
*/
@Override
default ContextView getContextView() {
return getContext();
}
/**
* Stop the observation. Remember to call this method, otherwise timing calculations
* won't be finished.
*/
void stop();
/**
* When put in scope, additional operations can take place by the
* {@link ObservationHandler}s such as putting entries in thread local.
* @return new scope
*/
Scope openScope();
/**
* Observes the passed {@link Runnable}, this means the followings:
*
* <ul>
* <li>Starts the {@code Observation}</li>
* <li>Opens a {@code Scope}</li>
* <li>Calls {@link Runnable#run()}</li>
* <li>Closes the {@code Scope}</li>
* <li>Signals the error to the {@code Observation} if any</li>
* <li>Stops the {@code Observation}</li>
* </ul>
* @param runnable the {@link Runnable} to run
*/
@SuppressWarnings("unused")
default void observe(Runnable runnable) {
start();
try (Scope scope = openScope()) {
runnable.run();
}
catch (Exception exception) {
error(exception);
throw exception;
}
finally {
stop();
}
}
default Runnable wrap(Runnable runnable) {
return () -> observe(runnable);
}
/**
* Observes the passed {@link CheckedRunnable}, this means the followings:
*
* <ul>
* <li>Starts the {@code Observation}</li>
* <li>Opens a {@code Scope}</li>
* <li>Calls {@link CheckedRunnable#run()}</li>
* <li>Closes the {@code Scope}</li>
* <li>Signals the error to the {@code Observation} if any</li>
* <li>Stops the {@code Observation}</li>
* </ul>
* @param checkedRunnable the {@link CheckedRunnable} to run
* @param <E> type of exception thrown
*/
@SuppressWarnings("unused")
default <E extends Throwable> void observeChecked(CheckedRunnable<E> checkedRunnable) throws E {
start();
try (Scope scope = openScope()) {
checkedRunnable.run();
}
catch (Throwable error) {
error(error);
throw error;
}
finally {
stop();
}
}
default <E extends Throwable> CheckedRunnable<E> wrapChecked(CheckedRunnable<E> checkedRunnable) throws E {
return () -> observeChecked(checkedRunnable);
}
/**
* Observes the passed {@link Supplier}, this means the followings:
*
* <ul>
* <li>Starts the {@code Observation}</li>
* <li>Opens a {@code Scope}</li>
* <li>Calls {@link Supplier#get()}</li>
* <li>Closes the {@code Scope}</li>
* <li>Signals the error to the {@code Observation} if any</li>
* <li>Stops the {@code Observation}</li>
* </ul>
* @param supplier the {@link Supplier} to call
* @param <T> the type parameter of the {@link Supplier}
* @return the result from {@link Supplier#get()}
*/
@SuppressWarnings("unused")
default <T> T observe(Supplier<T> supplier) {
start();
try (Scope scope = openScope()) {
return supplier.get();
}
catch (Exception exception) {
error(exception);
throw exception;
}
finally {
stop();
}
}
default <T> Supplier<T> wrap(Supplier<T> supplier) {
return () -> observe(supplier);
}
/**
* Observes the passed {@link CheckedCallable}, this means the followings:
*
* <ul>
* <li>Starts the {@code Observation}</li>
* <li>Opens a {@code Scope}</li>
* <li>Calls {@link CheckedCallable#call()}</li>
* <li>Closes the {@code Scope}</li>
* <li>Signals the error to the {@code Observation} if any</li>
* <li>Stops the {@code Observation}</li>
* </ul>
* @param checkedCallable the {@link CheckedCallable} to call
* @param <T> the return type of the {@link CheckedCallable}
* @param <E> type of exception checkedCallable throws
* @return the result from {@link CheckedCallable#call()}
*/
@SuppressWarnings("unused")
default <T, E extends Throwable> T observeChecked(CheckedCallable<T, E> checkedCallable) throws E {
start();
try (Scope scope = openScope()) {
return checkedCallable.call();
}
catch (Throwable error) {
error(error);
throw error;
}
finally {
stop();
}
}
default <T, E extends Throwable> CheckedCallable<T, E> wrapChecked(CheckedCallable<T, E> checkedCallable) throws E {
return () -> observeChecked(checkedCallable);
}
/**
* Wraps the given action in a scope and signals an error.
* @param runnable the {@link Runnable} to run
*/
@SuppressWarnings("unused")
default void scoped(Runnable runnable) {
try (Scope scope = openScope()) {
runnable.run();
}
catch (Exception exception) {
error(exception);
throw exception;
}
}
/**
* Wraps the given action in a scope and signals an error.
* @param checkedRunnable the {@link CheckedRunnable} to run
* @param <E> type of exception thrown
*/
@SuppressWarnings("unused")
default <E extends Throwable> void scopedChecked(CheckedRunnable<E> checkedRunnable) throws E {
try (Scope scope = openScope()) {
checkedRunnable.run();
}
catch (Throwable throwable) {
error(throwable);
throw throwable;
}
}
/**
* Wraps the given action in a scope and signals an error.
* @param supplier the {@link Supplier} to call
* @param <T> the return type of the {@link Supplier}
* @return the result from {@link Supplier#get()}
*/
@SuppressWarnings("unused")
default <T> T scoped(Supplier<T> supplier) {
try (Scope scope = openScope()) {
return supplier.get();
}
catch (Exception exception) {
error(exception);
throw exception;
}
}
/**
* Wraps the given action in a scope and signals an error.
* @param checkedCallable the {@link CheckedCallable} to call
* @param <T> the return type of the {@link CheckedCallable}
* @param <E> type of exception checkedCallable throws
* @return the result from {@link CheckedCallable#call()}
*/
@SuppressWarnings("unused")
default <T, E extends Throwable> T scopedChecked(CheckedCallable<T, E> checkedCallable) throws E {
try (Scope scope = openScope()) {
return checkedCallable.call();
}
catch (Throwable error) {
error(error);
throw error;
}
}
/**
* Tries to run the action against an Observation. If the Observation is null, we just
* run the action, otherwise we run the action in scope.
* @param parent observation, potentially {@code null}
* @param action action to run
*/
static void tryScoped(@Nullable Observation parent, Runnable action) {
if (parent != null) {
parent.scoped(action);
}
else {
action.run();
}
}
/**
* Tries to run the action against an Observation. If the Observation is null, we just
* run the action, otherwise we run the action in scope.
* @param parent observation, potentially {@code null}
* @param checkedRunnable the {@link CheckedRunnable} to run
* @param <E> type of exception checkedRunnable throws
*/
static <E extends Throwable> void tryScopedChecked(@Nullable Observation parent, CheckedRunnable<E> checkedRunnable)
throws E {
if (parent != null) {
parent.scopedChecked(checkedRunnable);
}
else {
checkedRunnable.run();
}
}
/**
* Tries to run the action against an Observation. If the Observation is null, we just
* run the action, otherwise we run the action in scope.
* @param parent observation, potentially {@code null}
* @param action action to run
* @return result of the action
*/
static <T> T tryScoped(@Nullable Observation parent, Supplier<T> action) {
if (parent != null) {
return parent.scoped(action);
}
return action.get();
}
/**
* Tries to run the action against an Observation. If the Observation is null, we just
* run the action, otherwise we run the action in scope.
* @param parent observation, potentially {@code null}
* @param checkedCallable the {@link CheckedCallable} to call
* @param <T> the return type of the {@link CheckedCallable}
* @param <E> type of exception checkedCallable throws
* @return the result from {@link CheckedCallable#call()}
*/
static <T, E extends Throwable> T tryScopedChecked(@Nullable Observation parent,
CheckedCallable<T, E> checkedCallable) throws E {
if (parent != null) {
return parent.scopedChecked(checkedCallable);
}
return checkedCallable.call();
}
/**
* Scope represent an action within which certain resources (e.g. tracing context) are
* put in scope (e.g. in a ThreadLocal). When the scope is closed the resources will
* be removed from the scope.
*
* @since 1.10.0
*/
interface Scope extends AutoCloseable {
/**
* No-op scope.
*/
Scope NOOP = NoopObservation.NoopScope.INSTANCE;
/**
* Current observation available within this scope.
* @return current observation that this scope was created by
*/
Observation getCurrentObservation();
@Override
void close();
/**
* Checks whether this {@link Scope} is no-op.
* @return {@code true} when this is a no-op scope
*/
default boolean isNoop() {
return this == NOOP;
}
}
/**
* A mutable holder of data required by an {@link ObservationHandler}. When extended
* you can provide your own, custom information to be processed by the handlers.
*
* @since 1.10.0
*/
@SuppressWarnings("unchecked")
class Context implements ContextView {
private final Map<Object, Object> map = new HashMap<>();
private String name;
@Nullable
private String contextualName;
@Nullable
private Throwable error;
@Nullable
private ObservationView parentObservation;
private final Map<String, KeyValue> lowCardinalityKeyValues = new LinkedHashMap<>();
private final Map<String, KeyValue> highCardinalityKeyValues = new LinkedHashMap<>();
/**
* The observation name.
* @return name
*/
@Override
public String getName() {
return this.name;
}
/**
* Sets the observation name.
* @param name observation name
*/
public void setName(String name) {
this.name = name;
}
/**
* Returns the contextual name. The name that makes sense within the current
* context (e.g. name derived from HTTP request).
* @return contextual name
*/
@Override
public String getContextualName() {
return this.contextualName;
}
/**
* Sets the contextual name.
* @param contextualName name
*/
public void setContextualName(@Nullable String contextualName) {
this.contextualName = contextualName;
}
/**
* Returns the parent {@link ObservationView}.
* @return parent observation or {@code null} if there was no parent
*/
@Nullable
public ObservationView getParentObservation() {
return parentObservation;
}
/**
* Sets the parent {@link Observation}.
* @param parentObservation parent observation to set
*/
public void setParentObservation(@Nullable ObservationView parentObservation) {
this.parentObservation = parentObservation;
}
/**
* Error that occurred while processing the {@link Observation}.
* @return error (null if there wasn't any)
*/
@Nullable
public Throwable getError() {
return this.error;
}
/**
* Sets an error that occurred while processing the {@link Observation}.
* @param error error
*/
public void setError(Throwable error) {
this.error = error;
}
/**
* Puts an element to the context.
* @param key key
* @param object value
* @param <T> value type
* @return this for chaining
*/
public <T> Context put(Object key, T object) {
this.map.put(key, object);
return this;
}
/**
* Gets an entry from the context. Returns {@code null} when entry is not present.
* @param key key
* @param <T> value type
* @return entry or {@code null} if not present
*/
@Override
@Nullable
public <T> T get(Object key) {
return (T) this.map.get(key);
}
/**
* Removes an entry from the context.
* @param key key by which to remove an entry
* @return the previous value associated with the key, or null if there was no
* mapping for the key
*/
public Object remove(Object key) {
return this.map.remove(key);
}
/**
* Gets an entry from the context. Throws exception when entry is not present.
* @param key key
* @param <T> value type
* @throws IllegalArgumentException if not present
* @return entry
*/
@Override
@NonNull
public <T> T getRequired(Object key) {
T object = (T) this.map.get(key);
if (object == null) {
throw new IllegalArgumentException("Context does not have an entry for key [" + key + "]");
}
return object;
}
/**
* Checks if context contains a key.
* @param key key
* @return {@code true} when the context contains the entry with the given key
*/
@Override
public boolean containsKey(Object key) {
return this.map.containsKey(key);
}
/**
* Returns an element or default if not present.
* @param key key
* @param defaultObject default object to return
* @param <T> value type
* @return object or default if not present
*/
@Override
public <T> T getOrDefault(Object key, T defaultObject) {
return (T) this.map.getOrDefault(key, defaultObject);
}
/**
* Returns an element or calls a mapping function if entry not present. The
* function will insert the value to the map.
* @param key key
* @param mappingFunction mapping function
* @param <T> value type
* @return object or one derived from the mapping function if not present
*/
public <T> T computeIfAbsent(Object key, Function<Object, ? extends T> mappingFunction) {
return (T) this.map.computeIfAbsent(key, mappingFunction);
}
/**
* Clears the entries from the context.
*/
public void clear() {
this.map.clear();
}
/**
* Adds a low cardinality key value - those will be appended to those fetched from
* the {@link ObservationConvention#getLowCardinalityKeyValues(Context)} method.
* @param keyValue a key value
* @return this context
*/
public Context addLowCardinalityKeyValue(KeyValue keyValue) {
this.lowCardinalityKeyValues.put(keyValue.getKey(), keyValue);
return this;
}
/**
* Adds a high cardinality key value - those will be appended to those fetched
* from the {@link ObservationConvention#getHighCardinalityKeyValues(Context)}
* method.
* @param keyValue a key value
* @return this context
*/
public Context addHighCardinalityKeyValue(KeyValue keyValue) {
this.highCardinalityKeyValues.put(keyValue.getKey(), keyValue);
return this;
}
/**
* Removes a low cardinality key value by looking at its key - those will be
* removed to those fetched from the
* {@link ObservationConvention#getLowCardinalityKeyValues(Context)} method.
* @param keyName name of the key
* @return this context
*/
public Context removeLowCardinalityKeyValue(String keyName) {
this.lowCardinalityKeyValues.remove(keyName);
return this;
}
/**
* Removes a high cardinality key value by looking at its key - those will be
* removed to those fetched from the
* {@link ObservationConvention#getHighCardinalityKeyValues(Context)} method.
* @param keyName name of the key
* @return this context
*/
public Context removeHighCardinalityKeyValue(String keyName) {
this.highCardinalityKeyValues.remove(keyName);
return this;
}
/**
* Adds multiple low cardinality key values at once.
* @param keyValues collection of key values
* @return this context
*/
public Context addLowCardinalityKeyValues(KeyValues keyValues) {
keyValues.stream().forEach(this::addLowCardinalityKeyValue);
return this;
}
/**
* Adds multiple high cardinality key values at once.
* @param keyValues collection of key values
* @return this context
*/
public Context addHighCardinalityKeyValues(KeyValues keyValues) {
keyValues.stream().forEach(this::addHighCardinalityKeyValue);
return this;
}
/**
* Removes multiple low cardinality key values at once.
* @param keyNames collection of key names
* @return this context
*/
public Context removeLowCardinalityKeyValues(String... keyNames) {
Arrays.stream(keyNames).forEach(this::removeLowCardinalityKeyValue);
return this;
}
/**
* Removes multiple high cardinality key values at once.