NeatlogsNeatlogs
Custom Instrumentation

trace() Context Manager

Create sub-spans inline with neatlogs.trace().

with neatlogs.trace(...) creates a span for a block of code inside an existing function. Use it when you need to add a span within a function that is already decorated with @span, or when you need to track prompt templates on an LLM call.

When to Use trace()

  • Prompt template tracking: wrap the compile() + LLM call to capture the template and variables in the span
  • Sub-spans inside @span functions: add granularity to a specific block without decorating a separate function
  • Grouping multiple top-level operations in a main() or script

Do not use trace() just to "create a span" around framework code — auto-instrumentation handles that automatically.

Signature

with neatlogs.trace(
    name,
    kind=None,
    prompt_template=None,
    user_prompt_template=None,
    version=None,
    **attributes,
) as span:
    ...

Prompt Template Tracking

The most common use of trace() is to link a prompt template to the LLM span it produces. Use kind="LLM" and place this at the smallest unit containing the LLM call:

from neatlogs import PromptTemplate, UserPromptTemplate

system_template = PromptTemplate([
    {"role": "system", "content": "You are a {{role}} assistant."},
])
user_template = UserPromptTemplate([
    {"role": "user", "content": "{{question}}"},
])

@neatlogs.span(kind="AGENT")
def answer_agent(question: str) -> str:
    with neatlogs.trace("answer_prompt", kind="LLM",
                        prompt_template=system_template,
                        user_prompt_template=user_template):
        system_msgs = system_template.compile(role="support")
        user_msgs = user_template.compile(question=question)
        response = openai_client.chat.completions.create(
            model="gpt-4o",
            messages=system_msgs + user_msgs,
        )
        return response.choices[0].message.content

Sub-span Inside a Function

Add a retriever or reranker sub-span inside a chain or agent function:

import json
import neatlogs

@neatlogs.span(kind="CHAIN")
def rag_pipeline(query: str) -> str:
    with neatlogs.trace("retrieve", kind="RETRIEVER") as span:
        span.set_attribute("neatlogs.retrieval.query", query)
        docs = my_retriever.search(query, k=5)
        span.set_attribute("neatlogs.retrieval.documents", json.dumps(docs))

    # ... generate answer ...

Grouping Multiple Top-Level Operations

In a script or main() where you have multiple top-level steps that aren't inside a @span function, wrap them together:

def main():
    with neatlogs.trace("data_pipeline"):
        data = fetch_data("api")
        result = process_data(data)
        output = generate_report(result)

Setting Custom Attributes

The span object returned by the context manager is a standard OpenTelemetry span:

with neatlogs.trace("my_step", kind="RETRIEVER") as span:
    span.set_attribute("neatlogs.retrieval.query", query)
    span.set_attribute("neatlogs.retrieval.top_k", 5)
    docs = retriever.search(query)
    span.set_attribute("neatlogs.retrieval.documents", json.dumps(docs))

See Custom Attributes for all available neatlogs.* attribute names.