Recently, I needed to implement a sophisticated log upload system with priority-based queuing using RxJS. The challenge was to create a service that captures console output from developer tools and uploads it to a remote server with intelligent batching and priority handling.
The Challenge
Our application needed a robust logging system that could:
- Capture all console activities (
console.log
,console.error
,console.warn
) - Upload logs efficiently without overwhelming the server
- Prioritize critical errors over routine warnings
System Requirements
After analyzing the requirements, I identified six key behaviors the system needed to implement:
-
Batch Processing: Each upload should gather all logs stored locally up to that point. Once successful, the local storage gets cleared to prevent memory bloat.
-
Single Active Upload: Only one upload request can be active at a time. New requests should cancel ongoing ones, but no logs should be lost since canceled request data remains in storage for the next upload.
-
High Priority Errors: Logs triggered by
console.error
are treated as high priority and must be uploaded immediately. -
Delayed Warnings: Logs triggered by
console.warn
are low priority and should be delayed by one minute before uploading. If multiple warnings occur within that timeframe, only the latest one should trigger the upload, ensuring we capture the most recent state. -
Priority Override: Error uploads should override and cancel any pending warning upload triggers. Since immediate error uploads already include the latest logs, separate warning uploads become redundant.
-
Queue Management: If an error upload is in progress when a warning is triggered, the warning should wait until the error upload completes before initiating its own upload.
This design ensures efficient log handling while prioritizing critical errors over routine warnings. Let's dive into the RxJS implementation.
Implementation Overview
The solution involves three main components:
- Console method interception
- Local log storage management
- Priority-based upload orchestration
Let's build this step by step.
Step 1: Console Method Interception
First, we need to intercept the native console methods to capture all logging activity. This involves creating a service that overrides the default console behavior while preserving the original functionality.
typescriptCopy code
Step 2: Local Log Storage Management
Next, we need a robust local storage system to buffer logs before uploading. For simplicity, we'll use an in-memory Map structure, but in production, you might want to consider IndexedDB for persistence across browser sessions.
The storage system needs to:
- Generate unique IDs for each log entry
- Store logs with timestamps
- Provide methods to retrieve and clear the log history
typescriptCopy code
Step 3: Priority-Based Upload Orchestration
Now comes the most critical part: implementing the upload logic with proper priority handling and queue management. This is where RxJS really shines.
3.1 HTTP Upload Service
First, let's set up the basic upload mechanism:
typescriptCopy code
3.2 Single Upload Constraint
To ensure only one upload is active at a time, we use RxJS's switchMap
operator, which automatically cancels the previous observable when a new one is emitted:
typescriptCopy code
3.3 Priority-Based Triggers
We separate console events into two priority streams:
typescriptCopy code
3.4 Basic Upload Subscription
Initially, we can set up simple subscriptions for both triggers:
typescriptCopy code
Step 4: Advanced Priority and Queue Management
Now we need to implement the sophisticated requirements that make this system truly robust:
Requirements Analysis
- Error Priority Override: Error logs must immediately cancel any pending warning uploads
- Warning Queue Management: Warning logs should wait if an error upload is in progress
- Single Upload Constraint: Only one upload active at any time to prevent network congestion
- State Persistence: Logs should not be lost during upload cancellations
4.1 Upload State Management
We need to track whether an upload is currently in progress:
typescriptCopy code
The withUploadBusy$
tracks upload state, while latestWarnEvent$
buffers the most recent warning event during busy periods.
4.2 Enhanced Upload Logic
First, let's clear logs after successful uploads:
typescriptCopy code
4.3 Error Upload Priority
Error uploads get immediate priority and set the busy flag:
typescriptCopy code
4.4 Warning Upload Queue Management
Warnings are either uploaded immediately or buffered based on system state:
typescriptCopy code
4.5 Processing Buffered Warnings
When the system becomes idle, process any buffered warning:
typescriptCopy code
This completes our advanced priority and queue management system. The final initUploading
method should be:
typescriptCopy code
And above is our all logic that fullfills the uploading requirement. The complete code is as follows:
typescriptCopy code