@cloudcare/browser-logs 1.1.23 → 1.2.0

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 (58) hide show
  1. package/README.md +5 -4
  2. package/bundle/dataflux-logs.js +1 -1
  3. package/cjs/boot/buildEnv.js +1 -1
  4. package/cjs/boot/logsPublicApi.js +133 -0
  5. package/cjs/boot/startLogs.js +63 -0
  6. package/cjs/domain/assembly.js +74 -0
  7. package/cjs/domain/configuration.js +55 -0
  8. package/cjs/domain/internalContext.js +22 -0
  9. package/cjs/domain/logger.js +28 -36
  10. package/cjs/domain/logsCollection/console/consoleCollection.js +39 -0
  11. package/cjs/domain/logsCollection/logger/loggerCollection.js +51 -0
  12. package/cjs/domain/logsCollection/networkError/networkErrorCollection.js +238 -0
  13. package/cjs/domain/logsCollection/report/reportCollection.js +50 -0
  14. package/cjs/domain/logsCollection/rumtimeError/runtimeErrorCollection.js +46 -0
  15. package/cjs/domain/{loggerSession.js → logsSessionManager.js} +19 -23
  16. package/cjs/entries/main.js +16 -0
  17. package/cjs/index.js +2 -2
  18. package/cjs/transport/startLogsBatch.js +15 -0
  19. package/esm/boot/buildEnv.js +1 -1
  20. package/esm/boot/logsPublicApi.js +122 -0
  21. package/esm/boot/startLogs.js +45 -0
  22. package/esm/domain/assembly.js +59 -0
  23. package/esm/domain/configuration.js +41 -0
  24. package/esm/domain/internalContext.js +15 -0
  25. package/esm/domain/logger.js +26 -36
  26. package/esm/domain/logsCollection/console/consoleCollection.js +31 -0
  27. package/esm/domain/logsCollection/logger/loggerCollection.js +38 -0
  28. package/esm/domain/logsCollection/networkError/networkErrorCollection.js +223 -0
  29. package/esm/domain/logsCollection/report/reportCollection.js +40 -0
  30. package/esm/domain/logsCollection/rumtimeError/runtimeErrorCollection.js +37 -0
  31. package/esm/domain/logsSessionManager.js +45 -0
  32. package/esm/entries/main.js +5 -0
  33. package/esm/index.js +1 -1
  34. package/esm/transport/startLogsBatch.js +7 -0
  35. package/package.json +3 -3
  36. package/src/boot/logsPublicApi.js +140 -0
  37. package/src/boot/startLogs.js +51 -0
  38. package/src/domain/assembly.js +101 -0
  39. package/src/domain/configuration.js +70 -0
  40. package/src/domain/internalContext.js +15 -0
  41. package/src/domain/logger.js +37 -58
  42. package/src/domain/logsCollection/console/consoleCollection.js +36 -0
  43. package/src/domain/logsCollection/logger/loggerCollection.js +48 -0
  44. package/src/domain/logsCollection/networkError/networkErrorCollection.js +260 -0
  45. package/src/domain/logsCollection/report/reportCollection.js +49 -0
  46. package/src/domain/logsCollection/rumtimeError/runtimeErrorCollection.js +35 -0
  47. package/src/domain/logsSessionManager.js +52 -0
  48. package/src/entries/main.js +7 -0
  49. package/src/index.js +1 -1
  50. package/src/transport/startLogsBatch.js +17 -0
  51. package/cjs/boot/log.entry.js +0 -115
  52. package/cjs/boot/log.js +0 -132
  53. package/esm/boot/log.entry.js +0 -87
  54. package/esm/boot/log.js +0 -115
  55. package/esm/domain/loggerSession.js +0 -46
  56. package/src/boot/log.entry.js +0 -119
  57. package/src/boot/log.js +0 -185
  58. package/src/domain/loggerSession.js +0 -56
