UNPKG

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
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}`, ], }, }; } }