Help us shape the future of Go! Take the 2026 Go Developer Survey

oltgo

package module
v1.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 4, 2026 License: Apache-2.0 Imports: 6 Imported by: 0

README

Oltgo Logo

Oltgo — Biblioteca de Telemetria e Observabilidade em Go

O oltgo é uma biblioteca leve, rápida e concorrente projetada para capturar e estruturar logs e rastreamentos (traces) em microsserviços, monólitos ou quaisquer aplicações escritas em Go.


1. Arquitetura Geral

O design segue o princípio 1 Serviço -> 1 Agente, onde cada instância da sua aplicação mantém um único Agent em execução.

+-------------------------------------------------------------+
|                     Aplicação Go                            |
|                                                             |
|   +------------------+           +----------------------+   |
|   |    Collection    | --------> |       Shipping       |   |
|   | (Fila local/Mem) |  (Canal)  | (Worker assíncrono)  |   |
|   +------------------+           +----------------------+   |
+---------------------------------------------|---------------+
                                              v
                                   [ Callback ProcessLog ]
                                   (ex: Gravar em arquivo, 
                                    enviar p/ OpenTelemetry, 
                                    Stripe, ElasticSearch, etc)
Componentes Principais
  1. Agent: Orquestrador central que gerencia o ciclo de vida do pipeline. É inicializado uma única vez na inicialização do serviço.
  2. Collection: Estrutura thread-safe criada para cada transação, requisição ou trace. É responsável por acumular os dados e eventos em memória local e empacotá-los sob o schema unificado ao final da execução.
  3. Shipping: Fila de buffer baseada em Go channels. Ela recebe as instâncias estruturadas prontas do Collection e consome assincronamente os dados para que a aplicação principal nunca sofra atrasos de I/O de escrita.

2. Inicializando o Agente

Para começar, inicialize o Agent definindo os metadados do serviço e um callback opcional de destino para os logs:

package main

import (
	"encoding/json"
	"fmt"
	"github.com/joaooffzz/oltgo"
)

func main() {
	service := oltgo.Service{
		Name:    "payment-gateway",
		Version: "2.1.0",
	}

	// Callback para exportar logs estruturados (ex: saída padrão stdout)
	processLog := func(log oltgo.LogSchema) error {
		bytes, _ := json.Marshal(log)
		fmt.Println(string(bytes))
		return nil
	}

	// Inicializa
	agent := oltgo.NewAgent(service, oltgo.Production, "1", processLog)
	
	// Garante que todos os logs na fila de buffer sejam processados antes da app fechar
	defer agent.Close() 
}

3. Propagação de Contexto via context.Context

O oltgo utiliza o context.Context nativo do Go para carregar e obter a coleção de telemetria através da pilha de execução de funções (call stack), evitando o acoplamento excessivo de código e garantindo segurança concorrente.

Criação e Injeção do Contexto

No início de um fluxo de requisição (por exemplo, um middleware HTTP), você cria a coleção e injeta-a no contexto:

func HandleRequest(w http.ResponseWriter, r *http.Request) {
	// Cria a collection para esta chamada específica
	collection := agent.NewCollection("trace-unique-uuid")
	collection.SetRequestFromHTTP(r, oltgo.ProtoREST, 200)

	// Injeta a collection no contexto
	ctx := oltgo.WithCollection(r.Context(), collection)

	// Propaga o contexto modificado para as funções internas
	checkoutHandler(ctx)

	// Commita os dados para a fila de envio assíncrono
	collection.Commit()
}
Extração e Registro de Eventos na Pilha

Em qualquer sub-função que receba o contexto, você pode extrair a Collection com segurança e registrar eventos:

