From 7262f4a174c97c603c8b84735664e7902f106487 Mon Sep 17 00:00:00 2001 From: logfox-agent Date: Mon, 27 Jul 2026 21:36:55 -0400 Subject: [PATCH 1/2] feat: unified ingestLogs RPC with session or API key auth Remove ingestLogsV1 and /v1/ingest direct calls; use ingestLogs RPC with bearer from authToken or apiKey. Point CloudWatch setup at /v1/ingestLogs. --- src/api.ts | 76 ++++++++++---------------------- src/commands/run.ts | 23 ++++------ src/commands/setup-cloudwatch.ts | 2 +- 3 files changed, 33 insertions(+), 68 deletions(-) diff --git a/src/api.ts b/src/api.ts index 36d0a91..98e23c5 100644 --- a/src/api.ts +++ b/src/api.ts @@ -8,24 +8,32 @@ type ApiResponse = { error: string }; +function getBearerToken(): string | undefined { + + const config = getConfig(); + + return config.apiKey ?? config.authToken; + +} + async function apiCall(method: string, body?: unknown): Promise> { const config = getConfig(); + const bearer = getBearerToken(); - if (!config.authToken) { + if (!bearer) { - return {ok: false, error: 'Not logged in. Run: logfox login'}; + return {ok: false, error: 'Not logged in. Run: logfox login or: logfox config set apiKey '}; } try { - // express-typed-rpc expects method name in URL path, args as body const response = await fetch(`${config.apiUrl}/v1/${method}`, { method: 'POST', headers: { 'Content-Type': 'application/json', - 'Authorization': `Bearer ${config.authToken}`, + 'Authorization': `Bearer ${bearer}`, }, body: JSON.stringify(body ?? {}), }); @@ -112,61 +120,25 @@ export type LogEntry = { export type Collector = 'cli' | 'cloudwatch-logs' | 'sdk' | 'vercel' | 'fluentbit'; -export async function ingestLogs(teamId: string, appId: string, env: string, logs: LogEntry[]): Promise> { +function normalizeEnv(env: string): string { - return apiCall('ingestLogs', {teamId, appId, env, logs}); + if (env === 'prod') return 'production'; + if (env === 'dev') return 'development'; + + return env; } -/** - * Ingest logs via the new /v1/ingest endpoint using API key authentication. - * This is the preferred method for all log ingestion going forward. - */ -export async function ingestLogsV1( - apiKey: string, +export async function ingestLogs( appId: string, env: string, - collector: Collector, logs: LogEntry[], -): Promise> { - - const config = getConfig(); - - try { - - const response = await fetch(`${config.apiUrl}/v1/ingest`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${apiKey}`, - }, - body: JSON.stringify({appId, env, collector, logs}), - }); - - if (!response.ok) { - - const text = await response.text(); - return {ok: false, error: `API error: ${response.status} ${text}`}; - - } - - const data = await response.json() as {success: boolean; logsIngested: number; error?: string}; - - if ('error' in data && data.error) { - - return {ok: false, error: data.error}; - - } - - return {ok: true, data}; - - } catch (err) { - - return {ok: false, error: `Request failed: ${err}`}; + collector?: Collector, +): Promise> { - } + return apiCall('ingestLogs', {appId, env: normalizeEnv(env), logs, collector}); } diff --git a/src/commands/run.ts b/src/commands/run.ts index 4f87b3a..588e041 100644 --- a/src/commands/run.ts +++ b/src/commands/run.ts @@ -153,27 +153,20 @@ export async function run(command: string[], options: RunOptions): Promise const logsToSend = logBuffer; logBuffer = []; - // Use API key auth for non-local, or if API key is available - if (config.apiKey) { + const appId = app?.id; - const result = await api.ingestLogsV1(config.apiKey, appName, env, 'cli', logsToSend); + if (!appId) { - if (!result.ok) { + console.error('[logfox] Cannot ingest logs without an app id'); + return; - console.error(`[logfox] Failed to send logs: ${result.error}`); - - } - - } else if (isLocal && config.teamId && app) { - - // Fall back to old auth token method for local - const result = await api.ingestLogs(config.teamId, app.id, 'local', logsToSend); + } - if (!result.ok) { + const result = await api.ingestLogs(appId, env, logsToSend, 'cli'); - console.error(`[logfox] Failed to send logs: ${result.error}`); + if (!result.ok) { - } + console.error(`[logfox] Failed to send logs: ${result.error}`); } diff --git a/src/commands/setup-cloudwatch.ts b/src/commands/setup-cloudwatch.ts index 64d649e..09c55d9 100644 --- a/src/commands/setup-cloudwatch.ts +++ b/src/commands/setup-cloudwatch.ts @@ -263,7 +263,7 @@ export async function setupCloudwatch(): Promise { } const lambdaEnv = { - LOGFOX_ENDPOINT: `${config.apiUrl}/v1/ingest`, + LOGFOX_ENDPOINT: `${config.apiUrl}/v1/ingestLogs`, LOGFOX_API_KEY: apiKey, LOG_GROUP_MAPPINGS: JSON.stringify(logGroupMappings), }; From 1c0a6e21146d17e505a66ab50f6f0034369cc4b7 Mon Sep 17 00:00:00 2001 From: logfox-agent Date: Mon, 27 Jul 2026 21:40:33 -0400 Subject: [PATCH 2/2] fix: prefer session token for RPC calls and restore non-local ingest Use authToken for typed RPC calls and apiKey for ingest only. Restore non-local logfox run by sending app name when no local app record exists. --- src/api.ts | 53 ++++++++++++++++++++++++++++++++++++--------- src/commands/run.ts | 9 +------- 2 files changed, 44 insertions(+), 18 deletions(-) diff --git a/src/api.ts b/src/api.ts index 98e23c5..aba4277 100644 --- a/src/api.ts +++ b/src/api.ts @@ -8,18 +8,10 @@ type ApiResponse = { error: string }; -function getBearerToken(): string | undefined { - - const config = getConfig(); - - return config.apiKey ?? config.authToken; - -} - async function apiCall(method: string, body?: unknown): Promise> { const config = getConfig(); - const bearer = getBearerToken(); + const bearer = config.authToken; if (!bearer) { @@ -139,6 +131,47 @@ export async function ingestLogs( logsIngested: number }>> { - return apiCall('ingestLogs', {appId, env: normalizeEnv(env), logs, collector}); + const config = getConfig(); + const bearer = config.apiKey ?? config.authToken; + + if (!bearer) { + + return {ok: false, error: 'Not logged in. Run: logfox login or: logfox config set apiKey '}; + + } + + try { + + const response = await fetch(`${config.apiUrl}/v1/ingestLogs`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${bearer}`, + }, + body: JSON.stringify({appId, env: normalizeEnv(env), logs, collector}), + }); + + if (!response.ok) { + + const text = await response.text(); + return {ok: false, error: `API error: ${response.status} ${text}`}; + + } + + const data = await response.json() as {success: boolean; logsIngested: number; error?: string}; + + if ('error' in data && data.error) { + + return {ok: false, error: data.error}; + + } + + return {ok: true, data}; + + } catch (err) { + + return {ok: false, error: `Request failed: ${err}`}; + + } } diff --git a/src/commands/run.ts b/src/commands/run.ts index 588e041..2941688 100644 --- a/src/commands/run.ts +++ b/src/commands/run.ts @@ -153,14 +153,7 @@ export async function run(command: string[], options: RunOptions): Promise const logsToSend = logBuffer; logBuffer = []; - const appId = app?.id; - - if (!appId) { - - console.error('[logfox] Cannot ingest logs without an app id'); - return; - - } + const appId = app?.id ?? appName; const result = await api.ingestLogs(appId, env, logsToSend, 'cli');