Usage Tracking
Comprehensive guide to implementing usage tracking and reporting for license compliance and analytics.
Overview
Usage tracking allows you to monitor how your licensed applications are being used, enforce usage limits, and provide valuable insights for both you and your customers.
Basic Usage Tracking
Simple Usage Reporting
Report basic usage metrics:
bash
curl -X POST https://your-license-api.com/v1/track-usage \
-H "Content-Type: application/json" \
-d '{
"key": "7yChyZcfMG23Dx1sjBoLziPFrH4n-6f28",
"usage": {
"active_users": 25,
"api_calls": 1500,
"storage_gb": 12.5
}
}'Response:
json
{
"tracked": true,
"timestamp": "2024-01-15T10:30:00Z",
"period": "current",
"limits": {
"users": 100,
"api_calls": 10000,
"storage_gb": 50
},
"usage_percentage": {
"users": 25.0,
"api_calls": 15.0,
"storage_gb": 25.0
}
}Advanced Usage Reporting
Include detailed metrics and context:
bash
curl -X POST https://your-license-api.com/v1/track-usage \
-H "Content-Type: application/json" \
-d '{
"key": "7yChyZcfMG23Dx1sjBoLziPFrH4n-6f28",
"instance_id": "server-001",
"usage": {
"active_users": 25,
"total_users": 87,
"api_calls": 1500,
"api_calls_by_type": {
"read": 1200,
"write": 250,
"admin": 50
},
"storage_gb": 12.5,
"bandwidth_gb": 3.2,
"processing_hours": 8.5,
"custom_metrics": {
"reports_generated": 45,
"exports_created": 12
}
},
"metadata": {
"reporting_period": "hourly",
"data_source": "application_metrics",
"version": "1.2.3"
}
}'Usage Tracking Implementation
Basic Usage Tracker
javascript
class UsageTracker {
constructor(licenseKey, apiUrl, options = {}) {
this.licenseKey = licenseKey;
this.apiUrl = apiUrl;
this.instanceId = options.instanceId;
this.reportingInterval = options.reportingInterval || 300000; // 5 minutes
this.batchSize = options.batchSize || 10;
this.currentUsage = {};
this.usageQueue = [];
this.isTracking = false;
this.reportingTimer = null;
}
start() {
if (this.isTracking) return;
this.isTracking = true;
console.log(`Starting usage tracking (reporting every ${this.reportingInterval / 1000}s)`);
// Start periodic reporting
this.reportingTimer = setInterval(() => {
this.reportCurrentUsage();
}, this.reportingInterval);
// Report immediately
this.reportCurrentUsage();
}
stop() {
if (!this.isTracking) return;
this.isTracking = false;
if (this.reportingTimer) {
clearInterval(this.reportingTimer);
this.reportingTimer = null;
}
// Final report
this.reportCurrentUsage();
console.log('Stopped usage tracking');
}
// Track different types of usage
trackUserActivity(userId, activity) {
this.incrementUsage('active_users', 1);
this.incrementUsage(`activity_${activity}`, 1);
// Track unique users
if (!this.currentUsage.unique_users) {
this.currentUsage.unique_users = new Set();
}
this.currentUsage.unique_users.add(userId);
}
trackAPICall(endpoint, method = 'GET') {
this.incrementUsage('api_calls', 1);
this.incrementUsage(`api_${method.toLowerCase()}`, 1);
// Track by endpoint
const endpointKey = `endpoint_${endpoint.replace(/[^a-zA-Z0-9]/g, '_')}`;
this.incrementUsage(endpointKey, 1);
}
trackStorageUsage(bytes) {
this.setUsage('storage_gb', bytes / (1024 * 1024 * 1024));
}
trackBandwidth(bytes) {
this.incrementUsage('bandwidth_bytes', bytes);
}
trackProcessingTime(milliseconds) {
this.incrementUsage('processing_ms', milliseconds);
}
trackCustomMetric(name, value, operation = 'increment') {
if (operation === 'increment') {
this.incrementUsage(name, value);
} else {
this.setUsage(name, value);
}
}
// Internal usage management
incrementUsage(metric, value = 1) {
this.currentUsage[metric] = (this.currentUsage[metric] || 0) + value;
}
setUsage(metric, value) {
this.currentUsage[metric] = value;
}
async reportCurrentUsage() {
if (Object.keys(this.currentUsage).length === 0) {
return; // Nothing to report
}
try {
const usage = this.prepareUsageData();
const result = await this.sendUsageReport(usage);
if (result.tracked) {
console.log('Usage reported successfully');
this.handleUsageResponse(result);
this.resetUsage();
} else {
console.warn('Usage report failed:', result.error);
}
} catch (error) {
console.error('Usage reporting error:', error.message);
// Queue for retry
this.queueUsageForRetry();
}
}
prepareUsageData() {
const usage = { ...this.currentUsage };
// Convert Sets to counts
if (usage.unique_users instanceof Set) {
usage.unique_users = usage.unique_users.size;
}
// Convert bandwidth to GB
if (usage.bandwidth_bytes) {
usage.bandwidth_gb = usage.bandwidth_bytes / (1024 * 1024 * 1024);
delete usage.bandwidth_bytes;
}
// Convert processing time to hours
if (usage.processing_ms) {
usage.processing_hours = usage.processing_ms / (1000 * 60 * 60);
delete usage.processing_ms;
}
return usage;
}
async sendUsageReport(usage) {
const payload = {
key: this.licenseKey,
usage,
timestamp: new Date().toISOString()
};
if (this.instanceId) {
payload.instance_id = this.instanceId;
}
const response = await fetch(`${this.apiUrl}/track-usage`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
timeout: 30000
});
if (!response.ok) {
const error = await response.json();
throw new Error(`HTTP ${response.status}: ${error.error}`);
}
return response.json();
}
handleUsageResponse(result) {
// Check for usage warnings
if (result.usage_percentage) {
for (const [metric, percentage] of Object.entries(result.usage_percentage)) {
if (percentage > 90) {
console.warn(`Usage warning: ${metric} at ${percentage.toFixed(1)}% of limit`);
} else if (percentage > 80) {
console.info(`Usage info: ${metric} at ${percentage.toFixed(1)}% of limit`);
}
}
}
// Check for limit violations
if (result.limits_exceeded && result.limits_exceeded.length > 0) {
console.error('Usage limits exceeded:', result.limits_exceeded);
this.handleLimitsExceeded(result.limits_exceeded);
}
}
handleLimitsExceeded(exceededLimits) {
// Implement limit enforcement logic
for (const limit of exceededLimits) {
console.error(`Limit exceeded: ${limit.metric} (${limit.current}/${limit.limit})`);
switch (limit.metric) {
case 'api_calls':
this.handleAPILimitExceeded();
break;
case 'storage_gb':
this.handleStorageLimitExceeded();
break;
case 'users':
this.handleUserLimitExceeded();
break;
}
}
}
handleAPILimitExceeded() {
// Implement API throttling
console.warn('API limit exceeded - implementing throttling');
}
handleStorageLimitExceeded() {
// Implement storage cleanup or block new uploads
console.warn('Storage limit exceeded');
}
handleUserLimitExceeded() {
// Block new user registrations
console.warn('User limit exceeded - blocking new registrations');
}
resetUsage() {
this.currentUsage = {};
}
queueUsageForRetry() {
this.usageQueue.push({
usage: this.prepareUsageData(),
timestamp: new Date().toISOString(),
retries: 0
});
// Limit queue size
if (this.usageQueue.length > this.batchSize) {
this.usageQueue.shift(); // Remove oldest
}
}
getCurrentUsage() {
return this.prepareUsageData();
}
}
// Usage
const tracker = new UsageTracker(
process.env.LICENSE_KEY,
'https://your-license-api.com/v1',
{
instanceId: 'server-001',
reportingInterval: 300000 // 5 minutes
}
);
tracker.start();
// Track various activities
tracker.trackUserActivity('user123', 'login');
tracker.trackAPICall('/api/users', 'GET');
tracker.trackStorageUsage(1024 * 1024 * 100); // 100MB
tracker.trackCustomMetric('reports_generated', 1);Advanced Usage Tracker with Metrics Collection
javascript
class AdvancedUsageTracker extends UsageTracker {
constructor(licenseKey, apiUrl, options = {}) {
super(licenseKey, apiUrl, options);
this.metricsCollectors = new Map();
this.historicalData = [];
this.maxHistorySize = options.maxHistorySize || 1000;
}
// Register custom metrics collectors
registerMetricsCollector(name, collector) {
this.metricsCollectors.set(name, collector);
}
// Collect metrics from all registered collectors
async collectAllMetrics() {
const metrics = {};
for (const [name, collector] of this.metricsCollectors) {
try {
const data = await collector();
metrics[name] = data;
} catch (error) {
console.warn(`Failed to collect metrics from ${name}:`, error.message);
}
}
return metrics;
}
async reportCurrentUsage() {
// Collect metrics from registered collectors
const collectedMetrics = await this.collectAllMetrics();
// Merge with current usage
const combinedUsage = {
...this.currentUsage,
...collectedMetrics
};
// Store in history
this.storeHistoricalData(combinedUsage);
// Report to server
if (Object.keys(combinedUsage).length > 0) {
try {
const usage = this.prepareUsageData(combinedUsage);
const result = await this.sendUsageReport(usage);
if (result.tracked) {
console.log('Advanced usage reported successfully');
this.handleUsageResponse(result);
this.resetUsage();
} else {
console.warn('Usage report failed:', result.error);
}
} catch (error) {
console.error('Usage reporting error:', error.message);
this.queueUsageForRetry();
}
}
}
prepareUsageData(usage = this.currentUsage) {
const prepared = super.prepareUsageData.call(this, usage);
// Add system metrics
prepared.system_metrics = this.getSystemMetrics();
// Add performance metrics
prepared.performance_metrics = this.getPerformanceMetrics();
return prepared;
}
getSystemMetrics() {
const os = require('os');
return {
cpu_usage: process.cpuUsage(),
memory_usage: process.memoryUsage(),
system_load: os.loadavg(),
uptime: process.uptime(),
node_version: process.version
};
}
getPerformanceMetrics() {
return {
event_loop_delay: this.getEventLoopDelay(),
gc_stats: this.getGCStats(),
active_handles: process._getActiveHandles().length,
active_requests: process._getActiveRequests().length
};
}
getEventLoopDelay() {
// This would require a more sophisticated implementation
// using perf_hooks or similar
return 0;
}
getGCStats() {
// This would require v8 module or gc-stats package
return {};
}
storeHistoricalData(usage) {
this.historicalData.push({
timestamp: new Date(),
usage
});
// Maintain history size limit
if (this.historicalData.length > this.maxHistorySize) {
this.historicalData.shift();
}
}
getHistoricalData(hours = 24) {
const cutoff = new Date(Date.now() - hours * 60 * 60 * 1000);
return this.historicalData.filter(entry => entry.timestamp >= cutoff);
}
getUsageAnalytics(hours = 24) {
const data = this.getHistoricalData(hours);
if (data.length === 0) {
return null;
}
const analytics = {
period: `${hours}h`,
data_points: data.length,
metrics: {}
};
// Calculate statistics for each metric
const metrics = new Set();
data.forEach(entry => {
Object.keys(entry.usage).forEach(key => metrics.add(key));
});
for (const metric of metrics) {
const values = data
.map(entry => entry.usage[metric])
.filter(value => typeof value === 'number');
if (values.length > 0) {
analytics.metrics[metric] = {
min: Math.min(...values),
max: Math.max(...values),
avg: values.reduce((a, b) => a + b, 0) / values.length,
latest: values[values.length - 1]
};
}
}
return analytics;
}
}
// Example collectors
const databaseCollector = async () => {
// Collect database metrics
return {
db_connections: 5,
db_query_count: 150,
db_slow_queries: 2
};
};
const cacheCollector = async () => {
// Collect cache metrics
return {
cache_hits: 1200,
cache_misses: 45,
cache_hit_rate: 96.4
};
};
// Usage
const advancedTracker = new AdvancedUsageTracker(
process.env.LICENSE_KEY,
'https://your-license-api.com/v1'
);
advancedTracker.registerMetricsCollector('database', databaseCollector);
advancedTracker.registerMetricsCollector('cache', cacheCollector);
advancedTracker.start();Usage Analytics and Reporting
Usage Report Generation
javascript
class UsageReporter {
constructor(licenseKey, apiUrl) {
this.licenseKey = licenseKey;
this.apiUrl = apiUrl;
}
async generateUsageReport(options = {}) {
const {
startDate = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000), // 30 days ago
endDate = new Date(),
granularity = 'daily', // hourly, daily, weekly, monthly
metrics = ['all'],
format = 'json' // json, csv, pdf
} = options;
try {
const response = await fetch(`${this.apiUrl}/usage-report`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
key: this.licenseKey,
start_date: startDate.toISOString(),
end_date: endDate.toISOString(),
granularity,
metrics,
format
}),
timeout: 60000 // Reports can take longer
});
if (!response.ok) {
const error = await response.json();
throw new Error(`Report generation failed: ${error.error}`);
}
if (format === 'json') {
return response.json();
} else {
return response.blob(); // For CSV or PDF
}
} catch (error) {
console.error('Usage report generation failed:', error.message);
throw error;
}
}
async getUsageSummary(period = '30d') {
try {
const response = await fetch(`${this.apiUrl}/usage-summary`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
key: this.licenseKey,
period
}),
timeout: 30000
});
if (!response.ok) {
const error = await response.json();
throw new Error(`Summary fetch failed: ${error.error}`);
}
return response.json();
} catch (error) {
console.error('Usage summary fetch failed:', error.message);
throw error;
}
}
async exportUsageData(format = 'csv', options = {}) {
try {
const report = await this.generateUsageReport({
format,
...options
});
if (format === 'csv') {
const blob = new Blob([report], { type: 'text/csv' });
this.downloadBlob(blob, 'usage-report.csv');
} else if (format === 'pdf') {
const blob = new Blob([report], { type: 'application/pdf' });
this.downloadBlob(blob, 'usage-report.pdf');
}
return report;
} catch (error) {
console.error('Usage export failed:', error.message);
throw error;
}
}
downloadBlob(blob, filename) {
// Browser-specific download implementation
if (typeof window !== 'undefined' && window.document) {
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.style.display = 'none';
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
window.URL.revokeObjectURL(url);
document.body.removeChild(a);
}
}
async generateDashboardData() {
const [summary, report] = await Promise.all([
this.getUsageSummary('30d'),
this.generateUsageReport({
granularity: 'daily',
metrics: ['api_calls', 'active_users', 'storage_gb']
})
]);
return {
summary,
charts: this.prepareChartData(report),
alerts: this.generateUsageAlerts(summary)
};
}
prepareChartData(report) {
const charts = {};
if (report.data && report.data.length > 0) {
// Prepare time series data
charts.timeline = report.data.map(entry => ({
date: entry.date,
api_calls: entry.usage.api_calls || 0,
active_users: entry.usage.active_users || 0,
storage_gb: entry.usage.storage_gb || 0
}));
// Prepare usage distribution
const latest = report.data[report.data.length - 1];
if (latest && latest.usage) {
charts.distribution = Object.entries(latest.usage)
.map(([metric, value]) => ({ metric, value }))
.sort((a, b) => b.value - a.value);
}
}
return charts;
}
generateUsageAlerts(summary) {
const alerts = [];
if (summary.usage_percentage) {
for (const [metric, percentage] of Object.entries(summary.usage_percentage)) {
if (percentage > 95) {
alerts.push({
level: 'critical',
message: `${metric} usage is at ${percentage.toFixed(1)}% of limit`,
action: 'Immediate action required'
});
} else if (percentage > 85) {
alerts.push({
level: 'warning',
message: `${metric} usage is at ${percentage.toFixed(1)}% of limit`,
action: 'Consider upgrading or optimizing usage'
});
}
}
}
return alerts;
}
}Real-time Usage Monitoring
Usage Monitor with WebSocket
javascript
class RealTimeUsageMonitor {
constructor(licenseKey, wsUrl) {
this.licenseKey = licenseKey;
this.wsUrl = wsUrl;
this.ws = null;
this.isConnected = false;
this.reconnectAttempts = 0;
this.maxReconnectAttempts = 5;
this.listeners = new Map();
}
connect() {
try {
this.ws = new WebSocket(`${this.wsUrl}?key=${encodeURIComponent(this.licenseKey)}`);
this.ws.onopen = () => {
console.log('Connected to usage monitoring');
this.isConnected = true;
this.reconnectAttempts = 0;
this.emit('connected');
};
this.ws.onmessage = (event) => {
try {
const data = JSON.parse(event.data);
this.handleUsageUpdate(data);
} catch (error) {
console.error('Invalid usage data received:', error);
}
};
this.ws.onclose = () => {
console.log('Disconnected from usage monitoring');
this.isConnected = false;
this.emit('disconnected');
this.attemptReconnect();
};
this.ws.onerror = (error) => {
console.error('Usage monitoring error:', error);
this.emit('error', error);
};
} catch (error) {
console.error('Failed to connect to usage monitoring:', error);
this.attemptReconnect();
}
}
disconnect() {
if (this.ws) {
this.ws.close();
this.ws = null;
}
this.isConnected = false;
}
attemptReconnect() {
if (this.reconnectAttempts >= this.maxReconnectAttempts) {
console.error('Max reconnection attempts reached');
this.emit('maxReconnectAttemptsReached');
return;
}
this.reconnectAttempts++;
const delay = Math.min(1000 * Math.pow(2, this.reconnectAttempts), 30000);
console.log(`Attempting to reconnect in ${delay}ms (attempt ${this.reconnectAttempts})`);
setTimeout(() => {
this.connect();
}, delay);
}
handleUsageUpdate(data) {
switch (data.type) {
case 'usage_update':
this.emit('usageUpdate', data.usage);
break;
case 'limit_warning':
this.emit('limitWarning', data);
break;
case 'limit_exceeded':
this.emit('limitExceeded', data);
break;
case 'license_status':
this.emit('licenseStatus', data);
break;
default:
console.warn('Unknown usage update type:', data.type);
}
}
on(event, callback) {
if (!this.listeners.has(event)) {
this.listeners.set(event, []);
}
this.listeners.get(event).push(callback);
}
off(event, callback) {
const callbacks = this.listeners.get(event);
if (callbacks) {
const index = callbacks.indexOf(callback);
if (index > -1) {
callbacks.splice(index, 1);
}
}
}
emit(event, data) {
const callbacks = this.listeners.get(event);
if (callbacks) {
callbacks.forEach(callback => {
try {
callback(data);
} catch (error) {
console.error(`Error in ${event} callback:`, error);
}
});
}
}
sendUsageUpdate(usage) {
if (this.isConnected && this.ws) {
this.ws.send(JSON.stringify({
type: 'usage_update',
usage,
timestamp: new Date().toISOString()
}));
}
}
}
// Usage
const monitor = new RealTimeUsageMonitor(
process.env.LICENSE_KEY,
'wss://your-license-api.com/ws'
);
monitor.on('usageUpdate', (usage) => {
console.log('Usage update:', usage);
updateDashboard(usage);
});
monitor.on('limitWarning', (warning) => {
console.warn('Usage limit warning:', warning);
showWarningNotification(warning);
});
monitor.on('limitExceeded', (exceeded) => {
console.error('Usage limit exceeded:', exceeded);
handleLimitExceeded(exceeded);
});
monitor.connect();Best Practices
1. Data Collection
- Collect meaningful metrics that align with license terms
- Balance detail with privacy concerns
- Use appropriate sampling for high-frequency events
- Include context and metadata for better insights
2. Performance
- Batch usage reports to reduce API calls
- Use asynchronous reporting to avoid blocking application
- Implement local caching for resilience
- Set appropriate reporting intervals
3. Privacy and Compliance
- Only collect necessary usage data
- Anonymize sensitive information
- Respect data retention policies
- Provide transparency about data collection
4. Error Handling
- Handle network failures gracefully
- Queue usage data during outages
- Implement retry logic with backoff
- Provide fallback for critical limits
5. Monitoring and Alerting
- Set up alerts for usage thresholds
- Monitor data collection health
- Track reporting success rates
- Implement usage-based automation
Next Steps
- Client Examples - Platform-specific tracking implementations
- Error Handling - Robust error management
- API Reference - Complete endpoint documentation