Standardize log formats, log levels, and trace instrumentation so that AI stops logging chaotically everywhere.

As AI-assisted programming becomes increasingly common, an easily overlooked problem is emerging:

AI is very good at writing logs, but not necessarily good at writing “useful logs.”

Ask AI to implement a feature, and it will likely generate something like:

logger.info("start")
logger.info("processing...")
logger.info("data loaded")
logger.error("failed")

Individually, these seem fine. But as the project grows larger, you’ll find your logs turning into something like this:

start
processing...
loading data...
done
request failed
retry...
success

When something actually breaks, it becomes very difficult to answer a few of the most basic questions:

  • Which request caused the error?
  • Which module caused the error?
  • Which step caused the error?

The problem is rarely “too few logs.” It’s usually:

There are plenty of logs, but no system behind them.

Therefore, just like exception handling, dependency injection, and interface contracts, logging should also be part of the project architecture — not left to the free rein of developers or AI.


📌 Technical Profile

Logging Architecture

Refers to the standardized management of a system’s operational records through unified logging components, formats, levels, contextual fields, and trace identifiers.

What it solves is not merely:

“How do I print a line of text?”

But rather:

After a problem occurs in the system, can we quickly reconstruct what happened?

A reasonably complete logging system typically includes:

Unified Log Entry Point
Unified Log Format
Log Level Standards
Request Context
Trace Identifier (request_id)
File / JSON / Log Platform
Search, Troubleshooting, Audit, Performance Analysis

Here, request_id can be understood as:

The ID card number of a single request.

As long as the entire call chain carries this number, you can reassemble logs scattered across different modules.


1. Why Can’t We Let AI Log Freely?

Suppose we ask AI to implement three modules.

AI might write them separately as:

logger.info("user created")
logger.info("Create agent success")
logger.info(f"knowledge base {kb_id} loaded")

And even:

print("start")

Each one works on its own.

But when the entire project is assembled, several typical problems emerge.

1.1 Inconsistent Formats

Some logs write:

create user success

Some write:

User created successfully

And some even write:

ok!!!

Machines can barely perform stable analysis on these.

1.2 Chaotic Log Levels

For example, AI easily writes:

logger.error("User not found")

But “user not found” is often just a normal business outcome and should not be logged as a system error.

Conversely:

logger.info(f"Database connection failed: {e}")

A database connection failure is logged as ordinary information.

Eventually it becomes:

ERROR ERROR ERROR ERROR ERROR

And genuinely important errors get drowned out.

1.3 Logs from the Same Request Can’t Be Correlated

A single chat request might pass through:

HTTP API
ChatService
AgentRunner
Tool
Knowledge Base
LLM

Each layer has its own logs.

Without a unified trace identifier, all you see is dozens of unrelated messages.


2. Logging Rules

2.1 Unified Log Entry Point

The most important first step in a logging system is not designing the format. It is:

The entire project has only one standard way to use logging.

Take miniagent as an example. The project centralizes core logging configuration in:

backend/app/core/logger_config.py

And provides a unified method:

def get_logger(name: str = None):
    if name:
        return logger.bind(name=name)
    return logger

Business modules uniformly use:

from app.core.logger_config import get_logger

logger = get_logger(__name__)

Instead of having some places doing:

import logging

Other places doing:

from loguru import logger

And still other places doing:

print(...)

miniagent currently uses Loguru as its logging library and manages console, file, error, and debug logs through a unified configuration.

This approach has one critically important value:

AI doesn’t need to redesign the logging system every time — it only needs to follow the entry point already defined by the project.


2.2 Unified Log Format

A good log format should at least answer:

When?
What level?
Which request?
Which module?
Which function?
What happened?

miniagent’s current console log format is roughly:

Time | Level | request_id | Module:Function:Line | Message

The actual configuration looks something like:

format=(
    "{time:YYYY-MM-DD HH:mm:ss.SSS} | "
    "{level: <8} | "
    "{extra[request_id]} | "
    "{extra[name]}:{function}:{line} | "
    "{message}"
)

The resulting log looks something like:

2026-08-12 18:21:31.426 | INFO     |
2b91c7... |
app.services.chat:send_message:126 |
Agent execution started

This single log entry already contains several core dimensions:

Time
Log Level
request_id
Module
Function
Code Line
Event

So troubleshooting no longer relies on “guessing.”


2.3 Clear Log Levels

Common log levels include:

DEBUG — Debug Information

DEBUG stands for Debug.

Primarily used during development to observe internal state, for example:

logger.debug(f"Retrieved {len(chunks)} chunks")

Suitable for recording:

Intermediate variables
Number of retrieval results
Routing decisions
Internal execution steps
Model parameters

Usually not output in large quantities in production environments.

INFO — Normal Operation Information

INFO stands for Information.

Indicates that the system is performing important actions normally:

logger.info("Agent execution started")

For example:

Application startup
User login
Task started
Agent invocation completed
Knowledge base loaded
Request completed

WARNING — Warnings

WARNING indicates:

The system can still continue running, but a noteworthy issue has occurred.

For example:

logger.warning("Knowledge base returned no result")

Or business exceptions:

logger.warning(f"NotFoundError: {exc}")

miniagent’s global exception handler currently logs predictable business exceptions like NotFoundError and AlreadyExistsError as WARNING, rather than treating them as system crashes.

This is a very important logging philosophy:

A business failure does not equal a system fault.

ERROR — System Errors

ERROR indicates:

A certain function can no longer be completed normally.

For example:

logger.error("Database initialization failed")

Suitable for:

Database connection failure
LLM call failure
File read failure
Critical service unavailable

CRITICAL — Severe Failures

CRITICAL stands for Critical.

Used for:

System cannot start
Core database corruption
Critical configuration missing
Core infrastructure unavailable

These logs typically mean:

The system may no longer be able to provide service.


3. Don’t “Log Everything”

There is another important principle in logging systems:

Not every step executed is worth becoming a log entry.

For example:

logger.info("enter function")
logger.info("get user")
logger.info("check user")
logger.info("start processing")
logger.info("processing...")
logger.info("return result")

The primary effect of such logs is usually just:

Generating noise.

A better approach is to log “events.”

For example:

logger.info(
    f"Agent execution started: agent_id={agent_id}"
)

And:

logger.info(
    f"Agent execution completed: agent_id={agent_id}, "
    f"duration={duration:.3f}s"
)

Logs should primarily record:

State changes
Key decisions
External calls
Exceptions
Performance metrics
Security events
Business audit events

Rather than:

I reached line 17.

4. The Truly Critical Step: Adding Trace Identifiers

As systems grow complex, a single HTTP request might pass through dozens of functions.

For example, in miniagent:

POST /chat
Chat API
ChatService
AgentRunner
     ├── LLM
     ├── Knowledge Base
     └── Web Search

If each module logs independently, it’s hard to know which logs belong to the same request.

The solution is:

request_id

That is:

Request Identifier

miniagent generates one for each request in the HTTP middleware:

request_id = str(uuid4())

Where UUID stands for:

Universally Unique Identifier

Then it writes:

request.state.request_id = request_id

Suppose this request gets:

request_id = 742fd2b1...

Now all subsequent logs carry:

742fd2b1...

And so:

742fd2b1 | HTTP request received
742fd2b1 | Agent started
742fd2b1 | KB retrieval started
742fd2b1 | KB returned 6 chunks
742fd2b1 | LLM started
742fd2b1 | Agent completed
742fd2b1 | HTTP 200

A complete call chain forms instantly.


5. miniagent Logging System: From a Single Request to a Fully Traceable Chain

Below is the overall relationship of miniagent’s current logging architecture:

flowchart TB A["HTTP Request
Client Request"] --> B["FastAPI Middleware
Request Logging Middleware"] B --> C["Generate request_id
Generate Unique Request Identifier"] C --> D["Logging Context
Log Context"] C --> E["Audit Context
Audit Context"] D --> F["Application Code
Business Code"] F --> F1["API / Service"] F --> F2["AgentRunner"] F --> F3["Knowledge Base / RAG"] F --> F4["LLM / Tool"] F1 --> G["get_logger(__name__)"] F2 --> G F3 --> G F4 --> G H["Third-party Libraries
Third-party Components"] --> H1["Uvicorn"] H --> H2["SQLAlchemy"] H --> H3["ChromaDB"] H1 --> I["Python logging"] H2 --> I H3 --> I I --> J["InterceptHandler
Standard Log Bridge"] G --> K["Loguru
Unified Log Center"] J --> K D -. "request_id auto-injected" .-> K K --> L["Console
Console"] K --> M["miniagent_YYYY-MM-DD.log
INFO and above"] K --> N["error.log
ERROR and above"] K --> O["debug.log
DEBUG / Development"] K --> P["Structured JSON Log
JSON Structured Log"] E --> Q["Audit Log DB
Database Audit Record"] C -. "same request_id" .-> Q B --> R["HTTP Response"] R --> S["X-Request-ID
Return Trace Identifier"] R --> T["X-Process-Time
Return Request Duration"]

The most important things in this diagram are actually three main lines.

The first line is:

HTTP Request
Middleware
request_id
Logging Context
Business Code
Loguru
Console / File / JSON

The second line is:

Uvicorn / SQLAlchemy / ChromaDB
      Python logging
     InterceptHandler
          Loguru

The third line is:

              request_id
             /          \
            ↓            ↓
      Application Logs   Audit Context
                     Audit Log DB

In other words, miniagent doesn’t simply “unify log printing.” It places:

Application logs
Third-party logs
Request traces
Audit records
Performance timing

into a single correlated system.


5.1 How to Automatically Propagate request_id

If every function passes it like this:

service.run(request_id=request_id)

That certainly works.

But as call depth increases, the code becomes very ugly.

miniagent uses Loguru’s contextualization mechanism:

with logger.contextualize(request_id=request_id):
    response = await call_next(request)

Logs executed within this request scope automatically receive the corresponding request_id.

So business code can still simply write:

logger.info("Agent started")

But the output automatically becomes:

742fd2b1 | Agent started

This is what we call:

Context Logging

Its core idea is:

Business Code
does not repeatedly pass request_id


Infrastructure Layer
auto-injects context

This is a very worthwhile architectural boundary to enforce with AI.


5.2 Returning request_id to the Frontend

The trace identifier isn’t just for the server’s own use.

miniagent also:

response.headers["X-Request-ID"] = request_id

HTTP Header stands for:

Hypertext Transfer Protocol Header

So if a frontend user reports:

The chat API returned an error.

Developers no longer need to ask:

Around what time?
Which user?
What did they send?

The frontend just needs to provide:

X-Request-ID: 742fd2b1...

The server searches directly for:

742fd2b1

And can reconstruct the entire request process.

This is logging evolving from:

“Printing text”

to:

A fault tracing system.


5.3 Trace Logs Can Also Connect with Audit Logs

miniagent goes a step further.

The audit context also stores:

@dataclass
class AuditRequestContext:
    request_id: str
    method: str
    path: str
    ip_address: Optional[str] = None
    user_id: Optional[int] = None
    username: Optional[str] = None

And when the HTTP request creates an audit context, it directly reuses the same:

request_id=request_id

So:

Application Logs
     │ request_id
742fd2b1

Database Audit Records
     │ request_id
742fd2b1

The two systems are now connected.

You can know not only:

What went wrong with the program

But also:

Who
When
Through which endpoint
Performed what operation
What the final result was

This is especially important for backend management systems.


5.4 Third-Party Library Logs Must Also Be Consolidated

Real-world projects have another common problem.

Your own code uses Loguru:

logger.info(...)

But many third-party libraries use Python’s standard logging module:

logging.getLogger(...)

For example:

Uvicorn
SQLAlchemy
ChromaDB

If left unhandled, you’ll see:

2026-08-12 | INFO | miniagent ...
INFO: uvicorn request ...
sqlalchemy.engine INFO ...

Log formatting becomes fragmented again.

miniagent implements:

class InterceptHandler(logging.Handler):

To forward all Python standard logging output to Loguru.

Then:

logging.basicConfig(
    handlers=[InterceptHandler()],
    level=0,
    force=True
)

And specifically intercepts:

uvicorn
uvicorn.error
uvicorn.access

So the final result is:

Business Code ──────┐
FastAPI ────────────┤
Uvicorn ────────────┤
                    ├──→ Loguru
SQLAlchemy ─────────┤
ChromaDB ───────────┘

All logs use the same format, the same file strategy, and the same trace mechanism.


5.5 Different Logs Should Go to Different Files

Stuffing all logs into a single:

app.log

is simple but not suitable for long-term operation.

miniagent currently splits logs by purpose.

General Logs

miniagent_2026-08-12.log

Records:

INFO
WARNING
ERROR
CRITICAL

Rotated daily, retaining 30 days.

Error Logs

error.log

Records only:

ERROR
CRITICAL

Rotated when the file reaches 10 MB, keeping recent historical files.

This way, when troubleshooting production issues, you can go directly to:

error.log

Without searching through hundreds of thousands of normal request logs for anomalies.

Debug Logs

In development mode:

debug.log

Records more detailed:

DEBUG

information.

This prevents production environments from continuously generating large volumes of meaningless debug logs.


5.6 Structured Logging: Preparing Logs for Machines

Traditional logs are meant for humans:

2026-08-12 18:20:31 | INFO | 742fd2b1 | Agent completed

But if you plan to integrate with a log analysis platform in the future, a format like this is more suitable:

{
  "time": "2026-08-12T18:20:31",
  "level": "INFO",
  "request_id": "742fd2b1",
  "module": "agent_runner",
  "event": "agent_completed",
  "duration_ms": 842
}

This approach is called:

Structured Logging

The most common format is JSON.

JSON stands for:

JavaScript Object Notation

Its biggest advantage isn’t “looking sophisticated” — it’s that machines can easily search, filter, aggregate

miniagent already supports generating structured log files via configuration:

JSON_LOG_ENABLED=true

And uses Loguru’s:

serialize=True

to output JSON logs.

After that, it becomes much easier to integrate with:

ELK
Loki
Grafana

Where:

ELK stands for:

Elasticsearch + Logstash + Kibana

Responsible for log storage/search, collection/processing, and visualization respectively.

Grafana is commonly used for:

Monitoring metrics and log visualization.

For miniagent’s current stage, local files are sufficient. When deployment scale grows, a centralized log platform can be added.

This is what we mean by:

Design the architectural boundaries first, rather than piling on complex infrastructure from the start.


6. How Should Logging Rules for AI Be Written?

Once we’ve established a logging system, we should further communicate the rules to AI.

For example, add this to the project rules:

## Logging Rules

1. Do not use `print()` for runtime logging.
2. Use `get_logger(__name__)` for application logging.
3. Do not create custom logging configurations within business modules.
4. Use `DEBUG` for internal diagnostic information.
5. Use `INFO` for important business events.
6. Use `WARNING` for recoverable or expected exceptional conditions.
7. Use `ERROR` for operation failures.
8. Use `logger.exception()` when a stack trace is needed.
9. Do not log passwords, tokens, API keys, or other sensitive data.
10. Do not manually generate `request_id` within business services.
11. Request-scoped logs will automatically inherit the middleware's `request_id`.
12. Prefer meaningful events over process messages such as "start", "processing", or "done".

Translated into architectural requirements, this means:

No print
Unified Logger
Unified Levels
Unified Format
Auto-attach request_id
No Sensitive Information
Only Log Valuable Events

From now on, when AI writes new features, it should no longer freely improvise the logging system.


7. Logging Caveats

7.1 Logs Also Need “Prohibited Actions” Defined

Log standards don’t just specify:

What should be logged.

They must also clearly state:

What must absolutely never be logged.

For example:

logger.info(f"password={password}")

Should absolutely never appear.

This also includes:

Passwords
JWTs
API Keys
Access Tokens
Refresh Tokens
Database passwords
Full cookies
ID numbers and other sensitive information

Where JWT stands for:

JSON Web Token

And API stands for:

Application Programming Interface

Log files are often retained for long periods and may be read by:

Developers
Operations staff
Log servers
Monitoring systems

Therefore:

Logs are not temporary debug windows — they are persistent data.


7.2 The Most Common Logging Mistakes AI Makes

When having AI write code in the future, you can focus on checking the following situations.

❌ Mistake 1: Overusing INFO

logger.info("enter method")
logger.info("checking param")
logger.info("query database")
logger.info("return result")

Process noise should be reduced.

❌ Mistake 2: Only Printing the Exception String

except Exception as e:
    logger.error(str(e))

This often loses the:

Stack Trace

It’s better to use:

except Exception:
    logger.exception("Agent execution failed")

❌ Mistake 3: Duplicate Exception Logging

For example:

logger.error(f"Failed: {exc}")
logger.exception(exc)

Unless there’s a specific purpose, this typically creates duplicate logs.

❌ Mistake 4: All Business Exceptions as ERROR

For example:

logger.error("User not found")

In many cases, the more appropriate choice is:

logger.warning("User not found")

❌ Mistake 5: Business Layer Generating Its Own request_id

request_id = uuid4()

This breaks the entire call chain.

You should reuse the request context already created at the entry layer.


8. What a Logging System Truly Constrains Is AI’s “Freedom”

The biggest problem with AI writing code is usually not an inability to implement features. Quite the opposite:

It implements features too easily.

Without project rules, every time AI writes a module, it might invent:

A new logging approach
A new exception handling approach
A new return structure
A new utility class
A new naming convention

The code works individually, but the project as a whole becomes increasingly chaotic.

This is where the value of a logging system lies:

Without a Logging Architecture:

AI
Freely decides format
Freely decides level
Freely decides fields
Freely decides whether to print
Logs gradually spiral out of control

After establishing rules:

AI
get_logger()
Logging Rules
Unified Level
Unified request_id
Unified Output

AI no longer needs to “design logging.”

It just needs to:

Write business code within existing boundaries.


Final Thoughts

A mature software project should not rely on developers “remembering how to log.”

Nor should it expect AI to automatically guess each time:

Which logs are important
What level they should be
What context they should carry
Where they should be stored

These things should be determined at the architecture level in advance.

The goal of a logging system is also not:

To make the system print more content.

But rather:

To reconstruct what actually happened in the system using as few — but sufficiently critical — logs as possible.

For AI programming, this constraint is especially important.

Because what truly needs to be standardized is not:

How to write logger.info()

But rather:

When AI is allowed to write logs, what logs to write, and how those logs enter the system’s observable trace chain.

Once log format, levels, context, and trace identifiers are all fixed, AI output is no longer a pile of scattered debug messages.

Instead, it becomes a set of:

Searchable, correlatable, troubleshootable, auditable, and analyzable engineering logs.

That is the real problem a logging architecture should solve.

Open Source Code


🪐 Wishing you good luck 🪐