Skip to content

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