@grafana/faro-react-native-tracing 1.0.0-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +833 -0
  3. package/dist/cjs/exporters/faroTraceExporter.js +101 -0
  4. package/dist/cjs/exporters/faroTraceExporter.js.map +1 -0
  5. package/dist/cjs/exporters/faroTraceExporter.utils.js +99 -0
  6. package/dist/cjs/exporters/faroTraceExporter.utils.js.map +1 -0
  7. package/dist/cjs/index.js +17 -0
  8. package/dist/cjs/index.js.map +1 -0
  9. package/dist/cjs/instrumentation.js +251 -0
  10. package/dist/cjs/instrumentation.js.map +1 -0
  11. package/dist/cjs/instrumentations/getDefaultOTELInstrumentations.js +77 -0
  12. package/dist/cjs/instrumentations/getDefaultOTELInstrumentations.js.map +1 -0
  13. package/dist/cjs/instrumentations/instrumentationUtils.js +89 -0
  14. package/dist/cjs/instrumentations/instrumentationUtils.js.map +1 -0
  15. package/dist/cjs/processors/faroMetaAttributesSpanProcessor.js +59 -0
  16. package/dist/cjs/processors/faroMetaAttributesSpanProcessor.js.map +1 -0
  17. package/dist/cjs/processors/httpRequestMonitorSpanProcessor.js +98 -0
  18. package/dist/cjs/processors/httpRequestMonitorSpanProcessor.js.map +1 -0
  19. package/dist/cjs/semconv.js +29 -0
  20. package/dist/cjs/semconv.js.map +1 -0
  21. package/dist/cjs/types.js +3 -0
  22. package/dist/cjs/types.js.map +1 -0
  23. package/dist/cjs/utils/sampler.js +21 -0
  24. package/dist/cjs/utils/sampler.js.map +1 -0
  25. package/dist/esm/exporters/faroTraceExporter.js +65 -0
  26. package/dist/esm/exporters/faroTraceExporter.js.map +1 -0
  27. package/dist/esm/exporters/faroTraceExporter.utils.js +88 -0
  28. package/dist/esm/exporters/faroTraceExporter.utils.js.map +1 -0
  29. package/dist/esm/index.js +7 -0
  30. package/dist/esm/index.js.map +1 -0
  31. package/dist/esm/instrumentation.js +183 -0
  32. package/dist/esm/instrumentation.js.map +1 -0
  33. package/dist/esm/instrumentations/getDefaultOTELInstrumentations.js +62 -0
  34. package/dist/esm/instrumentations/getDefaultOTELInstrumentations.js.map +1 -0
  35. package/dist/esm/instrumentations/instrumentationUtils.js +83 -0
  36. package/dist/esm/instrumentations/instrumentationUtils.js.map +1 -0
  37. package/dist/esm/processors/faroMetaAttributesSpanProcessor.js +54 -0
  38. package/dist/esm/processors/faroMetaAttributesSpanProcessor.js.map +1 -0
  39. package/dist/esm/processors/httpRequestMonitorSpanProcessor.js +93 -0
  40. package/dist/esm/processors/httpRequestMonitorSpanProcessor.js.map +1 -0
  41. package/dist/esm/semconv.js +26 -0
  42. package/dist/esm/semconv.js.map +1 -0
  43. package/dist/esm/types.js +2 -0
  44. package/dist/esm/types.js.map +1 -0
  45. package/dist/esm/utils/sampler.js +17 -0
  46. package/dist/esm/utils/sampler.js.map +1 -0
  47. package/dist/types/exporters/faroTraceExporter.d.ts +21 -0
  48. package/dist/types/exporters/faroTraceExporter.utils.d.ts +17 -0
  49. package/dist/types/index.d.ts +7 -0
  50. package/dist/types/instrumentation.d.ts +46 -0
  51. package/dist/types/instrumentations/getDefaultOTELInstrumentations.d.ts +17 -0
  52. package/dist/types/instrumentations/instrumentationUtils.d.ts +26 -0
  53. package/dist/types/processors/faroMetaAttributesSpanProcessor.d.ts +22 -0
  54. package/dist/types/processors/httpRequestMonitorSpanProcessor.d.ts +18 -0
  55. package/dist/types/semconv.d.ts +19 -0
  56. package/dist/types/types.d.ts +31 -0
  57. package/dist/types/utils/sampler.d.ts +12 -0
  58. package/package.json +77 -0
  59. package/src/exporters/faroTraceExporter.test.ts +110 -0
  60. package/src/exporters/faroTraceExporter.ts +64 -0
  61. package/src/exporters/faroTraceExporter.utils.ts +105 -0
  62. package/src/index.ts +16 -0
  63. package/src/instrumentation.ts +241 -0
  64. package/src/instrumentations/getDefaultOTELInstrumentations.test.ts +70 -0
  65. package/src/instrumentations/getDefaultOTELInstrumentations.ts +83 -0
  66. package/src/instrumentations/instrumentationUtils.test.ts +107 -0
  67. package/src/instrumentations/instrumentationUtils.ts +106 -0
  68. package/src/processors/faroMetaAttributesSpanProcessor.test.ts +127 -0
  69. package/src/processors/faroMetaAttributesSpanProcessor.ts +71 -0
  70. package/src/processors/httpRequestMonitorSpanProcessor.ts +106 -0
  71. package/src/semconv.ts +31 -0
  72. package/src/types.ts +39 -0
  73. package/src/utils/sampler.test.ts +51 -0
  74. package/src/utils/sampler.ts +19 -0
