react-native-test-adx 0.1.16 → 0.1.18
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.
- package/lib/module/sdkVersion.js +1 -1
- package/lib/module/services/logging/AdaxonLogger.js +132 -91
- package/lib/module/services/logging/sanitizeLogMetadata.js +20 -6
- package/lib/typescript/src/sdkVersion.d.ts +1 -1
- package/lib/typescript/src/services/logging/AdaxonLogger.d.ts +2 -1
- package/lib/typescript/src/services/logging/sanitizeLogMetadata.d.ts +6 -2
- package/package.json +1 -1
package/lib/module/sdkVersion.js
CHANGED
|
@@ -5,12 +5,16 @@ import { AdaxonSdkError } from "../../errors/AdaxonSdkError.js";
|
|
|
5
5
|
import { sanitizeLogMetadata } from "./sanitizeLogMetadata.js";
|
|
6
6
|
const DEFAULT_LEVEL = 'info';
|
|
7
7
|
|
|
8
|
-
/**
|
|
9
|
-
const
|
|
10
|
-
/** undefined = not loaded yet; null = unavailable in this app bundle */
|
|
8
|
+
/** Milestones + native dev warn/error → Sentry Message when Logs are off. */
|
|
9
|
+
const SENTRY_MESSAGE_EVENTS = new Set(['sdk.initialize.completed', 'sdk.initialize.failed', 'recommendation.response.received', 'ad.displayed', 'ad.dismissed', 'ad.show.failed']);
|
|
11
10
|
let cachedSentryModule;
|
|
12
11
|
let missingSentryPeerWarningShown = false;
|
|
13
|
-
|
|
12
|
+
function isDevBuild() {
|
|
13
|
+
return typeof __DEV__ !== 'undefined' && __DEV__;
|
|
14
|
+
}
|
|
15
|
+
function usesSentryLogs(platform) {
|
|
16
|
+
return platform === 'web' || !isDevBuild();
|
|
17
|
+
}
|
|
14
18
|
function loadSentryModule() {
|
|
15
19
|
if (cachedSentryModule !== undefined) {
|
|
16
20
|
return cachedSentryModule;
|
|
@@ -24,14 +28,14 @@ function loadSentryModule() {
|
|
|
24
28
|
return cachedSentryModule;
|
|
25
29
|
}
|
|
26
30
|
function warnSentryUnavailableOnce(hasDsn) {
|
|
27
|
-
if (!hasDsn || missingSentryPeerWarningShown ||
|
|
31
|
+
if (!hasDsn || missingSentryPeerWarningShown || !isDevBuild()) {
|
|
28
32
|
return;
|
|
29
33
|
}
|
|
30
34
|
if (loadSentryModule()) {
|
|
31
35
|
return;
|
|
32
36
|
}
|
|
33
37
|
missingSentryPeerWarningShown = true;
|
|
34
|
-
console.warn(
|
|
38
|
+
console.warn('[Adaxon] Activity logging is on and Sentry DSN is set, but @sentry/react-native could not be loaded. ' + 'Reinstall dependencies and rebuild. Until then, use [Adaxon activity] in Metro/logcat.');
|
|
35
39
|
}
|
|
36
40
|
function sentryLevel(level, target) {
|
|
37
41
|
if (level === 'warn') {
|
|
@@ -45,27 +49,57 @@ function sentryLevel(level, target) {
|
|
|
45
49
|
}
|
|
46
50
|
return 'info';
|
|
47
51
|
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
52
|
+
|
|
53
|
+
/** What to send after structured Logs (if any) are attempted. */
|
|
54
|
+
function planSentryDelivery(input, level, platform, sentStructuredLog, ownsSentryClient) {
|
|
55
|
+
if (level === 'debug') {
|
|
56
|
+
return {
|
|
57
|
+
breadcrumb: null,
|
|
58
|
+
sendMessage: false
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
const nativeDev = platform !== 'web' && isDevBuild();
|
|
62
|
+
let sendMessage = false;
|
|
63
|
+
if (ownsSentryClient && !sentStructuredLog) {
|
|
64
|
+
if (input.captureAsEvent && input.error === undefined) {
|
|
65
|
+
sendMessage = true;
|
|
66
|
+
} else if (SENTRY_MESSAGE_EVENTS.has(input.eventName)) {
|
|
67
|
+
sendMessage = true;
|
|
68
|
+
} else if (nativeDev && (level === 'warn' || level === 'error')) {
|
|
69
|
+
sendMessage = true;
|
|
70
|
+
}
|
|
51
71
|
}
|
|
52
|
-
|
|
53
|
-
|
|
72
|
+
let breadcrumb = null;
|
|
73
|
+
if (level === 'warn' || level === 'error') {
|
|
74
|
+
breadcrumb = 'adaxon.sdk';
|
|
75
|
+
} else if (nativeDev && !sentStructuredLog && !sendMessage) {
|
|
76
|
+
breadcrumb = 'adaxon.activity';
|
|
54
77
|
}
|
|
55
|
-
|
|
56
|
-
|
|
78
|
+
return {
|
|
79
|
+
breadcrumb,
|
|
80
|
+
sendMessage
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** Never throw or leave a rejected Sentry promise unhandled. */
|
|
85
|
+
function neverBreakSdk(run) {
|
|
86
|
+
try {
|
|
87
|
+
const result = run();
|
|
88
|
+
if (result && typeof result.then === 'function') {
|
|
89
|
+
Promise.resolve(result).catch(() => undefined);
|
|
90
|
+
}
|
|
91
|
+
} catch {
|
|
92
|
+
// Activity logging must not affect the SDK.
|
|
57
93
|
}
|
|
58
|
-
// Native dev + release: message fallback when Sentry Logs API did not accept the event.
|
|
59
|
-
return true;
|
|
60
94
|
}
|
|
61
|
-
function
|
|
95
|
+
function tryStructuredLog(sentry, level, eventName, attributes) {
|
|
62
96
|
try {
|
|
63
97
|
const loggerFn = sentry.logger?.[level];
|
|
64
98
|
if (typeof loggerFn !== 'function') {
|
|
65
99
|
return false;
|
|
66
100
|
}
|
|
67
101
|
loggerFn.call(sentry.logger, eventName, {
|
|
68
|
-
...
|
|
102
|
+
...attributes,
|
|
69
103
|
event_name: eventName
|
|
70
104
|
});
|
|
71
105
|
return true;
|
|
@@ -73,43 +107,30 @@ function emitStructuredLog(sentry, level, eventName, metadata) {
|
|
|
73
107
|
return false;
|
|
74
108
|
}
|
|
75
109
|
}
|
|
76
|
-
function
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
environment,
|
|
80
|
-
enableLogs: true,
|
|
81
|
-
enableAutoConsoleLogs: false,
|
|
82
|
-
tracesSampleRate: 0,
|
|
83
|
-
enableAutoSessionTracking: false,
|
|
84
|
-
sendDefaultPii: false
|
|
85
|
-
};
|
|
110
|
+
function isUnitTestRun() {
|
|
111
|
+
const env = globalThis.process?.env;
|
|
112
|
+
return env?.NODE_ENV === 'test';
|
|
86
113
|
}
|
|
87
|
-
function
|
|
88
|
-
|
|
89
|
-
|
|
114
|
+
function deferOnNative(platform, run) {
|
|
115
|
+
const sync = platform === 'web' || isUnitTestRun();
|
|
116
|
+
if (sync) {
|
|
117
|
+
run();
|
|
118
|
+
} else {
|
|
119
|
+
setTimeout(run, 0);
|
|
90
120
|
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
};
|
|
95
|
-
const sentry = loadSentryModule();
|
|
96
|
-
if (typeof sentry?.consoleSandbox === 'function') {
|
|
97
|
-
sentry.consoleSandbox(write);
|
|
121
|
+
}
|
|
122
|
+
function mirrorToDevConsole(level, eventName, metadata) {
|
|
123
|
+
if (!isDevBuild()) {
|
|
98
124
|
return;
|
|
99
125
|
}
|
|
100
|
-
|
|
101
|
-
}
|
|
102
|
-
function activityTags(eventName, platform) {
|
|
103
|
-
return {
|
|
104
|
-
adaxon_event: eventName,
|
|
105
|
-
adaxon_platform: platform,
|
|
106
|
-
adaxon_sdk_version: SDK_VERSION
|
|
107
|
-
};
|
|
126
|
+
const log = level === 'error' ? console.error : level === 'warn' ? console.warn : console.info;
|
|
127
|
+
log(`[Adaxon activity] ${eventName}`, metadata);
|
|
108
128
|
}
|
|
109
129
|
class AdaxonLoggerService {
|
|
110
130
|
enabled = false;
|
|
111
131
|
platform = 'web';
|
|
112
132
|
sentryReady = false;
|
|
133
|
+
ownsSentryClient = false;
|
|
113
134
|
configure(input) {
|
|
114
135
|
this.enabled = Boolean(input.enabled);
|
|
115
136
|
this.platform = input.platform;
|
|
@@ -124,6 +145,7 @@ class AdaxonLoggerService {
|
|
|
124
145
|
this.enabled = false;
|
|
125
146
|
this.sentryReady = false;
|
|
126
147
|
this.platform = 'web';
|
|
148
|
+
this.ownsSentryClient = false;
|
|
127
149
|
cachedSentryModule = undefined;
|
|
128
150
|
missingSentryPeerWarningShown = false;
|
|
129
151
|
}
|
|
@@ -132,71 +154,90 @@ class AdaxonLoggerService {
|
|
|
132
154
|
return;
|
|
133
155
|
}
|
|
134
156
|
const level = input.level ?? DEFAULT_LEVEL;
|
|
135
|
-
const
|
|
157
|
+
const baseMetadata = {
|
|
136
158
|
...input.metadata,
|
|
137
159
|
sdk_version: SDK_VERSION,
|
|
138
160
|
platform: this.platform,
|
|
139
161
|
timestamp: new Date().toISOString()
|
|
162
|
+
};
|
|
163
|
+
mirrorToDevConsole(level, input.eventName, sanitizeLogMetadata(baseMetadata));
|
|
164
|
+
const sentryAttributes = sanitizeLogMetadata({
|
|
165
|
+
...baseMetadata,
|
|
166
|
+
event_name: input.eventName
|
|
167
|
+
}, {
|
|
168
|
+
forSentry: true
|
|
140
169
|
});
|
|
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
|
-
|
|
170
|
+
deferOnNative(this.platform, () => neverBreakSdk(() => this.pushToSentry(input, level, sentryAttributes)));
|
|
171
|
+
}
|
|
172
|
+
pushToSentry(input, level, sentryAttributes) {
|
|
173
|
+
const sentry = loadSentryModule();
|
|
174
|
+
if (!sentry || !this.sentryReady) {
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
const sentStructuredLog = usesSentryLogs(this.platform) && tryStructuredLog(sentry, level, input.eventName, sentryAttributes);
|
|
178
|
+
const plan = planSentryDelivery(input, level, this.platform, sentStructuredLog, this.ownsSentryClient);
|
|
179
|
+
const tags = {
|
|
180
|
+
adaxon_event: input.eventName,
|
|
181
|
+
adaxon_platform: this.platform,
|
|
182
|
+
adaxon_sdk_version: SDK_VERSION
|
|
183
|
+
};
|
|
184
|
+
if (plan.breadcrumb) {
|
|
185
|
+
const category = plan.breadcrumb;
|
|
186
|
+
neverBreakSdk(() => sentry.addBreadcrumb({
|
|
187
|
+
category,
|
|
188
|
+
message: input.eventName,
|
|
189
|
+
level: sentryLevel(level, 'breadcrumb'),
|
|
190
|
+
data: sentryAttributes,
|
|
191
|
+
timestamp: Date.now() / 1000
|
|
192
|
+
}));
|
|
193
|
+
}
|
|
194
|
+
if (plan.sendMessage && typeof sentry.captureMessage === 'function') {
|
|
195
|
+
neverBreakSdk(() => sentry.captureMessage(input.eventName, {
|
|
196
|
+
level: sentryLevel(level, 'message'),
|
|
197
|
+
tags,
|
|
198
|
+
extra: sentryAttributes
|
|
199
|
+
}));
|
|
200
|
+
}
|
|
201
|
+
if (input.captureAsEvent && input.error !== undefined && this.ownsSentryClient) {
|
|
202
|
+
const err = input.error instanceof Error ? input.error : new Error(AdaxonSdkError.messageOf(input.error, `Adaxon SDK activity: ${input.eventName}`));
|
|
203
|
+
neverBreakSdk(() => sentry.captureException(err, {
|
|
204
|
+
tags,
|
|
205
|
+
extra: sentryAttributes
|
|
206
|
+
}));
|
|
207
|
+
}
|
|
174
208
|
}
|
|
175
209
|
ensureSentry(dsn, testMode) {
|
|
176
|
-
|
|
210
|
+
neverBreakSdk(() => {
|
|
177
211
|
const sentry = loadSentryModule();
|
|
178
212
|
if (!sentry) {
|
|
179
213
|
return;
|
|
180
214
|
}
|
|
215
|
+
this.ownsSentryClient = false;
|
|
181
216
|
if (!sentry.getClient()) {
|
|
182
217
|
if (!dsn?.trim()) {
|
|
183
218
|
return;
|
|
184
219
|
}
|
|
185
|
-
|
|
186
|
-
|
|
220
|
+
sentry.init({
|
|
221
|
+
dsn: dsn.trim(),
|
|
222
|
+
environment: testMode ? 'adaxon-sdk-test' : 'adaxon-sdk',
|
|
223
|
+
enableLogs: usesSentryLogs(this.platform),
|
|
224
|
+
enableAutoConsoleLogs: false,
|
|
225
|
+
enableNative: false,
|
|
226
|
+
autoInitializeNativeSdk: false,
|
|
227
|
+
defaultIntegrations: false,
|
|
228
|
+
integrations: [],
|
|
229
|
+
tracesSampleRate: 0,
|
|
230
|
+
enableAutoSessionTracking: false,
|
|
231
|
+
sendDefaultPii: false
|
|
232
|
+
});
|
|
233
|
+
this.ownsSentryClient = true;
|
|
234
|
+
}
|
|
235
|
+
for (const [key, value] of [['adaxon_sdk_version', SDK_VERSION], ['adaxon_platform', this.platform]]) {
|
|
236
|
+
sentry.setTag(key, value);
|
|
187
237
|
}
|
|
188
|
-
sentry.setTag('adaxon_sdk_version', SDK_VERSION);
|
|
189
|
-
sentry.setTag('adaxon_platform', this.platform);
|
|
190
238
|
this.sentryReady = true;
|
|
191
239
|
});
|
|
192
240
|
}
|
|
193
|
-
safe(run) {
|
|
194
|
-
try {
|
|
195
|
-
run();
|
|
196
|
-
} catch {
|
|
197
|
-
// Logging must never interrupt SDK behavior.
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
241
|
}
|
|
201
242
|
export const AdaxonLogger = new AdaxonLoggerService();
|
|
202
243
|
export function logSdkActivity(input) {
|
|
@@ -6,19 +6,28 @@ const MAX_NESTED_KEYS = 40;
|
|
|
6
6
|
function truncate(value) {
|
|
7
7
|
return value.length <= MAX_STRING_LENGTH ? value : `${value.slice(0, MAX_STRING_LENGTH)}…`;
|
|
8
8
|
}
|
|
9
|
-
function sanitizeField(key, value) {
|
|
9
|
+
function sanitizeField(key, value, forSentry) {
|
|
10
10
|
if (SENSITIVE_KEY.test(key)) {
|
|
11
11
|
return '[redacted]';
|
|
12
12
|
}
|
|
13
13
|
if (value === null || value === undefined) {
|
|
14
|
-
return value;
|
|
14
|
+
return forSentry ? undefined : value;
|
|
15
15
|
}
|
|
16
16
|
if (typeof value === 'string') {
|
|
17
17
|
return truncate(value);
|
|
18
18
|
}
|
|
19
|
-
if (typeof value === '
|
|
19
|
+
if (typeof value === 'boolean') {
|
|
20
20
|
return value;
|
|
21
21
|
}
|
|
22
|
+
if (typeof value === 'number') {
|
|
23
|
+
if (Number.isFinite(value)) {
|
|
24
|
+
return value;
|
|
25
|
+
}
|
|
26
|
+
return forSentry ? undefined : value;
|
|
27
|
+
}
|
|
28
|
+
if (forSentry) {
|
|
29
|
+
return truncate(String(value));
|
|
30
|
+
}
|
|
22
31
|
if (Array.isArray(value)) {
|
|
23
32
|
return value.map(item => typeof item === 'string' ? truncate(item) : item);
|
|
24
33
|
}
|
|
@@ -32,14 +41,19 @@ function sanitizeField(key, value) {
|
|
|
32
41
|
return String(value);
|
|
33
42
|
}
|
|
34
43
|
|
|
35
|
-
/** Redact secrets
|
|
36
|
-
export function sanitizeLogMetadata(metadata) {
|
|
44
|
+
/** Redact secrets, cap strings; optional Sentry flattening (primitives only). */
|
|
45
|
+
export function sanitizeLogMetadata(metadata, options = {}) {
|
|
37
46
|
if (!metadata) {
|
|
38
47
|
return {};
|
|
39
48
|
}
|
|
49
|
+
const forSentry = Boolean(options.forSentry);
|
|
40
50
|
const output = {};
|
|
41
51
|
for (const [key, value] of Object.entries(metadata)) {
|
|
42
|
-
|
|
52
|
+
const sanitized = sanitizeField(key, value, forSentry);
|
|
53
|
+
if (forSentry && sanitized === undefined) {
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
output[key] = sanitized;
|
|
43
57
|
}
|
|
44
58
|
return output;
|
|
45
59
|
}
|
|
@@ -18,11 +18,12 @@ declare class AdaxonLoggerService {
|
|
|
18
18
|
private enabled;
|
|
19
19
|
private platform;
|
|
20
20
|
private sentryReady;
|
|
21
|
+
private ownsSentryClient;
|
|
21
22
|
configure(input: AdaxonLoggerConfigureInput): void;
|
|
22
23
|
reset(): void;
|
|
23
24
|
logActivity(input: AdaxonActivityLogInput): void;
|
|
25
|
+
private pushToSentry;
|
|
24
26
|
private ensureSentry;
|
|
25
|
-
private safe;
|
|
26
27
|
}
|
|
27
28
|
export declare const AdaxonLogger: AdaxonLoggerService;
|
|
28
29
|
export declare function logSdkActivity(input: AdaxonActivityLogInput): void;
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
export type SanitizeLogMetadataOptions = {
|
|
2
|
+
/** Flatten to string/number/boolean for Sentry Logs and captureMessage (React Native). */
|
|
3
|
+
forSentry?: boolean;
|
|
4
|
+
};
|
|
5
|
+
/** Redact secrets, cap strings; optional Sentry flattening (primitives only). */
|
|
6
|
+
export declare function sanitizeLogMetadata(metadata: Record<string, unknown> | undefined, options?: SanitizeLogMetadataOptions): Record<string, unknown>;
|
|
3
7
|
//# sourceMappingURL=sanitizeLogMetadata.d.ts.map
|
package/package.json
CHANGED