@kubesense/kubesense-browser-logs 0.0.0-stage → 2.0.0-beta.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 (166) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +90 -2
  3. package/bundle/kubesense-logs.js +1 -0
  4. package/cjs/boot/logsPublicApi.d.ts +231 -0
  5. package/cjs/boot/logsPublicApi.js +75 -0
  6. package/cjs/boot/logsPublicApi.js.map +1 -0
  7. package/cjs/boot/preStartLogs.d.ts +8 -0
  8. package/cjs/boot/preStartLogs.js +105 -0
  9. package/cjs/boot/preStartLogs.js.map +1 -0
  10. package/cjs/boot/startLogs.d.ts +35 -0
  11. package/cjs/boot/startLogs.js +54 -0
  12. package/cjs/boot/startLogs.js.map +1 -0
  13. package/cjs/domain/assembly.d.ts +5 -0
  14. package/cjs/domain/assembly.js +35 -0
  15. package/cjs/domain/assembly.js.map +1 -0
  16. package/cjs/domain/configuration.d.ts +306 -0
  17. package/cjs/domain/configuration.js +86 -0
  18. package/cjs/domain/configuration.js.map +1 -0
  19. package/cjs/domain/console/consoleCollection.d.ts +20 -0
  20. package/cjs/domain/console/consoleCollection.js +37 -0
  21. package/cjs/domain/console/consoleCollection.js.map +1 -0
  22. package/cjs/domain/contexts/commonContext.d.ts +2 -0
  23. package/cjs/domain/contexts/commonContext.js +13 -0
  24. package/cjs/domain/contexts/commonContext.js.map +1 -0
  25. package/cjs/domain/contexts/internalContext.d.ts +7 -0
  26. package/cjs/domain/contexts/internalContext.js +11 -0
  27. package/cjs/domain/contexts/internalContext.js.map +1 -0
  28. package/cjs/domain/contexts/rumInternalContext.d.ts +2 -0
  29. package/cjs/domain/contexts/rumInternalContext.js +32 -0
  30. package/cjs/domain/contexts/rumInternalContext.js.map +1 -0
  31. package/cjs/domain/contexts/sessionContext.d.ts +4 -0
  32. package/cjs/domain/contexts/sessionContext.js +20 -0
  33. package/cjs/domain/contexts/sessionContext.js.map +1 -0
  34. package/cjs/domain/contexts/trackingConsentContext.d.ts +3 -0
  35. package/cjs/domain/contexts/trackingConsentContext.js +14 -0
  36. package/cjs/domain/contexts/trackingConsentContext.js.map +1 -0
  37. package/cjs/domain/createErrorFieldFromRawError.d.ts +10 -0
  38. package/cjs/domain/createErrorFieldFromRawError.js +15 -0
  39. package/cjs/domain/createErrorFieldFromRawError.js.map +1 -0
  40. package/cjs/domain/hooks.d.ts +28 -0
  41. package/cjs/domain/hooks.js +13 -0
  42. package/cjs/domain/hooks.js.map +1 -0
  43. package/cjs/domain/lifeCycle.d.ts +24 -0
  44. package/cjs/domain/lifeCycle.js +6 -0
  45. package/cjs/domain/lifeCycle.js.map +1 -0
  46. package/cjs/domain/logger/isAuthorized.d.ts +17 -0
  47. package/cjs/domain/logger/isAuthorized.js +34 -0
  48. package/cjs/domain/logger/isAuthorized.js.map +1 -0
  49. package/cjs/domain/logger/loggerCollection.d.ts +7 -0
  50. package/cjs/domain/logger/loggerCollection.js +47 -0
  51. package/cjs/domain/logger/loggerCollection.js.map +1 -0
  52. package/cjs/domain/logger.d.ts +48 -0
  53. package/cjs/domain/logger.js +112 -0
  54. package/cjs/domain/logger.js.map +1 -0
  55. package/cjs/domain/networkError/networkErrorCollection.d.ts +9 -0
  56. package/cjs/domain/networkError/networkErrorCollection.js +57 -0
  57. package/cjs/domain/networkError/networkErrorCollection.js.map +1 -0
  58. package/cjs/domain/plugins.d.ts +49 -0
  59. package/cjs/domain/plugins.js +12 -0
  60. package/cjs/domain/plugins.js.map +1 -0
  61. package/cjs/domain/remoteSdkConfiguration.d.ts +26 -0
  62. package/cjs/domain/remoteSdkConfiguration.js +67 -0
  63. package/cjs/domain/remoteSdkConfiguration.js.map +1 -0
  64. package/cjs/domain/report/reportCollection.d.ts +13 -0
  65. package/cjs/domain/report/reportCollection.js +30 -0
  66. package/cjs/domain/report/reportCollection.js.map +1 -0
  67. package/cjs/domain/reportError.d.ts +2 -0
  68. package/cjs/domain/reportError.js +20 -0
  69. package/cjs/domain/reportError.js.map +1 -0
  70. package/cjs/domain/runtimeError/runtimeErrorCollection.d.ts +14 -0
  71. package/cjs/domain/runtimeError/runtimeErrorCollection.js +29 -0
  72. package/cjs/domain/runtimeError/runtimeErrorCollection.js.map +1 -0
  73. package/cjs/domainContext.types.d.ts +11 -0
  74. package/cjs/entries/main.d.ts +33 -0
  75. package/cjs/entries/main.js +31 -0
  76. package/cjs/entries/main.js.map +1 -0
  77. package/cjs/logsEvent.types.d.ts +192 -0
  78. package/cjs/rawLogsEvent.types.d.ts +59 -0
  79. package/cjs/transport/startLogsBatch.d.ts +4 -0
  80. package/cjs/transport/startLogsBatch.js +22 -0
  81. package/cjs/transport/startLogsBatch.js.map +1 -0
  82. package/cjs/transport/startLogsBridge.d.ts +2 -0
  83. package/cjs/transport/startLogsBridge.js +13 -0
  84. package/cjs/transport/startLogsBridge.js.map +1 -0
  85. package/esm/boot/logsPublicApi.mjs +75 -0
  86. package/esm/boot/logsPublicApi.mjs.map +1 -0
  87. package/esm/boot/preStartLogs.mjs +105 -0
  88. package/esm/boot/preStartLogs.mjs.map +1 -0
  89. package/esm/boot/startLogs.mjs +54 -0
  90. package/esm/boot/startLogs.mjs.map +1 -0
  91. package/esm/domain/assembly.mjs +35 -0
  92. package/esm/domain/assembly.mjs.map +1 -0
  93. package/esm/domain/configuration.mjs +84 -0
  94. package/esm/domain/configuration.mjs.map +1 -0
  95. package/esm/domain/console/consoleCollection.mjs +36 -0
  96. package/esm/domain/console/consoleCollection.mjs.map +1 -0
  97. package/esm/domain/contexts/commonContext.mjs +13 -0
  98. package/esm/domain/contexts/commonContext.mjs.map +1 -0
  99. package/esm/domain/contexts/internalContext.mjs +11 -0
  100. package/esm/domain/contexts/internalContext.mjs.map +1 -0
  101. package/esm/domain/contexts/rumInternalContext.mjs +32 -0
  102. package/esm/domain/contexts/rumInternalContext.mjs.map +1 -0
  103. package/esm/domain/contexts/sessionContext.mjs +20 -0
  104. package/esm/domain/contexts/sessionContext.mjs.map +1 -0
  105. package/esm/domain/contexts/trackingConsentContext.mjs +14 -0
  106. package/esm/domain/contexts/trackingConsentContext.mjs.map +1 -0
  107. package/esm/domain/createErrorFieldFromRawError.mjs +15 -0
  108. package/esm/domain/createErrorFieldFromRawError.mjs.map +1 -0
  109. package/esm/domain/hooks.mjs +13 -0
  110. package/esm/domain/hooks.mjs.map +1 -0
  111. package/esm/domain/lifeCycle.mjs +7 -0
  112. package/esm/domain/lifeCycle.mjs.map +1 -0
  113. package/esm/domain/logger/isAuthorized.mjs +32 -0
  114. package/esm/domain/logger/isAuthorized.mjs.map +1 -0
  115. package/esm/domain/logger/loggerCollection.mjs +47 -0
  116. package/esm/domain/logger/loggerCollection.mjs.map +1 -0
  117. package/esm/domain/logger.mjs +110 -0
  118. package/esm/domain/logger.mjs.map +1 -0
  119. package/esm/domain/networkError/networkErrorCollection.mjs +56 -0
  120. package/esm/domain/networkError/networkErrorCollection.mjs.map +1 -0
  121. package/esm/domain/plugins.mjs +12 -0
  122. package/esm/domain/plugins.mjs.map +1 -0
  123. package/esm/domain/remoteSdkConfiguration.mjs +65 -0
  124. package/esm/domain/remoteSdkConfiguration.mjs.map +1 -0
  125. package/esm/domain/report/reportCollection.mjs +30 -0
  126. package/esm/domain/report/reportCollection.mjs.map +1 -0
  127. package/esm/domain/reportError.mjs +20 -0
  128. package/esm/domain/reportError.mjs.map +1 -0
  129. package/esm/domain/runtimeError/runtimeErrorCollection.mjs +29 -0
  130. package/esm/domain/runtimeError/runtimeErrorCollection.mjs.map +1 -0
  131. package/esm/entries/main.mjs +27 -0
  132. package/esm/entries/main.mjs.map +1 -0
  133. package/esm/transport/startLogsBatch.mjs +22 -0
  134. package/esm/transport/startLogsBatch.mjs.map +1 -0
  135. package/esm/transport/startLogsBridge.mjs +13 -0
  136. package/esm/transport/startLogsBridge.mjs.map +1 -0
  137. package/package.json +53 -3
  138. package/src/boot/logsPublicApi.ts +427 -0
  139. package/src/boot/preStartLogs.ts +192 -0
  140. package/src/boot/startLogs.ts +83 -0
  141. package/src/domain/assembly.ts +73 -0
  142. package/src/domain/configuration.ts +176 -0
  143. package/src/domain/console/consoleCollection.ts +58 -0
  144. package/src/domain/contexts/commonContext.ts +15 -0
  145. package/src/domain/contexts/internalContext.ts +19 -0
  146. package/src/domain/contexts/rumInternalContext.ts +60 -0
  147. package/src/domain/contexts/sessionContext.ts +34 -0
  148. package/src/domain/contexts/trackingConsentContext.ts +18 -0
  149. package/src/domain/createErrorFieldFromRawError.ts +22 -0
  150. package/src/domain/hooks.ts +40 -0
  151. package/src/domain/lifeCycle.ts +25 -0
  152. package/src/domain/logger/isAuthorized.ts +33 -0
  153. package/src/domain/logger/loggerCollection.ts +69 -0
  154. package/src/domain/logger.ts +202 -0
  155. package/src/domain/networkError/networkErrorCollection.ts +104 -0
  156. package/src/domain/plugins.ts +72 -0
  157. package/src/domain/remoteSdkConfiguration.ts +105 -0
  158. package/src/domain/report/reportCollection.ts +47 -0
  159. package/src/domain/reportError.ts +20 -0
  160. package/src/domain/runtimeError/runtimeErrorCollection.ts +51 -0
  161. package/src/domainContext.types.ts +21 -0
  162. package/src/entries/main.ts +65 -0
  163. package/src/logsEvent.types.ts +198 -0
  164. package/src/rawLogsEvent.types.ts +74 -0
  165. package/src/transport/startLogsBatch.ts +29 -0
  166. package/src/transport/startLogsBridge.ts +13 -0
@@ -0,0 +1,69 @@
1
+ import { timeStampNow } from '@kubesense/kubesense-js-core/time'
2
+ import type { TimeStamp } from '@kubesense/kubesense-js-core/time'
3
+ import type { Context } from '@kubesense/kubesense-browser-core'
4
+ import { ErrorSource } from '@kubesense/kubesense-browser-core'
5
+ import { originalConsoleMethods, globalConsole, ConsoleApiName, combine } from '@kubesense/kubesense-js-core/util'
6
+ import type { CommonContext, RawLogsEvent } from '../../rawLogsEvent.types'
7
+ import type { LifeCycle, RawLogsEventCollectedData } from '../lifeCycle'
8
+ import { LifeCycleEventType } from '../lifeCycle'
9
+ import type { Logger, LogsMessage } from '../logger'
10
+ import { HandlerType } from '../logger'
11
+ import { isAuthorized, StatusType } from './isAuthorized'
12
+
13
+ export function startLoggerCollection(lifeCycle: LifeCycle) {
14
+ function handleLog(
15
+ logsMessage: LogsMessage,
16
+ logger: Logger,
17
+ handlingStack?: string,
18
+ savedCommonContext?: CommonContext,
19
+ savedDate?: TimeStamp
20
+ ) {
21
+ const messageContext = combine(logger.getContext(), logsMessage.context)
22
+
23
+ if (isAuthorized(logsMessage.status, HandlerType.console, logger)) {
24
+ displayInConsole(logsMessage, messageContext)
25
+ }
26
+
27
+ if (isAuthorized(logsMessage.status, HandlerType.http, logger)) {
28
+ const rawLogEventData: RawLogsEventCollectedData<RawLogsEvent> = {
29
+ rawLogsEvent: combine(
30
+ {
31
+ date: savedDate || timeStampNow(),
32
+ message: logsMessage.message,
33
+ status: logsMessage.status,
34
+ origin: ErrorSource.LOGGER,
35
+ _kubesense: { debug_ids: logsMessage.debugIds },
36
+ },
37
+ messageContext
38
+ ),
39
+ savedCommonContext,
40
+ ktags: logger.getTags(),
41
+ }
42
+
43
+ if (handlingStack) {
44
+ rawLogEventData.domainContext = { handlingStack }
45
+ }
46
+
47
+ lifeCycle.notify(LifeCycleEventType.RAW_LOG_COLLECTED, rawLogEventData)
48
+ }
49
+ }
50
+
51
+ return {
52
+ handleLog,
53
+ }
54
+ }
55
+ const loggerToConsoleApiName: { [key in StatusType]: ConsoleApiName } = {
56
+ [StatusType.ok]: ConsoleApiName.debug,
57
+ [StatusType.debug]: ConsoleApiName.debug,
58
+ [StatusType.info]: ConsoleApiName.info,
59
+ [StatusType.notice]: ConsoleApiName.info,
60
+ [StatusType.warn]: ConsoleApiName.warn,
61
+ [StatusType.error]: ConsoleApiName.error,
62
+ [StatusType.critical]: ConsoleApiName.error,
63
+ [StatusType.alert]: ConsoleApiName.error,
64
+ [StatusType.emerg]: ConsoleApiName.error,
65
+ }
66
+
67
+ function displayInConsole({ status, message }: LogsMessage, messageContext: Context | undefined) {
68
+ originalConsoleMethods[loggerToConsoleApiName[status]].call(globalConsole, message, messageContext)
69
+ }
@@ -0,0 +1,202 @@
1
+ import type { Context, ContextManager, DebugIdEntry } from '@kubesense/kubesense-browser-core'
2
+ import { clocksNow } from '@kubesense/kubesense-js-core/time'
3
+ import { combine } from '@kubesense/kubesense-js-core/util'
4
+ import {
5
+ computeRawError,
6
+ ErrorHandling,
7
+ createContextManager,
8
+ ErrorSource,
9
+ sanitize,
10
+ NonErrorPrefix,
11
+ createHandlingStack,
12
+ buildTag,
13
+ sanitizeTag,
14
+ } from '@kubesense/kubesense-browser-core'
15
+ import { callMonitored } from '@kubesense/kubesense-js-core/monitor'
16
+
17
+ import { isAuthorized, StatusType } from './logger/isAuthorized'
18
+ import { createErrorFieldFromRawError } from './createErrorFieldFromRawError'
19
+
20
+ export interface LogsMessage {
21
+ message: string
22
+ status: StatusType
23
+ context?: Context
24
+ debugIds?: DebugIdEntry[]
25
+ }
26
+
27
+ export const HandlerType = {
28
+ console: 'console',
29
+ http: 'http',
30
+ silent: 'silent',
31
+ } as const
32
+
33
+ export type HandlerType = (typeof HandlerType)[keyof typeof HandlerType]
34
+ export const STATUSES = Object.keys(StatusType) as StatusType[]
35
+
36
+ // note: it is safe to merge declarations as long as the methods are actually defined on the prototype
37
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging, no-restricted-syntax
38
+ export class Logger {
39
+ private contextManager: ContextManager
40
+
41
+ // Tags are formatted as either `key:value` or `key_only`
42
+ private tags: string[]
43
+
44
+ constructor(
45
+ private handleLogStrategy: (logsMessage: LogsMessage, logger: Logger, handlingStack?: string) => void,
46
+ name?: string,
47
+ private handlerType: HandlerType | HandlerType[] = HandlerType.http,
48
+ private level: StatusType = StatusType.debug,
49
+ loggerContext: object = {}
50
+ ) {
51
+ this.contextManager = createContextManager('logger')
52
+ this.tags = []
53
+ this.contextManager.setContext(loggerContext)
54
+ if (name) {
55
+ this.contextManager.setContextProperty('logger', { name })
56
+ }
57
+ }
58
+
59
+ logImplementation(
60
+ message: string,
61
+ messageContext?: object,
62
+ status: StatusType = StatusType.info,
63
+ error?: Error,
64
+ handlingStack?: string
65
+ ) {
66
+ callMonitored(() => {
67
+ const sanitizedMessageContext = sanitize(messageContext) as Context
68
+ let context: Context
69
+ let debugIds: DebugIdEntry[] | undefined
70
+
71
+ if (error !== undefined && error !== null) {
72
+ const rawError = computeRawError({
73
+ originalError: error,
74
+ nonErrorPrefix: NonErrorPrefix.PROVIDED,
75
+ source: ErrorSource.LOGGER,
76
+ handling: ErrorHandling.HANDLED,
77
+ startClocks: clocksNow(),
78
+ })
79
+
80
+ debugIds = rawError.debugIds
81
+ context = combine(
82
+ {
83
+ error: createErrorFieldFromRawError(rawError, { includeMessage: true }),
84
+ },
85
+ rawError.context,
86
+ sanitizedMessageContext
87
+ )
88
+ } else {
89
+ context = sanitizedMessageContext
90
+ }
91
+
92
+ this.handleLogStrategy(
93
+ {
94
+ message: sanitize(message)!,
95
+ context,
96
+ status,
97
+ ...(debugIds ? { debugIds } : {}),
98
+ },
99
+ this,
100
+ handlingStack
101
+ )
102
+ })
103
+ }
104
+
105
+ log(message: string, messageContext?: object, status: StatusType = StatusType.info, error?: Error) {
106
+ let handlingStack: string | undefined
107
+
108
+ if (isAuthorized(status, HandlerType.http, this)) {
109
+ handlingStack = createHandlingStack('log')
110
+ }
111
+
112
+ this.logImplementation(message, messageContext, status, error, handlingStack)
113
+ }
114
+
115
+ setContext(context: object) {
116
+ this.contextManager.setContext(context)
117
+ }
118
+
119
+ getContext() {
120
+ return this.contextManager.getContext()
121
+ }
122
+
123
+ setContextProperty(key: string, value: any) {
124
+ this.contextManager.setContextProperty(key, value)
125
+ }
126
+
127
+ removeContextProperty(key: string) {
128
+ this.contextManager.removeContextProperty(key)
129
+ }
130
+
131
+ clearContext() {
132
+ this.contextManager.clearContext()
133
+ }
134
+
135
+ addTag(key: string, value?: string) {
136
+ this.tags.push(buildTag(key, value))
137
+ }
138
+
139
+ removeTagsWithKey(key: string) {
140
+ const sanitizedKey = sanitizeTag(key)
141
+
142
+ this.tags = this.tags.filter((tag) => tag !== sanitizedKey && !tag.startsWith(`${sanitizedKey}:`))
143
+ }
144
+
145
+ getTags() {
146
+ return this.tags.slice()
147
+ }
148
+
149
+ setHandler(handler: HandlerType | HandlerType[]) {
150
+ this.handlerType = handler
151
+ }
152
+
153
+ getHandler() {
154
+ return this.handlerType
155
+ }
156
+
157
+ setLevel(level: StatusType) {
158
+ this.level = level
159
+ }
160
+
161
+ getLevel() {
162
+ return this.level
163
+ }
164
+ }
165
+
166
+ /* eslint-disable local-rules/disallow-side-effects */
167
+ Logger.prototype.ok = createLoggerMethod(StatusType.ok)
168
+ Logger.prototype.debug = createLoggerMethod(StatusType.debug)
169
+ Logger.prototype.info = createLoggerMethod(StatusType.info)
170
+ Logger.prototype.notice = createLoggerMethod(StatusType.notice)
171
+ Logger.prototype.warn = createLoggerMethod(StatusType.warn)
172
+ Logger.prototype.error = createLoggerMethod(StatusType.error)
173
+ Logger.prototype.critical = createLoggerMethod(StatusType.critical)
174
+ Logger.prototype.alert = createLoggerMethod(StatusType.alert)
175
+ Logger.prototype.emerg = createLoggerMethod(StatusType.emerg)
176
+ /* eslint-enable local-rules/disallow-side-effects */
177
+
178
+ // note: it is safe to merge declarations as long as the methods are actually defined on the prototype
179
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging
180
+ export interface Logger {
181
+ ok(message: string, messageContext?: object, error?: Error): void
182
+ debug(message: string, messageContext?: object, error?: Error): void
183
+ info(message: string, messageContext?: object, error?: Error): void
184
+ notice(message: string, messageContext?: object, error?: Error): void
185
+ warn(message: string, messageContext?: object, error?: Error): void
186
+ error(message: string, messageContext?: object, error?: Error): void
187
+ critical(message: string, messageContext?: object, error?: Error): void
188
+ alert(message: string, messageContext?: object, error?: Error): void
189
+ emerg(message: string, messageContext?: object, error?: Error): void
190
+ }
191
+
192
+ function createLoggerMethod(status: StatusType) {
193
+ return function (this: Logger, message: string, messageContext?: object, error?: Error) {
194
+ let handlingStack: string | undefined
195
+
196
+ if (isAuthorized(status, HandlerType.http, this)) {
197
+ handlingStack = createHandlingStack('log')
198
+ }
199
+
200
+ this.logImplementation(message, messageContext, status, error, handlingStack)
201
+ }
202
+ }
@@ -0,0 +1,104 @@
1
+ import type {
2
+ FetchResolveContext,
3
+ XhrCompleteContext,
4
+ Observable,
5
+ BufferedData,
6
+ } from '@kubesense/kubesense-browser-core'
7
+ import {
8
+ BufferedDataType,
9
+ ErrorSource,
10
+ RequestType,
11
+ initFetchObservable,
12
+ computeStackTrace,
13
+ toStackTraceString,
14
+ noop,
15
+ isServerError,
16
+ ResponseBodyAction,
17
+ safeTruncate,
18
+ ONE_KIBI_BYTE,
19
+ buildDebugIdByUrl,
20
+ getStackTraceUrls,
21
+ } from '@kubesense/kubesense-browser-core'
22
+ import { isIntakeUrl } from '@kubesense/kubesense-js-core/transport'
23
+ import type { LogsConfiguration } from '../configuration'
24
+
25
+ /** arbitrary value, byte precision not needed */
26
+ export const DEFAULT_REQUEST_ERROR_RESPONSE_LENGTH_LIMIT = 32 * ONE_KIBI_BYTE
27
+ import type { LifeCycle } from '../lifeCycle'
28
+ import type { LogsEventDomainContext } from '../../domainContext.types'
29
+ import { LifeCycleEventType } from '../lifeCycle'
30
+ import { StatusType } from '../logger/isAuthorized'
31
+
32
+ export function startNetworkErrorCollection(
33
+ configuration: LogsConfiguration,
34
+ lifeCycle: LifeCycle,
35
+ bufferedDataObservable: Observable<BufferedData>
36
+ ) {
37
+ if (!configuration.forwardErrorsToLogs) {
38
+ return { stop: noop }
39
+ }
40
+
41
+ // Register responseBodyAction getter (no subscription needed)
42
+ initFetchObservable({
43
+ responseBodyAction: (context) => (isNetworkError(context) ? ResponseBodyAction.COLLECT : ResponseBodyAction.IGNORE),
44
+ })
45
+
46
+ const subscription = bufferedDataObservable.subscribe(({ data, type }) => {
47
+ if (type === BufferedDataType.FETCH && data.state === 'resolve') {
48
+ handleResponse(RequestType.FETCH, data)
49
+ } else if (type === BufferedDataType.XHR && data.state === 'complete') {
50
+ handleResponse(RequestType.XHR, data)
51
+ }
52
+ })
53
+
54
+ function isNetworkError(request: XhrCompleteContext | FetchResolveContext) {
55
+ return !isIntakeUrl(request.url) && !request.isAborted && (isRejected(request) || isServerError(request.status))
56
+ }
57
+
58
+ function handleResponse(type: RequestType, request: XhrCompleteContext | FetchResolveContext) {
59
+ if (!isNetworkError(request)) {
60
+ return
61
+ }
62
+ const stackTrace = 'error' in request && request.error ? computeStackTrace(request.error) : undefined
63
+ const stack = stackTrace ? toStackTraceString(stackTrace) : request.responseBody || 'Failed to load'
64
+ const debugIds = stackTrace ? buildDebugIdByUrl(getStackTraceUrls(stackTrace)) : undefined
65
+
66
+ const domainContext: LogsEventDomainContext<typeof ErrorSource.NETWORK> = {
67
+ handlingStack: request.handlingStack,
68
+ }
69
+
70
+ lifeCycle.notify(LifeCycleEventType.RAW_LOG_COLLECTED, {
71
+ rawLogsEvent: {
72
+ message: `${format(type)} error ${request.method} ${request.url}`,
73
+ date: request.startClocks.timeStamp,
74
+ error: {
75
+ stack: safeTruncate(stack, DEFAULT_REQUEST_ERROR_RESPONSE_LENGTH_LIMIT, '...'),
76
+ // We don't know if the error was handled or not, so we set it to undefined
77
+ handling: undefined,
78
+ },
79
+ _kubesense: { debug_ids: debugIds },
80
+ http: {
81
+ method: request.method as any, // Cast resource method because of case mismatch cf issue RUMF-1152
82
+ status_code: request.status,
83
+ url: request.url,
84
+ },
85
+ status: StatusType.error,
86
+ origin: ErrorSource.NETWORK,
87
+ },
88
+ domainContext,
89
+ })
90
+ }
91
+
92
+ return { stop: () => subscription.unsubscribe() }
93
+ }
94
+
95
+ function isRejected(request: { status: number; responseType?: string }) {
96
+ return request.status === 0 && request.responseType !== 'opaque'
97
+ }
98
+
99
+ function format(type: RequestType) {
100
+ if (RequestType.XHR === type) {
101
+ return 'XHR'
102
+ }
103
+ return 'Fetch'
104
+ }
@@ -0,0 +1,72 @@
1
+ import type { LogsPublicApi } from '../boot/logsPublicApi'
2
+ import type { StartLogsResult } from '../boot/startLogs'
3
+ import type { LogsInitConfiguration } from './configuration'
4
+ import type { AssembleHook } from './hooks'
5
+
6
+ /**
7
+ * onInit plugin API options.
8
+ *
9
+ * @experimental
10
+ */
11
+ export interface OnInitOptions {
12
+ initConfiguration: LogsInitConfiguration
13
+ publicApi: LogsPublicApi
14
+ /**
15
+ * Register a callback invoked when a log event is assembled, so plugins can enrich or override
16
+ * log fields before they are sent. Callbacks registered in `onInit` run before any log is
17
+ * assembled, including logs buffered during the pre-start phase.
18
+ */
19
+ registerAssembleEventHook: AssembleHook['register']
20
+ }
21
+
22
+ /**
23
+ * onLogsStart plugin API options.
24
+ *
25
+ * @experimental
26
+ */
27
+ export interface OnLogsStartOptions {
28
+ /**
29
+ * Emit a log with the same pipeline as a logger call.
30
+ */
31
+ handleLog: StartLogsResult['handleLog']
32
+ }
33
+
34
+ /**
35
+ * Plugin interface of the Logs browser SDK.
36
+ *
37
+ * The plugins API is unstable and experimental, and may change without
38
+ * notice. Please use only plugins provided by KubeSense matching the version of the SDK you are
39
+ * using.
40
+ *
41
+ * @experimental
42
+ */
43
+ export interface LogsPlugin {
44
+ name: string
45
+ getConfigurationTelemetry?(): Record<string, unknown>
46
+ onInit?(options: OnInitOptions): void
47
+ onLogsStart?(options: OnLogsStartOptions): void
48
+ }
49
+
50
+ type MethodNames = 'onInit' | 'onLogsStart'
51
+ type MethodParameter<MethodName extends MethodNames> = Parameters<NonNullable<LogsPlugin[MethodName]>>[0]
52
+
53
+ export function callPluginsMethod<MethodName extends MethodNames>(
54
+ plugins: LogsPlugin[] | undefined,
55
+ methodName: MethodName,
56
+ parameter: MethodParameter<MethodName>
57
+ ): void
58
+ export function callPluginsMethod<MethodName extends MethodNames>(
59
+ plugins: LogsPlugin[] | undefined,
60
+ methodName: MethodName,
61
+ parameter: any
62
+ ) {
63
+ if (!plugins) {
64
+ return
65
+ }
66
+ for (const plugin of plugins) {
67
+ const method = plugin[methodName]
68
+ if (method) {
69
+ method(parameter)
70
+ }
71
+ }
72
+ }
@@ -0,0 +1,105 @@
1
+ import type { RemoteSdkConfig } from '@kubesense/kubesense-browser-core'
2
+ import {
3
+ RawReportType,
4
+ getCachedSdkConfig,
5
+ objectValues,
6
+ readBoolean,
7
+ readSampleRate,
8
+ readString,
9
+ readStringList,
10
+ refreshSdkConfig,
11
+ } from '@kubesense/kubesense-browser-core'
12
+ import { ConsoleApiName } from '@kubesense/kubesense-js-core/util'
13
+ import type { LogsInitConfiguration } from './configuration'
14
+
15
+ /**
16
+ * Applies the dashboard-managed configuration document to the Logs init configuration.
17
+ *
18
+ * The Logs SDK reads the same cached document as RUM (they share one local-storage entry), but
19
+ * only consumes the `features.logs` switch and the `logs` section — RUM settings never leak
20
+ * into Logs, and vice versa.
21
+ *
22
+ * As in RUM, a missing or invalid field leaves the local `init()` value untouched, and
23
+ * identity/endpoint settings are never remotable.
24
+ */
25
+
26
+ export interface LogsRemoteSdkConfigurationResult {
27
+ initConfiguration: LogsInitConfiguration
28
+ /** `false` when the dashboard has switched Logs off entirely. */
29
+ logsEnabled: boolean
30
+ }
31
+
32
+ export function applyCachedLogsSdkConfiguration(
33
+ initConfiguration: LogsInitConfiguration
34
+ ): LogsRemoteSdkConfigurationResult {
35
+ const remoteConfig = getCachedSdkConfig(initConfiguration.clientToken)
36
+ if (!remoteConfig) {
37
+ return { initConfiguration, logsEnabled: true }
38
+ }
39
+ return applyLogsSdkConfiguration(initConfiguration, remoteConfig)
40
+ }
41
+
42
+ export function applyLogsSdkConfiguration(
43
+ initConfiguration: LogsInitConfiguration,
44
+ remoteConfig: RemoteSdkConfig
45
+ ): LogsRemoteSdkConfigurationResult {
46
+ const features = remoteConfig.features as { [key: string]: unknown } | undefined
47
+ if (readBoolean(features, 'logs') === false) {
48
+ return { initConfiguration, logsEnabled: false }
49
+ }
50
+
51
+ const logs = remoteConfig.logs
52
+ if (!logs) {
53
+ return { initConfiguration, logsEnabled: true }
54
+ }
55
+
56
+ const applied: LogsInitConfiguration = { ...initConfiguration }
57
+
58
+ assign(applied, 'sessionSampleRate', readSampleRate(logs, 'sessionSampleRate'))
59
+ assign(applied, 'telemetrySampleRate', readSampleRate(logs, 'telemetrySampleRate'))
60
+ assign(applied, 'forwardErrorsToLogs', readBoolean(logs, 'forwardErrorsToLogs'))
61
+ assign(applied, 'trackAnonymousUser', readBoolean(logs, 'trackAnonymousUser'))
62
+ assign(applied, 'trackSessionAcrossSubdomains', readBoolean(logs, 'trackSessionAcrossSubdomains'))
63
+ assign(applied, 'allowedTrackingOrigins', readStringList(logs.allowedTrackingOrigins))
64
+ assign(applied, 'forwardConsoleLogs', readForwardList(logs.forwardConsoleLogs, objectValues(ConsoleApiName)))
65
+ assign(applied, 'forwardReports', readForwardList(logs.forwardReports, objectValues(RawReportType)))
66
+
67
+ assign(applied, 'service', readString(logs, 'service'))
68
+ assign(applied, 'env', readString(logs, 'env'))
69
+ assign(applied, 'version', readString(logs, 'version'))
70
+
71
+ return { initConfiguration: applied, logsEnabled: true }
72
+ }
73
+
74
+ /**
75
+ * `forwardConsoleLogs` and `forwardReports` accept either the literal `'all'` or a subset of the
76
+ * known names. Unknown names are dropped rather than passed through, so a document written against
77
+ * a newer SDK cannot turn the option into a value this SDK will reject wholesale.
78
+ */
79
+ function readForwardList<T extends string>(value: unknown, allowedValues: readonly T[]): T[] | 'all' | undefined {
80
+ if (value === 'all') {
81
+ return 'all'
82
+ }
83
+ const names = readStringList(value)?.filter((name): name is T => (allowedValues as readonly string[]).includes(name))
84
+ return names?.length ? names : undefined
85
+ }
86
+
87
+ function assign<K extends keyof LogsInitConfiguration>(
88
+ target: LogsInitConfiguration,
89
+ key: K,
90
+ value: LogsInitConfiguration[K] | undefined
91
+ ) {
92
+ if (value !== undefined) {
93
+ target[key] = value
94
+ }
95
+ }
96
+
97
+ /**
98
+ * Refresh the shared cache for the next page load.
99
+ *
100
+ * When RUM and Logs both run on a page they each schedule a refresh; the requests are
101
+ * idempotent GETs and the last response simply wins, so no coordination is needed.
102
+ */
103
+ export function scheduleLogsSdkConfigurationRefresh(initConfiguration: LogsInitConfiguration) {
104
+ return refreshSdkConfig(initConfiguration)
105
+ }
@@ -0,0 +1,47 @@
1
+ import type { ClocksState } from '@kubesense/kubesense-js-core/time'
2
+ import type { Context } from '@kubesense/kubesense-browser-core'
3
+ import { timeStampNow } from '@kubesense/kubesense-js-core/time'
4
+ import { ErrorSource, getFileFromStackTraceString, initReportObservable } from '@kubesense/kubesense-browser-core'
5
+ import type { LogsConfiguration } from '../configuration'
6
+ import type { LifeCycle } from '../lifeCycle'
7
+ import { LifeCycleEventType } from '../lifeCycle'
8
+ import { StatusType } from '../logger/isAuthorized'
9
+ import { createErrorFieldFromRawError } from '../createErrorFieldFromRawError'
10
+
11
+ export interface ProvidedError {
12
+ startClocks: ClocksState
13
+ error: unknown
14
+ context?: Context
15
+ handlingStack: string
16
+ }
17
+
18
+ export function startReportCollection(configuration: LogsConfiguration, lifeCycle: LifeCycle) {
19
+ const reportSubscription = initReportObservable(configuration.forwardReports).subscribe((rawError) => {
20
+ let message = rawError.message
21
+ let error
22
+ const status = rawError.originalError.type === 'deprecation' ? StatusType.warn : StatusType.error
23
+
24
+ if (status === StatusType.error) {
25
+ error = createErrorFieldFromRawError(rawError)
26
+ } else if (rawError.stack) {
27
+ message += ` Found in ${getFileFromStackTraceString(rawError.stack)!}`
28
+ }
29
+
30
+ lifeCycle.notify(LifeCycleEventType.RAW_LOG_COLLECTED, {
31
+ rawLogsEvent: {
32
+ date: timeStampNow(),
33
+ message,
34
+ origin: ErrorSource.REPORT,
35
+ error,
36
+ _kubesense: { debug_ids: rawError.debugIds },
37
+ status,
38
+ },
39
+ })
40
+ })
41
+
42
+ return {
43
+ stop: () => {
44
+ reportSubscription.unsubscribe()
45
+ },
46
+ }
47
+ }
@@ -0,0 +1,20 @@
1
+ import { ErrorSource, addTelemetryDebug } from '@kubesense/kubesense-browser-core'
2
+ import { timeStampNow } from '@kubesense/kubesense-js-core/time'
3
+ import type { LifeCycle } from './lifeCycle'
4
+ import { LifeCycleEventType } from './lifeCycle'
5
+ import { StatusType } from './logger/isAuthorized'
6
+
7
+ export function startReportError(lifeCycle: LifeCycle) {
8
+ return (message: string) => {
9
+ lifeCycle.notify(LifeCycleEventType.RAW_LOG_COLLECTED, {
10
+ rawLogsEvent: {
11
+ message,
12
+ date: timeStampNow(),
13
+ origin: ErrorSource.AGENT,
14
+ status: StatusType.error,
15
+ },
16
+ })
17
+ // monitor-until: forever, to keep an eye on the errors reported to customers
18
+ addTelemetryDebug('Error reported to customer', { 'error.message': message })
19
+ }
20
+ }
@@ -0,0 +1,51 @@
1
+ import type { ClocksState } from '@kubesense/kubesense-js-core/time'
2
+ import { combine } from '@kubesense/kubesense-js-core/util'
3
+ import type { Context, Observable, BufferedData } from '@kubesense/kubesense-browser-core'
4
+ import { noop, ErrorSource, BufferedDataType } from '@kubesense/kubesense-browser-core'
5
+ import type { LogsConfiguration } from '../configuration'
6
+ import type { LifeCycle } from '../lifeCycle'
7
+ import { LifeCycleEventType } from '../lifeCycle'
8
+ import { StatusType } from '../logger/isAuthorized'
9
+ import { createErrorFieldFromRawError } from '../createErrorFieldFromRawError'
10
+
11
+ export interface ProvidedError {
12
+ startClocks: ClocksState
13
+ error: unknown
14
+ context?: Context
15
+ handlingStack: string
16
+ }
17
+
18
+ export function startRuntimeErrorCollection(
19
+ configuration: LogsConfiguration,
20
+ lifeCycle: LifeCycle,
21
+ bufferedDataObservable: Observable<BufferedData>
22
+ ) {
23
+ if (!configuration.forwardErrorsToLogs) {
24
+ return { stop: noop }
25
+ }
26
+
27
+ const rawErrorSubscription = bufferedDataObservable.subscribe((bufferedData) => {
28
+ if (bufferedData.type === BufferedDataType.RUNTIME_ERROR) {
29
+ const error = bufferedData.data
30
+ lifeCycle.notify(LifeCycleEventType.RAW_LOG_COLLECTED, {
31
+ rawLogsEvent: combine(
32
+ {
33
+ message: error.message,
34
+ date: error.startClocks.timeStamp,
35
+ error: createErrorFieldFromRawError(error),
36
+ _kubesense: { debug_ids: error.debugIds },
37
+ origin: ErrorSource.SOURCE,
38
+ status: StatusType.error,
39
+ },
40
+ error.context
41
+ ),
42
+ })
43
+ }
44
+ })
45
+
46
+ return {
47
+ stop: () => {
48
+ rawErrorSubscription.unsubscribe()
49
+ },
50
+ }
51
+ }
@@ -0,0 +1,21 @@
1
+ import type { ErrorSource } from '@kubesense/kubesense-browser-core'
2
+
3
+ export type LogsEventDomainContext<T extends ErrorSource = any> = T extends typeof ErrorSource.NETWORK
4
+ ? NetworkLogsEventDomainContext
5
+ : T extends typeof ErrorSource.CONSOLE
6
+ ? ConsoleLogsEventDomainContext
7
+ : T extends typeof ErrorSource.LOGGER
8
+ ? LoggerLogsEventDomainContext
9
+ : undefined
10
+
11
+ export interface NetworkLogsEventDomainContext {
12
+ handlingStack?: string
13
+ }
14
+
15
+ export interface ConsoleLogsEventDomainContext {
16
+ handlingStack: string
17
+ }
18
+
19
+ export interface LoggerLogsEventDomainContext {
20
+ handlingStack: string
21
+ }