ds-sfcoe-ailabs
Version:
AI-powered code review tool with static analysis integration for comprehensive code quality assessment.
529 lines (528 loc) • 17.8 kB
JavaScript
import * as process from 'node:process';
import { DatadogLogger, } from '../observability/logging/index.js';
import { DatadogMetrics, } from '../observability/metrics/index.js';
import { DATADOG_DEFAULT_CONSTANTS } from './constants.js';
/**
* Centralized logger utility for debug and error logging
* This serves as a facade to the Datadog logger backend
*
* Provides static methods for logging at different levels with structured
* metadata support and error handling capabilities.
*
* @example
* ```typescript
* // Initialize logger first
* Logger.initialize(datadogConfig);
*
* // Basic logging
* Logger.info('Application started');
* Logger.error('Failed to process request', { userId: 123 });
*
* // With error object
* Logger.error('Database connection failed', error);
* ```
*/
export class Logger {
static initialized = false;
/**
* Initialize the logger with Datadog configuration
*
* @param config - The Datadog logger configuration object
* @returns The initialized Winston logger instance
* @example
* ```typescript
* const config = {
* apiKey: 'your-datadog-api-key',
* service: 'ai-pr-review',
* environment: 'production'
* };
* const logger = Logger.initialize(config);
* ```
*/
static initialize(config) {
if (this.initialized) {
return DatadogLogger.getInstance();
}
const logger = DatadogLogger.initialize(config);
this.initialized = true;
return logger;
}
/**
* Check if logger is initialized
*
* @returns True if the logger is initialized, false otherwise
*/
static isInitialized() {
return this.initialized;
}
/**
* Log debug messages (only when debug is enabled)
*
* @param message - The debug message to log
* @param args - Additional arguments to include in the log metadata
* ```typescript
* Logger.debug('User logged in', { userId: '123', action: 'login' });
* Logger.debug('Process completed successfully');
* ```
*/
static debug(message, ...args) {
const meta = this.argsToMeta(args);
DatadogLogger.debug(message, meta);
}
/**
* Log info messages for general application information
*
* @param message - The informational message to log
* @param args - Additional arguments to include in the log metadata
* @example
* ```typescript
* Logger.info('User logged in', { userId: '123', action: 'login' });
* Logger.info('Process completed successfully');
* ```
*/
static info(message, ...args) {
const meta = this.argsToMeta(args);
DatadogLogger.info(message, meta);
}
/**
* Log warning messages for potential issues
*
* @param message - The warning message to log
* @param args - Additional arguments to include in the log metadata
* @example
* ```typescript
* Logger.warn('Rate limit approaching', { currentRate: 90, limit: 100 });
* Logger.warn('Deprecated API usage detected');
* ```
*/
static warn(message, ...args) {
const meta = this.argsToMeta(args);
DatadogLogger.warn(message, meta);
}
/**
* Log error messages for application errors and exceptions
*
* @param message - The error message to log
* @param args - Additional arguments to include in the log metadata (Error objects are handled specially)
* @example
* ```typescript
* // Log with error object
* Logger.error('Database connection failed', new Error('Connection timeout'));
*
* // Log with metadata
* Logger.error('Validation failed', { field: 'email', value: 'invalid-email' });
* ```
*/
static error(message, ...args) {
const meta = this.argsToMeta(args);
// Handle error objects in the args
const errorArg = args.find((arg) => arg instanceof Error);
if (errorArg) {
DatadogLogger.error(message, errorArg);
}
else {
DatadogLogger.error(message, meta);
}
}
/**
* Log with custom level and metadata (backward compatibility)
*
* @param level - The log level (debug, info, warn, error, etc.)
* @param message - The message to log
* @param args - Additional arguments to include in the log metadata
* @example
* ```typescript
* Logger.log('info', 'Custom log entry', { customField: 'value' });
* Logger.log('debug', 'Debug information', debugData);
* ```
*/
static log(level, message, ...args) {
const meta = this.argsToMeta(args);
DatadogLogger.log(level, message, meta);
}
/**
* Convert variadic arguments to metadata object for structured logging
*
* @param args - Array of arguments to convert to metadata
* @returns Metadata object with structured data for logging
* @example
* ```typescript
* const meta = Logger.argsToMeta([{ userId: '123' }, 'extra data']);
* // Returns: { userId: '123', data: 'extra data' }
* ```
*/
static argsToMeta(args) {
if (args.length === 0) {
return {};
}
// If first argument is already an object, use it
if (args.length === 1 &&
typeof args[0] === 'object' &&
args[0] !== null &&
!Array.isArray(args[0])) {
return args[0];
}
// Convert args to indexed object
const meta = {};
args.forEach((arg) => {
if (arg instanceof Error) {
meta['error'] = {
message: arg.message,
stack: arg.stack,
name: arg.name,
};
}
else {
meta['data'] = arg;
}
});
return meta;
}
}
/**
* Metrics utility class for tracking application performance and usage statistics
* Provides a facade for Datadog metrics collection with centralized configuration
*
* @example
* ```typescript
* // Initialize metrics
* const config = {
* apiKey: 'your-datadog-api-key',
* host: 'localhost',
* prefix: 'ai-pr-review.',
* globalTags: ['service:pr-review', 'env:production']
* };
* Metrics.initialize(config);
*
* // Track counters
* Metrics.increment('review.requests', 1, ['operation:full-review']);
*
* // Track timing
* Metrics.timing('review.duration', 1500, ['status:success']);
* ```
*/
export class Metrics {
static initialized = false;
/**
* Initialize the metrics system with Datadog configuration
*
* @param config - The Datadog metrics configuration object
* @example
* ```typescript
* const config = {
* apiKey: 'your-datadog-api-key',
* host: 'localhost',
* prefix: 'ai-pr-review.',
* globalTags: ['service:pr-review']
* };
* Metrics.initialize(config);
* ```
*/
static initialize(config) {
if (this.initialized) {
return;
}
DatadogMetrics.initialize(config);
this.initialized = true;
}
/**
* Check if metrics system is initialized
*
* @returns True if the metrics system is initialized, false otherwise
*/
static isInitialized() {
return this.initialized;
}
/**
* Send a counter metric to increment a value
*
* @param metric - The metric name to increment
* @param value - The value to increment by (defaults to 1)
* @param tags - Optional array of tags to attach to the metric
* @example
* ```typescript
* // Simple increment
* Metrics.increment('api.requests');
*
* // Increment by specific value with tags
* Metrics.increment('user.actions', 5, ['action:login', 'status:success']);
* ```
*/
static increment(metric, value = 1, tags = []) {
DatadogMetrics.increment(metric, value, tags);
}
/**
* Send a decrement metric to decrease a value
*
* @param metric - The metric name to decrement
* @param value - The value to decrement by (defaults to 1)
* @param tags - Optional array of tags to attach to the metric
* @example
* ```typescript
* // Simple decrement
* Metrics.decrement('queue.size');
*
* // Decrement by specific value with tags
* Metrics.decrement('active.connections', 3, ['service:database']);
* ```
*/
static decrement(metric, value = 1, tags = []) {
DatadogMetrics.decrement(metric, value, tags);
}
/**
* Send a gauge metric to record a specific value at a point in time
*
* @param metric - The metric name for the gauge
* @param value - The numeric value to record
* @param tags - Optional array of tags to attach to the metric
* @example
* ```typescript
* // Record current memory usage
* Metrics.gauge('memory.usage', 75.5, ['unit:percentage']);
*
* // Record queue depth
* Metrics.gauge('queue.depth', 42, ['queue:processing']);
* ```
*/
static gauge(metric, value, tags = []) {
DatadogMetrics.gauge(metric, value, tags);
}
/**
* Send a histogram metric to track the distribution of values
*
* @param metric - The metric name for the histogram
* @param value - The numeric value to add to the histogram
* @param tags - Optional array of tags to attach to the metric
* @example
* ```typescript
* // Track response time distribution
* Metrics.histogram('api.response_time', 250, ['endpoint:users']);
*
* // Track file size distribution
* Metrics.histogram('file.size', 1024, ['type:image']);
* ```
*/
static histogram(metric, value, tags = []) {
DatadogMetrics.histogram(metric, value, tags);
}
/**
* Send a timing metric to measure duration of operations
*
* @param metric - The metric name for the timing measurement
* @param value - The duration in milliseconds
* @param tags - Optional array of tags to attach to the metric
* @example
* ```typescript
* // Measure API call duration
* const start = Date.now();
* // ... perform operation ...
* Metrics.timing('api.duration', Date.now() - start, ['operation:create']);
*
* // Measure with predefined duration
* Metrics.timing('process.time', 1500, ['stage:validation']);
* ```
*/
static timing(metric, value, tags = []) {
DatadogMetrics.timing(metric, value, tags);
}
/**
* Send a distribution metric for statistical analysis
*
* @param metric - The metric name for the distribution
* @param value - The numeric value to add to the distribution
* @param tags - Optional array of tags to attach to the metric
* @example
* ```typescript
* // Track score distribution
* Metrics.distribution('review.score', 8.5, ['reviewer:ai']);
*
* // Track latency distribution
* Metrics.distribution('network.latency', 125, ['region:us-east']);
* ```
*/
static distribution(metric, value, tags = []) {
DatadogMetrics.distribution(metric, value, tags);
}
/**
* Send a set metric to count unique occurrences
*
* @param metric - The metric name for the set
* @param value - The unique value to add to the set (string or number)
* @param tags - Optional array of tags to attach to the metric
* @example
* ```typescript
* // Track unique users
* Metrics.set('users.unique', 'user123', ['action:login']);
*
* // Track unique IP addresses
* Metrics.set('ips.unique', '192.168.1.1', ['endpoint:api']);
* ```
*/
static set(metric, value, tags = []) {
DatadogMetrics.set(metric, value, tags);
}
/**
* Flush buffered metrics immediately to Datadog
*
* @example
* ```typescript
* // Send all pending metrics immediately
* Metrics.flush();
* ```
*/
static flush() {
DatadogMetrics.flush();
}
/**
* Close the metrics client and clean up resources
*
* @example
* ```typescript
* // Clean shutdown of metrics client
* Metrics.close();
* ```
*/
static close() {
DatadogMetrics.close();
}
}
/**
* Utility class to initialize observability components (logging and metrics)
* Provides centralized configuration and setup for Datadog integration
*
* @example
* ```typescript
* // Initialize with command flags
* const flags = {
* 'pull-request-id': '123',
* 'ai-provider': 'OpenAI',
* 'repo-dir': '/path/to/repo'
* };
* ObservabilityInitializer.initialize(flags);
*
* // Check initialization status
* if (ObservabilityInitializer.isInitialized()) {
* Logger.info('Observability is ready');
* Metrics.increment('app.startup');
* }
* ```
*/
export class ObservabilityInitializer {
static initialized = false;
/**
* Initialize both logging and metrics with provided configuration
*
* @param flags Record of command flags that will be included in logs and metrics.
* The following flags are included by default:
* - pull-request-id: Added as pullRequestId in logs and pull-request-id tag in metrics
* - repo-dir: Added as repoDir in logs and repo-dir tag in metrics
* - from: Added as from in logs and from tag in metrics
* - to: Added as to in logs and to tag in metrics
* - ai-provider: Added as aiProvider in logs and ai-provider tag in metrics
* - ai-model: Added as aiModel in logs and ai-model tag in metrics
* - git-provider: Added as gitProvider in logs and git-provider tag in metrics
* - git-owner: Added as gitOwner in logs and git-owner tag in metrics
* - git-repo: Added as gitRepo in logs and git-repo tag in metrics
*
* @example
* ```typescript
* // Basic usage with command flags
* ObservabilityInitializer.initialize(flags);
*
* // Usage with custom flags object
* ObservabilityInitializer.initialize({
* 'pull-request-id': '123',
* 'ai-provider': 'OpenAI',
* 'custom-flag': 'custom-value' // This won't be included unless added to default list
* });
* ```
*/
static initialize(flags) {
if (this.initialized) {
return;
}
// Build configuration
const config = this.buildConfigFromEnv(flags);
// Initialize logger
Logger.initialize(config.logger);
// Initialize metrics
Metrics.initialize(config.metrics);
// Mark as initialized
this.initialized = true;
}
/**
* Check if observability is initialized
*
* @returns True if both logging and metrics are initialized, false otherwise
* @example
* ```typescript
* if (!ObservabilityInitializer.isInitialized()) {
* ObservabilityInitializer.initialize(flags);
* }
* ```
*/
static isInitialized() {
return this.initialized;
}
/**
* Get default flags to include in observability metadata
*
* @returns Array of flag names that are included by default in logs and metrics
* @example
* ```typescript
* const defaultFlags = ObservabilityInitializer.getDefaultFlagsToInclude();
* console.log(defaultFlags); // ['pull-request-id', 'repo-dir', 'ai-provider', ...]
* ```
*/
static getDefaultFlagsToInclude() {
return [...DATADOG_DEFAULT_CONSTANTS.observabilityFlags];
}
/**
* Build configuration from environment variables and flags with custom flag inclusion
*
* @param flags - Optional record of command flags to include in observability metadata
* @returns Configuration object for both logging and metrics systems
* @example
* ```typescript
* const config = ObservabilityInitializer.buildConfigFromEnv({
* 'pull-request-id': '123',
* 'ai-provider': 'OpenAI'
* });
* // Returns { logger: {...}, metrics: {...} }
* ```
*/
static buildConfigFromEnv(flags) {
const flagsToInclude = this.getDefaultFlagsToInclude();
// Build custom metadata from flags
const customMeta = {};
if (flags) {
for (const flagKey of flagsToInclude) {
const flagValue = flags[flagKey];
if (flagValue !== undefined && flagValue !== null && flagValue !== '') {
customMeta[flagKey] = flagValue;
}
}
}
return {
logger: {
apiKey: process.env.DD_API_KEY ?? '',
ddsource: process.env.DD_SOURCE ?? DATADOG_DEFAULT_CONSTANTS.ddsource,
hostname: process.env.DD_HOSTNAME ?? DATADOG_DEFAULT_CONSTANTS.hostname,
service: process.env.DD_SERVICE ?? DATADOG_DEFAULT_CONSTANTS.service,
environment: process.env.DD_ENV ?? DATADOG_DEFAULT_CONSTANTS.environment,
logLevel: process.env.LOG_LEVEL ?? DATADOG_DEFAULT_CONSTANTS.logLevel,
customMeta,
},
metrics: {
apiKey: process.env.DD_API_KEY ?? '',
host: process.env.DD_HOSTNAME ?? DATADOG_DEFAULT_CONSTANTS.hostname,
prefix: `${process.env.DD_METRIC_PREFIX ?? DATADOG_DEFAULT_CONSTANTS.prefix}.`,
globalTags: [
`source:${process.env.DD_SOURCE ?? DATADOG_DEFAULT_CONSTANTS.ddsource}`,
`service:${process.env.DD_SERVICE ?? DATADOG_DEFAULT_CONSTANTS.service}`,
`env:${DATADOG_DEFAULT_CONSTANTS.environment}`,
],
},
};
}
}