@@ -0,0 +1,122 @@
1
+ import { BoundedBuffer, createContextManager, makePublicApi, display, deepClone, timeStampNow } from '@cloudcare/browser-core';
2
+ import { validateAndBuildLogsConfiguration } from '../domain/configuration';
3
+ import { Logger } from '../domain/logger';
4
+ export function makeLogsPublicApi(startLogsImpl) {
5
+ var isAlreadyInitialized = false;
6
+ var globalContextManager = createContextManager();
7
+ var customLoggers = {};
8
+
9
+ var getInternalContextStrategy = function getInternalContextStrategy() {
10
+ return undefined;
11
+ };
12
+
13
+ var beforeInitLoggerLog = new BoundedBuffer();
14
+
15
+ var _handleLogStrategy = function handleLogStrategy(logsMessage, logger, savedCommonContext, date) {
16
+ if (typeof savedCommonContext === 'undefined') {
17
+ savedCommonContext = deepClone(getCommonContext());
18
+ }
19
+
20
+ if (typeof date === 'undefined') {
21
+ date = timeStampNow();
22
+ }
23
+
24
+ beforeInitLoggerLog.add(function () {
25
+ return _handleLogStrategy(logsMessage, logger, savedCommonContext, date);
26
+ });
27
+ };
28
+
29
+ var getInitConfigurationStrategy = function getInitConfigurationStrategy() {
30
+ return undefined;
31
+ };
32
+
33
+ var mainLogger = new Logger(function () {
34
+ return _handleLogStrategy.apply(this, arguments);
35
+ });
36
+
37
+ function getCommonContext() {
38
+ return {
39
+ view: {
40
+ referrer: document.referrer,
41
+ url: window.location.href
42
+ },
43
+ context: globalContextManager.get()
44
+ };
45
+ }
46
+
47
+ return makePublicApi({
48
+ logger: mainLogger,
49
+ init: function init(initConfiguration) {
50
+ if (!canInitLogs(initConfiguration)) {
51
+ return;
52
+ }
53
+
54
+ var configuration = validateAndBuildLogsConfiguration(initConfiguration);
55
+
56
+ if (!configuration) {
57
+ return;
58
+ }
59
+
60
+ var _startLogsImpl = startLogsImpl(configuration, getCommonContext, mainLogger);
61
+
62
+ _handleLogStrategy = _startLogsImpl.handleLog;
63
+ getInternalContextStrategy = _startLogsImpl.getInternalContext;
64
+
65
+ getInitConfigurationStrategy = function getInitConfigurationStrategy() {
66
+ return deepClone(initConfiguration);
67
+ };
68
+
69
+ beforeInitLoggerLog.drain();
70
+ isAlreadyInitialized = true;
71
+ },
72
+
73
+ /** @deprecated: use getGlobalContext instead */
74
+ getGlobalContext: globalContextManager.getContext,
75
+
76
+ /** @deprecated: use setGlobalContext instead */
77
+ setGlobalContext: globalContextManager.setContext,
78
+
79
+ /** @deprecated: use setGlobalContextProperty instead */
80
+ setGlobalContextProperty: globalContextManager.setContextProperty,
81
+
82
+ /** @deprecated: use removeGlobalContextProperty instead */
83
+ removeGlobalContextProperty: globalContextManager.removeContextProperty,
84
+ clearGlobalContext: globalContextManager.clearContext,
85
+ createLogger: function createLogger(name, conf) {
86
+ if (typeof conf == 'undefined') {
87
+ conf = {};
88
+ }
89
+
90
+ customLoggers[name] = new Logger(function () {
91
+ return _handleLogStrategy(this.arguments);
92
+ }, name, conf.handler, conf.level, conf.context);
93
+ return customLoggers[name];
94
+ },
95
+ getLogger: function getLogger(name) {
96
+ return customLoggers[name];
97
+ },
98
+ getInitConfiguration: function getInitConfiguration() {
99
+ return getInitConfigurationStrategy();
100
+ },
101
+ getInternalContext: function getInternalContext(startTime) {
102
+ return getInternalContextStrategy(startTime);
103
+ }
104
+ });
105
+
106
+ function canInitLogs(initConfiguration) {
107
+ if (isAlreadyInitialized) {
108
+ if (!initConfiguration.silentMultipleInit) {
109
+ display.error('DD_LOGS is already initialized.');
110
+ }
111
+
112
+ return false;
113
+ }
114
+
115
+ if (!initConfiguration.datakitUrl && !initConfiguration.datakitOrigin) {
116
+ display.error('datakitOrigin is not configured, no RUM data will be collected.');
117
+ return false;
118
+ }
119
+
120
+ return true;
121
+ }
122
+ }
@@ -0,0 +1,45 @@
1
+ import { ErrorSource, LifeCycle, LifeCycleEventType } from '@cloudcare/browser-core';
2
+ import { startLogsSessionManager } from '../domain/logsSessionManager';
3
+ import { startLogsAssembly } from '../domain/assembly';
4
+ import { startConsoleCollection } from '../domain/logsCollection/console/consoleCollection';
5
+ import { startReportCollection } from '../domain/logsCollection/report/reportCollection';
6
+ import { startNetworkErrorCollection } from '../domain/logsCollection/networkError/networkErrorCollection';
7
+ import { startRuntimeErrorCollection } from '../domain/logsCollection/rumtimeError/runtimeErrorCollection';
8
+ import { startLoggerCollection } from '../domain/logsCollection/logger/loggerCollection';
9
+ import { startLogsBatch } from '../transport/startLogsBatch';
10
+ import { StatusType } from '../domain/logger';
11
+ import { startInternalContext } from '../domain/internalContext';
12
+ export function startLogs(configuration, getCommonContext, mainLogger) {
13
+ var lifeCycle = new LifeCycle();
14
+
15
+ var reportError = function reportError(error) {
16
+ lifeCycle.notify(LifeCycleEventType.RAW_LOG_COLLECTED, {
17
+ rawLogsEvent: {
18
+ message: error.message,
19
+ date: error.startClocks.timeStamp,
20
+ error: {
21
+ origin: ErrorSource.AGENT // Todo: Remove in the next major release
22
+
23
+ },
24
+ origin: ErrorSource.AGENT,
25
+ status: StatusType.error
26
+ }
27
+ });
28
+ };
29
+
30
+ startNetworkErrorCollection(configuration, lifeCycle);
31
+ startRuntimeErrorCollection(configuration, lifeCycle);
32
+ startConsoleCollection(configuration, lifeCycle);
33
+ startReportCollection(configuration, lifeCycle);
34
+
35
+ var _startLoggerCollection = startLoggerCollection(lifeCycle);
36
+
37
+ var session = startLogsSessionManager(configuration);
38
+ startLogsAssembly(session, configuration, lifeCycle, getCommonContext, mainLogger, reportError);
39
+ startLogsBatch(configuration, lifeCycle, reportError);
40
+ var internalContext = startInternalContext(session);
41
+ return {
42
+ handleLog: _startLoggerCollection.handleLog,
43
+ getInternalContext: internalContext.get
44
+ };
45
+ }
@@ -0,0 +1,59 @@
1
+ import { ErrorSource, extend2Lev, createEventRateLimiter, getRelativeTime, withSnakeCaseKeys, LifeCycleEventType, deviceInfo, each, RumEventType, isNullUndefinedDefaultValue } from '@cloudcare/browser-core';
2
+ import { STATUSES, HandlerType } from './logger';
3
+ import { isAuthorized } from './logsCollection/logger/loggerCollection';
4
+ export function startLogsAssembly(sessionManager, configuration, lifeCycle, getCommonContext, mainLogger, reportError) {
5
+ var statusWithCustom = STATUSES.concat(['custom']);
6
+ var logRateLimiters = {};
7
+ each(statusWithCustom, function (status) {
8
+ logRateLimiters[status] = createEventRateLimiter(status, configuration.eventRateLimiterThreshold, reportError);
9
+ });
10
+ lifeCycle.subscribe(LifeCycleEventType.RAW_LOG_COLLECTED, function (data) {
11
+ // { rawLogsEvent, messageContext = undefined, savedCommonContext = undefined, logger = mainLogger }
12
+ var rawLogsEvent = data.rawLogsEvent;
13
+ var messageContext = data.messageContext || undefined;
14
+ var savedCommonContext = data.messageContext || undefined;
15
+ var logger = data.logger || mainLogger;
16
+ var startTime = getRelativeTime(rawLogsEvent.date);
17
+ var session = sessionManager.findTrackedSession(startTime);
18
+
19
+ if (!session) {
20
+ return;
21
+ }
22
+
23
+ var commonContext = savedCommonContext || getCommonContext();
24
+ var log = extend2Lev({
25
+ service: configuration.service || 'browser',
26
+ env: configuration.env || '',
27
+ version: configuration.version || '',
28
+ _dd: {
29
+ sdkName: configuration.sdkName,
30
+ sdkVersion: configuration.sdkVersion
31
+ },
32
+ session: {
33
+ id: session.id
34
+ },
35
+ view: commonContext.view,
36
+ device: deviceInfo,
37
+ type: RumEventType.LOGGER
38
+ }, commonContext.context, getRUMInternalContext(startTime), rawLogsEvent, logger.getContext(), messageContext);
39
+
40
+ if ( // Todo: [RUMF-1230] Move this check to the logger collection in the next major release
41
+ !isAuthorized(rawLogsEvent.status, HandlerType.http, logger) || configuration.beforeSend && configuration.beforeSend(log) === false || log.error && log.error.origin !== ErrorSource.AGENT && isNullUndefinedDefaultValue(logRateLimiters[log.status], logRateLimiters['custom']).isLimitReached()) {
42
+ return;
43
+ }
44
+
45
+ lifeCycle.notify(LifeCycleEventType.LOG_COLLECTED, withSnakeCaseKeys(log));
46
+ });
47
+ }
48
+ export function getRUMInternalContext(startTime) {
49
+ return getInternalContextFromRumGlobal(window.DATAFLUX_RUM);
50
+
51
+ function getInternalContextFromRumGlobal(rumGlobal) {
52
+ if (rumGlobal && rumGlobal.getInternalContext) {
53
+ return rumGlobal.getInternalContext(startTime);
54
+ }
55
+ }
56
+ }
57
+ export function resetRUMInternalContext() {
58
+ logsSentBeforeRumInjectionTelemetryAdded = false;
59
+ }
@@ -0,0 +1,41 @@
1
+ import { assign, ONE_KIBI_BYTE, validateAndBuildConfiguration, display, unique, RawReportType, isArray, every, ConsoleApiName, includes, values } from '@cloudcare/browser-core';
2
+ import { buildEnv } from '../boot/buildEnv';
3
+ /**
4
+ * arbitrary value, byte precision not needed
5
+ */
6
+
7
+ export var DEFAULT_REQUEST_ERROR_RESPONSE_LENGTH_LIMIT = 32 * ONE_KIBI_BYTE;
8
+ export function validateAndBuildLogsConfiguration(initConfiguration) {
9
+ var baseConfiguration = validateAndBuildConfiguration(initConfiguration);
10
+ var forwardConsoleLogs = validateAndBuildForwardOption(initConfiguration.forwardConsoleLogs, values(ConsoleApiName), 'Forward Console Logs');
11
+ var forwardReports = validateAndBuildForwardOption(initConfiguration.forwardReports, values(RawReportType), 'Forward Reports');
12
+
13
+ if (!baseConfiguration || !forwardConsoleLogs || !forwardReports) {
14
+ return;
15
+ }
16
+
17
+ if (initConfiguration.forwardErrorsToLogs && !includes(forwardConsoleLogs, ConsoleApiName.error)) {
18
+ forwardConsoleLogs.push(ConsoleApiName.error);
19
+ }
20
+
21
+ return assign({
22
+ forwardErrorsToLogs: initConfiguration.forwardErrorsToLogs !== false,
23
+ forwardConsoleLogs: forwardConsoleLogs,
24
+ forwardReports: forwardReports,
25
+ requestErrorResponseLengthLimit: DEFAULT_REQUEST_ERROR_RESPONSE_LENGTH_LIMIT
26
+ }, baseConfiguration, buildEnv);
27
+ }
28
+ export function validateAndBuildForwardOption(option, allowedValues, label) {
29
+ if (option === undefined) {
30
+ return [];
31
+ }
32
+
33
+ if (!(option === 'all' || isArray(option) && every(option, function (api) {
34
+ return includes(allowedValues, api);
35
+ }))) {
36
+ display.error(label + ' should be "all" or an array with allowed values "' + allowedValues.join('", "') + '"');
37
+ return;
38
+ }
39
+
40
+ return option === 'all' ? allowedValues : unique(option);
41
+ }
@@ -0,0 +1,15 @@
1
+ export function startInternalContext(sessionManager) {
2
+ return {
3
+ get: function get(startTime) {
4
+ var trackedSession = sessionManager.findTrackedSession(startTime);
5
+
6
+ if (trackedSession) {
7
+ return {
8
+ session: {
9
+ id: trackedSession.id
10
+ }
11
+ };
12
+ }
13
+ }
14
+ };
15
+ }
@@ -1,27 +1,19 @@
1
- import { createContextManager, extend2Lev, keys, ErrorSource, jsonStringify } from '@cloudcare/browser-core';
1
+ import { deepClone, assign, extend2Lev, createContextManager, ErrorSource, keys } from '@cloudcare/browser-core';
2
2
  export var StatusType = {
3
3
  debug: 'debug',
4
- critical: 'critical',
5
4
  error: 'error',
6
5
  info: 'info',
7
- warn: 'warning'
8
- }; // eslint-disable-next-line @typescript-eslint/no-redeclare
9
-
10
- var STATUS_PRIORITIES = {
11
- [StatusType.debug]: 0,
12
- [StatusType.info]: 1,
13
- [StatusType.warn]: 2,
14
- [StatusType.error]: 3,
15
- [StatusType.critical]: 4
6
+ warn: 'warn',
7
+ critical: 'critical'
16
8
  };
17
- export var STATUSES = keys(StatusType);
18
9
  export var HandlerType = {
19
10
  console: 'console',
20
11
  http: 'http',
21
12
  silent: 'silent'
22
- }; // eslint-disable-next-line @typescript-eslint/no-redeclare
13
+ };
14
+ export var STATUSES = keys(StatusType); // eslint-disable-next-line @typescript-eslint/no-redeclare
23
15
 
24
- export function Logger(sendLog, handlerType, level, loggerContext) {
16
+ export function Logger(handleLogStrategy, name, handlerType, level, loggerContext) {
25
17
  this.contextManager = createContextManager();
26
18
 
27
19
  if (typeof handlerType === 'undefined') {
@@ -36,10 +28,14 @@ export function Logger(sendLog, handlerType, level, loggerContext) {
36
28
  loggerContext = {};
37
29
  }
38
30
 
39
- this.sendLog = sendLog;
31
+ this.handleLogStrategy = handleLogStrategy;
40
32
  this.handlerType = handlerType;
41
33
  this.level = level;
42
- this.contextManager.set(loggerContext);
34
+ this.contextManager.set(assign({}, loggerContext, name ? {
35
+ logger: {
36
+ name: name
37
+ }
38
+ } : undefined));
43
39
  }
44
40
  Logger.prototype = {
45
41
  log: function log(message, messageContext, status) {
@@ -47,26 +43,11 @@ Logger.prototype = {
47
43
  status = StatusType.info;
48
44
  }
49
45
 
50
- if (STATUS_PRIORITIES[status] >= STATUS_PRIORITIES[this.level]) {
51
- switch (this.handlerType) {
52
- case HandlerType.http:
53
- var logMessage = extend2Lev({
54
- message: message,
55
- status: status
56
- }, {
57
- tags: this.contextManager.get()
58
- }, messageContext);
59
- this.sendLog(logMessage);
60
- break;
61
-
62
- case HandlerType.console:
63
- console.log(status + ' : ' + message, extend2Lev(this.contextManager.get(), messageContext));
64
- break;
65
-
66
- case HandlerType.silent:
67
- break;
68
- }
69
- }
46
+ this.handleLogStrategy({
47
+ message: message,
48
+ context: deepClone(messageContext),
49
+ status: status
50
+ }, this);
70
51
  },
71
52
  debug: function debug(message, messageContext) {
72
53
  this.log(message, messageContext, StatusType.debug);
@@ -91,6 +72,9 @@ Logger.prototype = {
91
72
  setContext: function setContext(context) {
92
73
  this.contextManager.set(context);
93
74
  },
75
+ getContext: function getContext() {
76
+ return this.contextManager.get();
77
+ },
94
78
  addContext: function addContext(key, value) {
95
79
  this.contextManager.add(key, value);
96
80
  },
@@ -100,7 +84,13 @@ Logger.prototype = {
100
84
  setHandler: function setHandler(handler) {
101
85
  this.handlerType = handler;
102
86
  },
87
+ getHandler: function getHandler() {
88
+ return this.handlerType;
89
+ },
103
90
  setLevel: function setLevel(level) {
104
91
  this.level = level;
92
+ },
93
+ getLevel: function getLevel() {
94
+ return this.level;
105
95
  }
106
96
  };
@@ -0,0 +1,31 @@
1
+ import { timeStampNow, ConsoleApiName, ErrorSource, initConsoleObservable, LifeCycleEventType } from '@cloudcare/browser-core';
2
+ import { StatusType } from '../../logger';
3
+ var LogStatusForApi = {
4
+ [ConsoleApiName.log]: StatusType.info,
5
+ [ConsoleApiName.debug]: StatusType.debug,
6
+ [ConsoleApiName.info]: StatusType.info,
7
+ [ConsoleApiName.warn]: StatusType.warn,
8
+ [ConsoleApiName.error]: StatusType.error
9
+ };
10
+ export function startConsoleCollection(configuration, lifeCycle) {
11
+ var consoleSubscription = initConsoleObservable(configuration.forwardConsoleLogs).subscribe(function (log) {
12
+ lifeCycle.notify(LifeCycleEventType.RAW_LOG_COLLECTED, {
13
+ rawLogsEvent: {
14
+ date: timeStampNow(),
15
+ message: log.message,
16
+ origin: ErrorSource.CONSOLE,
17
+ error: log.api === ConsoleApiName.error ? {
18
+ origin: ErrorSource.CONSOLE,
19
+ // Todo: Remove in the next major release
20
+ stack: log.stack
21
+ } : undefined,
22
+ status: LogStatusForApi[log.api]
23
+ }
24
+ });
25
+ });
26
+ return {
27
+ stop: function stop() {
28
+ consoleSubscription.unsubscribe();
29
+ }
30
+ };
31
+ }
@@ -0,0 +1,38 @@
1
+ import { includes, display, extend2Lev, ErrorSource, timeStampNow, LifeCycleEventType, isArray } from '@cloudcare/browser-core';
2
+ import { StatusType, HandlerType } from '../../logger';
3
+ export var STATUS_PRIORITIES = {
4
+ [StatusType.debug]: 0,
5
+ [StatusType.info]: 1,
6
+ [StatusType.warn]: 2,
7
+ [StatusType.error]: 3
8
+ };
9
+ export function startLoggerCollection(lifeCycle) {
10
+ function handleLog(logsMessage, logger, savedCommonContext, savedDate) {
11
+ var messageContext = logsMessage.context;
12
+
13
+ if (isAuthorized(logsMessage.status, HandlerType.console, logger)) {
14
+ display(logsMessage.status, logsMessage.message, extend2Lev(logger.getContext(), messageContext));
15
+ }
16
+
17
+ lifeCycle.notify(LifeCycleEventType.RAW_LOG_COLLECTED, {
18
+ rawLogsEvent: {
19
+ date: savedDate || timeStampNow(),
20
+ message: logsMessage.message,
21
+ status: logsMessage.status,
22
+ origin: ErrorSource.LOGGER
23
+ },
24
+ messageContext: messageContext,
25
+ savedCommonContext: savedCommonContext,
26
+ logger: logger
27
+ });
28
+ }
29
+
30
+ return {
31
+ handleLog: handleLog
32
+ };
33
+ }
34
+ export function isAuthorized(status, handlerType, logger) {
35
+ var loggerHandler = logger.getHandler();
36
+ var sanitizedHandlerType = isArray(loggerHandler) ? loggerHandler : [loggerHandler];
37
+ return STATUS_PRIORITIES[status] >= STATUS_PRIORITIES[logger.getLevel()] && includes(sanitizedHandlerType, handlerType);
38
+ }
@@ -0,0 +1,223 @@
1
+ import { ErrorSource, initXhrObservable, RequestType, initFetchObservable, computeStackTrace, toStackTraceString, noop, each, LifeCycleEventType, getStatusGroup, urlParse, replaceNumberCharByPath } from '@cloudcare/browser-core';
2
+ import { StatusType } from '../../logger';
3
+ export function startNetworkErrorCollection(configuration, lifeCycle) {
4
+ if (!configuration.forwardErrorsToLogs) {
5
+ return {
6
+ stop: noop
7
+ };
8
+ }
9
+
10
+ var xhrSubscription = initXhrObservable().subscribe(function (context) {
11
+ if (context.state === 'complete') {
12
+ handleCompleteRequest(RequestType.XHR, context, configuration);
13
+ }
14
+ });
15
+ var fetchSubscription = initFetchObservable().subscribe(function (context) {
16
+ if (context.state === 'complete') {
17
+ handleCompleteRequest(RequestType.FETCH, context, configuration);
18
+ }
19
+ });
20
+
21
+ function handleCompleteRequest(type, request) {
22
+ if (!configuration.isIntakeUrl(request.url) && (isRejected(request) || isServerError(request) || configuration.isServerError(request))) {
23
+ if ('xhr' in request) {
24
+ computeXhrResponseData(request.xhr, configuration, onResponseDataAvailable);
25
+ } else if (request.response) {
26
+ computeFetchResponseText(request.response, configuration, onResponseDataAvailable);
27
+ } else if (request.error) {
28
+ computeFetchErrorText(request.error, configuration, onResponseDataAvailable);
29
+ }
30
+ }
31
+
32
+ function onResponseDataAvailable(responseData) {
33
+ var urlObj = urlParse(request.url).getParse();
34
+ lifeCycle.notify(LifeCycleEventType.RAW_LOG_COLLECTED, {
35
+ rawLogsEvent: {
36
+ message: format(type) + ' error ' + request.method + ' ' + request.url,
37
+ date: request.startClocks.timeStamp,
38
+ error: {
39
+ origin: ErrorSource.NETWORK,
40
+ // Todo: Remove in the next major release
41
+ stack: responseData || 'Failed to load'
42
+ },
43
+ http: {
44
+ method: request.method,
45
+ // Cast resource method because of case mismatch cf issue RUMF-1152
46
+ status_code: request.status,
47
+ url: request.url,
48
+ statusGroup: getStatusGroup(request.status),
49
+ urlHost: urlObj.Host,
50
+ urlPath: urlObj.Path,
51
+ urlPathGroup: replaceNumberCharByPath(urlObj.Path)
52
+ },
53
+ status: StatusType.error,
54
+ origin: ErrorSource.NETWORK
55
+ }
56
+ });
57
+ }
58
+ }
59
+
60
+ return {
61
+ stop: function stop() {
62
+ xhrSubscription.unsubscribe();
63
+ fetchSubscription.unsubscribe();
64
+ }
65
+ };
66
+ } // TODO: ideally, computeXhrResponseData should always call the callback with a string instead of
67
+ // `unknown`. But to keep backward compatibility, in the case of XHR with a `responseType` different
68
+ // than "text", the response data should be whatever `xhr.response` is. This is a bit confusing as
69
+ // Logs event 'stack' is expected to be a string. This should be changed in a future major version
70
+ // as it could be a breaking change.
71
+
72
+ export function computeXhrResponseData(xhr, configuration, callback) {
73
+ if (typeof xhr.response === 'string') {
74
+ callback(truncateResponseText(xhr.response, configuration));
75
+ } else {
76
+ callback(xhr.response);
77
+ }
78
+ }
79
+ export function computeFetchErrorText(error, configuration, callback) {
80
+ callback(truncateResponseText(toStackTraceString(computeStackTrace(error)), configuration));
81
+ }
82
+ export function computeFetchResponseText(response, configuration, callback) {
83
+ if (!window.TextDecoder) {
84
+ // If the browser doesn't support TextDecoder, let's read the whole response then truncate it.
85
+ //
86
+ // This should only be the case on early versions of Edge (before they migrated to Chromium).
87
+ // Even if it could be possible to implement a workaround for the missing TextDecoder API (using
88
+ // a Blob and FileReader), we found another issue preventing us from reading only the first
89
+ // bytes from the response: contrary to other browsers, when reading from the cloned response,
90
+ // if the original response gets canceled, the cloned response is also canceled and we can't
91
+ // know about it. In the following illustration, the promise returned by `reader.read()` may
92
+ // never be fulfilled:
93
+ //
94
+ // fetch('/').then((response) => {
95
+ // const reader = response.clone().body.getReader()
96
+ // readMore()
97
+ // function readMore() {
98
+ // reader.read().then(
99
+ // (result) => {
100
+ // if (result.done) {
101
+ // console.log('done')
102
+ // } else {
103
+ // readMore()
104
+ // }
105
+ // },
106
+ // () => console.log('error')
107
+ // )
108
+ // }
109
+ // response.body.getReader().cancel()
110
+ // })
111
+ response.clone().text().then(function (text) {
112
+ return callback(truncateResponseText(text, configuration));
113
+ }, function (error) {
114
+ return callback('Unable to retrieve response: ' + error);
115
+ });
116
+ } else if (!response.body) {
117
+ callback();
118
+ } else {
119
+ truncateResponseStream(response.clone().body, configuration.requestErrorResponseLengthLimit, function (error, responseText) {
120
+ if (error) {
121
+ callback('Unable to retrieve response: ' + error);
122
+ } else {
123
+ callback(responseText);
124
+ }
125
+ });
126
+ }
127
+ }
128
+
129
+ function isRejected(request) {
130
+ return request.status === 0 && request.responseType !== 'opaque';
131
+ }
132
+
133
+ function isServerError(request) {
134
+ return request.status >= 500;
135
+ }
136
+
137
+ function truncateResponseText(responseText, configuration) {
138
+ if (responseText.length > configuration.requestErrorResponseLengthLimit) {
139
+ return responseText.substring(0, configuration.requestErrorResponseLengthLimit) + '...';
140
+ }
141
+
142
+ return responseText;
143
+ }
144
+
145
+ function format(type) {
146
+ if (RequestType.XHR === type) {
147
+ return 'XHR';
148
+ }
149
+
150
+ return 'Fetch';
151
+ }
152
+
153
+ function truncateResponseStream(stream, limit, callback) {
154
+ readLimitedAmountOfBytes(stream, limit, function (error, bytes, limitExceeded) {
155
+ if (error) {
156
+ callback(error);
157
+ } else {
158
+ var responseText = new TextDecoder().decode(bytes);
159
+
160
+ if (limitExceeded) {
161
+ responseText += '...';
162
+ }
163
+
164
+ callback(undefined, responseText);
165
+ }
166
+ });
167
+ }
168
+ /**
169
+ * Read bytes from a ReadableStream until at least `limit` bytes have been read (or until the end of
170
+ * the stream). The callback is invoked with the at most `limit` bytes, and indicates that the limit
171
+ * has been exceeded if more bytes were available.
172
+ */
173
+
174
+
175
+ function readLimitedAmountOfBytes(stream, limit, callback) {
176
+ var reader = stream.getReader();
177
+ var chunks = [];
178
+ var readBytesCount = 0;
179
+ readMore();
180
+
181
+ function readMore() {
182
+ reader.read().then(function (result) {
183
+ if (result.done) {
184
+ onDone();
185
+ return;
186
+ }
187
+
188
+ chunks.push(result.value);
189
+ readBytesCount += result.value.length;
190
+
191
+ if (readBytesCount > limit) {
192
+ onDone();
193
+ } else {
194
+ readMore();
195
+ }
196
+ }, function (error) {
197
+ return callback(error);
198
+ });
199
+ }
200
+
201
+ function onDone() {
202
+ reader.cancel().catch( // we don't care if cancel fails, but we still need to catch the error to avoid reporting it
203
+ // as an unhandled rejection
204
+ noop);
205
+ var completeBuffer;
206
+
207
+ if (chunks.length === 1) {
208
+ // optim: if the response is small enough to fit in a single buffer (provided by the browser), just
209
+ // use it directly.
210
+ completeBuffer = chunks[0];
211
+ } else {
212
+ // else, we need to copy buffers into a larger buffer to concatenate them.
213
+ completeBuffer = new Uint8Array(readBytesCount);
214
+ var offset = 0;
215
+ each(chunks, function (chunk) {
216
+ completeBuffer.set(chunk, offset);
217
+ offset += chunk.length;
218
+ });
219
+ }
220
+
221
+ callback(undefined, completeBuffer.slice(0, limit), completeBuffer.length > limit);
222
+ }
223
+ }