Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

⚙️ refactor: Enhance Logging, Navigation And Error Handling #5910

Merged
merged 7 commits into from
Feb 16, 2025
56 changes: 24 additions & 32 deletions api/utils/axios.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,40 +5,32 @@ const { logger } = require('~/config');
*
* @param {Object} options - The options object.
* @param {string} options.message - The custom message to be logged.
* @param {Error} options.error - The Axios error object.
* @param {import('axios').AxiosError} options.error - The Axios error object.
*/
const logAxiosError = ({ message, error }) => {
const timedOutMessage = 'Cannot read properties of undefined (reading \'status\')';
if (error.response) {
logger.error(
`${message} The request was made and the server responded with a status code that falls out of the range of 2xx: ${
error.message ? error.message : ''
}. Error response data:\n`,
{
headers: error.response?.headers,
status: error.response?.status,
data: error.response?.data,
},
);
} else if (error.request) {
logger.error(
`${message} The request was made but no response was received: ${
error.message ? error.message : ''
}. Error Request:\n`,
{
request: error.request,
},
);
} else if (error?.message?.includes(timedOutMessage)) {
logger.error(
`${message}\nThe request either timed out or was unsuccessful. Error message:\n`,
error,
);
} else {
logger.error(
`${message}\nSomething happened in setting up the request. Error message:\n`,
error,
);
try {
if (error.response?.status) {
const { status, headers, data } = error.response;
logger.error(`${message} The server responded with status ${status}: ${error.message}`, {
status,
headers,
data,
});
} else if (error.request) {
const { method, url } = error.config || {};
logger.error(
`${message} No response received for ${method ? method.toUpperCase() : ''} ${url || ''}: ${error.message}`,
{ requestInfo: { method, url } },
);
} else if (error?.message?.includes('Cannot read properties of undefined (reading \'status\')')) {
logger.error(
`${message} It appears the request timed out or was unsuccessful: ${error.message}`,
);
} else {
logger.error(`${message} An error occurred while setting up the request: ${error.message}`);
}
} catch (err) {
logger.error(`Error in logAxiosError: ${err.message}`);
}
};

Expand Down
12 changes: 8 additions & 4 deletions client/src/components/Chat/Input/HeaderOptions.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
import { useRecoilState } from 'recoil';
import { Settings2 } from 'lucide-react';
import { Root, Anchor } from '@radix-ui/react-popover';
import { useState, useEffect, useMemo } from 'react';
import { tConvoUpdateSchema, EModelEndpoint, isParamEndpoint } from 'librechat-data-provider';
import { Root, Anchor } from '@radix-ui/react-popover';
import {
EModelEndpoint,
isParamEndpoint,
isAgentsEndpoint,
tConvoUpdateSchema,
} from 'librechat-data-provider';
import type { TPreset, TInterfaceConfig } from 'librechat-data-provider';
import { EndpointSettings, SaveAsPresetDialog, AlternativeSettings } from '~/components/Endpoints';
import { PluginStoreDialog, TooltipAnchor } from '~/components';
Expand Down Expand Up @@ -42,7 +47,6 @@ export default function HeaderOptions({
if (endpoint && noSettings[endpoint]) {
setShowPopover(false);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [endpoint, noSettings]);

const saveAsPreset = () => {
Expand All @@ -67,7 +71,7 @@ export default function HeaderOptions({
<div className="my-auto lg:max-w-2xl xl:max-w-3xl">
<span className="flex w-full flex-col items-center justify-center gap-0 md:order-none md:m-auto md:gap-2">
<div className="z-[61] flex w-full items-center justify-center gap-2">
{interfaceConfig?.modelSelect === true && (
{interfaceConfig?.modelSelect === true && !isAgentsEndpoint(endpoint) && (
<ModelSelect
conversation={conversation}
setOption={setOption}
Expand Down
16 changes: 8 additions & 8 deletions client/src/hooks/Conversations/useNavigateToConvo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { useNavigate } from 'react-router-dom';
import { useQueryClient } from '@tanstack/react-query';
import { QueryKeys, EModelEndpoint, LocalStorageKeys, Constants } from 'librechat-data-provider';
import type { TConversation, TEndpointsConfig, TModelsConfig } from 'librechat-data-provider';
import { buildDefaultConvo, getDefaultEndpoint, getEndpointField } from '~/utils';
import { buildDefaultConvo, getDefaultEndpoint, getEndpointField, logger } from '~/utils';
import store from '~/store';

const useNavigateToConvo = (index = 0) => {
Expand All @@ -20,7 +20,7 @@ const useNavigateToConvo = (index = 0) => {
invalidateMessages = false,
) => {
if (!conversation) {
console.log('Conversation not provided');
logger.warn('conversation', 'Conversation not provided to `navigateToConvo`');
return;
}
hasSetConversation.current = true;
Expand All @@ -34,10 +34,10 @@ const useNavigateToConvo = (index = 0) => {
}

let convo = { ...conversation };
if (!convo.endpoint) {
/* undefined endpoint edge case */
const endpointsConfig = queryClient.getQueryData<TEndpointsConfig>([QueryKeys.endpoints]);
if (!convo.endpoint || !endpointsConfig?.[convo.endpoint]) {
/* undefined/removed endpoint edge case */
const modelsConfig = queryClient.getQueryData<TModelsConfig>([QueryKeys.models]);
const endpointsConfig = queryClient.getQueryData<TEndpointsConfig>([QueryKeys.endpoints]);
const defaultEndpoint = getDefaultEndpoint({
convoSetup: conversation,
endpointsConfig,
Expand All @@ -51,10 +51,10 @@ const useNavigateToConvo = (index = 0) => {
const models = modelsConfig?.[defaultEndpoint ?? ''] ?? [];

convo = buildDefaultConvo({
models,
conversation,
endpoint: defaultEndpoint,
lastConversationSetup: conversation,
models,
});
}
clearAllConversations(true);
Expand All @@ -68,7 +68,7 @@ const useNavigateToConvo = (index = 0) => {
invalidateMessages?: boolean,
) => {
if (!conversation) {
console.log('Conversation not provided');
logger.warn('conversation', 'Conversation not provided to `navigateToConvo`');
return;
}
// set conversation to the new conversation
Expand All @@ -78,7 +78,7 @@ const useNavigateToConvo = (index = 0) => {
lastSelectedTools =
JSON.parse(localStorage.getItem(LocalStorageKeys.LAST_TOOLS) ?? '') ?? [];
} catch (e) {
// console.error(e);
logger.error('conversation', 'Error parsing last selected tools', e);
}
const hasTools = (conversation.tools?.length ?? 0) > 0;
navigateToConvo(
Expand Down
8 changes: 4 additions & 4 deletions client/src/utils/buildDefaultConvo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@ import type { TConversation } from 'librechat-data-provider';
import { getLocalStorageItems } from './localStorage';

const buildDefaultConvo = ({
models,
conversation,
endpoint = null,
models,
lastConversationSetup,
}: {
conversation: TConversation;
endpoint: EModelEndpoint | null;
models: string[];
conversation: TConversation;
endpoint?: EModelEndpoint | null;
lastConversationSetup: TConversation | null;
}): TConversation => {
const { lastSelectedModel, lastSelectedTools } = getLocalStorageItems();
Expand All @@ -33,7 +33,7 @@ const buildDefaultConvo = ({
const model = lastConversationSetup?.model ?? lastSelectedModel?.[endpoint] ?? '';
const secondaryModel: string | null =
endpoint === EModelEndpoint.gptPlugins
? lastConversationSetup?.agentOptions?.model ?? lastSelectedModel?.secondaryModel ?? null
? (lastConversationSetup?.agentOptions?.model ?? lastSelectedModel?.secondaryModel ?? null)
: null;

let possibleModels: string[], secondaryModels: string[];
Expand Down
21 changes: 13 additions & 8 deletions client/src/utils/logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,17 @@ const loggerFilter = import.meta.env.VITE_LOGGER_FILTER || '';

type LogFunction = (...args: unknown[]) => void;

const createLogFunction = (consoleMethod: LogFunction): LogFunction => {
const createLogFunction = (
consoleMethod: LogFunction,
type?: 'log' | 'warn' | 'error' | 'info' | 'debug' | 'dir',
): LogFunction => {
return (...args: unknown[]) => {
if (isDevelopment || isLoggerEnabled) {
const tag = typeof args[0] === 'string' ? args[0] : '';
if (shouldLog(tag)) {
if (tag && args.length > 1) {
if (tag && typeof args[1] === 'string' && type === 'error') {
consoleMethod(`[${tag}] ${args[1]}`, ...args.slice(2));
} else if (tag && args.length > 1) {
consoleMethod(`[${tag}]`, ...args.slice(1));
} else {
consoleMethod(...args);
Expand All @@ -20,12 +25,12 @@ const createLogFunction = (consoleMethod: LogFunction): LogFunction => {
};

const logger = {
log: createLogFunction(console.log),
warn: createLogFunction(console.warn),
error: createLogFunction(console.error),
info: createLogFunction(console.info),
debug: createLogFunction(console.debug),
dir: createLogFunction(console.dir),
log: createLogFunction(console.log, 'log'),
dir: createLogFunction(console.dir, 'dir'),
warn: createLogFunction(console.warn, 'warn'),
info: createLogFunction(console.info, 'info'),
error: createLogFunction(console.error, 'error'),
debug: createLogFunction(console.debug, 'debug'),
};

function shouldLog(tag: string): boolean {
Expand Down
Loading