src/internal/utils/log.ts (111 lines of code) (raw):
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
import { hasOwn } from './values';
import { type BaseAnthropic } from '../../client';
import { RequestOptions } from '../request-options';
type LogFn = (message: string, ...rest: unknown[]) => void;
export type Logger = {
error: LogFn;
warn: LogFn;
info: LogFn;
debug: LogFn;
};
export type LogLevel = 'off' | 'error' | 'warn' | 'info' | 'debug';
const levelNumbers = {
off: 0,
error: 200,
warn: 300,
info: 400,
debug: 500,
};
export const parseLogLevel = (
maybeLevel: string | undefined,
sourceName: string,
client: BaseAnthropic,
): LogLevel | undefined => {
if (!maybeLevel) {
return undefined;
}
if (hasOwn(levelNumbers, maybeLevel)) {
return maybeLevel;
}
loggerFor(client).warn(
`${sourceName} was set to ${JSON.stringify(maybeLevel)}, expected one of ${JSON.stringify(
Object.keys(levelNumbers),
)}`,
);
return undefined;
};
function noop() {}
function makeLogFn(fnLevel: keyof Logger, logger: Logger | undefined, logLevel: LogLevel) {
if (!logger || levelNumbers[fnLevel] > levelNumbers[logLevel]) {
return noop;
} else {
// Don't wrap logger functions, we want the stacktrace intact!
return logger[fnLevel].bind(logger);
}
}
const noopLogger = {
error: noop,
warn: noop,
info: noop,
debug: noop,
};
let cachedLoggers = new WeakMap<Logger, [LogLevel, Logger]>();
export function loggerFor(client: BaseAnthropic): Logger {
const logger = client.logger;
const logLevel = client.logLevel ?? 'off';
if (!logger) {
return noopLogger;
}
const cachedLogger = cachedLoggers.get(logger);
if (cachedLogger && cachedLogger[0] === logLevel) {
return cachedLogger[1];
}
const levelLogger = {
error: makeLogFn('error', logger, logLevel),
warn: makeLogFn('warn', logger, logLevel),
info: makeLogFn('info', logger, logLevel),
debug: makeLogFn('debug', logger, logLevel),
};
cachedLoggers.set(logger, [logLevel, levelLogger]);
return levelLogger;
}
export const formatRequestDetails = (details: {
options?: RequestOptions | undefined;
headers?: Headers | Record<string, string> | undefined;
retryOfRequestLogID?: string | undefined;
retryOf?: string | undefined;
url?: string | undefined;
status?: number | undefined;
method?: string | undefined;
durationMs?: number | undefined;
message?: unknown;
body?: unknown;
}) => {
if (details.options) {
details.options = { ...details.options };
delete details.options['headers']; // redundant + leaks internals
}
if (details.headers) {
details.headers = Object.fromEntries(
(details.headers instanceof Headers ? [...details.headers] : Object.entries(details.headers)).map(
([name, value]) => [
name,
(
name.toLowerCase() === 'x-api-key' ||
name.toLowerCase() === 'authorization' ||
name.toLowerCase() === 'cookie' ||
name.toLowerCase() === 'set-cookie'
) ?
'***'
: value,
],
),
);
}
if ('retryOfRequestLogID' in details) {
if (details.retryOfRequestLogID) {
details.retryOf = details.retryOfRequestLogID;
}
delete details.retryOfRequestLogID;
}
return details;
};