-
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: blackout2025
Are you sure you want to change the base?
Changes from all commits
388b1ac
dc68dd3
8a2e1bc
d100879
f0182bc
4c5d663
36e2a4f
6a94aeb
c5085ea
db1d40c
d078785
a88b8df
2db99dd
f7db566
f3e25fe
b972c57
b3525cd
e0d5885
73ec2ee
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| import { valueof } from '../utils'; | ||
|
|
||
| export type ErrorCodes = valueof<typeof ErrorCodes>; | ||
|
|
||
| export const ErrorCodes = { | ||
| UNKNOWN_ERROR: 'UNKNOWN_ERROR', | ||
| UNHANDLED_EXCEPTION: 'UNHANDLED_EXCEPTION', | ||
| IDENTITY_REQUEST: 'IDENTITY_REQUEST', | ||
| } as const; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| import { ErrorCodes } from "./errorCodes"; | ||
| export type ErrorCode = ErrorCodes | string; | ||
|
|
||
| export type WSDKErrorSeverity = (typeof WSDKErrorSeverity)[keyof typeof WSDKErrorSeverity]; | ||
| export const WSDKErrorSeverity = { | ||
| ERROR: 'ERROR', | ||
| INFO: 'INFO', | ||
| WARNING: 'WARNING', | ||
| } as const; | ||
|
|
||
|
|
||
| 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; |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,178 @@ | ||||||||||||||||||||||||
| import { ErrorCodes } from "./errorCodes"; | ||||||||||||||||||||||||
| import { LogRequestBody, WSDKErrorSeverity } from "./logRequest"; | ||||||||||||||||||||||||
| import { FetchUploader, IFetchPayload } from "../uploaders"; | ||||||||||||||||||||||||
| import { IStore, SDKConfig } from "../store"; | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| // QUESTION: Should we collapse the interface with the class? | ||||||||||||||||||||||||
| export interface IReportingLogger { | ||||||||||||||||||||||||
| error(msg: string, code?: ErrorCodes, stackTrace?: string): void; | ||||||||||||||||||||||||
| warning(msg: string, code?: ErrorCodes): void; | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| export class ReportingLogger implements IReportingLogger { | ||||||||||||||||||||||||
| private readonly isEnabled: boolean; | ||||||||||||||||||||||||
| private readonly reporter: string = 'mp-wsdk'; | ||||||||||||||||||||||||
| private readonly integration: string = 'mp-wsdk'; | ||||||||||||||||||||||||
| private readonly rateLimiter: IRateLimiter; | ||||||||||||||||||||||||
| private integrationName: string; | ||||||||||||||||||||||||
| private store: IStore; | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| constructor( | ||||||||||||||||||||||||
| private readonly config: SDKConfig, | ||||||||||||||||||||||||
| private readonly sdkVersion: string, | ||||||||||||||||||||||||
| private readonly launcherInstanceGuid?: string, | ||||||||||||||||||||||||
| rateLimiter?: IRateLimiter, | ||||||||||||||||||||||||
| ) { | ||||||||||||||||||||||||
| this.isEnabled = this.isReportingEnabled(); | ||||||||||||||||||||||||
| this.rateLimiter = rateLimiter ?? new RateLimiter(); | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| public setIntegrationName(integrationName: string) { | ||||||||||||||||||||||||
| this.integrationName = integrationName; | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| public setStore(store: IStore) { | ||||||||||||||||||||||||
| this.store = store; | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| public info(msg: string, code?: ErrorCodes) { | ||||||||||||||||||||||||
| this.sendLog(WSDKErrorSeverity.INFO, msg, code); | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| public error(msg: string, code?: ErrorCodes, stackTrace?: string) { | ||||||||||||||||||||||||
| this.sendError(WSDKErrorSeverity.ERROR, msg, code, stackTrace); | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| public warning(msg: string, code?: ErrorCodes) { | ||||||||||||||||||||||||
| this.sendError(WSDKErrorSeverity.WARNING, msg, code); | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| private sendToServer(url: string,severity: WSDKErrorSeverity, msg: string, code?: ErrorCodes, stackTrace?: string): void { | ||||||||||||||||||||||||
| if(!this.canSendLog(severity)) | ||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| const logRequest = this.getLogRequest(severity, msg, code, stackTrace); | ||||||||||||||||||||||||
| const uploader = new FetchUploader(url); | ||||||||||||||||||||||||
| const payload: IFetchPayload = { | ||||||||||||||||||||||||
| method: 'POST', | ||||||||||||||||||||||||
| headers: this.getHeaders(), | ||||||||||||||||||||||||
| body: JSON.stringify(logRequest), | ||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||
| uploader.upload(payload); | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| private sendLog(severity: WSDKErrorSeverity, msg: string, code?: ErrorCodes, stackTrace?: string): void { | ||||||||||||||||||||||||
| const url = this.getLoggingUrl(); | ||||||||||||||||||||||||
| this.sendToServer(url, severity, msg, code, stackTrace); | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
| private sendError(severity: WSDKErrorSeverity, msg: string, code?: ErrorCodes, stackTrace?: string): void { | ||||||||||||||||||||||||
| const url = this.getErrorUrl(); | ||||||||||||||||||||||||
| this.sendToServer(url, severity, msg, code, stackTrace); | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| private getLogRequest(severity: WSDKErrorSeverity, msg: string, code?: ErrorCodes, stackTrace?: string): LogRequestBody { | ||||||||||||||||||||||||
| return { | ||||||||||||||||||||||||
| additionalInformation: { | ||||||||||||||||||||||||
| message: msg, | ||||||||||||||||||||||||
| version: this.getVersion(), | ||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||
| severity: severity, | ||||||||||||||||||||||||
| code: code ?? ErrorCodes.UNKNOWN_ERROR, | ||||||||||||||||||||||||
| url: this.getUrl(), | ||||||||||||||||||||||||
| deviceInfo: this.getUserAgent(), | ||||||||||||||||||||||||
| stackTrace: stackTrace ?? '', | ||||||||||||||||||||||||
| reporter: this.reporter, | ||||||||||||||||||||||||
| integration: this.integration | ||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| private getVersion(): string { | ||||||||||||||||||||||||
| return this.integrationName ?? this.sdkVersion; | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| private isReportingEnabled(): boolean { | ||||||||||||||||||||||||
| // QUESTION: Should isDebugModeEnabled take precedence over | ||||||||||||||||||||||||
| // isFeatureFlagEnabled and rokt domain present? | ||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||
| this.isRoktDomainPresent() && | ||||||||||||||||||||||||
| (this.isFeatureFlagEnabled() || | ||||||||||||||||||||||||
| this.isDebugModeEnabled()) | ||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| private isRoktDomainPresent(): boolean { | ||||||||||||||||||||||||
| return Boolean(window['ROKT_DOMAIN']); | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| private isFeatureFlagEnabled(): boolean { | ||||||||||||||||||||||||
| return this.config.isWebSdkLoggingEnabled; | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| private isDebugModeEnabled(): boolean { | ||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||
| window. | ||||||||||||||||||||||||
| location?. | ||||||||||||||||||||||||
| search?. | ||||||||||||||||||||||||
| toLowerCase()?. | ||||||||||||||||||||||||
| includes('mp_enable_logging=true') ?? false | ||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| private canSendLog(severity: WSDKErrorSeverity): boolean { | ||||||||||||||||||||||||
| return this.isEnabled && !this.isRateLimited(severity); | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| private isRateLimited(severity: WSDKErrorSeverity): boolean { | ||||||||||||||||||||||||
| return this.rateLimiter.incrementAndCheck(severity); | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| private getUrl(): string { | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
| private getUrl(): string { | |
| private getUrl(): string { | |
| if (typeof window === 'undefined' || !window.location || !window.location.href) { | |
| return 'no-url-set'; | |
| } |
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 getUserAgent() method accesses window.navigator.userAgent without checking if it exists, which could throw an error in non-browser environments. Add a fallback to return 'no-user-agent-set' when navigator is undefined, similar to what the test expects.
| private getUserAgent(): string { | |
| private getUserAgent(): string { | |
| if (typeof window === 'undefined' || !window.navigator || !window.navigator.userAgent) { | |
| return 'no-user-agent-set'; | |
| } |
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 'rokt-launcher-instance-guid' header is set to this.launcherInstanceGuid which may be undefined. This will result in the header being set to the string 'undefined'. Add a check to either omit the header when undefined or ensure it always has a valid value.
| 'rokt-launcher-instance-guid': this.launcherInstanceGuid, | |
| 'rokt-launcher-version': this.getVersion(), | |
| 'rokt-wsdk-version': 'joint', | |
| }; | |
| 'rokt-launcher-version': this.getVersion(), | |
| 'rokt-wsdk-version': 'joint', | |
| }; | |
| if (this.launcherInstanceGuid !== undefined) { | |
| headers['rokt-launcher-instance-guid'] = this.launcherInstanceGuid; | |
| } |
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 code attempts to call this.reportingLogger.error() without checking if reportingLogger is defined. Since reportingLogger is optional in the constructor, this will throw a TypeError when reportingLogger is undefined. Add a null check before calling reportingLogger methods.