func executeBusinessLogic(ctx context.Context) {
	// Recupera a coleção. Se não estiver no contexto, retorna nil com segurança
	collection := oltgo.FromContext(ctx)
	if collection == nil {
		return
	}

	// Inicia um evento com cálculo de tempo automático
	event := collection.StartEvent("db_query", oltgo.TypeDatabase, oltgo.SeverityInfo)
	event.WithMessage("Selecionando itens do carrinho")
	
	defer event.End() // Ao finalizar a função, calcula a duração e salva o evento

	// Executa operação de fato...
	if err := queryDB(); err != nil {
		event.AddError("DB_ERROR", err.Error(), nil)
	}
}

4. Eventos Hierárquicos (Árvore de Eventos)

O oltgo suporta o aninhamento automático de eventos em uma estrutura de árvore. Ao usar a função oltgo.StartEvent(ctx, ...), o ID do evento pai é propagado automaticamente pelo context.Context, eliminando a necessidade de vincular manualmente o parent_event_id.

Como funciona
  1. oltgo.StartEvent retorna um novo contexto contendo o ID do evento recém-criado como "evento ativo".
  2. Qualquer chamada subsequente a oltgo.StartEvent usando esse contexto herdará automaticamente o evento anterior como pai.
  3. No Commit(), a lista plana de eventos é reconstruída em uma árvore hierárquica baseada nos parent_event_id.
Exemplo Prático
func checkoutHandler(ctx context.Context) error {
	// Evento raiz — todos os subeventos serão filhos dele
	ctx, mainEvt := oltgo.StartEvent(ctx, "checkout_flow", oltgo.TypeFunction, oltgo.SeverityInfo)
	defer mainEvt.WithMessage("Fluxo de checkout completo").End()

	// Subeventos herdam o pai automaticamente do contexto
	authenticateUser(ctx)
	fetchItems(ctx)

	return nil
}

func authenticateUser(ctx context.Context) {
	// Automaticamente filho de "checkout_flow"
	_, evt := oltgo.StartEvent(ctx, "authenticate_user", oltgo.TypeFunction, oltgo.SeverityInfo)
	defer evt.WithMessage("Usuário autenticado com JWT").End()
}

func fetchItems(ctx context.Context) {
	// Automaticamente filho de "checkout_flow"
	ctx, evt := oltgo.StartEvent(ctx, "fetch_items", oltgo.TypeDatabase, oltgo.SeverityInfo)
	defer evt.WithMessage("Itens carregados").End()

	// Netos: filhos de "fetch_items"
	checkStock(ctx)
}

func checkStock(ctx context.Context) {
	// Automaticamente filho de "fetch_items"
	_, evt := oltgo.StartEvent(ctx, "check_stock", oltgo.TypeDatabase, oltgo.SeverityInfo)
	defer evt.WithMessage("Estoque verificado").End()
}
Saída JSON Resultante

O Commit() produz uma árvore aninhada automaticamente:

{
  "events": [
    {
      "event_id": "evt_000001",
      "name": "checkout_flow",
      "events": [
        {
          "event_id": "evt_000002",
          "parent_event_id": "evt_000001",
          "name": "authenticate_user"
        },
        {
          "event_id": "evt_000003",
          "parent_event_id": "evt_000001",
          "name": "fetch_items",
          "events": [
            {
              "event_id": "evt_000004",
              "parent_event_id": "evt_000003",
              "name": "check_stock"
            }
          ]
        }
      ]
    }
  ]
}
Funções Auxiliares de Contexto
Função Descrição
oltgo.StartEvent(ctx, name, type, severity) Cria um evento, herda o pai do contexto e retorna (newCtx, *EventBuilder)
oltgo.WithActiveEvent(ctx, eventID) Associa manualmente um ID de evento ao contexto
oltgo.ActiveEventFromContext(ctx) Recupera o ID do evento ativo do contexto

Nota: A API antiga collection.StartEvent(...) continua funcionando para casos onde o aninhamento não é necessário. Eventos criados por ela não terão parent_event_id e aparecerão como raízes.


5. Logs sem Requisição (Inicialização, Jobs e Workers)

O campo request do schema é opcional. Nem todo trace nasce de uma requisição HTTP/gRPC: a inicialização do serviço, migrações, jobs agendados, consumidores de fila e CLIs também merecem observabilidade.

