CodeGym /Kurslar /ChatGPT Apps /Strukturlaşdırılmış loglar və sorğuların korrelyasiyası

Strukturlaşdırılmış loglar və sorğuların korrelyasiyası

ChatGPT Apps
Səviyyə , Dərs
Mövcuddur

1. Niyə ümumiyyətlə ChatGPT App‑də strukturlaşdırılmış loglara ehtiyacınız var

Təsəvvür edin, product sizə yazır: “İstifadəçilər şikayət edir ki, hədiyyə seçərkən bəzən boş siyahı göstərilir, bəzən də checkout yıxılır. Sabahkı demoya qədər düzəldə bilərik?”. Sizin əlinizdə bunlar var:

  • Bəzən App‑inizi çağıran, bəzən isə yox, elə ChatGPT.
  • Sandbox‑da widget.
  • Xarici məhsul bazasını və ACP‑ni çağıran MCP‑server.
  • Ödəniş provayderindən webhook‑lar.

Və yalnız pərakəndə mətn logları, məsələn, haradasa MCP‑də “something went wrong” və haradasa backend‑də “order failed”. Paralel sorğular zamanı bu xaosa çevrilir: hansı logun hansı istifadəçiyə və hansı sorğuya aid olduğunu anlamaq mümkün olmur.

Strukturlaşdırılmış JSON‑loglar və vahid trace_id məhz bunun üçündür ki:

  • tək identifikatorla bütün zənciri görəsiniz: ChatGPT sorğusundan "order.created" webhook‑una qədər;
  • logları xidmətə, alətə, istifadəçiyə, ssenariyə görə filtrləyəsiniz;
  • “checkout niyə yıxıldı” və “agent halüsinasiyaya başlamazdan əvvəl nə edirdi” kimi suallara tez cavab verəsiniz.

Yəni məqsəd sadədir: prod‑dakı GiftGenius‑u adi mikroservis tətbiqindən geri qalmayacaq səviyyədə debug və monitor etmək.

2. Sətir logları vs strukturlaşdırılmış loglar: nə üçün console.log("ay") artıq işləmir

Adi Next.js inkişafında çoxları sətir (string) logları ilə kifayətlənir: insan‑oxunaqlı bir cümlə və bəzən bir‑iki dəyər çap edirlər. Tək bir servis üçün bu hələ dözüləndir. Amma ChatGPT App stack‑ində belə loglar çox tez “püresi”nə çevrilir.

Mətn logu — sadəcə faylda və ya konsolda bir sətirdir. Məsələn:


console.error(`Error in suggestGifts for user ${userId}: ${error.message}`);

Belə mesajlar yüz minlərlə olanda “dünən checkout zamanı userId=… ilə MCP səhvlərinin hamısını” tapmaq artıq çətinləşir. Alət səhvləri üzrə dashboardu avtomatik qurmaq isə demək olar ki, qeyri‑mümkündür.

Strukturlaşdırılmış log — bu, mesaj mətnindən başqa sahələr də olan JSON obyektidir: səviyyə, vaxt, servis, identifikatorlar, texniki və biznes konteksti. Yuxarıdakı nümunənin analoqu:

logger.error({
    message: "suggest_gifts failed",
    user_id: userId,
    trace_id,
    service: "mcp",
    tool_name: "suggest_gifts",
    error_message: error.message,
});

Hər bir sahə log sistemi (ELK, Loki, Better Stack, Datadog və s.) tərəfindən indekslənir və sonra bu kimi sorğular yaza bilərsiniz: service="mcp" AND level="error" AND tool_name="suggest_gifts" və ya sadəcə trace_id="..." üzrə axtarın.

Anlaşıqlı olsun deyə — kiçik bir cədvəl.

Nəyi müqayisə edirik Sətir logları Strukturlaşdırılmış (JSON) loglar
Parslama Əllə, regex vasitəsilə Sahələr üzrə avtomatik
Sahələr üzrə axtarış Mürəkkəb regexp sorğuları Sadə ifadələr field=value
Aqreqasiyalar və dashboard‑lar Çətindir, çoxlu “kustar” həllər Sadə: count() , group by field
Kontekstlə zənginləşdirmə Mesajın mətni daxilində Sxemi dəyişmədən yeni sahələr
Sorğuların korrelyasiyası Paralel sorğularda demək olar qeyri‑mümkün trace_id/request_id üzrə adi axtarış

LLM tətbiqləri dünyasında problemlərin yarısı “500 səhvi” deyil, “model düzgün aləti çağırmadı” olur — strukturlaşdırılmış logsuz siz sözün əsl mənasında kor olursunuz.

3. ChatGPT App üçün JSON‑logun anatomiyası

Daha sonra GiftGenius‑un bütün laylarında istifadə edəcəyiniz “minimal standart” log yazısını razılaşdıraq. İdeal deyil, amma ehtiyacların 80%‑ni örtür.

Log sahələrini bir neçə qrupa bölək.

Texniki sahələr

Texniki sahələr müşahidə vasitələrinə yazının ümumiyyətlə haradan gəldiyini anlamağa kömək edir.

Bunları TypeScript tipi ilə təsvir etmək olar:

type LogLevel = "debug" | "info" | "warn" | "error";

interface BaseLogFields {
    timestamp: string;    // ISO 8601 UTC
    level: LogLevel;      // "info", "error"...
    service: string;      // "app-widget", "mcp", "agent", "commerce", "webhook"
    env: "dev" | "staging" | "prod";
    message: string;      // Hadisənin qısa təsviri
}

timestamp‑i UTC ISO formatında ("2025-11-21T10:15:30.123Z") yazmaq daha yaxşıdır — belə olduqda müxtəlif servisləri vaxt üzrə timezone “rəqsi” olmadan sıralamaq olar. serviceenv prod‑dakı MCP loglarını dev‑dəki widget loglarından ayırmağa kömək edir. Bu xüsusilə OpenTelemetry ilə inteqrasiya edib ümumi konvensiyalardan (service.name, service.version və s.) istifadə etmək istəyəndə aktualdır.

Korrelyasiya sahələri

Bu, bu mühazirənin ən vacib hissəsidir. Onlar olmadan hadisələri bir‑biri ilə bağlaya bilməyəcəksiniz.

İnterfeysə əlavə edək:

interface CorrelationFields {
    trace_id: string;        // Bütün ssenari üçün end-to-end ID
    span_id?: string;        // (opsional) Konkret əməliyyatın ID-si
    parent_span_id?: string; // (opsional) Valideyn əməliyyat
    request_id?: string;     // Lokal HTTP sorğunun və ya tool-call-un ID-si
    agent_run_id?: string;   // Agentin işə salınmasının ID-si (əgər varsa)
    tool_call_id?: string;   // Konkret alət çağırışının ID-si
    checkout_session_id?: string; // ACP/ödəniş sessiyasının ID-si
}

trace_id — əsas qəhrəmandır. “İstifadəçi hədiyyə seçməyimizi istədi, biz seçdik, sifariş yaratdıq, webhook aldıq” ssenarisinə aid bütün loglarda eyni olmalıdır. span_idparent_span_id sonradan distributed tracing üslubunda “əməliyyat ağacı” qurmağa imkan verir, amma başlanğıc üçün təkcə trace_idrequest_id kifayət edir.

Biznes konteksti

Biznes kontekstsiz texniki log “nə isə, haradasa, nə vaxtsa baş verdi”yə çevrilir. Hansı istifadəçi və ssenarinin hansı addımında təsirləndiyini başa düşmək lazımdır.

İnterfeysi genişləndirək:

interface BusinessFields {
    user_id?: string;     // Anonim ID, email DEYİL
    tenant_id?: string;   // Təşkilat/hesab, əgər B2B-dirsə
    flow?: string;        // Məsələn, "gift_recommendation" və ya "checkout"
    step?: string;        // Məsələn, "collect_requirements" və ya "create_checkout"
}

Burada prinsip çox sadədir: identifikatorlar daxili (məsələn, DB‑dəki UUID) ola bilər, amma PII (email, telefon, tam ad) ehtiva etməməlidir. Bu barədə təhlükəsizlik bölməsində yenə danışacağıq.

Səhv sahələri

Səhvlər ayrıca mövzudur. Tipik səhv logunu ən azı növ, kod və mətni bölmək istəyərik:

interface ErrorFields {
    error_type?: "validation" | "upstream" | "timeout" | "system";
    error_code?: string;       // HTTP statusu, DB kodu və ya öz enum-unuz
    error_message?: string;    // Qısa və təhlükəsiz
    stack?: string;            // Stack, həcmlə və PII ilə ehtiyatlı olun
}

Vacibdir ki, error_message həssas məlumatlar saxlamasın (məsələn, “failed for card 4111 1111 1111 1111”). Daha yaxşısı "payment provider declined card" və hansısa təhlükəsiz koddur.

Tam log interfeysi

Hamısını birləşdirək:

export interface LogEvent
    extends BaseLogFields,
        CorrelationFields,
        BusinessFields,
        ErrorFields {
    // əlavə sahələr üçün ehtiyat saxlayırıq
    [key: string]: unknown;
}

Bu interfeysi MCP‑serverdə, commerce backend‑də və agentdə istifadə edə bilərsiniz. Onda bütün servislər eyni formatda log yazacaq və korrelyasiya kvest yox, xoş bir gəzintiyə çevriləcək.

4. GiftGenius üçün ən sadə JSON‑logger (MCP‑server)

Tam minimaldan başlayaq. Tutaq ki, MCP‑serveriniz Node.js/TypeScript tətbiqidir. logger utilini yaradaq:

// mcp/logging.ts
import { LogEvent, LogLevel } from "./types";

function log(level: LogLevel, event: Omit<LogEvent, "level" | "timestamp">) {
    const enriched: LogEvent = {
        timestamp: new Date().toISOString(),
        level,
        env: process.env.NODE_ENV === "production" ? "prod" : "dev",
        ...event,
    };

    // JSON-u stdout-a çıxarırıq — bundan sonra onu log sistemi toplayacaq
    console.log(JSON.stringify(enriched));
}

export const logger = {
    debug: (event: Omit<LogEvent, "level" | "timestamp">) =>
        log("debug", event),
    info: (event: Omit<LogEvent, "level" | "timestamp">) =>
        log("info", event),
    warn: (event: Omit<LogEvent, "level" | "timestamp">) =>
        log("warn", event),
    error: (event: Omit<LogEvent, "level" | "timestamp">) =>
        log("error", event),
};

Bu nə Pino, nə də Winston‑dur, amma kurs üçün bizə məhz ideya vacibdir: hər şey normal sahələrlə JSON kimi yazılır.

İndi isə onu suggest_gifts MCP alətinin handler‑ində istifadə edək.

5. MCP alətinin loglanması: girişdən çıxışa

Tutaq ki, sizdə artıq istifadəçi üstünlüklərini qəbul edib SKU siyahısı qaytaran suggest_gifts alətinin handler‑i var. Gəlin ora loglar əlavə edək.

Güman edək ki, əvvəlcədən trace_id‑ni x-trace-id HTTP başlığından götürmüşük (onu ora necə qoymağı — korrelyasiya bölməsinin növbəti hissəsində danışacağıq).

// mcp/tools/suggestGifts.ts
import { logger } from "../logging";

export async function suggestGiftsTool(args: SuggestGiftsArgs, ctx: {
  traceId: string;
  userId?: string;
}) {
  logger.info({
    message: "suggest_gifts called",
    service: "mcp",
    trace_id: ctx.traceId,
    user_id: ctx.userId,
    tool_name: "suggest_gifts",
    flow: "gift_recommendation",
    step: "fetch_candidates",
  });

  try {
    const gifts = await fetchGiftsFromCatalog(args);

    logger.info({
      message: "suggest_gifts succeeded",
      service: "mcp",
      trace_id: ctx.traceId,
      user_id: ctx.userId,
      tool_name: "suggest_gifts",
      flow: "gift_recommendation",
      step: "rank_candidates",
      result_count: gifts.length,
    });

    return gifts;
  } catch (error: any) {
    logger.error({
      message: "suggest_gifts failed",
      service: "mcp",
      trace_id: ctx.traceId,
      user_id: ctx.userId,
      tool_name: "suggest_gifts",
      flow: "gift_recommendation",
      step: "fetch_candidates",
      error_type: "upstream",
      error_message: error.message,
    });
    throw error;
  }
}

İndi təkcə bir trace_id ilə aşağıdakılara baxa biləcəksiniz:

  • alətin ümumiyyətlə çağırılıb‑çağırılmadığına;
  • neçə namizəd tapıldığına;
  • hansı addımda yıxıldığına.

Bu zaman heç yerdə email və ya istifadəçi adı yoxdur — yalnız daxili user_id.

6. trace_id ChatGPT App‑də harada yaranır

Gəlin baxaq, trace_id harada yaranmalıdır. Anlamaq vacibdir ki, o konkret sorğuya bağlı deyil. trace_id — məhz biznes əməliyyatının identifikatorudur. Buna görə də iki tipik situasiyanı fərqləndirmək lazımdır:

“Dar” MCP aləti

Alət bir kompakt əməliyyat icra edib dərhal nəticə qaytaranda (interaktiv UI olmadan):

  • get_gifts_for_budget
  • calculate_price
  • save_lead və s.

Bu halda rahatdır ki: bir MCP alət çağırışı = bir biznes sorğu = bir trace. Skvoz trace_id MCP‑gateway / MCP‑server tərəfində tool‑call daxil olduqda yaranır (və ya OpenTelemetry istifadə edirsinizsə mövcud tracing kontekstindən götürülür). Daha sonra bu trace_id bütün daxili çağırışlarda (REST servislər, bazalar, növbələr) istifadə olunur və loglara trace_id sahəsi kimi düşür.

ChatGPT və Apps SDK buraya qarışmır: onlar sadəcə JSON‑RPC tool‑call göndərir, tracing isə sizin idarə etdiyiniz zonada başlayır.

“Geniş” MCP aləti (widget qaytarır)

Burada alət biznes əməliyyatını sonadək tamamlamır, əksinə interaktiv səhnə işə salır: widget qaytarır, o isə sandbox‑da onlarla fetch() sorğusu edir (hədiyyə siyahısının yüklənməsi, filtrlər, checkout və s.).

Belə ssenaridə skvoz tracing fərqli qurulur:

  • əsas biznes əməliyyatları widget‑dən backend‑ə HTTP sorğularında yaşayır;
  • ona görə də widget‑dən backend‑inizə atılan hər bir əhəmiyyətli fetch() öz trace_id‑sini alır və bu, artıq backend / gateway‑də (həmin fetch üçün ilk server hop) yaranır.

Nə ChatGPT, nə də widget trace_id üçün “həqiqət mənbəyi” deyil: onlar sorğuda yalnız köməkçi identifikatorlar (session_id, widget_id, user_id) ötürə bilərlər, amma trace_id‑in yaradılması və idarə olunması serverdə baş verir.

“Dar” MCP aləti: bir tool‑call üçün bir trace

Widget olmadan “dar” alət üçün axın belə görünür:

sequenceDiagram
    participant ChatGPT as ChatGPT / Agent
    participant MCP as MCP Server
    participant GiftAPI as Gift API
    participant Pricing as Pricing API

    ChatGPT->>MCP: JSON-RPC tools.call get_gifts
    MCP->>MCP: start trace (trace_id = T-123)
    MCP->>GiftAPI: GET /gifts (x-trace-id = T-123)
    GiftAPI-->>MCP: 200 OK (trace_id = T-123)
    MCP->>Pricing: GET /price (x-trace-id = T-123)
    Pricing-->>MCP: 200 OK (trace_id = T-123)
    MCP-->>ChatGPT: tool result (istəyə bağlı trace_id ilə)

Nümunə:

  • tool‑call MCP‑yə daxil olduqda siz trace yaradırsınız (və ya onu traceparent/x-trace-id‑dən götürürsünüz);
  • bu tool‑call‑un bütün sonrakı yolu (servis çağırışları, DB, keşlər) eyni trace_id ilə loglanır;
  • loglarda widget iştirak etmir, çünki ümumiyyətlə widget yoxdur.

Bu yanaşma verir:

  • bir əməliyyatın dəqiq “şəkli”: “MCP aləti suggest_gifts → Gift API → Pricing API → cavab”;
  • bir alət çağırışına bir trace_id.

“Geniş” MCP aləti: widget və bir neçə trace

İndi MCP alətinin widget qaytardığı GiftGenius ssenarisi:

  1. ChatGPT MCP alətini çağırır, məsələn, open_gift_widget.
  2. MCP aləti widget təsvirini (layout, ilkin vəziyyət) formalaşdırıb qaytarır.
  3. Widget sandbox‑da montaj olunur və öz həyatı ilə yaşayır:
    • GET /api/gifts?budget=50&page=1
    • GET /api/gifts?budget=50&filter=for_developers
    • POST /api/checkout
    • POST /api/save-lead
  4. Hər belə HTTP sorğusu sizin Next.js backend‑inizə / gateway‑ə gəlir — və orada yeni trace yaradırsınız:
fetch #1  -> trace_id = T-501  (hədiyyələrin birinci səhifəsini yükləmək)
fetch #2  -> trace_id = T-502  (“developerlər üçün” filtri tətbiq etmək)
fetch #3  -> trace_id = T-503  (checkout yaratmaq)
...

Yəni:

  • MCP aləti “genişdir”: onun əsas vəzifəsi widget‑i açmaqdır, bütün biznes zəncirini icra etmək yox;
  • real biznes məntiqi (hədiyyə siyahısı, top hədiyyənin seçilməsi, checkout) backenddə yaşayır və widget‑in fetch() sorğularını emal edir;
  • eyni biznes ssenarisinə aid fetch() sorğuları qrupu özünəməxsus trace_id alır — siz onu HTTP sorğusunun serverə girişində generasiya edirsiniz.

Əlavə olaraq hər trace‑ə ötürə bilərsiniz:

  • session_id (ChatGPT sessiya ID‑si varsa),
  • widget_id,
  • user_id,
  • tool_run_id və ya istənilən başqa kontekst.

trace_id ilə konkret əməliyyata (“checkout #3”) baxırsınız, session_id / widget_id ilə isə bir widget/sessiya çərçivəsində baş verənlərin hamısını görürsünüz.

7. Sorğuların korrelyasiyası: trace_id App, MCP, widget və backend boyunca necə keçir

Ən maraqlı hissəyə keçirik: lazım olan identifikatorların bütün laylardan — ChatGPT, MCP‑server, widget, commerce backend və webhook‑lardan — keçməsini necə təmin etməli.

trace_id ilə sorğu axını (“geniş” hal üçün diaqram)

GiftGenius üçün bunun necə göründüyünə dair kiçik sxem:

sequenceDiagram
    participant ChatGPT as ChatGPT UI
    participant MCP as MCP Server
    participant Widget as GiftGenius Widget
    participant Backend as Next.js Backend
    participant ACP as Commerce API
    participant WH as Webhook Handler

    ChatGPT->>MCP: tools.call open_gift_widget
    MCP-->>ChatGPT: Widget description (layout, config)
    ChatGPT->>Widget: Widget-in sandbox-da renderi

    Widget->>Backend: GET /api/gifts (trace_id = T-501, Backend-də yaranır)
    Backend->>ACP: GET /gifts (x-trace-id = T-501)
    ACP-->>Backend: 200 OK (trace_id = T-501)
    Backend-->>Widget: Hədiyyələrlə JSON (loglarda trace_id = T-501)

    Widget->>Backend: POST /api/checkout (trace_id = T-503, Backend-də yaranır)
    Backend->>ACP: POST /checkout (x-trace-id = T-503)
    ACP-->>Backend: 200 OK (trace_id = T-503)
    ACP-->>WH: webhook order.created (x-trace-id = T-503)
    WH->>WH: Hadisəni loglayır (trace_id = T-503)

Nəzərə alın:

  • bu sxemdə trace_id widget tərəfindən generasiya olunmur;
  • o, backend‑inizdə HTTP sorğusunun giriş nöqtəsində (Next.js route handler, API‑gateway və s.) əmələ gəlir;
  • daha sonra bu trace_id ötürülür:
    • backend loglarına,
    • ACP çağırılarkən x-trace-id başlığına,
    • və əgər ACP onu geri qaytarır/irəli ötürürsə webhook‑lara.

6.5. Widget‑dən gələn çağırışlar üçün backend‑də trace_id generasiya və ötürmə

Nümunəni elə yazaq ki, aydın görünsün: trace_id backenddə yaranır, widget‑də yox.

// app/api/mcp/tools/call/route.ts (Next.js backend, MCP-yə proxy)
import { NextRequest, NextResponse } from "next/server";
import { v4 as uuidv4 } from "uuid";
import { logger } from "@/mcp/logging";

export async function POST(req: NextRequest) {
  // Əgər trace_id xaricdən (məsələn, gateway-dən) gəlibsə — ondan istifadə edirik.
  // Əks halda — backend-ə girişdə yenisini generasiya edirik.
  const incomingTraceId = req.headers.get("x-trace-id");
  const traceId = incomingTraceId ?? uuidv4();
  const requestId = uuidv4();

  logger.info({
    message: "mcp.tools.call received from widget",
    service: "backend",
    trace_id: traceId,
    request_id: requestId,
  });

  const body = await req.json();

  const res = await fetch(process.env.MCP_SERVER_URL!, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "x-trace-id": traceId,
    },
    body: JSON.stringify(body),
  });

  const json = await res.json();

  logger.info({
    message: "mcp.tools.call completed",
    service: "backend",
    trace_id: traceId,
    request_id: requestId,
  });

  return NextResponse.json(json);
}

MCP‑server tərəfində isə bu başlığı sadəcə oxuyur və öz loglarınızda trace_id kimi istifadə edirsiniz (5‑ci bölmədəki nümunələrdə olduğu kimi).

Widget bununla belə trace_id‑nin mövcudluğunu heç bilməyə də bilər — onun üçün /api/mcp/tools/call çağırmaq kifayətdir. Amma UI hərəkətlərini trace ilə əlaqələndirərək göstərmək və ya loglamaq rahatdırsa, cavabda trace_id qaytara və öz JSON‑loglarınızda (klientdə və ya SaaS analitikada) "service": "app-widget" yaza bilərsiniz.

Widget‑dən MCP‑yə müştəri tərəfli çağırış nümunəsi

// app/lib/mcpClient.ts (widget)
export async function callMcpTool(toolName: string, args: unknown) {
  const res = await fetch("/api/mcp/tools/call", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      // trace_id BURADA generasiya etmirik — o, backend-də yaranacaq
    },
    body: JSON.stringify({ toolName, args }),
  });

  // Əgər backend cavabda trace_id qaytarsa, onu saxlaya bilərsiniz:
  const data = await res.json();
  return data;
}

İstəsəniz, backend handler‑i elə genişləndirə bilərsiniz ki, JSON cavabına trace_id əlavə etsin, və o zaman widget:

  • "service": "app-widget", "trace_id": "..." tipli hadisələri loglaya,
  • istinad üçün developer‑lərə trace linkləri göstərə bilər.

Amma prinsiplər dəyişmir: trace_id‑nin mənbəyi — serverdir, widget yox.

trace_id‑ni ACP/commerce‑ə doğru ötürmək

İndi create_checkout_session MCP alətinin daxilində commerce API‑nizi çağırırıq və trace_id‑ni başlıqlarda daşımağa davam edirik:

// mcp/tools/createCheckout.ts
import { logger } from "../logging";

export async function createCheckoutTool(
  args: CreateCheckoutArgs,
  ctx: { traceId: string; userId?: string }
) {
  logger.info({
    message: "create_checkout called",
    service: "mcp",
    trace_id: ctx.traceId,
    user_id: ctx.userId,
    tool_name: "create_checkout_session",
    flow: "checkout",
    step: "create_session",
  });

  const res = await fetch(process.env.COMMERCE_URL + "/checkout", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "x-trace-id": ctx.traceId,
    },
    body: JSON.stringify({
      userId: ctx.userId,
      ...args,
    }),
  });

  if (!res.ok) {
    logger.error({
      message: "checkout API failed",
      service: "mcp",
      trace_id: ctx.traceId,
      user_id: ctx.userId,
      flow: "checkout",
      step: "create_session",
      error_type: "upstream",
      error_code: String(res.status),
    });
    throw new Error("Checkout API failed");
  }

  const data = await res.json();

  logger.info({
    message: "checkout session created",
    service: "mcp",
    trace_id: ctx.traceId,
    user_id: ctx.userId,
    flow: "checkout",
    step: "create_session",
    checkout_session_id: data.sessionId,
  });

  return data;
}

Öz növbəsində commerce backend də x-trace-id başlığını oxuyur və onu öz JSON‑loglarına yazır. Onda bir trace_id ilə aşağıdakıları görəcəksiniz:

  • widget‑dən backend‑ə daxil olan HTTP sorğunu (harada trace doğulub);
  • MCP‑yə proxy (əgər varsa);
  • create_checkout_session daxili çağırışını;
  • commerce API sorğusunu;
  • commerce backend cavabını;
  • və əgər o da başlığı ötürürsə, order.created webhook‑unu.

8. Log səviyyələri: LLM tətbiqi kontekstində DEBUG, INFO, WARN, ERROR

Log səviyyələri məlumatda batmamağa kömək edir. ChatGPT App üçün onları belə şərh etmək rahatdır:

  • DEBUG — dev/staging üçün faydalı detallı texniki məlumat. Məsələn, qısaldılmış promptlar, agentin aralıq vəziyyətləri, xarici API‑lərin “xam” cavabları (PII olmadan). Prod‑da bununla çox ehtiyatlı olmaq lazımdır.
  • INFO — normal biznes hadisələri: “suggest_gifts succeeded, 10 candidates”, “checkout session created”, “webhook order.created processed”. Bu logları prod‑da açıq saxlamaq olar.
  • WARN — nəsə qeyri‑standart oldu, ancaq sistem işləməyə davam etdi. Məsələn: “fallback to cached catalog because upstream timeout”, “model returned invalid tool args, retry with different schema”.
  • ERROR — açıq uğursuzluq: ssenari lazımi kimi tamamlanmadı. Məsələn: “checkout API failed”, “failed to persist order”, “tool crashed with unhandled exception”.

Rahatlıq üçün sadə bir helper əlavə edə bilərsiniz ki, sətrləri əl ilə yazmayasınız:

type LogLevel = "debug" | "info" | "warn" | "error";

function isProd() {
  return process.env.NODE_ENV === "production";
}

export function shouldLogLevel(level: LogLevel): boolean {
  if (isProd()) {
    return level === "info" || level === "warn" || level === "error";
  }
  return true; // dev-də hamısını aktiv edirik
}

logger.debug yalnız shouldLogLevel("debug") true qaytardıqda çağırın.

Xüsusilə prod‑da modeli tam prompt və cavabla DEBUG səviyyəsində yazmaq təhlükəlidir: orada parollar, açarlar, istifadəçinin təsadüfən çata yapışdırdığı istənilən PII ola bilər.

9. Logların təhlükəsizliyi: PII‑scrub və sirlər

Loglarla asanlıqla həddi aşa bilərsiniz. “Hər şeyi” yazsanız, siz:

  • məlumatların qorunması qanunlarını pozacaqsınız;
  • təhlükəsizlik pozanların işini asanlaşdıracaqsınız (sirlər və tokenlər sadəcə loglardan çıxarılır);
  • özünüz log sisteminə giriş verməkdən çəkinəcəksiniz.

Buna görə sadə prinsip işləyir: baş verəni anlamaq üçün kifayət qədər, amma məlumatı oğurlamaq üçün kifayət etməyən məlumat yazılır.

Yaxşı təcrübələr:

  1. user_id loglayın, email və ya telefon yox. Debug üçün email həqiqətən lazımdırsa, onun hash‑ini loglayın və ya maskalayın ("a***@gmail.com").
  2. Heç vaxt loglarda tam tokenlər ("sk-..."), refresh tokenlər, client_secret, parollar yazmayın. Çox ehtiyac varsa — yalnız ilk/son 4 simvol və tip (“sk-***1234”).
  3. tool_inputtool_output ilə ehtiyatlı olun. Onlarda istifadəçinin yazdığı hər şey ola bilər. Prod‑da onları tam loglamayın, ya da:
    • yalnız artıq validasiyadan keçmiş tiplənmiş sahələri loglayın;
    • məntiqli ölçüyə qədər kəsin və scrub tətbiq edin — regex ilə maskalama (email, kart nömrələri və s.).

Ən sadə sanitizer nümunəsi (çox sadələşdirilib):

export function sanitize(text: string): string {
  return text
    .replace(/sk-[a-zA-Z0-9]{20,}/g, "sk-***redacted***")
    .replace(/\b\d{16}\b/g, "****-****-****-****"); // kart nömrələri
}

Və istifadəçi girişini loglayarkən:

logger.debug({
  message: "raw_user_message",
  service: "app-widget",
  trace_id,
  user_id,
  raw: sanitize(userMessage),
});

Bu kod sənaye səviyyəsindən uzaqdır, amma ideyanı yaxşı göstərir: əvvəl təmizləyirik, sonra loglayırıq.

10. Praktika: GiftGenius üçün gift_recommended hadisəsi

İndi məşqi edək: gift_recommended log hadisəsini layihələndirək — GiftGenius istifadəçi üçün “top hədiyyə”ni yekun seçəndə yazılır.

Hadisə aşağıdakı suallara cavab verməlidir:

  • hansı istifadəçi (daxili ID);
  • hansı hədiyyə (SKU);
  • hansı ssenari və hansı addım üzrə;
  • hansı trace_id — qalan loglarla əlaqələndirmək üçün.

Və eyni zamanda PII və sirləri ehtiva etməməlidir.

Nümunə:

{
  "timestamp": "2025-11-21T10:22:33.456Z",
  "level": "info",
  "service": "agent",
  "env": "prod",
  "message": "gift_recommended",
  "trace_id": "a3b9e8c2-1f47-4ec5-9bdf-9d4e0c123abc",
  "agent_run_id": "run_7f1d2c",
  "user_id": "u_123456",
  "flow": "gift_recommendation",
  "step": "final_choice",
  "recommended_sku": "SKU-SPACE-MUG-001",
  "price_cents": 2499,
  "currency": "USD",
  "reason_summary": "recipient_likes_space_and_practical_gadgets"
}

Burada vacib olanlar:

  • user_id loglayırıq, amma email və ya ad yox;
  • SKU və qiymət — normal biznes məlumatıdır, PII sayılmır;
  • reason_summary — tam istifadəçi cümləsi yox, qısa texniki teqdir;
  • trace_idagent_run_id var ki, agentin bu seçime gedən yolda hansı alətləri çağırdığını görə biləsiniz.

Və bunları qəti loglamayın:

  • modelin cavab mətni tam şəkildə “insani” izahla;
  • istifadəçi promptu (“iş yoldaşım üçün hədiyyə istəyirəm, onun telefonu budur, ünvanı budur”);
  • istənilən ödəniş məlumatı.

11. Log nümunələri: uğurlu tool‑call və ACP xətası

Möhkəmlətmək üçün — iki kiçik JSON nümunəsi.

MCP‑də uğurlu tools.call

{
  "timestamp": "2025-11-21T10:20:00.000Z",
  "level": "info",
  "service": "mcp",
  "env": "prod",
  "message": "tools.call completed",
  "trace_id": "a3b9e8c2-1f47-4ec5-9bdf-9d4e0c123abc",
  "request_id": "req_01JCQ5CZ0YQ6TM7E5W8H3N3F2Y",
  "tool_name": "suggest_gifts",
  "user_id": "u_123456",
  "flow": "gift_recommendation",
  "step": "rank_candidates",
  "result_count": 12,
  "latency_ms": 430
}

Tək bir belə logdan artıq görünür ki:

  • hansı alət;
  • hansı istifadəçi üçün;
  • hansı ssenari üzrə;
  • nə qədər vaxt apardı və neçə namizəd qaytarıldı.

trace_id üzrə eyni sorğuya aid UI və agent loglarını asanlıqla tapacaqsınız.

ACP/checkout xətası

{
  "timestamp": "2025-11-21T10:21:05.789Z",
  "level": "error",
  "service": "commerce",
  "env": "prod",
  "message": "checkout failed",
  "trace_id": "a3b9e8c2-1f47-4ec5-9bdf-9d4e0c123abc",
  "checkout_session_id": "cs_test_9YpQvJH8",
  "user_id": "u_123456",
  "flow": "checkout",
  "step": "charge_customer",
  "error_type": "upstream",
  "error_code": "PAYMENT_DECLINED",
  "error_message": "payment provider declined card",
  "provider": "stripe",
  "amount_cents": 2499,
  "currency": "USD"
}

Yenə, heç bir kart nömrəsi yox — yalnız səhv kodu və təhlükəsiz mesaj. Və yenə eyni trace_id, beləliklə bunu gift_recommended ilə əlaqələndirib zəncirin hansı mərhələdə qırıldığını anlaya bilərsiniz.

12. Logları zibilə çevirməmək üçün

Çox cazibədardır: “madem hər şeyi gözəl loglaya bilirik, gəlin tamamilə hər şeyi loglayaq”. Beləliklə tez bir zamanda gigabaytlarla JSON səs‑küyü əldə edəcəksiniz və faydalı hadisələr itəcək.

Bir neçə praktiki məsləhət:

  • “X funksiyasına daxil oldum” tipli təkrarlanan loglar əlavə məlumat olmadan zəif faydalıdır. Daha yaxşısı mənalı hadisələri loglamaqdır: ssenarinin başlanğıcı/sonu, xarici API çağırışı, workflow addım keçidi, səhvlər.
  • Tez‑tez olan əməliyyatlar (məsələn, məhsul kataloqu sorğuları) üçün sampling qoşun: sorğuların 1‑ini N‑dən tam loglayın, qalanları isə yalnız səhvlərdə.
  • Prod‑da DEBUG‑i söndürülü saxlayın (və ya çox selektiv). Əgər prompt/cavab loglanacaqsa — məhdud və scrub ilə olsun.

Metrix və SLO barədə ayrıca növbəti mühazirədə danışacağıq, amma artıq indi başa düşmək vacibdir: loglar təkcə “debug üçün” deyil, bütün ChatGPT stack‑inin müşahidə olunmasının təməlidir.

Mühazirənin əvvəlində “boş siyahı” və yıxılan checkout haqqında danışan productu xatırlayırsınız? Təsvir etdiyimiz log sxemi ilə siz bir neçə dəqiqəyə lazım olan trace_id ilə bütün sorğuları tapar, suggest_gifts‑ə baxar (alət neçə namizəd qaytardı, hansı addımda yıxıldı) və ödəniş provayderindən gələn "checkout failed" loglarını error_code ilə birlikdə görərdiniz. Bu artıq “log xaosu” üzrə istintaq deyil, “sorğudan webhook‑a qədər” aydın ssenaridir.

Nəticədə ChatGPT App üçün yaxşı loglama stack‑i “stdout‑a nəsə yazırıq” deyil, həm də:

  • trace_id‑nin düzgün yaranma yerləri (“dar” alətlər üçün MCP‑gateway/server, “geniş” ssenarilərdə widget‑in fetch() sorğuları üçün backend girişi);
  • hər məntiqli biznes çağırışı üçün App → MCP → commerce → webhook‑lar boyunca vahid trace_id;
  • ümumi JSON‑log sxemi (service, env, user_id, flow, step, tool_name və s.);
  • PII və sirlərlə ehtiyatlı davranış (scrub, maskalama, prod‑da məhdud DEBUG);
  • mənalı log səviyyələri və səs‑küyün olmaması.

Belə bir baza ilə müşahidə alətlərinin (metrixlər, SLO, alertlər) qalanı xeyli faydalı olur və “log toplamaq”dan daha artığını — həqiqətən ChatGPT App‑inizin keyfiyyətini və stabilliyini idarə etməyi — təmin edir.

13. Strukturlaşdırılmış loglar və korrelyasiya ilə işləyərkən tipik səhvlər

Səhv №1: bütün servislər boyunca vahid trace_id yoxluğu.
Klassik hal: MCP‑gateway bir ID generasiya edir, commerce backend ikinci, webhook‑lar isə ümumiyyətlə korrelyasiya haqqında heç nə bilmir, widget loglarında isə trace_id görünmür. Nəticədə korrelyasiya “bax, vaxt oxşar kimi” tipli əl axtarışına çevrilir. Düzgün yanaşma — trace_id‑ni idarə olunan giriş nöqtələrində (MCP‑server “dar” alətlər üçün, widget‑dən gələn fetch() üçün backend/gateway) generasiya edib onu bütün sərhədlərdən keçirmək: HTTP başlıqları, JSON sahələri, agent konteksti.

Səhv №2: trace_id‑ni widget‑də generasiya etməyə cəhd və onu “həqiqət” saymaq.
Bəzən məntiqli görünür: “gəlin elə React widget‑də crypto.randomUUID() edək və onu başlıqlara qoyaq”. Problem ondadır ki, onda trace_id klientdə yaşayır və real server tracingi (OpenTelemetry, gateway, digər servislər) ilə üst‑üstə düşməyə bilər. Çox daha etibarlısı — trace_id‑nin bütün server yolunu idarə etdiyiniz yerdə (Next.js backend, API‑gateway və ya MCP‑server) yaranmasıdır. Widget istəyə bağlı bu ID‑ni yalnız oxuya və loglaya bilər.

Səhv №3: “debug rahatlığı” üçün PII və sirləri loglamaq.
İnkişafın əvvəlində “çox rahatdır” ki, promptun bütün mətnini, tokenləri, kart nömrələrini və emaili loga yazasınız. Bir neçə aydan sonra bu gecikdirilmiş mina olur: loglara giriş “toksik”ləşir, təhlükəsizlik auditi xoş olmayan suallar verir və siz hətta səhv skrinini göstərməkdən çəkinirsiniz. Elə əvvəldən scrub tətbiq edin və sabah çətinliklə təmizləməli olacağınız şeyləri loglamayın.

Səhv №4: laylardan birində struktursuz sətir logları.
Bəzən komanda MCP və commerce‑də əla JSON‑loglar edir, amma widget‑də console.log("step 1", data) saxlayır. Nəticədə zəncirin başlanğıcı və sonu qırıq qalır.

Səhv №5: ERROR səviyyəsindən sui‑istifadə.
Əgər istənilən xırda kənarlaşma (tipli “model 0 namizəd qaytardı, fallback göstəririk”) ERROR kimi loglanırsa, prod alertləri daima yanacaq. Komanda tez bir zamanda ümumiyyətlə alertlərə reaksiya verməyi dayandırır. Səviyyələri dürüst ayırmağa çalışın: “WARN — qəribədir, amma öhdəsindən gəldik; ERROR — istifadəçi ssenarisi həqiqətən pozuldu”.

Səhv №6: servislər arasında uyğunsuz log sxemləri.
Bir servisdə sahə traceId, digərində correlation_id, üçüncüdə isə requestId adlanırsa, heç bir log sistemi xilas etməyəcək. Vahid sxemdə razılaşmaq (biz LogEvent ilə etdik) və komponentlərin hamısında (App widget, MCP‑server, agentlər, ACP, webhook‑lar) ona əməl etmək vacibdir. Onda skvoz dashboardlar qurmaq və insident araşdırması dəqiqələrin, günlərin deyil, işi olacaq.

Səhv №7: log ölçüsünü “optimallaşdırmaq” üçün açar sahələri atmaq.
Bəzən yerə qənaət dalınca kimsə qərar verir: “gəlin user_id və ya flow sahəsini çıxaraq, onsuz da xırdadır”. Sonra qəfil belə sual yaranır: “hansı istifadəçilərdə checkout daha çox yıxılır?” — və məlum olur ki, məlumat yoxdur. Nəyisə atmalı olsanız, uzun mətn payloadlarını (sorğu/cavab gövdələri) və debug sahələrini atın, identifikatorları və açar kontekst attributlarını yox.

Şərhlər
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION