npm package: @redducklabs/platform-observability-sdk
Description: TypeScript SDK for Platform Observability log ingestion
Author: Platform Observability Team
Latest version: 1.0.2

# Platform Observability TypeScript SDK

A TypeScript/JavaScript SDK for sending logs to the Platform Observability log ingestion API.

## Important: Frontend API Key Security with Origin Restrictions

When using this SDK in browser/frontend applications, your API keys can be configured with **origin restrictions** for enhanced security. This feature ensures that API keys can only be used from specific domains, preventing unauthorized use if the key is exposed.

### How Origin Restrictions Work

1. **Configure allowed origins** when creating your API key (e.g., `https://app.example.com`, `*.example.com`)
2. **The browser automatically sends** the `Origin` header with each request
3. **The API validates** that the request origin matches the allowed patterns
4. **Requests from unauthorized origins are rejected** with an `OriginNotAllowedError`

### Handling Origin Restriction Errors

```typescript
import { PlatformObservabilityClient, OriginNotAllowedError } from '@redducklabs/platform-observability-sdk';

try {
  await logger.info('User action', { action: 'button_click' });
} catch (error) {
  if (error instanceof OriginNotAllowedError) {
    console.error('This domain is not allowed to use this API key');
    console.error('Allowed origins:', error.allowedOrigins);
    // Handle the error appropriately
  }
}
```

## Installation

```bash
# From npm
npm install @redducklabs/platform-observability-sdk

# From GitHub Packages
npm install @redducklabs/platform-observability-sdk --registry https://npm.pkg.github.com

# From source (development)
cd sdks/typescript
npm install
npm run build
```

## Quick Start

```typescript
import { PlatformObservabilityClient, PlatformObservabilityLogger, LogLevel } from '@redducklabs/platform-observability-sdk';

// Initialize client
const client = new PlatformObservabilityClient({
  apiKey: 'your_api_key_here',
  baseUrl: 'http://observability.redducklabs.com/api/v1', // Note: temporary HTTP due to cert rate limits
  skipConnectionCheck: true  // Skip initial health check to avoid 404 errors
});

// Initialize logger with labels for Grafana filtering
const logger = new PlatformObservabilityLogger(client, {
  defaultSource: 'my-app',
  defaultLabels: {
    app: 'my-app',        // REQUIRED for Grafana queries
    environment: 'production',
    service: 'web-api'
  }
});

// Log messages
await logger.info('Application started', { version: '1.0.0' });
await logger.error('Database connection failed', error, { retry_count: 3 });

// Flush pending logs
await logger.flush();
```

## Configuration

### Client Configuration

```typescript
const client = new PlatformObservabilityClient({
  apiKey: 'your_api_key_here',            // Required: Your API key
  baseUrl: 'http://observability.redducklabs.com/api/v1', // Required: API base URL (must end with /api/v1)
  timeout: 10000,                         // Optional: Request timeout (ms)
  batchSize: 100,                         // Optional: Max logs per batch
  batchTimeout: 5000,                     // Optional: Max wait time for batch (ms)
  enableRetry: true,                      // Optional: Enable retry on failure
  maxRetries: 3,                          // Optional: Max retry attempts
  debug: false,                           // Optional: Enable debug logging
  skipConnectionCheck: false              // Optional: Skip initial health check (set to true if getting 404 errors)
});
```

### Logger Configuration

```typescript
const logger = new PlatformObservabilityLogger(client, {
  defaultSource: 'my-service',            // Required: Default source for logs
  defaultLabels: {                        // Optional: Default labels for all logs
    environment: 'production',
    team: 'backend'
  },
  defaultFields: {                        // Optional: Default fields for all logs
    application: 'my-app',
    version: '1.0.0'
  },
  minLevel: LogLevel.INFO,                // Optional: Minimum log level
  enableConsoleLogging: false             // Optional: Also log to console
});
```

## Usage Examples

### Basic Logging

```typescript
// Different log levels
await logger.debug('Debug information', { userId: 123 });
await logger.info('User logged in', { userId: 123, method: 'oauth' });
await logger.warn('Rate limit approaching', { currentRate: 95, limit: 100 });
await logger.error('Payment failed', error, { amount: 50.00, currency: 'USD' });
await logger.fatal('Database unavailable', error, { service: 'postgres' });
```

### HTTP Request Logging

```typescript
await logger.logHttpRequest(
  'POST',
  '/api/users',
  201,
  250,                    // duration in ms
  'Mozilla/5.0...',       // user agent
  'user-123',             // user ID
  { request_id: 'req-456' } // additional fields
);
```

### Performance Monitoring

```typescript
const start = Date.now();
try {
  await performDatabaseOperation();
  await logger.logPerformance('database_query', Date.now() - start, true, {
    query_type: 'SELECT',
    table: 'users'
  });
} catch (error) {
  await logger.logPerformance('database_query', Date.now() - start, false, {
    error: error.message
  });
}
```

### Business Events

```typescript
await logger.logEvent('user_signup', {
  plan: 'premium',
  referral_code: 'FRIEND123',
  marketing_source: 'google_ads'
}, 'user-123', 'session-456');
```

### Security Events

```typescript
await logger.logSecurity('suspicious_login', 'high', {
  login_attempts: 5,
  time_window: '5_minutes',
  user_agent: 'suspicious-bot/1.0'
}, 'user-123', '192.168.1.100');
```

### Batch Operations

```typescript
// Send logs immediately
await logger.log(LogLevel.ERROR, 'Critical error', { error: 'details' }, {}, { immediate: true });

// Add to batch with custom options
await logger.log(LogLevel.INFO, 'Regular message', {}, {}, { 
  maxSize: 50,     // Send when 50 logs accumulated
  maxWaitTime: 2000 // Or send after 2 seconds
});

// Send multiple logs at once
await logger.logBatch([
  { timestamp: new Date(), level: LogLevel.INFO, message: 'First message', labels: {}, fields: {} },
  { timestamp: new Date(), level: LogLevel.WARN, message: 'Second message', labels: {}, fields: {} }
]);
```

### Child Loggers

```typescript
// Create a child logger with additional context
const requestLogger = logger.child(
  { request_id: 'req-123' },  // additional labels
  { trace_id: 'trace-456' }   // additional fields
);

// All logs from child logger will include the additional context
await requestLogger.info('Processing request');
await requestLogger.error('Request failed', error);
```

## Direct Client Usage

```typescript
import { LogLevel } from '@redducklabs/platform-observability-sdk';

// Send logs directly using the client
await client.sendLogs([
  {
    timestamp: new Date(),
    level: LogLevel.INFO,
    message: 'Direct log message',
    source: 'my-service',
    labels: { environment: 'prod' },
    fields: { userId: 123 }
  }
]);

// Add logs to batch
await client.addToBatch([...logs]);

// Flush batch manually
await client.flushBatch();
```

## API Management

```typescript
// Create API key (Note: Origin restrictions are configured via the Red Duck Labs dashboard)
const apiKey = await client.createAPIKey({
  name: 'Production API Key',
  description: 'Key for production logging',
  expires_at: '2024-12-31T23:59:59Z'
});

// List API keys
const keys = await client.listAPIKeys();

// Rotate API key
const result = await client.rotateAPIKey('key-id');

// Delete API key
await client.deleteAPIKey('key-id');

// Get key metrics
const metrics = await client.getKeyMetrics('key-id');
```

## Analytics

```typescript
// Get usage analytics
const analytics = await client.getUsageAnalytics(
  '2024-01-01T00:00:00Z',  // start time
  '2024-01-31T23:59:59Z'   // end time
);

console.log('Total logs ingested:', analytics.total_logs_ingested);
console.log('Data volume:', analytics.data_volume_bytes);
```

## Error Handling

```typescript
import {
  AuthenticationError,
  OriginNotAllowedError,
  RateLimitError,
  ValidationError,
  NetworkError
} from '@redducklabs/platform-observability-sdk';

try {
  await logger.info('Test message');
} catch (error) {
  if (error instanceof AuthenticationError) {
    console.error('Invalid API key');
  } else if (error instanceof OriginNotAllowedError) {
    console.error('Origin not allowed for this API key');
    console.error('Allowed origins:', error.allowedOrigins);
  } else if (error instanceof RateLimitError) {
    console.error('Rate limited, retry after:', error.retryAfter);
  } else if (error instanceof ValidationError) {
    console.error('Invalid request:', error.details);
  } else if (error instanceof NetworkError) {
    console.error('Network error:', error.message);
  }
}
```

## Rate Limiting

The SDK automatically handles rate limiting and includes rate limit information in responses:

```typescript
// Check current rate limit status
const rateLimitInfo = client.getRateLimitInfo();
if (rateLimitInfo) {
  console.log('Requests remaining:', rateLimitInfo.remaining);
  console.log('Reset time:', new Date(rateLimitInfo.reset * 1000));
}
```

## Health Monitoring

```typescript
// Check API health
const health = await client.healthCheck();
console.log('API Status:', health.status);
console.log('Services:', health.services);
```

## Best Practices

1. **Use Structured Logging**: Include relevant fields and labels for better searchability
2. **Set Appropriate Log Levels**: Use debug for development, info for normal operations, warn for issues, error for failures
3. **Batch Logs**: Let the SDK batch logs automatically for better performance
4. **Handle Errors**: Always handle potential errors when logging critical information
5. **Use Child Loggers**: Create child loggers for request-scoped or component-scoped logging
6. **Security Events**: Use immediate logging for critical security events
7. **Resource Cleanup**: Call `client.destroy()` when shutting down to cleanup resources

### Frontend Security Best Practices

1. **Configure Origin Restrictions**: Always set allowed origins for API keys used in browser applications
2. **Use Environment-Specific Keys**: Different API keys for development, staging, and production
3. **Minimal Scopes**: Only grant the minimum required permissions (e.g., `logs:write` only)
4. **Handle Origin Errors Gracefully**: Implement proper error handling for `OriginNotAllowedError`
5. **Monitor Usage**: Regularly review API key usage patterns for anomalies
6. **Rotate Keys Regularly**: Periodically rotate API keys, especially if exposed

## Development

```bash
# Install dependencies
npm install

# Build the SDK
npm run build

# Run tests
npm test

# Lint code
npm run lint

# Format code
npm run format

# Pack for local testing (creates .tgz file)
npm run pack:test

# Version management
npm run version:patch  # Bump patch version (1.0.0 -> 1.0.1)
npm run version:minor  # Bump minor version (1.0.0 -> 1.1.0)
npm run version:major  # Bump major version (1.0.0 -> 2.0.0)
```

## Publishing

The SDK is published to both npm and GitHub Packages. Publishing happens automatically when:
- A GitHub release is created
- The publish workflow is manually triggered

Available npm tags:
- `latest` - Stable releases
- `beta` - Beta releases
- `alpha` - Alpha releases  
- `next` - Next/preview releases

## Important Concepts

### Labels vs Fields

**Labels** (low-cardinality, indexed):
- Used for filtering and querying in Grafana
- Should have limited unique values
- Examples: `app`, `environment`, `version`, `region`
- REQUIRED: At least the `app` label must be set for Grafana visibility

**Fields** (high-cardinality, not indexed):
- Additional context for debugging
- Can have many unique values  
- Examples: `user_id`, `request_id`, `duration_ms`

### Querying Logs in Grafana

Once logs are ingested, query them in Grafana using LogQL:

```logql
# All logs from your app
{app="my-app"}

# Filter by environment
{app="my-app", environment="production"}

# Search for errors
{app="my-app"} |= "error"

# Parse JSON and filter
{app="my-app"} | json | user_id="user123"
```

## Troubleshooting

### Logs Not Appearing in Grafana?

1. **Verify API Key**: Ensure your API key is valid and active
2. **Check Endpoint**: Confirm you're using the correct endpoint URL (must end with `/api/v1`)
3. **Validate Labels**: Ensure labels are properly set (at least `app` label is required)
4. **Flush Logs**: Call `logger.flush()` to ensure batched logs are sent
5. **Skip Health Check**: Set `skipConnectionCheck: true` if getting 404 errors on initialization
6. **Enable Debug**: Set `debug: true` in client config to see detailed logging

### Common Issues

| Issue | Cause | Solution |
|-------|-------|----------|
| 404 on health check | Health endpoint not available | Set `skipConnectionCheck: true` |
| Logs not visible in Grafana | Missing labels | Always set at least the `app` label |
| Network/Certificate errors | HTTPS certificate issues | Use HTTP endpoint temporarily |
| Logs not batching | Flush not called | Call `flush()` before program exit |

## Examples

Complete working examples are available:
- `examples/typescript-sdk-test.js` - Basic JavaScript usage of compiled SDK
- `examples/typescript-sdk-example/` - Full TypeScript example project

## License

MIT