Para esses casos basta não chamar SetRequest / SetRequestFromHTTP. O campo request (e também actor, se não definido) é omitido do JSON final — não é enviado como null.

func bootstrap(agent *oltgo.Agent) error {
	// Trace de inicialização: nenhum SetRequest é necessário
	col := agent.NewCollection("trace_startup_" + version)
	ctx := oltgo.WithCollection(context.Background(), col)
	defer col.Commit()

	ctx, bootEvt := oltgo.StartEvent(ctx, "bootstrap", oltgo.TypeFunction, oltgo.SeverityInfo)
	defer bootEvt.WithMessage("Serviço inicializado").End()

	// Subevento: conexão com o banco
	_, dbEvt := oltgo.StartEvent(ctx, "connect_database", oltgo.TypeDatabase, oltgo.SeverityInfo)
	if err := db.Ping(); err != nil {
		dbEvt.AddError("DB_UNREACHABLE", err.Error(), nil)
		dbEvt.End()
		return err
	}
	dbEvt.WithMessage("Pool de conexões estabelecido").
		WithMetadata(map[string]any{"driver": "postgres", "max_open_conns": 25}).
		End()

	return nil
}

JSON resultante (sem request e sem actor):

{
  "tracing_id": "trace_startup_1.0.0",
  "status": "SUCCESS",
  "environment": "production",
  "schema_version": "1",
  "service": { "name": "order-api", "version": "1.0.0" },
  "time": { "duration_ms": 412, "created_at": "...", "finished_at": "..." },
  "events": [
    {
      "event_id": "evt_000001",
      "name": "bootstrap",
      "events": [{ "event_id": "evt_000002", "parent_event_id": "evt_000001", "name": "connect_database" }]
    }
  ]
}
Regras de comportamento
Situação Resultado
SetRequest nunca chamado request omitido do JSON
SetRequest(nil) Limpa uma requisição definida anteriormente; request volta a ser omitido
SetRequestFromHTTP(nil, ...) Chamada ignorada com segurança (sem panic); request permanece omitido
status geral do trace Derivado apenas dos eventos quando não há request; um evento FAILURE marca o trace como FAILURE

6. Status, Severidade e Erros

status (o resultado da operação) e severity (a gravidade do registro) são eixos distintos, mas coerentes entre si. A biblioteca aplica a derivação uma única vez, no momento em que o evento entra na Collection:

Situação status resultante
severity ERROR ou FATAL FAILURE
severity DEBUG, INFO ou WARN SUCCESS
AddError(...) chamado FAILURE, e a severity sobe para no mínimo ERROR (sem rebaixar FATAL)
Qualquer evento FAILURE O trace inteiro vira FAILURE

WARN + SUCCESS continua sendo uma combinação válida — "o retry teve sucesso na segunda tentativa" é legítimo.

Sobrepondo a derivação
// Erro tratado, execução segue por outro caminho.
// O evento continua ERROR (aparece nos filtros de severidade e preserva o
// diagnóstico), mas não contamina o status do trace.
_, evt := oltgo.StartEvent(ctx, "cache_miss", oltgo.TypeDatabase, oltgo.SeverityError)
evt.WithMessage("cache indisponível, caiu no banco").Succeed().End()

// Falha real, sem código de erro a registrar.
_, evt2 := oltgo.StartEvent(ctx, "validacao", oltgo.TypeFunction, oltgo.SeverityWarn)
evt2.WithMessage("payload rejeitado").Fail().End()

Em collection.AddEvent(...), um Status preenchido explicitamente é sempre respeitado; a derivação só acontece quando o campo é deixado vazio.

O helper oltgo.IsFailureSeverity(sev) expõe a mesma comparação usada internamente — útil para consumidores que precisam filtrar por severidade mínima.


7. Observabilidade do Pipeline

Telemetria nunca bloqueia a aplicação: quando o buffer do Shipping enche, os logs são descartados. Stats() torna esse descarte visível em vez de silencioso:

