0Pricing
AI Prompt Engineering · Lesson

Logging and Documentation Strategies

Recording prompt versions, inputs, and outputs for reproducible debugging.

Logging and Documentation Strategies is a free AI Prompt Engineering lesson on CoddyKit — lesson 4 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the AI Prompt Engineering learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.

Why Prompt Logging Matters

Without logging, prompt failures are invisible until a user reports them. With logging, you can:

  • Detect regressions the moment they occur
  • Reproduce any past failure exactly as it happened
  • Measure improvement over time as prompts evolve
  • Audit model behavior for compliance or safety

Logging is not optional for production prompt systems — it is the foundation of reliable LLM applications.

The Minimum Viable Log Entry

Every prompt interaction should log these fields at minimum:

  • timestamp: ISO 8601 UTC
  • prompt_id: which prompt template was used
  • model: exact model name and version
  • temperature: sampling parameter
  • input: the user message (or a hash if PII)
  • output: the model response
  • latency_ms: response time
  • tokens_used: input + output tokens
import time, json
from datetime import datetime, timezone

def logged_call(prompt_id, system_prompt, user_message, model='gpt-4o', temperature=0.7):
    start = time.time()
    resp = client.chat.completions.create(
        model=model,
        messages=[
            {'role': 'system', 'content': system_prompt},
            {'role': 'user', 'content': user_message}
        ],
        temperature=temperature
    )
    latency = int((time.time() - start) * 1000)
    output = resp.choices[0].message.content
    log_entry = {
        'timestamp': datetime.now(timezone.utc).isoformat(),
        'prompt_id': prompt_id,
        'model': model,
        'temperature': temperature,
        'input': user_message,
        'output': output,
        'latency_ms': latency,
        'input_tokens': resp.usage.prompt_tokens,
        'output_tokens': resp.usage.completion_tokens
    }
    append_log(log_entry)
    return output

Structured Logging Format

Use newline-delimited JSON (JSONL) for log files. Each line is a complete, valid JSON object. This format is:

  • Easy to append to without locking
  • Readable by jq, pandas, and all log aggregators
  • Streaming-friendly — each line can be processed as it arrives
import json

LOG_FILE = 'prompt_logs.jsonl'

def append_log(entry):
    with open(LOG_FILE, 'a') as f:
        f.write(json.dumps(entry) + '\n')

def read_logs():
    with open(LOG_FILE) as f:
        return [json.loads(line) for line in f if line.strip()]

# Query: all entries for prompt_id 'summarize_v3'
logs = read_logs()
summarize_logs = [e for e in logs if e['prompt_id'] == 'summarize_v3']
print(f'Total calls to summarize_v3: {len(summarize_logs)}')

Prompt Versioning

Prompts change over time. Without versioning, you cannot reproduce past behavior or compare model outputs across prompt versions. Use a version identifier in every log entry.

Simple versioning: a semantic version string (e.g., v1.2.3) or a git commit hash. Store prompt versions in a dedicated file so any version can be retrieved for replay.

PROMPTS = {
    'summarize': {
        'v1': 'Summarize the following text.',
        'v2': 'Summarize the following text in 3 sentences.',
        'v3': 'Summarize the following text in exactly 3 sentences. '
              'Start each sentence on a new line. No bullet points.'
    }
}

CURRENT_VERSIONS = {'summarize': 'v3'}

def get_prompt(prompt_id):
    version = CURRENT_VERSIONS[prompt_id]
    return version, PROMPTS[prompt_id][version]

version, prompt = get_prompt('summarize')
log_entry['prompt_version'] = version

Handling PII in Logs

User inputs may contain personally identifiable information (PII). Logging raw inputs may violate GDPR or CCPA. Options:

  • Hash: store SHA-256 of input — reproducible for deduplication but not for replay
  • Redact: use a regex or NER model to replace PII before logging
  • Separate storage: log PII in an encrypted store with access controls; log only a reference ID in the main log
import hashlib, re

def redact_pii(text):
    # Redact email addresses
    text = re.sub(r'[\w.-]+@[\w.-]+\.\w+', '[EMAIL]', text)
    # Redact phone numbers (US format)
    text = re.sub(r'\b\d{3}[-.]\d{3}[-.]\d{4}\b', '[PHONE]', text)
    return text

def hash_input(text):
    return hashlib.sha256(text.encode()).hexdigest()[:16]

log_entry['input'] = redact_pii(user_message)
log_entry['input_hash'] = hash_input(user_message)

Latency and Cost Tracking

Logs enable cost and latency dashboards. Track per-prompt-version metrics to detect regressions in performance or cost after a prompt change:

def compute_cost(entry, price_per_1m_input=5.0, price_per_1m_output=15.0):
    input_cost = entry['input_tokens'] / 1_000_000 * price_per_1m_input
    output_cost = entry['output_tokens'] / 1_000_000 * price_per_1m_output
    return input_cost + output_cost

def prompt_stats(prompt_id, version):
    logs = [e for e in read_logs()
            if e['prompt_id'] == prompt_id and e.get('prompt_version') == version]
    if not logs:
        return
    avg_latency = sum(e['latency_ms'] for e in logs) / len(logs)
    total_cost = sum(compute_cost(e) for e in logs)
    print(f'{prompt_id} {version}: {len(logs)} calls, avg {avg_latency:.0f}ms, total ${total_cost:.4f}')

Output Evaluation Logging

Beyond raw logs, store evaluation scores alongside each log entry. This enables trend analysis: is output quality improving across prompt versions?

def evaluated_call(prompt_id, system_prompt, user_message, evaluator_fn):
    output = logged_call(prompt_id, system_prompt, user_message)
    score = evaluator_fn(user_message, output)
    # Update the last log entry with the evaluation score
    logs = read_logs()
    last = logs[-1]
    last['eval_score'] = score
    last['eval_pass'] = score >= 0.8
    # Rewrite the last line
    with open(LOG_FILE, 'a') as f:
        # In practice, use a DB or separate eval log
        pass
    return output, score

Prompt Documentation

Each prompt template should have a companion documentation entry covering:

  • Purpose: what task this prompt performs
  • Variables: what placeholders exist and what they expect
  • Known limitations: inputs where it is known to fail
  • Version history: what changed in each version and why
  • Test cases: link to the test suite for this prompt
PROMPT_DOCS = {
    'summarize': {
        'purpose': 'Summarize a single text passage into 3 sentences.',
        'variables': {'text': 'The passage to summarize (max 2000 tokens)'},
        'known_limitations': [
            'Fails to preserve numbers accurately for texts with many statistics',
            'May not summarize correctly for non-English text'
        ],
        'versions': {
            'v1': 'Initial version — vague length instruction',
            'v2': 'Added 3-sentence limit',
            'v3': 'Added line-break and no-bullet formatting fix'
        },
        'test_suite': 'tests/test_summarize.py'
    }
}

Using Centralized Logging Services

For production systems, write logs to a centralized service rather than local files:

  • LangSmith: LangChain's native tracing and evaluation platform
  • Weights and Biases Prompts: experiment tracking for prompts
  • Datadog / Grafana: standard ops dashboards with custom metrics
  • Supabase / PostgreSQL: query logs with SQL for ad-hoc analysis

The schema is the same; only the destination changes.

# Example: writing to Supabase
from supabase import create_client

supabase = create_client('https://xxx.supabase.co', 'your-anon-key')

def log_to_supabase(entry):
    supabase.table('prompt_logs').insert(entry).execute()

# Now query with SQL:
# SELECT prompt_id, prompt_version, AVG(latency_ms), COUNT(*)
# FROM prompt_logs
# WHERE timestamp > NOW() - INTERVAL '7 days'
# GROUP BY prompt_id, prompt_version
# ORDER BY COUNT(*) DESC;

Alerting on Failure Spikes

Configure alerts when failure rates spike above a threshold. For example: if more than 10% of calls to a prompt return invalid JSON in a 5-minute window, send an alert.

from collections import deque
from datetime import datetime, timezone, timedelta

recent_results = deque(maxlen=100)  # sliding window

def track_and_alert(prompt_id, success, alert_fn, threshold=0.10):
    recent_results.append({'success': success, 'time': datetime.now(timezone.utc)})
    window = [
        r for r in recent_results
        if r['time'] > datetime.now(timezone.utc) - timedelta(minutes=5)
    ]
    if not window:
        return
    fail_rate = sum(1 for r in window if not r['success']) / len(window)
    if fail_rate > threshold:
        alert_fn(f'ALERT: {prompt_id} failure rate {fail_rate:.0%} in last 5 min')

Retention and Archiving

Define a log retention policy:

  • Raw call logs: 30 days (rolling) — high volume, needed for debugging recent issues
  • Aggregated metrics: 1 year — needed for trend analysis and cost forecasting
  • Failure logs: indefinitely — needed for root cause patterns

Compress and archive raw logs after 30 days. Never delete failure logs — they are your institutional memory for prompt engineering.

Knowledge Check

What is the primary advantage of using newline-delimited JSON (JSONL) format for prompt logs compared to a single large JSON array?

Recap: Logging and Documentation

Key practices for prompt logging and documentation:

  • Log every call: timestamp, prompt_id, version, model, temperature, input, output, latency, tokens
  • Use JSONL format: append-friendly, queryable with standard tools
  • Version prompts: every change gets a new version; logs reference the version
  • Handle PII: redact or hash sensitive inputs before logging
  • Track cost and latency: detect regressions after prompt updates
  • Alert on failure spikes: sliding window failure rate monitoring

This concludes Course 17 on Debugging Prompt Failures. Next: Prompt Injection and Defense.

Frequently asked questions

Is the “Logging and Documentation Strategies” lesson free?

Yes — the full text of “Logging and Documentation Strategies” is free to read here on the web, and the AI Prompt Engineering course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the AI Prompt Engineering course, upgrade to CoddyKit PRO.

What will I learn in “Logging and Documentation Strategies”?

Recording prompt versions, inputs, and outputs for reproducible debugging. You practise AI Prompt Engineering with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.

Do I need any experience to start AI Prompt Engineering?

No prior experience is required. AI Prompt Engineering on CoddyKit is structured for beginners through advanced learners; this is — lesson 4 of 4, so you can start here or from the beginning and move at your own pace.

How long does the “Logging and Documentation Strategies” lesson take?

Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.

Can I write and run code in this AI Prompt Engineering lesson?

Yes. Every AI Prompt Engineering lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.

All lessons in this course

  1. Diagnosing Unexpected Outputs
  2. Root Cause Analysis for Prompts
  3. Systematic Debugging Approach
  4. Logging and Documentation Strategies
← Back to AI Prompt Engineering