ai-machine-learning

LLM-loggning bästa praxis: 9 regler vi följer i produktion [2026]

Skriven av Mert Batur
Aug 2, 2026
14 läsning
LLM-loggning bästa praxis: 9 regler vi följer i produktion [2026]

LLM-loggning bästa praxis: 9 regler vi följer i produktion [2026]

Dessa nio regler för LLM-loggning är den bästa praxis vår produktionsstack faktiskt kör: vi loggar 1,2 miljoner LLM-anrop i månaden över fyra tjänster, och varje anrop landar i Grafana Loki som en enda JSON-rad med modell, tokens, latens, cost_usd och trace_id. structlog 25.4.0 skriver posten, Presidio tar bort PII först, och hela pipelinen är loggningshalvan av vår observabilitetsstack.

Det viktigaste

  • Logga varje LLM-anrop som strukturerad JSON med minst 14 namngivna fält, aldrig fri text.
  • Maskera PII före loggskrivningen med Presidio eller motsvarande, inte efteråt.
  • Sätt OpenTelemetry GenAI semconv-attribut på varje spår.
  • Vid 1 miljon anrop/dag kostar samma 60 GB 108 $/månad i Datadog, 30 $ i Loki och 1,20 $ i ClickHouse.

Vad LLM-loggning faktiskt innebär (och varför "logga allt" inte fungerar)

LLM-loggning innebär att fånga en strukturerad post av varje modell-anrop och svar: prompten, svaret, tokenantal, latens, kostnad och spåret som binder allt till en användarsession. Det är inte infrastrukturloggning. CPU, minne och poddomstarter hör hemma i din metrikstack; det här inlägget täcker bara posten på anropsnivå som gör att du kan felsöka, kosta och granska modellbeteendet.

Instinkten att "logga allt" sitter hårt i, och den är dyr. Fullständiga prompter och svar vid 1 miljon anrop om dagen producerar ungefär 60 GB text i månaden, och en del av den texten är kund-PII som du nu lagrar på obestämd tid. GDPR:s dataminimeringsprincip i artikel 5 kräver att personuppgifter är "adekvata, relevanta och begränsade till vad som är nödvändigt", och en rå promptdump klarar inte det testet dag ett. Att logga allt är ingen strategi; det är en skuld med månadsfaktura.

Vilka är de 9 reglerna för LLM-loggning?

Nio regler, i den ordning vi skulle införa dem: logga hela prompter och svar med hashade identifierare, skicka ut strukturerad JSON, fånga tokens och kostnad per anrop, bifoga OpenTelemetry-spårkontext, maskera PII före skrivningen, sampla vid hög volym, sätt lagringsnivåer, separera säkerhetshändelser och gör resultatet sökbart. Varje regel nedan följer med koden eller tabellen som upprätthåller den.

Regel 1: Logga hela prompten och svaret (med hashar, inte rå PII)

Logga den kompletta prompten och det kompletta svaret för varje anrop, eftersom partiella loggar är hur du hamnar framför en incident utan någon uppgift om vad modellen faktiskt såg. Det enda undantaget är identitet: skriv aldrig råa användar-ID:n, e-postadresser eller namn i posten. Lagra en SHA-256-hash av användar-ID:t istället. En hash gör att du fortfarande kan rekonstruera en användares hela sessionshistorik med en offlineuppslagning, medan själva loggraden förblir värdelös för alla som inte borde läsa den. Samma logik för systemprompter: hasha dem, logga hashen och behåll klartexten i din promptregistry där den redan är versionshanterad.

Regel 2: Använd strukturerad JSON: varje fält namngivet, inget som fri text

För bästa praxis för LLM-loggning i Python eller något annat språk är strukturerad loggning i JSON den icke-förhandlingsbara: varje fält namngivet, typat och sökbart, inget dumpat som en formaterad sträng. En fri-text-rad som INFO called gpt-4o, took 812ms kan bara grepas. En JSON-post kan aggregeras per modell, summeras per kostnad och joinas till ett spår. OpenAI:s egna produktionsrekommendationer driver samma idé: fånga strukturerad metadata på SDK-lagret istället för med print-satser.

Här är schemat som varje Techsy-tjänst skickar, fjorton fält:

json
{
  "request_id": "req_01J9XK4M7Q",
  "timestamp": "2026-07-18T09:24:31.482Z",
  "environment": "production",
  "model": "claude-sonnet-4-20250514",
  "prompt_tokens": 1284,
  "completion_tokens": 396,
  "latency_ms": 812,
  "cost_usd": 0.0098,
  "system_prompt_hash": "sha256:9f2c1a4e...",
  "user_id_hash": "sha256:b7e4d831...",
  "rag_sources": ["kb/pricing-2026.md", "kb/refund-policy.md"],
  "guardrail_result": "pass",
  "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736",
  "span_id": "00f067aa0ba902b7"
}

Tre fält förtjänar en kommentar. cost_usd beräknas vid anropstillfället från tokenantalet och modellens publicerade pris, aldrig efterfyllt av ett nattjobb. De två hashfälten är Regel 1-kompromissen: korrelerbara offline, ogenomskinliga i loggen. Och trace_id och span_id är W3C trace-context-värden, vilket är exakt vad Regel 4 handlar om.

Om fyra tjänster som anropar leverantörer direkt låter som fyra ställen att instrumentera, centraliserar en LiteLLM-proxy det: en loggningshook framför varje leverantör.

Regel 3: Fånga tokenantal och kostnad per anrop

Tokenanvändningsspårning hör hemma i själva loggraden, inte i ett warehouse-jobb som kör i morgon. Varje leverantör returnerar tokenantal för indata och utdata i svaret; multiplicera med modellens per-tokenpris vid just det tillfället och skriv cost_usd i posten. Priser ändras, och skiljer sig för cachade kontra färska indatatokens, så att beräkna kostnaden senare med en statisk pristabell skriver tyst om historiken. Med kostnad på varje rad blir "vilken funktion är dyr?" en enradsfråga istället för ett finansprojekt, och det matar direkt in i arbetet med att minska dina LLM-API-kostnader.

Regel 4: Bifoga spårkontext (OpenTelemetry GenAI Semconv)

En loggrad utan spår-ID är föräldralös: du kan läsa den, men du kan inte säga vilken retry, vilket RAG-steg eller vilken användartur som producerade den. Lösningen är OpenTelemetry:s GenAI semconv, standardattributnamnen för att instrumentera modell-anrop. Skicka ut loggen inuti en aktiv span så bifogar trace_id och span_id sig själva, så ett klick i Grafana tar dig från spårvattenfallet direkt till den råa posten.

Attributen värda att sätta på varje gen_ai-span:

AttributTypExempelSyfte
gen_ai.systemstring"anthropic"Leverantörsnamn
gen_ai.request.modelstring"claude-sonnet-4-20250514"Modellen du bad om
gen_ai.response.modelstring"claude-sonnet-4-20250514"Modellen som faktiskt svarade
gen_ai.usage.input_tokensint1284Promptstorlek
gen_ai.usage.output_tokensint396Svarsstorlek
gen_ai.response.finish_reasonsstring[]["stop"]Varför genereringen avslutades
gen_ai.response.idstring"msg_01XK9..."Leverantörens svars-ID
python
from opentelemetry import trace

tracer = trace.get_tracer("ai-sdr")

with tracer.start_as_current_span("chat.completion") as span:
    span.set_attribute("gen_ai.system", "anthropic")
    span.set_attribute("gen_ai.request.model", "claude-sonnet-4-20250514")
    response = client.messages.create(**payload)
    span.set_attribute("gen_ai.usage.input_tokens", response.usage.input_tokens)
    span.set_attribute("gen_ai.usage.output_tokens", response.usage.output_tokens)
    span.set_attribute("gen_ai.response.finish_reasons", [response.stop_reason])
    log.info("llm.request", cost_usd=cost)  # Rule 2 record, trace IDs auto-attached

Regel 5: Maskera PII före loggskrivningen

PII-maskning måste ske innan posten skrivs, inte rensas efteråt. När en e-postadress väl ligger i Loki ligger den också i dina objektlagringsbackuper, och "vi tog bort den senare" är inget GDPR-svar. I vår setup kör Microsoft Presidio som en structlog-processor och fångar 94 % av e-postadresser och telefonnummer innan de når Loki; missarna är nästan alla udda formatering, som vi patchar in i egna recognizers när vi hittar dem.

Hela hooken är femton rader:

python
from presidio_analyzer import AnalyzerEngine
from presidio_anonymizer import AnonymizerEngine

analyzer = AnalyzerEngine()
anonymizer = AnonymizerEngine()

PII_ENTITIES = ["PERSON", "EMAIL_ADDRESS", "PHONE_NUMBER", "CREDIT_CARD", "IBAN_CODE"]

def redact_pii(text: str) -> str:
    results = analyzer.analyze(text=text, language="en", entities=PII_ENTITIES)
    return anonymizer.anonymize(text=text, analyzer_results=results).text

# In the structlog processor chain, before JSONRenderer:
# event_dict["prompt"] = redact_pii(event_dict["prompt"])

Maskeringen ligger i samma pipeline-lager som dina indata- och utdatafilter, och den bör testas på samma sätt. Vår guardrail-pipeline behandlar en läckt e-postadress i en logg som ett underkänt eval, inte som en fotnot i driften.

Regel 6: Sampla smart vid hög volym

Under ungefär 100 000 anrop om dagen, logga allt. Över det är fullvolymloggning en lagringsskatt på data du aldrig kommer att läsa, och samplering är hur du behåller posterna som spelar roll. Haken: slumpmässig samplering är det sämsta alternativet för LLM-trafik, eftersom fel, vägringar och fem-dollar-anrop är sällsynta per definition, så en uniform 10 %-nivå kastar exakt de händelser du felsöker. Sampla efter utfall, inte efter slantsingling.

StrategiNär den användsKomplexitet
Slump (fast 10 %)Baslinjevolym vid stabil trafikLåg
RegelbaseradBehåll alltid specifika modeller, tenants eller routarLåg
SvansbaseradBehåll långsamma, dyra eller felande anrop; släng normalaMedel
TriggerbaseradFull kontext bara när en guardrail utlöses eller ett eval misslyckasMedel
AdaptivSamplingsnivån stiger och faller med trafikvolymenHög

En vanlig setup är regelbaserad i kanterna (produktion och företagstenanter: logga alltid) plus svansbaserad i mitten. Loggvinkeln på det här ramverket: dina guardrail_result- och cost_usd-fält är sampleringssignalerna, redan på plats om du följde Regel 2 och 8.

Regel 7: Sätt en lagringspolicy innan du behöver en

En lagringspolicy för loggar är ett beslut du fattar medan du är lugn, eftersom alternativet är att fatta det under en kostnadsgenomgång vid dubbel volym. GDPR:s lagringsbegränsningsprincip i artikel 5 säger att personuppgifter inte bör sparas "längre än vad som är nödvändigt", vilket i praktiken innebär nivåindelad lagring:

NivåLagringstidLagringAnvändningsfall
Varm (hot)7 dagarLoki / ClickHouse lokal diskLivefelsökning, jourfrågor
Ljum (warm)30 dagarObjektlagringsbaserat index (S3)Sprintkostnadsanalys, incidentgranskning
Kall (cold)1 årKomprimerat S3/GCS-arkivEfterlevnadsförfrågningar, årliga revisioner

Varm svarar på "vad hände för tio minuter sedan?" snabbt och dyrt; kall svarar på "vad sa vi till den här kunden i mars?" långsamt och billigt. Radera enligt schema, automatiskt, annars är nivåerna bara ett diagram.

Regel 8: Logga guardrail- och säkerhetshändelser separat

Säkerhetshändelser (guardrail-block, vägringar, policyöverträdelser) är inte telemetri; de är revisionsposter, och de hör hemma i en egen ström. Tre skäl. Larm: en topp i blockerade promptinjektioner ska pages någon, och du kan inte trimma det larmet mot 1 miljon rutinerade rader. Lagring: efterlevnad kan kräva att säkerhetsposter överlever debugloggar med år. Åtkomst: revisorer får säkerhetsströmmen, inte hela din dataflod. Tagga utslaget i huvudposten (guardrail_result: "block") och routa hela posten till den separata strömmen. Vad som räknas som en säkerhetshändelse täcks i vår guide till guardrail-händelser.

Regel 9: Gör loggar sökbara, inte bara lagrade

En logg du inte kan söka i på under en minut är en backup, inte en observabilitetssignal. Sökbar betyder indexerade fält, ett frågespråk din jour faktiskt kan, och dashboards byggda före incidenten. Vi kör Loki och frågar det mer än 30 gånger i veckan om kostnadsavvikelser, latensregressioner och "visa varje vägran för tenant X igår". Grafana Loki-dokumentationen är referensen för syntaxen; mönstret som tjänar sitt värde är att filtrera direkt på parsade JSON-fält:

logql
{service="ai-sdr"} |= "llm.request" | json
  | model = "claude-sonnet-4-20250514"
  | cost_usd > 0.05
  | line_format "{{.timestamp}} {{.request_id}} ${{.cost_usd}} {{.latency_ms}}ms"

Fem rader, ingen export till en notebook. Om din nuvarande lagring inte kan göra det är det problemet att lösa först.

Vad vi faktiskt loggar i produktion

Nog med teori. Här är den maskerade konfigurationen från vår AI SDR-pipeline, tjänsten bakom siffran 1,2 miljoner anrop i månaden i inledningen. Den kör structlog 25.4.0 som renderar JSON, skeppas till Grafana Cloud Loki via Promtail. Modellen på den här pipelinen är claude-sonnet-4-20250514, och varje anrop går genom exakt den processorkedja från Regel 2 och 5:

python
import structlog

structlog.configure(
    processors=[
        structlog.contextvars.merge_contextvars,
        structlog.processors.add_log_level,
        structlog.processors.TimeStamper(fmt="iso"),
        redact_pii,          # Rule 5: Presidio, before serialization
        add_otel_trace_ids,  # Rule 4: trace_id + span_id from the active span
        structlog.processors.JSONRenderer(),
    ],
)
log = structlog.get_logger()

Två siffror från första kvartalet med den här setupen. Månadsintaget stabiliserade sig på 47 GB över fyra tjänster, och p95-loggskrivningslatens är 3 ms, det vill säga pipelinen lägger inte till något mätbart till anropstiden.

Konfigurationsändringen som betalade för sig: vi lade till cost_usd i varje loggpost i mars 2026. Inom en vecka hittade vi en promptmall som brände 340 $/månad i retry-loopar. Ett övergående API-fel utlöste tre retries, som var och en skickade om hela 4 000-token-kontexten. Loggarna gjorde det till en enradsfråga; utan kostnad per anrop hade det dykt upp som en oförklarad post i nästa kvartals budgetgenomgång.

Vad kostar LLM-logglagring i skala?

Vid 1 miljon anrop om dagen kostar LLM-logglagring mellan ungefär 1,20 $ och 108 $ i månaden för samma data, beroende på lagringen. Matten: en fullständig strukturerad post är i genomsnitt cirka 2 KB, så 1 miljon anrop om dagen är 2 GB om dagen, eller 60 GB i månaden. De leverantörspublicerade priserna nedan (juli 2026) är vad de 60 GB kostar i tre vanliga backends.

BackendPrissättningsmodell (leverantörspublicerad, juli 2026)60 GB/månadNoter
Datadog LLM Observability0,10 $/GB intag + 1,70 $/GB indexerat~108 $Indexeringen är den dyra raden
Grafana Cloud Loki~0,50 $/GB via objektlagring~30 $Ännu billigare självhostat
ClickHouse (självhostat, S3)~0,02 $/GB komprimerad lagring~1,20 $ + beräkningBeräkning är den verkliga kostnaden

Källor: Datadog-prissättning, Grafana Loki och ClickHouse observabilitetsdokumentation.

Två förbehåll, eftersom det här är vår beräkning från leverantörspriser, inte ett benchmark vi körde. För det första antar Datadogs siffra att du indexerar allt; de flesta team indexerar en delmängd och betalar mycket mindre, medan Loki och ClickHouse främst tar betalt för det du lagrar. För det andra döljer självhostat ClickHouse på 1,20 $ en verklig räkning: beräkningen för att köra klustret och ingenjörstimmarna för att driva det. Vid 60 GB i månaden är en hanterad tjänst nästan alltid det billigare totalsvaret. Självhostning börjar löna sig över ungefär 1 TB i månaden, där gapet per GB överväldigar driftskostnaden.

Spridningen är poängen. Vid 1 miljon anrop per dag är gapet mellan Datadog-indexerat och självhostat ClickHouse ungefär 90 gånger: 108 $ mot 1,20 $ för samma 60 GB. Välj lagringen vid arkitekturtillfället, inte efter att fakturan anländer.

Vilket loggningsverktyg ska du välja?

För de flesta team kokar valet ner till fyra alternativ: en LLM-native-plattform (Langfuse eller LangSmith), ett proxy-lager-verktyg (Helicone), eller en vanlig OpenTelemetry-pipeline in i infrastruktur du redan kör. Tabellen täcker beslutsställena som faktiskt skiljer sig; dashboards, uppspelning och promptversionshantering är grundkrav i alla fyra.

LangfuseLangSmithHeliconeOTel-native (Loki/ClickHouse)
SjälvhostbarJa (open-source-kärna)Nej (SaaS)Ja (open source)Fullt ut
OTel-kompatibelJa (OTLP-intag)Delvis (OTLP-export)DelvisNative
KostnadsspårningJaJaJaGör det själv (beräkna cost_usd själv)
Inbyggd PII-maskningNej (förbehandla)NejNejNej (Presidio, enligt Regel 5)
GratisnivåJa (moln + självhost)Ja (begränsad)JaGratis mjukvara; du betalar infra

Vår åsikt, rakt på sak: vi kör OTel-native plus Loki eftersom vi redan hade Grafana-stacken för allt annat, och att lägga till ytterligare en datakälla slog att ta in en fjärde leverantör. Om du börjar från noll utan någon observabilitetsstack alls är Langfuse spårningsmodell och dess gratisnivå den snabbaste vägen till användbar, och självhostningsalternativet håller utgången öppen. Om du väljer mellan de två LLM-native-ledarna kör vår Langfuse vs LangSmith-genomgång hela jämförelsen. Och om loggning är en del av ett större övervakningsbeslut täcker den fullständiga plattformsjämförelsen det bredare fältet.

Vilka är de vanligaste misstagen med LLM-loggning?

Sex misstag står för det mesta av de trasiga LLM-loggningssetupar vi har tittat på. Vart och ett är billigt att undvika om du fångar det innan loggvolymen gör det:

  • Logga rå PII utan maskning. Det vanligaste och det dyraste. En supportexport eller en läckt bucket gör promptloggar till en dataskyddsincident. Maskera före skrivningen (Regel 5), inte vid läsning.
  • Ingen lagringspolicy. Obegränsad lagring är standard överallt, och den fördubblar tyst din räkning varje år. Om du aldrig raderar har du inget loggningssystem; du har ett arkiv med storhetsvansinne.
  • Ostrukturerade textloggar. Print-utdata som du bara kan grepa fungerar i demoskala och kollapsar vid 100 000 anrop om dagen, när "hitta varje misslyckat anrop för modell X" blir en shellskript-eftermiddag istället för en fråga.
  • Bara logga fel. Framgångsrika anrop är baslinjen du upptäcker drift mot, och de är råmaterialet i din eval-pipeline. Logga vinsterna också, samplade om volymen tvingar.
  • Ignorera kostnadsfält. Ingen cost_usd per anrop betyder inga kostnadslarm, ingen attributering per funktion, och 340 $/månad-retry-loopen från produktionsavsnittet ovan förblir osynlig tills kvartalsräkningen.
  • Ingen spårkorrelation. Loggar som är frikopplade från spans gör felsökning av flerstegsagenter till gissningar. Om din loggrad saknar trace_id är Regel 4 lösningen.

Om författaren

Mert Batur är medgrundare av Techsy.io, där teamet skeppar AI-agenter, automationssystem och röst-/SDR-pipelines för B2B-kunder. Han skriver om LLM-verktygsstacken som Techsy-teamet faktiskt använder i produktion. Kontakta på LinkedIn.

Vanliga frågor

Vad bör du logga för varje LLM-anrop?

Som minimum: hela prompten och svaret med PII maskerad, modellnamnet, tokenantal för indata och utdata, latens, kostnad i USD, en hashad användaridentifierare och OpenTelemetry spår- och span-ID:n. Lägg till RAG-käll-ID:n och guardrail-utslaget om din pipeline har de stegen. Fjorton namngivna fält, en JSON-rad per anrop.

Vad är det bästa formatet för LLM-loggar?

Strukturerad JSON, ett objekt per anrop, med varje fält uttryckligen namngivet. Fri-text-loggar kan bara grepas; JSON-poster kan aggregeras per modell, summeras per kostnad och joinas till spår. Skicka ut posten med en strukturerad logger som structlog i Python eller pino i Node, och rendera den med en JSON-serialiserare, aldrig strängformatering.

Hur hanterar du PII i LLM-loggar?

Maskera före loggskrivningen, inte efter. Kör prompten och svaret genom en detektor som Microsoft Presidio inuti din loggningspipeline, och ersätt namn, e-postadresser och telefonnummer med tokens som <EMAIL_ADDRESS>. När rå PII väl når din logglagring ligger den också i dina backuper, och efterföljande radering uppfyller sällan GDPR:s minimeringstest.

Vad kostar LLM-logglagring i skala?

För 1 miljon anrop om dagen, cirka 60 GB i månaden vid 2 KB per post, räkna med ungefär 108 $/månad på Datadogs indexerade LLM Observability-prissättning, 30 $/månad på Grafana Cloud Loki, eller cirka 1,20 $/månad i komprimerad S3-lagring för självhostat ClickHouse plus beräkning. Det är leverantörspublicerade priser per juli 2026; självhostning lägger till ingenjörstid utöver det.

Vad är OpenTelemetry GenAI semconv?

Det är OpenTelemetry:s standardattributnamn för att instrumentera LLM-anrop: gen_ai.system för leverantören, gen_ai.request.model för modellen, gen_ai.usage.input_tokens och output_tokens för tokenantal, och gen_ai.response.finish_reasons för varför genereringen stoppades. Att använda dem innebär att valfri OTel-kompatibel backend, från Jaeger till Tempo till Langfuse, läser dina spår utan egna parsers.

Hur samplar du LLM-loggar vid hög trafik?

Behåll varje fel, varje guardrail-block och varje anrop över en kostnadströskel, och sampla sedan resten. Den här svansbaserade metoden bevarar de sällsynta händelser du faktiskt felsöker, medan uniform slumpsamplering kastar dem i samma takt som tråkig trafik. Under 100 000 anrop om dagen, hoppa över samplering helt och logga allt.

Hur länge bör du lagra LLM-loggar?

Nivåindela: 7 dagar varmt för livefelsökning, 30 dagar ljumt för incidentgranskning och kostnadsanalys, och upp till 1 år kallt i komprimerad objektlagring för efterlevnad och revisioner. GDPR:s lagringsbegränsningsprincip förbjuder att behålla personuppgifter längre än nödvändigt, så para ihop varje nivå med automatisk radering istället för manuell rensning.

Vad är skillnaden mellan LLM-loggning och LLM-spårning?

En logg är en platt post av en händelse: det här anropet hände, med de här fälten. Ett spår är ett orsaksträd av spans över en hel anropssökväg, säg hämtning, sedan modell-anropet, sedan två verktygsanrop. Loggar berättar vad; spår berättar var och varför. Produktionssetupar skickar ut båda, sammanbundna med trace_id.

Slutsats

Sammanfattning: logga varje anrop som JSON med namngivna fält, beräkna kostnaden vid anropstillfället, bifoga OTel-spårkontext, maskera PII före skrivningen, sampla efter utfall när du passerar 100 000 anrop om dagen och välj en lagring du faktiskt kan söka i. De nio reglerna är ordnade så att du kan införa en per sprint, och Regel 2, 4 och 5 är de tre som betalar tillbaka snabbast. Om du väljer den bredare övervakningsstacken runt loggarna, börja med vår genomgång av de bästa AI-observabilitetsplattformarna. Och om du behöver hjälp att koppla ihop strukturerad loggning för din LLM-stack, ta en gratis konsultation.

Taggar

llm-loggning bästa praxisstrukturerad loggningopentelemetry genaipii-maskningllm-observabilitetlagringstid för loggar

Dela denna artikel

Starta ditt projekt

Redo att bygga något utöver det vanliga?

Låt oss göra verklighet av din idé. Vårt team hjälper dig gärna att bygga mjukvara som gör skillnad.