Sign in

    agent-telemetry

    Download zip
    Version
    0.1.6
    License
    MulanPSL-2.0
    Last updated
    3 months ago
    Downloads
    93

    #agent-telemetry

    OpenTelemetry instrumentation library for MoonBit Agent / LLM / Tool scenarios.

    It wraps the boilerplate OTel tracer / span / attribute code into business-semantic helpers, so agent developers only need to decide when to create spans and what business data to record.

    #Design Layers

    • Thin wrapper (lib.mbt): provider initialization, tracer cache, span lifecycle helpers such as start_span / end_span.
    • Semantic wrapper (genai.mbt, tool.mbt, agent.mbt): sets attributes according to the OpenTelemetry GenAI semantic conventions, covering chat, tool execution, and agent turn spans.

    #Quick Start

    moon add cybershang/agent-telemetry

    // Option 1: one-line initialization from environment variables.
    // Reads OTEL_SERVICE_NAME, OTEL_STDOUT, and OTEL_EXPORTER_OTLP_ENDPOINT.
    // Use ProcessUniqueRandom to avoid duplicate trace/span IDs across restarts.
    let providers = @telemetry.init_from_env(
    id_generator=@telemetry.ProcessUniqueRandom,
    )

    // Option 2: manually select an exporter.
    let config = @telemetry.TelemetryConfig::new(service_name="my-agent")
    let providers = @telemetry.init_telemetry(
    config,
    @telemetry.Otlp("http://localhost:4318"),
    id_generator=@telemetry.ProcessUniqueRandom,
    )

    // Metrics and logs need background tasks.
    @async.with_task_group((group) => {
    providers.spawn_background_tasks(group)

    // Create a GenAI chat span.
    let tracer = @telemetry.tracer("my-agent/llm")
    let meter = @telemetry.meter("my-agent/llm")
    let span = @telemetry.start_chat_span(
    tracer,
    "stepfun",
    "step-3.7-flash",
    1024,
    temperature=Some(0.7),
    stream=Some(true),
    )
    // ... send the request and obtain response_json ...
    @telemetry.set_usage(span, 100L, 25L, cache_read_input_tokens=50L)
    @telemetry.set_response(span, response_json, output_messages~)
    @telemetry.end_span(span)

    providers.force_flush()
    providers.shutdown()
    })

    #Main API

    ScenarioFunctions
    GeneralTelemetryConfig::new, init_telemetry, init_from_env, TelemetryProviders, IdGeneratorOption, tracer, meter, logger, conversation_logger, start_span, end_span, set_attributes, set_string, set_int, set_double, set_bool, set_json
    LLM chatstart_chat_span, set_usage, set_response, set_http_error
    Toolstart_tool_span, set_tool_result, set_tool_error
    Agent turnstart_agent_turn_span, set_turn, set_turn_exhausted
    Agent invocationstart_invoke_agent_span
    Planstart_plan_span
    Metricsrecord_llm_latency, record_usage, record_tool_call, record_turn, record_agent_duration, record_tool_duration
    Logsemit_log, log_info, log_warn, log_error, log_conversation_message

    #Custom Span Attributes

    The library already sets the standard GenAI semantic attributes for chat/tool/agent spans. When you need to record your own domain-specific metadata, use the typed attribute helpers:

    let span = @telemetry.start_span(tracer, "my.step")
    @telemetry.set_string(span, "app.user.id", "user-42")
    @telemetry.set_int(span, "app.retry.count", 3L)
    @telemetry.set_double(span, "app.score", 0.95)
    @telemetry.set_bool(span, "app.cached", true)
    @telemetry.set_json(span, "app.metadata", { "source": "api", "depth": 2 })
    @telemetry.end_span(span)

    set_json serializes the Json value to a string, which is useful for structured metadata that does not fit the scalar attribute types. For arbitrary attribute lists you can still use set_attributes(span, attrs) with @otel.KeyValue values.

    #Instrumentation Guide

    See docs/instrumentation.md for a step-by-step guide that shows how to instrument LLM chat requests, tool executions, and agent turns using the agent-observability sample application as a reference.

    #Environment Variables

    init_from_env reads the following variables:

    VariableDescriptionDefault
    OTEL_SERVICE_NAMEService nameagent-telemetry
    OTEL_STDOUTUse the stdout exporter when set to truefalse
    OTEL_EXPORTER_OTLP_ENDPOINTOTLP/HTTP endpointhttp://localhost:4318
    OTEL_EXPORTER_OTLP_HEADERSGlobal OTLP headers, comma-separated key=value pairs(empty)
    OTEL_EXPORTER_OTLP_TRACES_HEADERSTrace-specific OTLP headers(empty, falls back to global)
    OTEL_EXPORTER_OTLP_METRICS_HEADERSMetric-specific OTLP headers(empty, falls back to global)
    OTEL_EXPORTER_OTLP_LOGS_HEADERSLog-specific OTLP headers(empty, falls back to global)
    GREPTIME_TRACE_PIPELINEGreptimeDB trace pipeline namegreptime_trace_v1
    GREPTIME_LOG_TABLETarget table for conversation logsgenai_conversations

    Signal-specific header variables take precedence over OTEL_EXPORTER_OTLP_HEADERS. This is useful for backends that require authentication tokens or custom routing headers.

    For example:

    OTEL_EXPORTER_OTLP_LOGS_HEADERS="Authorization=Bearer my-token, X-Scope-OrgID=tenant-1"

    #Log Routing

    The library maintains two separate loggers:

    • Default logger (logger / log_info / log_warn / log_error) writes to the standard OTLP logs table (opentelemetry_logs by default).
    • Conversation logger (conversation_logger / log_conversation_message) writes to the table configured by GREPTIME_LOG_TABLE (default genai_conversations).

    When exporting to GreptimeDB, this lets you keep general application logs and LLM conversation logs in separate tables.

    #Flushing and Shutting Down

    TelemetryProviders exposes force_flush() and shutdown() methods that flush/shutdown all three providers (traces, metrics, logs) and print any errors to stdout.

    providers.force_flush()
    providers.shutdown()

    Always call shutdown before the process exits so pending telemetry is exported.

    #ID Generator Option

    The id_generator parameter of init_telemetry / init_from_env supports:

    OptionDescription
    @telemetry.SdkDefaultUse the SDK default RandomIdGenerator
    @telemetry.ProcessUniqueRandomGenerate a process-unique seed from the current timestamp and a counter, avoiding duplicate trace/span IDs across restarts
    @telemetry.Custom(generator)Provide your own IdGenerator

    #Target Backend

    This library depends on opentelemetry/otlp, whose async/http and async/socket interfaces are native-only. Therefore moon.mod declares preferred_target = "native". Projects using this library should also run and test with --target native.

    #Example Project

    A complete agent example using this library can be found at:

    #Testing

    moon test -p cybershang/agent-telemetry --target native

    #License

    Mulan PSL v2

    ExporterType

    pub(all) enum ExporterType {
    Stdout
    Otlp(String)
    Custom(
    SpanExporter
    )
    NoOp
    }

    Exporter type for telemetry initialization.

    IdGeneratorOption

    pub(all) enum IdGeneratorOption {
    SdkDefault
    ProcessUniqueRandom
    Custom(
    IdGenerator
    )
    }

    ID generator option for telemetry initialization.

    TelemetryConfig

    pub struct TelemetryConfig {
    service_name : String
    } derive(
    Debug
    )

    Configuration for initializing OpenTelemetry in an agent application.

    TelemetryConfig::new

    fn TelemetryConfig::new(service_name? : String) -> TelemetryConfig

    Create a new telemetry configuration.

    TelemetryProviders

    Holder for all initialized OpenTelemetry SDK providers.

    TelemetryProviders::force_flush

    async fn TelemetryProviders::force_flush(self : TelemetryProviders) -> Unit

    Flush all providers and print any errors.

    TelemetryProviders::shutdown

    async fn TelemetryProviders::shutdown(self : TelemetryProviders) -> Unit

    Shut down all providers and print any errors.

    TelemetryProviders::spawn_background_tasks

    fn TelemetryProviders::spawn_background_tasks(self : TelemetryProviders, group :
    TaskGroup
    [Unit], allow_failure? : Bool) -> Unit

    Spawn background tasks required by metrics (periodic reader) and logs (batch processor).

    conversation_logger

    fn conversation_logger(scope_name : String) ->
    Logger

    Get or create a logger for conversation messages. Logs from this logger go to the configured GenAI conversations table (default genai_conversations).

    emit_log

    Emit a structured log record.

    end_span

    End a span, optionally setting a final status.

    init_from_env

    async fn init_from_env(service_name? : String, id_generator? : IdGeneratorOption) -> TelemetryProviders

    Initialize the global providers from standard environment variables.

    Reads:
    • OTEL_SERVICE_NAME (overrides service_name)
    • OTEL_STDOUT (true selects the stdout exporter)
    • OTEL_EXPORTER_OTLP_ENDPOINT (when OTEL_STDOUT is not true)

    init_telemetry

    async fn init_telemetry(config : TelemetryConfig, exporter_type : ExporterType, id_generator? : IdGeneratorOption) -> TelemetryProviders

    Initialize the global OpenTelemetry providers (traces, metrics, logs).

    log_conversation_message

    async fn log_conversation_message(scope_name : String, role : String, content : String, index? : Int, trace_context? :
    SpanContext
    ?) -> Unit

    Emit a conversation message log compatible with the genai-observability GreptimeDB dashboard SQL.

    Body JSON shape:
    • user / tool: {"content":"..."}
    • assistant: {"index":0,"message":{"role":"assistant","content":"..."}}

    log_error

    async fn log_error(scope_name : String, body : String, attributes? : Array[
    KeyValue
    ], trace_context? :
    SpanContext
    ?) -> Unit

    Emit an error log.

    log_info

    async fn log_info(scope_name : String, body : String, attributes? : Array[
    KeyValue
    ], trace_context? :
    SpanContext
    ?) -> Unit

    Emit an info log.

    log_warn

    async fn log_warn(scope_name : String, body : String, attributes? : Array[
    KeyValue
    ], trace_context? :
    SpanContext
    ?) -> Unit

    Emit a warning log.

    logger

    Get or create a logger for the given scope name. Logs from this logger go to the default opentelemetry_logs table.

    make_random_id_generator

    Create an IdGenerator with a process-unique seed.

    This helper works around the SDK default RandomIdGenerator's fixed ChaCha8 seed, which produces duplicate trace/span IDs across process restarts.

    meter

    Get or create a meter for the given scope name.

    now_seconds

    async fn now_seconds() -> Double

    Current wall-clock time in seconds since the Unix epoch.

    record_agent_duration

    fn record_agent_duration(meter :
    Meter
    , agent_name : String, seconds~ : Double) -> Unit

    Record the duration of one GenAI agent invocation in seconds.

    Metric: gen_ai.invoke_agent.duration (Histogram, unit="s")

    record_llm_latency

    fn record_llm_latency(meter :
    Meter
    , provider_name : String, model : String, seconds~ : Double) -> Unit

    Record the duration of one GenAI chat operation in seconds.

    record_tool_call

    fn record_tool_call(meter :
    Meter
    , tool_name : String, success~ : Bool) -> Unit

    Record one tool execution.

    record_tool_duration

    fn record_tool_duration(meter :
    Meter
    , tool_name : String, seconds~ : Double) -> Unit

    Record the duration of one GenAI tool execution in seconds.

    Metric: gen_ai.execute_tool.duration (Histogram, unit="s")

    record_turn

    fn record_turn(meter :
    Meter
    , max_tool_turns_reached~ : Bool) -> Unit

    Record one completed agent turn.

    record_usage

    fn record_usage(meter :
    Meter
    , provider_name : String, model : String, token_type : String, value : Int64) -> Unit

    Record token usage for one GenAI chat operation.

    set_attributes

    Set multiple attributes on a span.

    set_bool

    fn set_bool(span :
    Span
    , name : String, value : Bool) -> Unit

    Set a boolean on a span.

    set_double

    fn set_double(span :
    Span
    , name : String, value : Double) -> Unit

    Set a floating-point on a span.

    set_http_error

    fn set_http_error(span :
    Span
    , status_code : Int, message? : String) -> Unit

    Mark a chat span as failed due to an error.

    Sets error.type, records the span status as error, and emits a gen_ai.client.operation.exception event following the OTel GenAI semantic conventions.

    set_int

    fn set_int(span :
    Span
    , name : String, value : Int64) -> Unit

    Set an integer on a span.

    set_json

    fn set_json(span :
    Span
    , name : String, value : Json) -> Unit

    Set a JSON on a span. The JSON value is serialized to a string, which is useful for structured custom metadata that does not have a dedicated scalar attribute.

    set_response

    fn set_response(span :
    Span
    , response_json : Json, output_messages? : Json?, time_to_first_chunk? : Double?) -> Unit

    Set response attributes for a successful chat request.

    Extracts gen_ai.response.id, gen_ai.response.model and gen_ai.response.finish_reasons from response_json. Also sets gen_ai.output.messages and gen_ai.response.time_to_first_chunk when provided.

    set_string

    fn set_string(span :
    Span
    , name : String, value : String) -> Unit

    Set a string on a span.

    set_tool_error

    fn set_tool_error(span :
    Span
    , description : String) -> Unit

    Mark a tool span as failed with a description.

    set_tool_result

    fn set_tool_result(span :
    Span
    , result : String) -> Unit

    Set the result of a tool execution and mark the span as successful.

    set_turn

    fn set_turn(span :
    Span
    , actual_turns : Int, tool_call_count : Int, output : String) -> Unit

    Record turn-level attributes before ending an agent turn span.

    Sets:
    • agent.turn.actual_turns
    • agent.turn.tool_call_count
    • agent.turn.output

    set_turn_exhausted

    fn set_turn_exhausted(span :
    Span
    ) -> Unit

    Mark an agent turn span as failed due to reaching the maximum tool turns.

    set_usage

    fn set_usage(span :
    Span
    , prompt_tokens : Int64, completion_tokens : Int64, cache_read_input_tokens? : Int64, reasoning_output_tokens? : Int64) -> Unit

    Set usage attributes from a GenAI response usage object.

    Sets gen_ai.usage.input_tokens and gen_ai.usage.output_tokens when present. Also sets gen_ai.usage.cache_read.input_tokens and gen_ai.usage.reasoning.output_tokens when provided.

    start_agent_turn_span

    Start a span for a single agent turn.

    Sets:
    • agent.turn.input
    • agent.turn.max_tool_turns

    start_chat_span

    fn start_chat_span(tracer :
    Tracer
    , provider_name : String, model : String, max_tokens : Int, temperature? : Double?, top_p? : Double?, stream? : Bool?, reasoning_level? : String?, stop_sequences? : Array[String]?, frequency_penalty? : Double?, presence_penalty? : Double?, seed? : Int64?, input_messages? : Json?, server_address? : String?, server_port? : Int?, parent_context? :
    Context
    ) ->
    Span

    Start a span for a GenAI chat request following OpenTelemetry GenAI conventions.

    Sets:
    • gen_ai.operation.name = "chat"
    • gen_ai.provider.name
    • gen_ai.request.model
    • gen_ai.request.max_tokens
    • gen_ai.request.temperature (when provided)
    • gen_ai.request.top_p (when provided)
    • gen_ai.request.stream (when provided)
    • gen_ai.request.reasoning.level (when provided)
    • gen_ai.request.stop_sequences (when provided)
    • gen_ai.request.frequency_penalty (when provided)
    • gen_ai.request.presence_penalty (when provided)
    • gen_ai.request.seed (when provided)
    • gen_ai.input.messages (when input_messages is provided)
    • server.address and server.port (when provided)

    start_invoke_agent_span

    Start a span for a GenAI agent invocation within the same process.

    Follows the gen_ai.invoke_agent.internal semantic convention.

    Sets:
    • gen_ai.operation.name = "invoke_agent"
    • gen_ai.agent.name (when provided)

    Span name: invoke_agent {name} or invoke_agent when name is unavailable.

    start_plan_span

    Start a span for an agent planning or task decomposition phase.

    Follows the gen_ai.plan.internal semantic convention.

    Sets:
    • gen_ai.operation.name = "plan"
    • gen_ai.agent.name (when provided)

    Span name: plan {name} or plan when name is unavailable.

    start_tool_span

    fn start_tool_span(tracer :
    Tracer
    , name : String, arguments : String, call_id? : String?, tool_type? : String?, parent_context? :
    Context
    ) ->
    Span

    Start a span for a GenAI tool execution following OTel GenAI conventions.

    Sets:
    • gen_ai.operation.name = "execute_tool"
    • gen_ai.tool.name
    • gen_ai.tool.call.arguments
    • gen_ai.tool.call.id (when provided)
    • gen_ai.tool.type (when provided)

    Span name: execute_tool {name}

    tracer

    fn tracer(scope_name : String, version? : String) ->
    Tracer

    Get or create a tracer for the given scope name.

    Powered by MoonBit

    Site sourceReport issuePackagesBuild queueSkillsStatistics

    © 2026 mooncakes.io