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:
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 🪐