NeatlogsNeatlogs
Custom Instrumentation

@span Decorator

Instrument your own functions with neatlogs.span().

@neatlogs.span(kind="...") wraps a function with a span. Use it on any custom code that auto-instrumentation doesn't cover — your own agents, chains, tools, and pipelines.

Basic Usage

import neatlogs

@neatlogs.span(kind="WORKFLOW")
def handle_request(user_input: str) -> str:
    ...

@neatlogs.span(kind="AGENT")
def research_agent(state: dict) -> dict:
    ...

@neatlogs.span(kind="CHAIN")
def rag_pipeline(query: str) -> str:
    ...

Works on both sync and async functions.

Parameters

ParameterKindDescription
kindAllRequired. One of: WORKFLOW, AGENT, CHAIN, TOOL, RETRIEVER, EMBEDDING, GUARDRAIL, MCP_TOOL
nameAllSpan name shown in the dashboard. Defaults to the function name.
roleAGENTThe agent's role (e.g., "Researcher", "Router")
goalAGENTThe agent's goal or objective
tool_nameTOOL, MCP_TOOLTool identifier shown in the dashboard
descriptionTOOL, MCP_TOOLHuman-readable tool description
modelEMBEDDINGEmbedding model name
dimensionEMBEDDINGVector dimension
versionAllVersion string for tracking changes
capture_inputAllWhether to record function arguments (default: True)
capture_outputAllWhether to record the return value (default: True)

Examples

Agent with role and goal

@neatlogs.span(kind="AGENT", name="routing_agent", role="Router", goal="Route to the right tool")
def route_request(query: str) -> dict:
    ...

Tool

@neatlogs.span(kind="TOOL", name="check_order", tool_name="check_order_status")
def check_order_status(order_id: str) -> dict:
    ...

MCP tool

@neatlogs.span(kind="MCP_TOOL", name="get_time", tool_name="get_time")
async def get_time() -> str:
    ...

Retriever (auto-extracts query and documents)

When using @span(kind="RETRIEVER"), the decorator automatically extracts the query from the function's first string argument (looking for parameters named query, question, or text) and extracts the documents from the return value (if it's a list or dict):

@neatlogs.span(kind="RETRIEVER", name="vector_search")
def retrieve_docs(query: str) -> list:
    # query is auto-captured as retrieval.query
    # return value (list of docs) is auto-captured as retrieval.documents
    return vector_db.search(query, top_k=5)

For custom document formats or when you need more control, use with neatlogs.trace(kind="RETRIEVER") as span: and set attributes manually. See Custom Attributes.

Disable content capture

@neatlogs.span(kind="CHAIN", capture_input=False, capture_output=False)
def process_sensitive_data(payload: dict) -> dict:
    ...

Content capture can also be disabled globally with the NEATLOGS_TRACE_CONTENT=false environment variable.