@@ -0,0 +1,127 @@
1
+ import { ROOT_CONTEXT } from '@opentelemetry/api';
2
+ import { BasicTracerProvider } from '@opentelemetry/sdk-trace-base';
3
+
4
+ import type { Metas } from '@grafana/faro-core';
5
+
6
+ import { FaroMetaAttributesSpanProcessor } from './faroMetaAttributesSpanProcessor';
7
+
8
+ describe('FaroMetaAttributesSpanProcessor', () => {
9
+ let provider: BasicTracerProvider;
10
+ let mockProcessor: any;
11
+ let mockMetas: Metas;
12
+
13
+ beforeEach(() => {
14
+ provider = new BasicTracerProvider();
15
+ mockProcessor = {
16
+ forceFlush: jest.fn().mockResolvedValue(undefined),
17
+ onStart: jest.fn(),
18
+ onEnd: jest.fn(),
19
+ shutdown: jest.fn().mockResolvedValue(undefined),
20
+ };
21
+
22
+ mockMetas = {
23
+ value: {
24
+ session: {
25
+ id: 'test-session-id',
26
+ },
27
+ user: {
28
+ id: 'test-user-id',
29
+ email: 'test@example.com',
30
+ username: 'testuser',
31
+ fullName: 'Test User',
32
+ roles: 'admin, user',
33
+ hash: 'test-hash',
34
+ },
35
+ },
36
+ add: jest.fn(),
37
+ remove: jest.fn(),
38
+ addListener: jest.fn(),
39
+ removeListener: jest.fn(),
40
+ };
41
+ });
42
+
43
+ it('should add session ID to span attributes', () => {
44
+ const processor = new FaroMetaAttributesSpanProcessor(mockProcessor, mockMetas);
45
+ const tracer = provider.getTracer('test');
46
+ const span = tracer.startSpan('test-span', {}, ROOT_CONTEXT);
47
+
48
+ processor.onStart(span as any, ROOT_CONTEXT);
49
+
50
+ expect((span as any).attributes['session.id']).toBe('test-session-id');
51
+ });
52
+
53
+ it('should add user attributes to span', () => {
54
+ const processor = new FaroMetaAttributesSpanProcessor(mockProcessor, mockMetas);
55
+ const tracer = provider.getTracer('test');
56
+ const span = tracer.startSpan('test-span', {}, ROOT_CONTEXT);
57
+
58
+ processor.onStart(span as any, ROOT_CONTEXT);
59
+
60
+ expect((span as any).attributes['user.id']).toBe('test-user-id');
61
+ expect((span as any).attributes['user.email']).toBe('test@example.com');
62
+ expect((span as any).attributes['user.name']).toBe('testuser');
63
+ expect((span as any).attributes['user.full_name']).toBe('Test User');
64
+ expect((span as any).attributes['user.hash']).toBe('test-hash');
65
+ });
66
+
67
+ it('should parse and add user roles as array', () => {
68
+ const processor = new FaroMetaAttributesSpanProcessor(mockProcessor, mockMetas);
69
+ const tracer = provider.getTracer('test');
70
+ const span = tracer.startSpan('test-span', {}, ROOT_CONTEXT);
71
+
72
+ processor.onStart(span as any, ROOT_CONTEXT);
73
+
74
+ expect((span as any).attributes['user.roles']).toEqual(['admin', 'user']);
75
+ });
76
+
77
+ it('should handle missing session gracefully', () => {
78
+ mockMetas.value = { user: mockMetas.value.user };
79
+ const processor = new FaroMetaAttributesSpanProcessor(mockProcessor, mockMetas);
80
+ const tracer = provider.getTracer('test');
81
+ const span = tracer.startSpan('test-span', {}, ROOT_CONTEXT);
82
+
83
+ processor.onStart(span as any, ROOT_CONTEXT);
84
+
85
+ expect((span as any).attributes['session.id']).toBeUndefined();
86
+ expect((span as any).attributes['user.id']).toBe('test-user-id');
87
+ });
88
+
89
+ it('should handle missing user gracefully', () => {
90
+ mockMetas.value = { session: mockMetas.value.session };
91
+ const processor = new FaroMetaAttributesSpanProcessor(mockProcessor, mockMetas);
92
+ const tracer = provider.getTracer('test');
93
+ const span = tracer.startSpan('test-span', {}, ROOT_CONTEXT);
94
+
95
+ processor.onStart(span as any, ROOT_CONTEXT);
96
+
97
+ expect((span as any).attributes['session.id']).toBe('test-session-id');
98
+ expect((span as any).attributes['user.id']).toBeUndefined();
99
+ });
100
+
101
+ it('should delegate onEnd to wrapped processor', () => {
102
+ const processor = new FaroMetaAttributesSpanProcessor(mockProcessor, mockMetas);
103
+ const tracer = provider.getTracer('test');
104
+ const span = tracer.startSpan('test-span', {}, ROOT_CONTEXT);
105
+ span.end();
106
+
107
+ processor.onEnd(span as any);
108
+
109
+ expect(mockProcessor.onEnd).toHaveBeenCalledWith(span);
110
+ });
111
+
112
+ it('should delegate forceFlush to wrapped processor', async () => {
113
+ const processor = new FaroMetaAttributesSpanProcessor(mockProcessor, mockMetas);
114
+
115
+ await processor.forceFlush();
116
+
117
+ expect(mockProcessor.forceFlush).toHaveBeenCalled();
118
+ });
119
+
120
+ it('should delegate shutdown to wrapped processor', async () => {
121
+ const processor = new FaroMetaAttributesSpanProcessor(mockProcessor, mockMetas);
122
+
123
+ await processor.shutdown();
124
+
125
+ expect(mockProcessor.shutdown).toHaveBeenCalled();
126
+ });
127
+ });
@@ -0,0 +1,71 @@
1
+ import type { Context } from '@opentelemetry/api';
2
+ import type { ReadableSpan, Span, SpanProcessor } from '@opentelemetry/sdk-trace-base';
3
+
4
+ import type { Metas } from '@grafana/faro-core';
5
+
6
+ import { ATTR_SESSION_ID } from '../semconv';
7
+
8
+ /**
9
+ * Span processor that adds Faro meta attributes to spans
10
+ *
11
+ * This processor enriches spans with:
12
+ * - Session ID
13
+ * - User information (email, id, username, etc.)
14
+ *
15
+ * IMPORTANT: This processor delegates to a wrapped processor and does NOT log
16
+ * to avoid infinite loops.
17
+ */
18
+ export class FaroMetaAttributesSpanProcessor implements SpanProcessor {
19
+ constructor(
20
+ private processor: SpanProcessor,
21
+ private metas: Metas
22
+ ) {}
23
+
24
+ forceFlush(): Promise<void> {
25
+ return this.processor.forceFlush();
26
+ }
27
+
28
+ onStart(span: Span, parentContext: Context): void {
29
+ const session = this.metas.value.session;
30
+
31
+ if (session?.id) {
32
+ span.attributes[ATTR_SESSION_ID] = session.id;
33
+ }
34
+
35
+ const user = this.metas.value.user ?? {};
36
+
37
+ if (user.email) {
38
+ span.attributes['user.email'] = user.email;
39
+ }
40
+
41
+ if (user.id) {
42
+ span.attributes['user.id'] = user.id;
43
+ }
44
+
45
+ if (user.username) {
46
+ span.attributes['user.name'] = user.username;
47
+ }
48
+
49
+ if (user.fullName) {
50
+ span.attributes['user.full_name'] = user.fullName;
51
+ }
52
+
53
+ if (user.roles) {
54
+ span.attributes['user.roles'] = user.roles.split(',').map((role) => role.trim());
55
+ }
56
+
57
+ if (user.hash) {
58
+ span.attributes['user.hash'] = user.hash;
59
+ }
60
+
61
+ this.processor.onStart(span, parentContext);
62
+ }
63
+
64
+ onEnd(span: ReadableSpan): void {
65
+ this.processor.onEnd(span);
66
+ }
67
+
68
+ shutdown(): Promise<void> {
69
+ return this.processor.shutdown();
70
+ }
71
+ }
@@ -0,0 +1,106 @@
1
+ import type { Context } from '@opentelemetry/api';
2
+ import type { ReadableSpan, Span, SpanProcessor } from '@opentelemetry/sdk-trace-base';
3
+
4
+ import {
5
+ type HttpRequestMessagePayload,
6
+ notifyHttpRequestEnd,
7
+ notifyHttpRequestStart,
8
+ } from '@grafana/faro-react-native';
9
+
10
+ const ATTR_HTTP_METHOD = 'http.method';
11
+ const ATTR_HTTP_STATUS_CODE = 'http.status_code';
12
+ const ATTR_HTTP_URL = 'http.url';
13
+
14
+ /**
15
+ * Get string attribute from span.
16
+ * Handles both SDK attribute format and OTLP-style attribute values.
17
+ */
18
+ function getAttr(span: Span | ReadableSpan, key: string): string | undefined {
19
+ const attrs = span.attributes as Record<string, unknown> | undefined;
20
+ if (attrs == null) return undefined;
21
+ const raw = typeof attrs['get'] === 'function' ? (attrs as { get: (k: string) => unknown })['get'](key) : attrs[key];
22
+ if (raw == null) return undefined;
23
+ if (typeof raw === 'string') return raw.length > 0 ? raw : undefined;
24
+ if (typeof raw === 'object' && raw !== null && 'stringValue' in raw) {
25
+ const s = String((raw as { stringValue?: string }).stringValue ?? '');
26
+ return s.length > 0 ? s : undefined;
27
+ }
28
+ const str = String(raw);
29
+ return str.length > 0 ? str : undefined;
30
+ }
31
+
32
+ /**
33
+ * Check if a span is an HTTP span (from FetchInstrumentation).
34
+ */
35
+ function isHttpSpan(span: Span | ReadableSpan): boolean {
36
+ return getAttr(span, ATTR_HTTP_URL) != null || getAttr(span, ATTR_HTTP_METHOD) != null;
37
+ }
38
+
39
+ /**
40
+ * SpanProcessor that notifies httpRequestMonitor when HTTP spans start and end.
41
+ *
42
+ * This enables user action correlation (UserActionController halt logic) when
43
+ * TracingInstrumentation is used instead of HttpInstrumentation.
44
+ *
45
+ * IMPORTANT: Must not use console or trigger any instrumentation to avoid loops.
46
+ */
47
+ export class HttpRequestMonitorSpanProcessor implements SpanProcessor {
48
+ constructor(private readonly processor: SpanProcessor) {}
49
+
50
+ onStart(span: Span, parentContext: Context): void {
51
+ if (isHttpSpan(span)) {
52
+ const url = getAttr(span, ATTR_HTTP_URL) ?? '';
53
+ const method = getAttr(span, ATTR_HTTP_METHOD) ?? 'GET';
54
+ const requestId = span.spanContext().spanId;
55
+ const startTimeMs = Date.now();
56
+
57
+ const payload: HttpRequestMessagePayload = {
58
+ requestId,
59
+ url,
60
+ method,
61
+ startTime: startTimeMs,
62
+ };
63
+ notifyHttpRequestStart(payload);
64
+ }
65
+
66
+ this.processor.onStart(span, parentContext);
67
+ }
68
+
69
+ onEnd(span: ReadableSpan): void {
70
+ if (isHttpSpan(span)) {
71
+ const url = getAttr(span, ATTR_HTTP_URL) ?? '';
72
+ const method = getAttr(span, ATTR_HTTP_METHOD) ?? 'GET';
73
+ const requestId = span.spanContext().spanId;
74
+ const statusAttr = getAttr(span, ATTR_HTTP_STATUS_CODE);
75
+ const status = statusAttr != null ? parseInt(statusAttr, 10) : undefined;
76
+ const spanWithTime = span as ReadableSpan & {
77
+ startTimeUnixNano?: number | bigint;
78
+ endTimeUnixNano?: number | bigint;
79
+ };
80
+ const startNs = spanWithTime.startTimeUnixNano;
81
+ const endNs = spanWithTime.endTimeUnixNano;
82
+ const startTimeMs = startNs != null && !Number.isNaN(Number(startNs)) ? Number(startNs) / 1_000_000 : Date.now();
83
+ const endTimeMs = endNs != null && !Number.isNaN(Number(endNs)) ? Number(endNs) / 1_000_000 : Date.now();
84
+
85
+ const payload: HttpRequestMessagePayload = {
86
+ requestId,
87
+ url,
88
+ method,
89
+ startTime: startTimeMs,
90
+ endTime: endTimeMs,
91
+ status: !Number.isNaN(status) ? status : undefined,
92
+ };
93
+ notifyHttpRequestEnd(payload);
94
+ }
95
+
96
+ this.processor.onEnd(span);
97
+ }
98
+
99
+ forceFlush(): Promise<void> {
100
+ return this.processor.forceFlush();
101
+ }
102
+
103
+ shutdown(): Promise<void> {
104
+ return this.processor.shutdown();
105
+ }
106
+ }
package/src/semconv.ts ADDED
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Semantic convention attributes for React Native tracing
3
+ * Based on OpenTelemetry semantic conventions with React Native-specific additions
4
+ */
5
+
6
+ // Session attributes
7
+ export const ATTR_SESSION_ID = 'session.id';
8
+
9
+ // Device/Platform attributes for React Native
10
+ export const ATTR_DEVICE_MODEL = 'device.model';
11
+ export const ATTR_DEVICE_BRAND = 'device.brand';
12
+ export const ATTR_DEVICE_PLATFORM = 'device.platform';
13
+ export const ATTR_DEVICE_OS_VERSION = 'device.os.version';
14
+ export const ATTR_DEVICE_TYPE = 'device.type';
15
+ export const ATTR_DEVICE_LOCALE = 'device.locale';
16
+
17
+ // App attributes
18
+ export const ATTR_APP_VERSION = 'app.version';
19
+ export const ATTR_APP_BUILD = 'app.build';
20
+
21
+ // Service attributes
22
+ export const ATTR_SERVICE_NAMESPACE = 'service.namespace';
23
+ export const ATTR_DEPLOYMENT_ENVIRONMENT_NAME = 'deployment.environment.name';
24
+
25
+ // Telemetry attributes
26
+ export const ATTR_TELEMETRY_DISTRO_NAME = 'telemetry.distro.name';
27
+ export const ATTR_TELEMETRY_DISTRO_VERSION = 'telemetry.distro.version';
28
+
29
+ // Process/Runtime attributes
30
+ export const ATTR_PROCESS_RUNTIME_NAME = 'process.runtime.name';
31
+ export const ATTR_PROCESS_RUNTIME_VERSION = 'process.runtime.version';
package/src/types.ts ADDED
@@ -0,0 +1,39 @@
1
+ import type { Attributes, ContextManager, TextMapPropagator } from '@opentelemetry/api';
2
+ import type { Instrumentation } from '@opentelemetry/instrumentation';
3
+ import type { FetchCustomAttributeFunction } from '@opentelemetry/instrumentation-fetch';
4
+ import type { XHRCustomAttributeFunction } from '@opentelemetry/instrumentation-xml-http-request';
5
+ import type { SpanProcessor } from '@opentelemetry/sdk-trace-base';
6
+
7
+ import type { API, Patterns } from '@grafana/faro-core';
8
+
9
+ export type InstrumentationOption = Instrumentation | Instrumentation[];
10
+
11
+ export interface FaroTraceExporterConfig {
12
+ api: API;
13
+ }
14
+
15
+ export interface TracingInstrumentationOptions {
16
+ resourceAttributes?: Attributes;
17
+ propagator?: TextMapPropagator;
18
+ contextManager?: ContextManager;
19
+ instrumentations?: InstrumentationOption[];
20
+ spanProcessor?: SpanProcessor;
21
+ instrumentationOptions?: Omit<DefaultInstrumentationsOptions, 'ignoreUrls'>;
22
+ }
23
+
24
+ export type MatchUrlDefinitions = Patterns;
25
+
26
+ export type DefaultInstrumentationsOptions = {
27
+ ignoreUrls?: MatchUrlDefinitions;
28
+ propagateTraceHeaderCorsUrls?: MatchUrlDefinitions;
29
+
30
+ fetchInstrumentationOptions?: {
31
+ applyCustomAttributesOnSpan?: FetchCustomAttributeFunction;
32
+ ignoreNetworkEvents?: boolean;
33
+ };
34
+
35
+ xhrInstrumentationOptions?: {
36
+ applyCustomAttributesOnSpan?: XHRCustomAttributeFunction;
37
+ ignoreNetworkEvents?: boolean;
38
+ };
39
+ };
@@ -0,0 +1,51 @@
1
+ import { SamplingDecision } from '@opentelemetry/sdk-trace-base';
2
+
3
+ import { getSamplingDecision } from './sampler';
4
+
5
+ describe('Sampler', () => {
6
+ afterEach(() => {
7
+ jest.restoreAllMocks();
8
+ });
9
+
10
+ it('Set SamplingDecision to NOT_RECORD (0) if session is not part of the sample', () => {
11
+ const samplingDecision = getSamplingDecision({
12
+ attributes: {
13
+ isSampled: 'false',
14
+ },
15
+ });
16
+
17
+ expect(samplingDecision).toBe(SamplingDecision.NOT_RECORD);
18
+ });
19
+
20
+ it('Set SamplingDecision to RECORD_AND_SAMPLED (2) if session is part of the sample', () => {
21
+ const samplingDecision = getSamplingDecision({
22
+ attributes: {
23
+ isSampled: 'true',
24
+ },
25
+ });
26
+
27
+ expect(samplingDecision).toBe(SamplingDecision.RECORD_AND_SAMPLED);
28
+ });
29
+
30
+ it('Set SamplingDecision to NOT_RECORD if session has no attributes', () => {
31
+ const samplingDecision = getSamplingDecision({});
32
+
33
+ expect(samplingDecision).toBe(SamplingDecision.NOT_RECORD);
34
+ });
35
+
36
+ it('Set SamplingDecision to NOT_RECORD if session is undefined', () => {
37
+ const samplingDecision = getSamplingDecision(undefined);
38
+
39
+ expect(samplingDecision).toBe(SamplingDecision.NOT_RECORD);
40
+ });
41
+
42
+ it('Set SamplingDecision to NOT_RECORD if isSampled is missing', () => {
43
+ const samplingDecision = getSamplingDecision({
44
+ attributes: {
45
+ someOtherKey: 'value',
46
+ },
47
+ });
48
+
49
+ expect(samplingDecision).toBe(SamplingDecision.NOT_RECORD);
50
+ });
51
+ });
@@ -0,0 +1,19 @@
1
+ import { SamplingDecision } from '@opentelemetry/sdk-trace-base';
2
+
3
+ import type { MetaSession } from '@grafana/faro-core';
4
+
5
+ /**
6
+ * Get sampling decision based on session configuration
7
+ *
8
+ * If the session is sampled, traces will be collected.
9
+ * If the session is not sampled, traces will be dropped.
10
+ *
11
+ * @param sessionMeta - Current Faro session meta
12
+ * @returns OTEL sampling decision
13
+ */
14
+ export function getSamplingDecision(sessionMeta: MetaSession = {}): SamplingDecision {
15
+ const isSessionSampled = sessionMeta.attributes?.['isSampled'] === 'true';
16
+ const samplingDecision = isSessionSampled ? SamplingDecision.RECORD_AND_SAMPLED : SamplingDecision.NOT_RECORD;
17
+
18
+ return samplingDecision;
19
+ }