diff --git a/.github/workflows/run_js_test.yml b/.github/workflows/run_js_test.yml index fd3d1b534ab..c1b345add38 100644 --- a/.github/workflows/run_js_test.yml +++ b/.github/workflows/run_js_test.yml @@ -37,7 +37,7 @@ jobs: - name: Upload `coverage` folder to R2 if: ${{ always() }} - uses: ryand56/r2-upload-action@latest + uses: himanshu-dixit/r2-upload-action-parallel@v1.3 with: r2-account-id: ${{ secrets.R2_ACCOUNT_ID }} r2-access-key-id: ${{ secrets.R2_ACCESS_KEY_ID }} @@ -53,7 +53,7 @@ jobs: - name: Upload jest html-reporters folder to R2 if: ${{ always() }} - uses: ryand56/r2-upload-action@latest + uses: himanshu-dixit/r2-upload-action-parallel@v1.3 with: r2-account-id: ${{ secrets.R2_ACCOUNT_ID }} r2-access-key-id: ${{ secrets.R2_ACCESS_KEY_ID }} diff --git a/js/src/env/factory.ts b/js/src/env/factory.ts index ed77a1bc4b5..e1922da69e3 100644 --- a/js/src/env/factory.ts +++ b/js/src/env/factory.ts @@ -27,7 +27,14 @@ export class WorkspaceFactory { if (this.workspace) { return; } - logger.debug(`Creating workspace with env=${this.workspaceConfig.env} and kwargs=${JSON.stringify(this.workspaceConfig.config)}`); + + const sanitizedConfig = { + ...this.workspaceConfig, + host: this.workspaceConfig.config.composioBaseURL, + composioAPIKey: this.workspaceConfig.config.composioAPIKey ? "REDACTED" : "NOT DEFINED" + }; + logger.debug("Creating workspace with config", sanitizedConfig); + let workspace: Workspace | null = null; switch (this.workspaceConfig.env) { case ExecEnv.DOCKER: diff --git a/js/src/sdk/base.toolset.ts b/js/src/sdk/base.toolset.ts index 22e4ccadac9..a5b8c339642 100644 --- a/js/src/sdk/base.toolset.ts +++ b/js/src/sdk/base.toolset.ts @@ -171,7 +171,6 @@ export class ComposioToolSet { } return true; }).map((action: any) => { - console.log("Action is", action); return action.schema; }); diff --git a/js/src/sdk/index.ts b/js/src/sdk/index.ts index c1e3a9a5748..c2a57b00775 100644 --- a/js/src/sdk/index.ts +++ b/js/src/sdk/index.ts @@ -17,6 +17,7 @@ import { CEG, ERROR } from './utils/error'; import { ActionProxyRequestConfigDTO } from './client'; import apiClient from '../sdk/client/client'; import { GetConnectorInfoResDTO } from './client'; +import logger from '../utils/logger'; export class Composio { /** @@ -44,7 +45,8 @@ export class Composio { // // Parse the base URL and API key, falling back to environment variables or defaults if not provided. const { baseURL: baseURLParsed, apiKey: apiKeyParsed } = getClientBaseConfig(baseUrl, apiKey); - console.log("Using API Key: ", apiKeyParsed , "and baseURL: ", baseURLParsed); + + logger.info(`Using API Key: [REDACTED] and baseURL: ${baseURLParsed}`); if(!apiKeyParsed){ CEG.throwCustomError(ERROR.COMMON.API_KEY_UNAVAILABLE,{}); diff --git a/js/src/sdk/models/Entity.ts b/js/src/sdk/models/Entity.ts index 79cacf114be..4f31b5b69f3 100644 --- a/js/src/sdk/models/Entity.ts +++ b/js/src/sdk/models/Entity.ts @@ -7,6 +7,7 @@ import { ConnectedAccounts } from "./connectedAccounts"; import { BackendClient } from "./backendClient"; import { Triggers } from "./triggers"; import { CEG } from "../utils/error"; +import logger from "../../utils/logger"; const LABELS = { PRIMARY: "primary", @@ -243,13 +244,13 @@ export class Entity { if (!isTestConnectorAvailable && app.no_auth === false) { if (!authMode) { // @ts-ignore - console.log( + logger.info( "Auth schemes not provided, available auth schemes and authConfig" ); // @ts-ignore for (const authScheme of app.auth_schemes) { // @ts-ignore - console.log( + logger.info( "autheScheme:", authScheme.name, "\n", diff --git a/js/src/sdk/models/backendClient.ts b/js/src/sdk/models/backendClient.ts index 946fec1ade8..132d560c912 100644 --- a/js/src/sdk/models/backendClient.ts +++ b/js/src/sdk/models/backendClient.ts @@ -1,5 +1,6 @@ import apiClient from "../client/client" -import { client as axiosClient, client } from "../client/services.gen" +import { client as axiosClient } from "../client/services.gen" +import { setAxiosClientConfig } from "../utils/config"; import { CEG } from "../utils/error"; /** @@ -74,5 +75,6 @@ export class BackendClient { throwOnError: true }); + setAxiosClientConfig(axiosClient.instance); } } diff --git a/js/src/sdk/utils/config.ts b/js/src/sdk/utils/config.ts index 718342f6bcb..897ee9cfe5b 100644 --- a/js/src/sdk/utils/config.ts +++ b/js/src/sdk/utils/config.ts @@ -5,6 +5,8 @@ import { getEnvVariable } from "../../utils/shared"; import { client as axiosClient } from "../client/services.gen" import apiClient from "../client/client" +import axios, { AxiosInstance, AxiosRequestConfig } from 'axios'; +import logger from '../../utils/logger'; // Constants @@ -12,6 +14,34 @@ const LOCAL_CACHE_DIRECTORY_NAME = '.composio'; const USER_DATA_FILE_NAME = 'user_data.json'; const DEFAULT_BASE_URL = "https://backend.composio.dev"; +export const setAxiosClientConfig = (axiosClientInstance: AxiosInstance) => { + + axiosClientInstance.interceptors.request.use( + (request) => { + logger.debug(`API Req [${request.method?.toUpperCase()}] ${request.url}`, { + data: request.data + }); + return request; + } + ); + axiosClientInstance.interceptors.response.use( + (response) => { + const responseSize = Math.round(JSON.stringify(response.data).length / 1024); + logger.debug(`API Res [${response.status}] ${response.config.method?.toUpperCase()} ${response.config.url} ${responseSize} KB`); + return response; + }, + (error) => { + logger.debug(`API Error [${error.response?.status || 'Unknown'}] ${error.config?.method?.toUpperCase()} ${error.config?.url}`, { + headers: error.response?.headers, + data: error.response?.data, + error: error.message + }); + return Promise.reject(error); + } + ); + +} + export const userDataPath = () => path.join(os.homedir(), LOCAL_CACHE_DIRECTORY_NAME, USER_DATA_FILE_NAME); export const getUserDataJson = () => { @@ -46,6 +76,8 @@ export function getAPISDK(baseUrl?: string, apiKey?: string) { throwOnError: true }); + setAxiosClientConfig(axiosClient.instance); + return apiClient; } @@ -65,8 +97,8 @@ export const writeToFile = (filePath: string, data: any) => { // Write the data to the file as a formatted JSON string fs.writeFileSync(filePath, JSON.stringify(data, null, 2)); }catch(error){ - console.error("Oops! We couldn't save your settings. Here's why:", (error as Error).message); - console.log("Need help? Check file permissions for file:", filePath); + logger.error("Oops! We couldn't save your settings. Here's why:", (error as Error).message); + logger.info("Need help? Check file permissions for file:", filePath); } } diff --git a/js/src/utils/logger.ts b/js/src/utils/logger.ts index b0e78879dfc..c72fbbc2360 100644 --- a/js/src/utils/logger.ts +++ b/js/src/utils/logger.ts @@ -1,65 +1,58 @@ import { getEnvVariable } from './shared'; +import winston from 'winston'; const levels = { - error: 'ERROR', - warn: 'WARN', - info: 'INFO', - debug: 'DEBUG' + error: 0, + warn: 1, + info: 2, + debug: 3 } as const; const colors = { - red: (str: string) => `\x1b[31m${str}\x1b[0m`, - yellow: (str: string) => `\x1b[33m${str}\x1b[0m`, - blue: (str: string) => `\x1b[34m${str}\x1b[0m`, - green: (str: string) => `\x1b[32m${str}\x1b[0m`, - gray: (str: string) => `\x1b[90m${str}\x1b[0m` -}; - -/** - * Colorize log level and timestamp. - * @param {string} level - The log level. - * @param {string} timestamp - The timestamp. - * @returns {Object} - Object containing colored level and timestamp. - */ -const colorize = (level: keyof typeof levels, timestamp: string): { level: string; timestamp: string } => { - switch (level) { - case 'error': - return { level: colors.red(levels[level]), timestamp: colors.gray(timestamp) }; - case 'warn': - return { level: colors.yellow(levels[level]), timestamp: colors.gray(timestamp) }; - case 'info': - return { level: colors.blue(levels[level]), timestamp: colors.gray(timestamp) }; - case 'debug': - return { level: colors.green(levels[level]), timestamp: colors.gray(timestamp) }; - default: - return { level, timestamp }; - } + error: 'red', + warn: 'yellow', + info: 'blue', + debug: 'green' }; /** * Get the current log level from environment variables. - * @returns {string} - The current log level. + * @returns {keyof typeof levels} - The current log level. */ const getLogLevel = (): keyof typeof levels => { - const envLevel = getEnvVariable("COMPOSIO_DEBUG", "0"); - return envLevel === "1" ? 'debug' : 'info'; + const envLevel = getEnvVariable("COMPOSIO_LOGGING_LEVEL", "info"); + if (!envLevel || !(envLevel in levels)) { + return 'info'; // Default to info instead of warn to match default param + } + return envLevel as keyof typeof levels; }; -/** - * Logger utility to log messages with different levels. - */ -const logger = { +winston.addColors(colors); + +const format = winston.format.combine( + winston.format.timestamp(), + winston.format.colorize(), + winston.format.printf(({ timestamp, level, message, ...meta }) => { + let metaString = ''; + if (Object.keys(meta).length) { + try { + metaString = ` - ${JSON.stringify(meta)}`; + } catch (err) { + metaString = ` - [Meta object contains circular reference]`; + } + } + const parsedTimestamp = timestamp.slice(0, 19).replace('T', ' '); + return `[${level}] : ${parsedTimestamp} - ${message}${metaString}`; + }) +); + +const logger = winston.createLogger({ level: getLogLevel(), - log: (level: keyof typeof levels, message: string, meta?: any): void => { - const timestamp = new Date().toLocaleString(); - const { level: coloredLevel, timestamp: coloredTimestamp } = colorize(level, timestamp); - const metaInfo = meta ? ` - ${JSON.stringify(meta)}` : ''; - console.log(`[${coloredLevel}] ${coloredTimestamp} ${message}${metaInfo}`); - }, - error: (message: string, meta?: any): void => logger.log('error', message, meta), - warn: (message: string, meta?: any): void => logger.log('warn', message, meta), - info: (message: string, meta?: any): void => logger.log('info', message, meta), - debug: (message: string, meta?: any): void => logger.log('debug', message, meta) -}; + levels, + format, + transports: [ + new winston.transports.Console() + ] +}); export default logger;