feat: log generation requests

This commit is contained in:
Oleg Proskurin 2025-10-06 23:25:20 +07:00
parent 13e8824000
commit 367f1d1946
4 changed files with 144 additions and 4 deletions

View File

@ -18,3 +18,6 @@ UPLOADS_DIR=./uploads/temp
# Logging Configuration # Logging Configuration
LOG_LEVEL=info LOG_LEVEL=info
# Text-to-Image Logging (optional)
# TTI_LOG=./apps/api-service/logs/tti.log

View File

@ -9,6 +9,7 @@ import {
GeminiParams, GeminiParams,
} from "../types/api"; } from "../types/api";
import { StorageFactory } from "./StorageFactory"; import { StorageFactory } from "./StorageFactory";
import { TTILogger, TTILogEntry } from "./TTILogger";
export class ImageGenService { export class ImageGenService {
private ai: GoogleGenAI; private ai: GoogleGenAI;
@ -39,7 +40,12 @@ export class ImageGenService {
let generatedData: GeneratedImageData; let generatedData: GeneratedImageData;
let geminiParams: GeminiParams; let geminiParams: GeminiParams;
try { try {
const aiResult = await this.generateImageWithAI(prompt, referenceImages); const aiResult = await this.generateImageWithAI(
prompt,
referenceImages,
finalOrgId,
finalProjectId,
);
generatedData = aiResult.generatedData; generatedData = aiResult.generatedData;
geminiParams = aiResult.geminiParams; geminiParams = aiResult.geminiParams;
} catch (error) { } catch (error) {
@ -114,8 +120,13 @@ export class ImageGenService {
*/ */
private async generateImageWithAI( private async generateImageWithAI(
prompt: string, prompt: string,
referenceImages?: ReferenceImage[], referenceImages: ReferenceImage[] | undefined,
): Promise<{ generatedData: GeneratedImageData; geminiParams: GeminiParams }> { orgId: string,
projectId: string,
): Promise<{
generatedData: GeneratedImageData;
geminiParams: GeminiParams;
}> {
const contentParts: any[] = []; const contentParts: any[] = [];
// Add reference images if provided // Add reference images if provided
@ -135,6 +146,8 @@ export class ImageGenService {
text: prompt, text: prompt,
}); });
// CRITICAL: Calculate exact values before SDK call
// These exact objects will be passed to both SDK and logger
const contents = [ const contents = [
{ {
role: "user" as const, role: "user" as const,
@ -155,7 +168,29 @@ export class ImageGenService {
}, },
}; };
// Log TTI request BEFORE SDK call - using exact same values
const ttiLogger = TTILogger.getInstance();
const logEntry: TTILogEntry = {
timestamp: new Date().toISOString(),
orgId,
projectId,
prompt,
model: this.primaryModel,
config,
...(referenceImages &&
referenceImages.length > 0 && {
referenceImages: referenceImages.map((img) => ({
mimetype: img.mimetype,
size: img.buffer.length,
originalname: img.originalname,
})),
}),
};
ttiLogger.log(logEntry);
try { try {
// Use the EXACT same config and contents objects calculated above
const response = await this.ai.models.generateContent({ const response = await this.ai.models.generateContent({
model: this.primaryModel, model: this.primaryModel,
config, config,

View File

@ -0,0 +1,101 @@
import { writeFileSync, appendFileSync, existsSync, mkdirSync } from "fs";
import { dirname } from "path";
export interface TTILogEntry {
timestamp: string;
orgId: string;
projectId: string;
prompt: string;
referenceImages?: Array<{
mimetype: string;
size: number;
originalname: string;
}>;
model: string;
config: any;
}
export class TTILogger {
private static instance: TTILogger | null = null;
private logFilePath: string | null = null;
private isEnabled: boolean = false;
private constructor() {
const ttiLogPath = process.env["TTI_LOG"];
if (ttiLogPath) {
this.logFilePath = ttiLogPath;
this.isEnabled = true;
this.initializeLogFile();
}
}
static getInstance(): TTILogger {
if (!TTILogger.instance) {
TTILogger.instance = new TTILogger();
}
return TTILogger.instance;
}
private initializeLogFile(): void {
if (!this.logFilePath) return;
try {
// Ensure directory exists
const dir = dirname(this.logFilePath);
if (!existsSync(dir)) {
mkdirSync(dir, { recursive: true });
}
// Reset/clear the log file on service start
writeFileSync(this.logFilePath, "# Text-to-Image Generation Log\n\n", {
encoding: "utf-8",
});
console.log(`[TTILogger] Log file initialized: ${this.logFilePath}`);
} catch (error) {
console.error(`[TTILogger] Failed to initialize log file:`, error);
this.isEnabled = false;
}
}
log(entry: TTILogEntry): void {
if (!this.isEnabled || !this.logFilePath) {
return;
}
try {
const logEntry = this.formatLogEntry(entry);
appendFileSync(this.logFilePath, logEntry, { encoding: "utf-8" });
} catch (error) {
console.error(`[TTILogger] Failed to write log entry:`, error);
}
}
private formatLogEntry(entry: TTILogEntry): string {
const { timestamp, orgId, projectId, prompt, referenceImages, model, config } = entry;
// Format date from ISO timestamp
const date = new Date(timestamp);
const formattedDate = date.toISOString().replace("T", " ").slice(0, 19);
let logText = `## ${formattedDate}\n`;
logText += `${orgId}/${projectId}\n\n`;
logText += `**Prompt:** ${prompt}\n\n`;
if (referenceImages && referenceImages.length > 0) {
logText += `**Reference Images:** ${referenceImages.length} image${referenceImages.length > 1 ? "s" : ""}\n`;
for (const img of referenceImages) {
const sizeMB = (img.size / (1024 * 1024)).toFixed(2);
logText += `- ${img.originalname} (${img.mimetype}, ${sizeMB} MB)\n`;
}
logText += "\n";
}
logText += `**Model:** ${model}\n`;
logText += `**Config:** ${JSON.stringify(config)}\n\n`;
logText += `---\n\n`;
return logText;
}
}

View File

@ -42,6 +42,7 @@ services:
- LOG_LEVEL=${LOG_LEVEL} - LOG_LEVEL=${LOG_LEVEL}
- PORT=${PORT} - PORT=${PORT}
- CORS_ORIGIN=${CORS_ORIGIN} - CORS_ORIGIN=${CORS_ORIGIN}
- TTI_LOG=${TTI_LOG}
restart: unless-stopped restart: unless-stopped
postgres: postgres: