-
Notifications
You must be signed in to change notification settings - Fork 57
feat: Create logging service #1137
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: development
Are you sure you want to change the base?
feat: Create logging service #1137
Conversation
5078a52 to
b972c57
Compare
mattbodle
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Update the description please
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR introduces a centralized logging service infrastructure to the mParticle Web SDK, enabling remote error and warning reporting to Rokt endpoints. The implementation includes a ReportingLogger class with rate limiting, feature flag controls, and integration with the existing Logger class.
Key changes:
- Adds ReportingLogger service with rate limiting and conditional enablement
- Integrates remote logging into the existing Logger class
- Adds support for error codes and stack trace reporting
- Configures logging and error URL endpoints via SDK configuration
Reviewed changes
Copilot reviewed 12 out of 12 changed files in this pull request and generated 65 comments.
Show a summary per file
| File | Description |
|---|---|
| test/jest/reportingLogger.spec.ts | Adds comprehensive test coverage for ReportingLogger and RateLimiter classes |
| src/logging/reportingLogger.ts | Implements the core ReportingLogger service with rate limiting and conditional logging |
| src/logging/logRequest.ts | Defines request body types and severity enum for logging API |
| src/logging/errorCodes.ts | Defines ErrorCodes enum for error categorization |
| src/logger.ts | Integrates ReportingLogger into existing Logger class |
| src/mp-instance.ts | Initializes ReportingLogger during SDK setup and wires configuration |
| src/sdkRuntimeModels.ts | Updates SDKLoggerApi and SDKInitConfig interfaces to support error codes |
| src/store.ts | Adds loggingUrl and errorUrl to SDKConfig interface |
| src/uploaders.ts | Extends IFetchPayload headers to support rokt-account-id header |
| src/constants.ts | Adds default logging and error URLs for both standard and CNAME configurations |
| src/identityApiClient.ts | Updates error logging to include ErrorCodes |
| src/roktManager.ts | Adds TODO comment for future enhancement |
Comments suppressed due to low confidence (1)
test/jest/reportingLogger.spec.ts:91
- Superfluous argument passed to constructor of class ReportingLogger.
logger = new ReportingLogger(baseUrl, sdkVersion, accountId, mockRateLimiter);
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
src/logging/reportingLogger.ts
Outdated
| private sendLog( | ||
| severity: WSDKErrorSeverity, | ||
| msg: string, | ||
| code: ErrorCodes, |
Copilot
AI
Dec 17, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The sendLog() method signature shows 'code: ErrorCodes' as required, but since error() and warning() methods should accept optional codes, this should also be optional ('code?: ErrorCodes'). Additionally, when code is undefined, the logRequest object should handle this appropriately (possibly omitting the code field or using a default value).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah this
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It should default to "UNKNOWN_ERROR"
src/logging/reportingLogger.ts
Outdated
| }; | ||
|
|
||
| public warning(msg: string, code: ErrorCodes) { | ||
| this.sendLog(WSDKErrorSeverity.WARNING, msg, code); | ||
| }; |
Copilot
AI
Dec 17, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unnecessary semicolon after the method body. Remove the trailing semicolon for consistency with JavaScript/TypeScript conventions.
| }; | |
| public warning(msg: string, code: ErrorCodes) { | |
| this.sendLog(WSDKErrorSeverity.WARNING, msg, code); | |
| }; | |
| } | |
| public warning(msg: string, code: ErrorCodes) { | |
| this.sendLog(WSDKErrorSeverity.WARNING, msg, code); | |
| } |
test/jest/reportingLogger.spec.ts
Outdated
| return ++count > 3; | ||
| }), | ||
| }; | ||
| logger = new ReportingLogger(baseUrl, sdkVersion, accountId, mockRateLimiter); |
Copilot
AI
Dec 17, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same issue as line 27 - the ReportingLogger constructor signature doesn't match what's being called in the test. The correct signature is (sdkVersion, rateLimiter?, launcherInstanceGuid?). Update to match the actual constructor signature.
test/jest/reportingLogger.spec.ts
Outdated
| expect(rateLimiter.incrementAndCheck(LogRequestSeverity.Error)).toBe(true); | ||
| expect(rateLimiter.incrementAndCheck(LogRequestSeverity.Error)).toBe(true); |
Copilot
AI
Dec 17, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The test uses LogRequestSeverity.Error but should use WSDKErrorSeverity.ERROR to match the actual implementation.
test/jest/reportingLogger.spec.ts
Outdated
|
|
||
| it('allows up to 10 warning logs then rate limits', () => { | ||
| for (let i = 0; i < 10; i++) { | ||
| expect(rateLimiter.incrementAndCheck(LogRequestSeverity.Warning)).toBe(false); |
Copilot
AI
Dec 17, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The test uses LogRequestSeverity.Warning but should use WSDKErrorSeverity.WARNING to match the actual implementation.
test/jest/reportingLogger.spec.ts
Outdated
| for (let i = 0; i < 10; i++) { | ||
| expect(rateLimiter.incrementAndCheck(LogRequestSeverity.Info)).toBe(false); | ||
| } | ||
| expect(rateLimiter.incrementAndCheck(LogRequestSeverity.Info)).toBe(true); |
Copilot
AI
Dec 17, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The base expression of this property access is always undefined.
test/jest/reportingLogger.spec.ts
Outdated
| expect(rateLimiter.incrementAndCheck(LogRequestSeverity.Info)).toBe(false); | ||
| } | ||
| expect(rateLimiter.incrementAndCheck(LogRequestSeverity.Info)).toBe(true); | ||
| expect(rateLimiter.incrementAndCheck(LogRequestSeverity.Info)).toBe(true); |
Copilot
AI
Dec 17, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The base expression of this property access is always undefined.
test/jest/reportingLogger.spec.ts
Outdated
|
|
||
| it('tracks rate limits independently per severity', () => { | ||
| for (let i = 0; i < 10; i++) { | ||
| rateLimiter.incrementAndCheck(LogRequestSeverity.Error); |
Copilot
AI
Dec 17, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The base expression of this property access is always undefined.
test/jest/reportingLogger.spec.ts
Outdated
| for (let i = 0; i < 10; i++) { | ||
| rateLimiter.incrementAndCheck(LogRequestSeverity.Error); | ||
| } | ||
| expect(rateLimiter.incrementAndCheck(LogRequestSeverity.Error)).toBe(true); |
Copilot
AI
Dec 17, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The base expression of this property access is always undefined.
test/jest/reportingLogger.spec.ts
Outdated
| rateLimiter.incrementAndCheck(LogRequestSeverity.Error); | ||
| } | ||
| expect(rateLimiter.incrementAndCheck(LogRequestSeverity.Error)).toBe(true); | ||
| expect(rateLimiter.incrementAndCheck(LogRequestSeverity.Warning)).toBe(false); |
Copilot
AI
Dec 17, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The base expression of this property access is always undefined.
src/logging/reportingLogger.ts
Outdated
| code: code, | ||
| url: this.getUrl(), | ||
| deviceInfo: this.getUserAgent(), | ||
| stackTrace: stackTrace ?? 'this is my stack trace', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Assuming this is temporary as we don't need a fallback?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mattbodle should we have it default to an empty string or undefined?
src/identityApiClient.ts
Outdated
| Logger.error('Error sending identity request to servers' + ' - ' + errorMessage); | ||
| Logger.error( | ||
| 'Error sending identity request to servers' + ' - ' + errorMessage, | ||
| ErrorCodes.UNHANDLED_EXCEPTION |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would use this error code as a fallback if there is no errorcode. In this example I would create a code similar to IDENTITY_REQUEST
src/logging/reportingLogger.ts
Outdated
| private readonly rateLimiter: IRateLimiter; | ||
| private loggingUrl: string; | ||
| private errorUrl: string; | ||
| private accountId: string; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we need to maintain this here? Should it be stored as state elsewhere?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note: We should inject _Store so we can persist accountId and grab other properties later.
mattbodle
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could we add an integration test as part of this PR?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.
… up imports in reportingLogger
… window for location and ROKT_DOMAIN
…locker parameter from related functions
…Uploader and XHRUploader
…proved type safety
… and update tests for new rate limiting behavior
…d update related tests
…n apiClient tests for improved compatibility
…rtingLogger for better encapsulation and update related tests
… with SDKConfig adjustments
06e9d53 to
22114bf
Compare
|
|
A general question about this repo, are we using snake case or camel case for file names? Can we choose one and add to linting rules? |
| @@ -0,0 +1,23 @@ | |||
| import { ErrorCodes } from "./errorCodes"; | |||
| export type ErrorCode = ErrorCodes | string; | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Question on the decision to separate the files here - why are error codes in a separate file, but severity is combined in this one?
| if (this.logger.warning && | ||
| (this.logLevel === LogLevelType.Verbose || this.logLevel === LogLevelType.Warning)) { | ||
| this.logger.warning(msg); | ||
| this.reportingLogger?.warning(msg, code); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's not send warnings yet. Let's start with errors.
| Logger.error('Error sending identity request to servers' + ' - ' + errorMessage); | ||
| Logger.error( | ||
| 'Error sending identity request to servers' + ' - ' + errorMessage, | ||
| ErrorCodes.IDENTITY_REQUEST |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
only upload errors that have error codes so that we can trickle the errors in as opposed to floding
| version: this.getVersion(), | ||
| }, | ||
| severity: severity, | ||
| code: code ?? ErrorCodes.UNKNOWN_ERROR, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
update this to only sen if an error code exists
| export type ErrorsRequestBody = { | ||
| additionalInformation?: Record<string, string>; | ||
| code: ErrorCode; | ||
| severity: WSDKErrorSeverity; | ||
| stackTrace?: string; | ||
| deviceInfo?: string; | ||
| integration?: string; | ||
| reporter?: string; | ||
| url?: string; | ||
| }; | ||
|
|
||
| export type LogRequestBody = ErrorsRequestBody; No newline at end of file |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any reason to not just do
| export type ErrorsRequestBody = { | |
| additionalInformation?: Record<string, string>; | |
| code: ErrorCode; | |
| severity: WSDKErrorSeverity; | |
| stackTrace?: string; | |
| deviceInfo?: string; | |
| integration?: string; | |
| reporter?: string; | |
| url?: string; | |
| }; | |
| export type LogRequestBody = ErrorsRequestBody; | |
| export type LogRequestBody = { | |
| additionalInformation?: Record<string, string>; | |
| code: ErrorCode; | |
| severity: WSDKErrorSeverity; | |
| stackTrace?: string; | |
| deviceInfo?: string; | |
| integration?: string; | |
| reporter?: string; | |
| url?: string; | |
| }; |
| private readonly reporter: string = 'mp-wsdk'; | ||
| private readonly integration: string = 'mp-wsdk'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would these ver differ and if not should we make them one string? or have a constant and set each to the constant?
| this.sendError(WSDKErrorSeverity.WARNING, msg, code); | ||
| } | ||
|
|
||
| private sendToServer(url: string,severity: WSDKErrorSeverity, msg: string, code?: ErrorCodes, stackTrace?: string): void { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| private sendToServer(url: string,severity: WSDKErrorSeverity, msg: string, code?: ErrorCodes, stackTrace?: string): void { | |
| private sendToServer(url: string, severity: WSDKErrorSeverity, msg: string, code?: ErrorCodes, stackTrace?: string): void { |
| } | ||
|
|
||
| private sendLog(severity: WSDKErrorSeverity, msg: string, code?: ErrorCodes, stackTrace?: string): void { | ||
| const url = this.getLoggingUrl(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should compute the string once in the constructor and store them on the class vs computing it every time a log is sent
| this.sendToServer(url, severity, msg, code, stackTrace); | ||
| } | ||
| private sendError(severity: WSDKErrorSeverity, msg: string, code?: ErrorCodes, stackTrace?: string): void { | ||
| const url = this.getErrorUrl(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should compute the string once in the constructor and store them on the class vs computing it every time a log is sent
| this.sendToServer(url, severity, msg, code, stackTrace); | ||
| } | ||
|
|
||
| private getLogRequest(severity: WSDKErrorSeverity, msg: string, code?: ErrorCodes, stackTrace?: string): LogRequestBody { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not a getter.
| private getLogRequest(severity: WSDKErrorSeverity, msg: string, code?: ErrorCodes, stackTrace?: string): LogRequestBody { | |
| private buildLogRequest(severity: WSDKErrorSeverity, msg: string, code?: ErrorCodes, stackTrace?: string): LogRequestBody { |
| this.rateLimiter = rateLimiter ?? new RateLimiter(); | ||
| } | ||
|
|
||
| public setIntegrationName(integrationName: string) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When would this be called?
| // QUESTION: Should isDebugModeEnabled take precedence over | ||
| // isFeatureFlagEnabled and rokt domain present? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think keeping it this way is fine. Technically Rokt domain is required for this to be work at all.
| function runPreConfigFetchInitialization(mpInstance, apiKey, config) { | ||
| mpInstance.Logger = new Logger(config); | ||
| mpInstance._Store = new Store(config, mpInstance, apiKey); | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
|
|
||
| for (const prop in Constants.DefaultConfig) { | ||
| if (Constants.DefaultConfig.hasOwnProperty(prop)) { | ||
| config[prop] = Constants.DefaultConfig[prop]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should this be
| config[prop] = Constants.DefaultConfig[prop]; | |
| sdkConfig[prop] = Constants.DefaultConfig[prop]; |
| private processFlags(config: SDKInitConfig): IFeatureFlags { | ||
| const flags: IFeatureFlags = {}; | ||
| const { | ||
| ReportBatching, | ||
| EventBatchingIntervalMillis, | ||
| OfflineStorage, | ||
| DirectUrlRouting, | ||
| CacheIdentity, | ||
| AudienceAPI, | ||
| CaptureIntegrationSpecificIds, | ||
| CaptureIntegrationSpecificIdsV2, | ||
| AstBackgroundEvents | ||
| } = Constants.FeatureFlags; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this matches the store's function processFlags. can we DRY it up?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
actually it looks like a few methods were moved here. so please remove them from store.ts
|
|
||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
rmi22186
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking really good overall. mostly minor comments
|
|
||
| if (this.logger.error) { | ||
| this.logger.error(msg); | ||
| this.reportingLogger.error(msg, code); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| this.reportingLogger.error(msg, code); | |
| this.reportingLogger?.error(msg, code); |
|
|
||
| // QUESTION: Should we collapse the interface with the class? | ||
| export interface IReportingLogger { | ||
| error(msg: string, code?: ErrorCodes, stackTrace?: string): void; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should this have info?


Background
What Has Changed
Screenshots/Video
Checklist
Additional Notes
Reference Issue (For employees only. Ignore if you are an outside contributor)