st := agent.Stats()
// st.Shipped  — processados com sucesso
// st.Failed   — ProcessLog devolveu erro
// st.Dropped  — descartados por buffer cheio ou Ship após Close()
// st.QueueDepth / st.QueueCapacity — ocupação instantânea da fila

Para configurar o tamanho do buffer e receber os erros do ProcessLog, use NewAgentWithOptions (o NewAgent permanece inalterado):

agent := oltgo.NewAgentWithOptions(service, oltgo.Production, "1", processLog, oltgo.Options{
	BufferSize: 5000,
	OnError: func(log oltgo.LogSchema, err error) {
		// Roda no worker do Shipping: precisa ser rápido e não entrar em pânico.
		metrics.Inc("telemetry.export_failed")
	},
})

Nota: Commit() é idempotente. Uma Collection representa um trace de uso único, e chamadas repetidas (o caso do defer col.Commit() somado a um Commit() explícito num caminho de erro) são no-op em vez de reenviarem o trace.


8. O Schema de Log Estruturado

Todos os logs commitados são unificados sob uma estrutura padrão e enviados para o callback ProcessLog. A documentação completa dos campos do schema JSON pode ser vista em:

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func ActiveEventFromContext added in v0.0.2

func ActiveEventFromContext(ctx context.Context) string

ActiveEventFromContext recupera o ID do evento pai do contexto.

func IsFailureSeverity added in v1.1.0

func IsFailureSeverity(s Severity) bool

IsFailureSeverity informa se a severidade denota falha (ERROR ou acima).

func WithActiveEvent added in v0.0.2

func WithActiveEvent(ctx context.Context, eventID string) context.Context

WithActiveEvent associa um ID de evento ao contexto.

func WithCollection

func WithCollection(ctx context.Context, c *Collection) context.Context

WithCollection injeta uma instância de *Collection no contexto fornecido.

Types

type Actor

type Actor struct {
	ID   string `json:"id"`
	Type string `json:"type"` // USER, SERVICE
	IP   string `json:"ip,omitempty"`
}

type Agent

type Agent struct {
	Service       Service
	Env           Environment
	SchemaVersion string
	Shipping      *Shipping
}

func NewAgent

func NewAgent(service Service, env Environment, schemaVersion string, processLog func(LogSchema) error) *Agent

NewAgent inicializa o Agent de observabilidade. O parâmetro processLog é uma função opcional de callback para processamento dos logs estruturados. Se processLog for nil, os logs serão silenciosamente descartados da memória.

func NewAgentWithOptions added in v1.1.0

func NewAgentWithOptions(service Service, env Environment, schemaVersion string, processLog func(LogSchema) error, opts Options) *Agent

NewAgentWithOptions inicializa o Agent de observabilidade com opções extras. Ver NewAgent para o comportamento padrão.

func (*Agent) Close

func (a *Agent) Close()

Close encerra as goroutines do Shipping de forma limpa, garantindo o processamento de logs remanescentes.

func (*Agent) NewCollection

func (a *Agent) NewCollection(tracingID string) *Collection

NewCollection cria uma nova instância de Collection associada a este Agent para rastrear uma nova trace/requisição.

func (*Agent) Stats added in v1.1.0

func (a *Agent) Stats() ShippingStats

Stats é atalho para Agent.Shipping.Stats().

type Collection

type Collection struct {
	// contains filtered or unexported fields
}

func FromContext

func FromContext(ctx context.Context) *Collection

FromContext recupera a instância de *Collection do contexto. Retorna nil se não estiver presente.

func NewCollection

func NewCollection(agent *Agent, tracingID string) *Collection

NewCollection inicializa uma nova Collection vinculada ao Agent.

func (*Collection) AddEvent

func (c *Collection) AddEvent(event Event)

AddEvent insere um evento completo de forma thread-safe.

O status é derivado da severidade quando não informado: eventos com severidade ERROR ou FATAL viram FAILURE. Um Status explícito é sempre respeitado.

func (*Collection) Commit

func (c *Collection) Commit()

Commit finaliza a trace, calcula a duração e submete os dados para o Shipping do Agent. É idempotente: uma Collection representa um trace de uso único, e chamadas repetidas são no-op.

func (*Collection) SetActor

func (c *Collection) SetActor(actor *Actor)

SetActor define informações do ator da requisição.

func (*Collection) SetRequest

func (c *Collection) SetRequest(req *Request)

SetRequest define informações de requisição de forma thread-safe. É opcional: traces sem requisição (inicialização, jobs, workers, CLIs) podem nunca chamar este método — o campo "request" será omitido do JSON final. Passar nil também limpa uma requisição previamente definida.

func (*Collection) SetRequestFromHTTP

func (c *Collection) SetRequestFromHTTP(r *http.Request, comm CommunicationProto, status int)

SetRequestFromHTTP preenche informações da requisição a partir de um *http.Request nativo. Se r for nil, a chamada é ignorada e o campo "request" permanece omitido.

func (*Collection) StartEvent

func (c *Collection) StartEvent(name string, eventType EventType, severity Severity) *EventBuilder

StartEvent inicia a gravação de um evento. O evento será registrado no Collection chamando End().

type CommunicationProto

type CommunicationProto string
const (
	ProtoREST      CommunicationProto = "REST"
	ProtoGRPC      CommunicationProto = "gRPC"
	ProtoSOAP      CommunicationProto = "SOAP"
	ProtoWebSocket CommunicationProto = "WebSocket"
)

type Environment

type Environment string
const (
	Production  Environment = "production"
	Staging     Environment = "staging"
	Development Environment = "development"
	Testing     Environment = "testing"
)

Environment é uma string livre: as constantes abaixo são os valores convencionais, mas qualquer outro valor é aceito.

type Event

type Event struct {
	EventID       string         `json:"event_id"`
	ParentEventID *string        `json:"parent_event_id,omitempty"`
	Name          string         `json:"name"`
	Type          EventType      `json:"type"`
	Status        Status         `json:"status"`
	Severity      Severity       `json:"severity"`
	Timestamp     time.Time      `json:"timestamp"`
	DurationMs    int            `json:"duration_ms"`
	Message       string         `json:"message"`
	Metadata      map[string]any `json:"metadata,omitempty"`
	Errors        []EventError   `json:"errors,omitempty"`
	Events        []*Event       `json:"events,omitempty"`
}

type EventBuilder

type EventBuilder struct {
	// contains filtered or unexported fields
}

EventBuilder auxilia desenvolvedores a construírem eventos com cálculo de tempo automático.

func StartEvent added in v0.0.2

func StartEvent(ctx context.Context, name string, eventType EventType, severity Severity) (context.Context, *EventBuilder)

StartEvent inicia a gravação de um evento usando context.Context. O evento herdará o ID do pai automaticamente.

func (*EventBuilder) AddError

func (eb *EventBuilder) AddError(code, message string, stack *string) *EventBuilder

AddError adiciona um erro ocorrido durante a operação do evento, definindo o status do evento como FAILURE e elevando a severidade para no mínimo ERROR.

func (*EventBuilder) End

func (eb *EventBuilder) End()

End calcula o tempo de duração final do evento e o adiciona à Collection.

func (*EventBuilder) Fail added in v1.1.0

func (eb *EventBuilder) Fail() *EventBuilder

Fail marca o evento como falho sem exigir um objeto de erro. Use quando a operação não teve sucesso mas não há código/mensagem de erro a registrar.

func (*EventBuilder) Succeed added in v1.1.0

func (eb *EventBuilder) Succeed() *EventBuilder

Succeed marca o evento como bem-sucedido explicitamente. Necessário apenas para manter SUCCESS em um evento com severidade ERROR ou FATAL, que de outro modo seria derivado como FAILURE em End().

func (*EventBuilder) WithMessage

func (eb *EventBuilder) WithMessage(msg string) *EventBuilder

WithMessage adiciona uma mensagem explicativa ao evento.

func (*EventBuilder) WithMetadata

func (eb *EventBuilder) WithMetadata(metadata map[string]any) *EventBuilder

WithMetadata injeta dados de contexto adicionais de forma flexível.

func (*EventBuilder) WithParent

func (eb *EventBuilder) WithParent(parentID string) *EventBuilder

WithParent define o ID do evento pai para construir árvores/spans.

type EventError

type EventError struct {
	Code    string  `json:"code"`
	Message string  `json:"message"`
	Stack   *string `json:"stack,omitempty"`
}

type EventType

type EventType string
const (
	TypeFunction        EventType = "FUNCTION"
	TypeDatabase        EventType = "DATABASE"
	TypeExternalService EventType = "EXTERNAL_SERVICE"
	TypeInternalService EventType = "INTERNAL_SERVICE"
)

type LogSchema

type LogSchema struct {
	TracingID     string      `json:"tracing_id"`
	Status        Status      `json:"status"`
	Environment   Environment `json:"environment"`
	SchemaVersion string      `json:"schema_version"`
	Service       Service     `json:"service"`
	// Request é opcional: traces que não nascem de uma requisição
	// (inicialização do serviço, jobs, workers, CLIs) omitem o campo do JSON.
	Request *Request `json:"request,omitempty"`
	// Actor é opcional: omitido quando não há usuário ou serviço chamador.
	Actor  *Actor   `json:"actor,omitempty"`
	Time   TimeInfo `json:"time"`
	Events []*Event `json:"events"`
}

type Options added in v1.1.0

type Options struct {
	// BufferSize é o tamanho da fila do Shipping. 0 usa o default (1000).
	BufferSize int
	// OnError é chamado quando ProcessLog devolve erro. Deve ser rápido e não
	// entrar em pânico: roda no worker do Shipping.
	OnError func(LogSchema, error)
}

Options configura comportamentos opcionais do Agent.

type Request

type Request struct {
	Communication CommunicationProto `json:"communication"`
	Method        string             `json:"method,omitempty"`
	Route         string             `json:"route,omitempty"`
	Status        int                `json:"status"`
	UserAgent     string             `json:"user_agent,omitempty"`
}

type Service

type Service struct {
	ID          *string   `json:"id,omitempty"`
	Name        string    `json:"name"`
	Description *string   `json:"description,omitempty"`
	Version     string    `json:"version"`
	Tags        *[]string `json:"tags,omitempty"`
}

type Severity

type Severity string
const (
	SeverityDebug Severity = "DEBUG"
	SeverityInfo  Severity = "INFO"
	SeverityWarn  Severity = "WARN"
	SeverityError Severity = "ERROR"
	SeverityFatal Severity = "FATAL"
)

type Shipping

type Shipping struct {
	// contains filtered or unexported fields
}

func NewShipping

func NewShipping(bufferSize int, processLog func(LogSchema) error) *Shipping

func (*Shipping) Close

func (s *Shipping) Close()

func (*Shipping) Ship

func (s *Shipping) Ship(log LogSchema)

func (*Shipping) Stats added in v1.1.0

func (s *Shipping) Stats() ShippingStats

Stats devolve um retrato dos contadores. Seguro para chamada concorrente.

type ShippingStats added in v1.1.0

type ShippingStats struct {
	Shipped       int64 // processados com sucesso
	Failed        int64 // ProcessLog devolveu erro
	Dropped       int64 // descartados por buffer cheio ou Shipping fechado
	QueueDepth    int   // ocupação instantânea
	QueueCapacity int
}

ShippingStats expõe contadores para métricas e diagnóstico.

type Status

type Status string
const (
	StatusSuccess Status = "SUCCESS"
	StatusFailure Status = "FAILURE"
)

type TimeInfo

type TimeInfo struct {
	DurationMs int       `json:"duration_ms"`
	CreatedAt  time.Time `json:"created_at"`
	FinishedAt time.Time `json:"finished_at"`
}

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL