-
-
Notifications
You must be signed in to change notification settings - Fork 358
Expand file tree
/
Copy pathlogEnricherIntegration.ts
More file actions
141 lines (125 loc) · 4.84 KB
/
logEnricherIntegration.ts
File metadata and controls
141 lines (125 loc) · 4.84 KB
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
/* eslint-disable complexity */
import type { Integration, Log } from '@sentry/core';
import { debug, getCurrentScope, getGlobalScope, getIsolationScope } from '@sentry/core';
import type { ReactNativeClient } from '../client';
import { NATIVE } from '../wrapper';
const INTEGRATION_NAME = 'LogEnricher';
export const logEnricherIntegration = (): Integration => {
return {
name: INTEGRATION_NAME,
setup(client: ReactNativeClient) {
client.on('afterInit', () => {
cacheLogContext().then(
() => {
client.on('beforeCaptureLog', (log: Log) => {
processLog(log, client);
});
},
reason => {
debug.log(reason);
},
);
});
},
};
};
let NativeCache: Record<string, unknown> | undefined = undefined;
/**
* Sets a log attribute if the value exists and the attribute key is not already present.
*
* @param logAttributes - The log attributes object to modify.
* @param key - The attribute key to set.
* @param value - The value to set (only sets if not null/undefined and key not present).
* @param setEvenIfPresent - Whether to set the attribute if it is present. Defaults to true.
*/
function setLogAttribute(
logAttributes: Record<string, unknown>,
key: string,
value: unknown,
setEvenIfPresent = true,
): void {
if (value != null && (!logAttributes[key] || setEvenIfPresent)) {
logAttributes[key] = value;
}
}
async function cacheLogContext(): Promise<void> {
try {
const response = await NATIVE.fetchNativeLogAttributes();
NativeCache = {
...(response?.contexts?.device && {
brand: response.contexts.device?.brand,
model: response.contexts.device?.model,
family: response.contexts.device?.family,
}),
...(response?.contexts?.os && {
os: response.contexts.os.name,
version: response.contexts.os.version,
}),
...(response?.contexts?.release && {
release: response.contexts.release,
}),
};
} catch (e) {
return Promise.reject(`[LOGS]: Failed to prepare attributes from Native Layer: ${e}`);
}
return Promise.resolve();
}
function processLog(log: Log, client: ReactNativeClient): void {
if (NativeCache === undefined) {
return;
}
// Save log.attributes to a new variable
const logAttributes = log.attributes ?? {};
// Apply scope attributes from all active scopes (global, isolation, and current)
// These are applied first so they can be overridden by more specific attributes
const scopeAttributes = collectScopeAttributes();
Object.keys(scopeAttributes).forEach((key: string) => {
setLogAttribute(logAttributes, key, scopeAttributes[key], false);
});
// Use setLogAttribute with the variable instead of direct assignment
setLogAttribute(logAttributes, 'device.brand', NativeCache.brand);
setLogAttribute(logAttributes, 'device.model', NativeCache.model);
setLogAttribute(logAttributes, 'device.family', NativeCache.family);
setLogAttribute(logAttributes, 'os.name', NativeCache.os);
setLogAttribute(logAttributes, 'os.version', NativeCache.version);
setLogAttribute(logAttributes, 'sentry.release', NativeCache.release);
const replay = client.getIntegrationByName<Integration & { getReplayId: () => string | null }>('MobileReplay');
setLogAttribute(logAttributes, 'sentry.replay_id', replay?.getReplayId());
// Set log.attributes to the variable
log.attributes = logAttributes;
}
/**
* Extracts primitive attributes from a scope and merges them into the target object.
* Only string, number, and boolean attribute values are included.
*
* @param scope - The scope to extract attributes from
* @param target - The target object to merge attributes into
*/
function extractScopeAttributes(
scope: ReturnType<typeof getCurrentScope>,
target: Record<string, string | number | boolean>,
): void {
if (scope && typeof scope.getScopeData === 'function') {
const scopeAttrs = scope?.getScopeData?.().attributes || {};
for (const [key, value] of Object.entries(scopeAttrs)) {
if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
target[key] = value;
}
}
}
}
/**
* Collects attributes from all active scopes (global, isolation, and current).
* Only string, number, and boolean attribute values are supported.
* Attributes are merged in order of precedence: global < isolation < current.
*
* @returns A merged object containing all scope attributes.
*/
function collectScopeAttributes(): Record<string, string | number | boolean> {
const attributes: Record<string, string | number | boolean> = {};
// Collect attributes from all scopes in order of precedence
extractScopeAttributes(getGlobalScope(), attributes);
extractScopeAttributes(getIsolationScope(), attributes);
extractScopeAttributes(getCurrentScope(), attributes);
return attributes;
}