Google ADK

Trace a Google ADK agent with Neatlogs (Python and Go).

Capture a Google ADK agent's runs, model calls, and tool calls. In Python you wrap the Runner; in Go, ADK auto-instruments through the global OpenTelemetry provider that Init installs — so its spans flow through with no per-call wrapping.

Prerequisites

pip install -U "neatlogs[google-adk]" google-adk

Usage

import os
import neatlogs

neatlogs.init(api_key=os.environ["NEATLOGS_API_KEY"], workflow_name="google-adk-demo")

from google.adk.runners import Runner

# `my_agent` and `session_service` are your existing ADK objects.
runner = neatlogs.wrap(Runner(agent=my_agent, app_name="my-app", session_service=session_service))

# Drive the runner as usual — its runs are now traced.

The ADK run becomes a WORKFLOW/AGENT trace root automatically, with nested AGENT, LLM, and TOOL spans.

Note

In Go, ADK records prompt/completion text on the OpenTelemetry logs signal rather than on spans. Plain passthrough captures model, token usage, tool calls, and finish reasons; wrap the model with nladk.WrapModel(...) (as above) to also put request/response text on the trace. For agent-to-agent (A2A) tracing across the HTTP boundary, see the Go SDK guide.

Sessions & end-user

Say your Google ADK agent fires off a handful of runs in a single back-and-forth and you'd like them filed under one session, tied to whoever's chatting — that's a quick win. In Python you wrap the run in identify(); in Go you stamp the context with Identify(), and either way ADK's own passthrough spans quietly inherit that identity through the identity processor:

# Same session_id every turn → one session; end_user_id attributes it to your user.
with neatlogs.identify(session_id=f"conv_{conversation_id}", end_user_id=user_id):
    # drive the runner as usual — its spans inherit the neatlogs session + end-user
    runner.run(...)

Not every run is a conversation. A standalone workflow — a one-off job, not a back-and-forth — is a single trace with no turns to group. You still attribute it to the customer it ran for, sourcing the id and any metadata from your own user or request object:

Warning

A standalone workflow must not carry a session_id. Omit it entirely — Neatlogs sets session_id = trace_id, so the run is its own single-turn session, fully attributed to the end-user. Reusing one session_id across unrelated runs wrongly folds them into a single multi-turn conversation.

def run_agent(request, user):
    # Standalone workflow — attribute to the end-user; do NOT pass session_id.
    with neatlogs.identify(
        end_user_id=str(user.id),
        end_user_metadata={"plan": user.plan},
    ):
        # drive the runner as usual — its spans carry the end-user
        return runner.run(request)

For the whole picture, see Sessions and End-User Identity.

On this page

Ask Neatlogs AI

Answers from the docs

How can I help?

Ask anything about instrumenting, tracing, or the Neatlogs dashboard.