loghubident

package module
v0.5.2 Latest Latest
Warning

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

Go to latest
Published: Oct 3, 2026 License: MIT Imports: 14 Imported by: 0

README

lib-loghub-ident

Biblioteca Go que resolve e expõe a identidade de um software dentro do ecossistema Loghub. O objetivo principal é dar ao software que a utiliza duas coisas, estáveis entre reinícios:

  • uma identidade única — quem é este agente: AgentUUID (UUIDv7 gerado e persistido), AgentName e Hostname;
  • uma localização virtual — onde ele está: MachineID (nó físico ou virtual) e Workspace (tenant lógico).

O sexto campo, DataDir, é suporte: o diretório onde as identidades geradas são persistidas, não uma identidade. Todos vêm de variáveis de ambiente, arquivos e fallbacks do sistema. Projetada para microserviços em containers.

  • Leve e rápida: após Initialize, cada getter é apenas a leitura de uma variável de pacote — sem locks, adequado a centenas de threads chamando milhares de vezes por milissegundo.
  • Imutável após Initialize: os valores são escritos uma única vez no boot e nunca mais modificados.
  • Multiplataforma: UUIDv7 gerado localmente via go-loghub-uuidv7, sem dependência de /proc.

A especificação técnica completa está modularizada em docs/00-INDEX.md (com sumário em SPEC.md). Para um guia prático de integração e receitas para novos serviços, consulte a skill/ — que também pode ser instalada nos projetos consumidores (veja Skill para equipes e agentes de IA).

Instalação

go get github.com/patrickbrandao/go-loghub-ident

Requer Go 1.22+.

Uso

Arquivo: go.mod

module ident-test

go 1.22

require github.com/patrickbrandao/go-loghub-ident v0.5.2

Arquivo: main.go

// Defina LOGHUB_IDENT_DEBUG=1 para ver a origem de cada valor em stderr.
package main

import (
    "fmt"

    lhident "github.com/patrickbrandao/go-loghub-ident"
)

func main() {
    lhident.Initialize() // encerra o processo se algo falhar

    fmt.Printf("DataDir:   %s\n", lhident.DataDir())
    fmt.Printf("MachineID: %s\n", lhident.MachineID())
    fmt.Printf("AgentName: %s\n", lhident.AgentName())
    fmt.Printf("AgentUUID: %s\n", lhident.AgentUUID())
    fmt.Printf("Hostname:  %s\n", lhident.Hostname())
    fmt.Printf("Workspace: %s\n", lhident.Workspace())
}

Compilar:

go mod tidy && go build -o ident-test .

Compilar (multiplataforma):

# Windows
GOOS=windows GOARCH=amd64 go build -o ident-test.exe .

# macOS
GOOS=darwin GOARCH=amd64 go build -o ident-test .

# Linux (outros processadores)
GOOS=linux GOARCH=arm64 go build -o ident-test .

Rodar:

./ident-test;
    # DataDir:   /data
    # MachineID: 0123456789abcdef0123456789abcdef
    # AgentName: ident-test
    # AgentUUID: 018f3a5b-7c8d-7e9f-8a1b-2c3d4e5f6a7b
    # Hostname:  node01.example.com
    # Workspace: default
Regras de uso
  • Chame Initialize() uma única vez, no início de main(), antes de criar qualquer goroutine que vá ler os getters. Esse contrato é o que torna a leitura concorrente segura sem sincronização.
  • Uma segunda chamada a Initialize() encerra o processo com código 112.
  • Antes de Initialize(), todos os getters devolvem "" (zero value). Use IsInitialized() para validar em runtime se a inicialização já ocorreu.

API

Função Retorno Descrição
Initialize() — resolve a identidade; encerra em falha
IsInitialized() bool informa se Initialize concluiu com sucesso
DataDir() string diretório de dados
MachineID() string id da máquina (32 hex, sem hífen)
AgentName() string nome do agente
AgentUUID() string UUIDv7 canônico (com hífens)
Hostname() string hostname
Workspace() string workspace/tenant

Resolução de cada campo

Precedência geral: variável de ambiente → arquivo de disco → fallback. Para cada fonte, env e arquivo igualmente: ler 1ª linha → remover BOM → trim de espaços e caracteres de controle → lowercase → sanear → validar. Uma fonte ausente cai para a próxima; uma env presente mas inválida encerra o processo.

As regexes abaixo descrevem o conjunto de caracteres aceito, mas não são compiladas: a validação usa verificadores manuais equivalentes, o que mantém o pacote regexp fora da árvore de dependências. Além do conjunto de caracteres, valem as regras que uma classe não expressa:

  • AGENT_NAME e WORKSPACE: no máximo 64 caracteres, e . e .. são recusados (evita travessia de diretório em quem usa o valor para compor um caminho).
  • HOSTNAME: regras de rótulo da RFC 1123 — no máximo 253 caracteres, rótulos de 1 a 63, sem rótulo vazio e sem hífen nas bordas.
  • Qualquer arquivo-fonte é lido com teto de 4 KiB e precisa ser um arquivo comum: um FIFO ou um /dev/zero apontado por engano vira "fonte inválida" em vez de travar ou consumir toda a memória do nó.
Campo Env Fontes de fallback Regex
DATADIR DATADIR /data (existência validada de forma preguiçosa) caminho absoluto
MACHINE_ID MACHINE_ID $MACHINE_ID_FILE (def. /etc/machine-id) → $DATADIR/machine_id → gerado ^[0-9a-f]{32}$
AGENT_NAME AGENT_NAME $DATADIR/agent_name → argv[0] (base, sem .exe) ^[a-z0-9._-]+$
AGENT_UUID AGENT_UUID $DATADIR/agent_uuid → gerado (UUIDv7) UUIDv7 canônico
HOSTNAME HOSTNAME os.Hostname() ^[a-z0-9.-]+$
WORKSPACE WORKSPACE $DATADIR/workspace → default ^[a-z0-9.-]+$
Persistência
  • Apenas identidades geradas aleatoriamente (machine_id, agent_uuid) são gravadas em $DATADIR, para que o mesmo valor seja reobtido em execuções futuras. Valores determinísticos (argv[0], default) nunca gravam arquivo.
  • A permissão 0644 é garantida, não um teto: a biblioteca aplica um chmod explícito, então um umask restritivo do container não deixa os arquivos 0600 e ilegíveis para um sidecar com outro UID.
  • A gravação é durável (fsync) e atômica: a criação usa exclusão mútua do filesystem e a regeneração usa arquivo temporário + rename. Um crash no meio da escrita não deixa arquivo truncado, e processos irmãos que sobem ao mesmo tempo sobre o mesmo volume convergem para uma identidade única — quem perde a corrida adota o valor de quem chegou primeiro.
  • Isso vale para os dois cenários: arquivo ainda inexistente (arranque a frio) e arquivo existente porém corrompido (recuperação depois de uma queda). No segundo, a disputa é arbitrada por um registro de regeneração $DATADIR/.machine_id.regen / $DATADIR/.agent_uuid.regen — arquivos ocultos que só aparecem se uma identidade corrompida tiver sido substituída, e que a biblioteca preserva de propósito: apagá-los devolveria o nome à disputa. Um registro obsoleto é removido sozinho quando a identidade é criada do zero.
  • Um arquivo de identidade vazio (um touch de provisionamento, uma cópia interrompida) não é "ausente": se for antigo, é tratado como corrompido na hora — aviso e regeneração, sem espera; se tiver sido modificado há menos de 10 s, pode ser o que um processo irmão acabou de publicar num volume de rede, e a biblioteca espera o que falta dessa janela antes de decidir.
  • $DATADIR precisa ser um caminho absoluto; um caminho relativo aborta com 100. Do contrário a identidade dependeria do diretório de trabalho do processo.
  • A biblioteca não cria o DATADIR; o container/orquestrador deve montar o volume. No caminho feliz (todas as envs presentes), $DATADIR nunca é tocado — compatível com filesystem read-only. Se alguma env faltar, o $DATADIR é apenas lido: um diretório ausente é tratado como fonte ausente e a cadeia segue para o fallback. Ele só é obrigatório quando há identidade a gerar e persistir (machine_id, agent_uuid). Erros de permissão ou I/O ao verificar ou ler o diretório abortam com código 100.
  • Segurança e symlinks: arquivos de identidade dentro de $DATADIR são lidos sem seguir links simbólicos (Lstat antes de abrir, O_NOFOLLOW no open em Linux, macOS e BSDs, e os.SameFile depois). Um link simbólico é recusado, impedindo exfiltração de arquivos sensíveis do host ou container. Além disso, avisos de erro nunca expõem o conteúdo bruto de arquivos inválidos.
  • Atenção ao compartilhamento de volume: réplicas ou containers independentes NUNCA devem compartilhar o mesmo volume montado em $DATADIR para escrita. Processos que compartilham o $DATADIR convergem para a mesma identidade (machine_id e agent_uuid), gerando colisão de nós no servidor Loghub. O compartilhamento de volume gravável só é aceitável entre processos do mesmo nó (ex.: sidecars de um mesmo pod) que intencionalmente compartilham a identidade. Nessa topologia o AgentUUID identifica a instância (o pod), e a distinção entre os processos vem do AgentName; se cada container precisar de um AgentUUID próprio, defina AGENT_UUID na env de cada um ou dê a cada um o seu DATADIR.
  • A identidade só é estável se o volume sobreviver à recriação do container ou do pod: volume nomeado no Docker (-v loghub-ident:/data) e PVC dedicado no Kubernetes. Um volume anônimo ou um emptyDir é apagado na recriação, e a biblioteca gera identidades novas sem aviso — não há arquivo corrompido, apenas ausente.

Diagnóstico

Defina a env LOGHUB_IDENT_DEBUG (qualquer valor não vazio) para que Initialize() escreva em stderr uma linha por campo — os seis, DATADIR inclusive — com a origem do valor (env / file / generated / fallback) e o valor final:

lib-loghub-ident: debug: DATADIR: env = "/data"
lib-loghub-ident: debug: MACHINE_ID: file /etc/machine-id = "0123456789abcdef0123456789abcdef"
lib-loghub-ident: debug: AGENT_NAME: fallback argv[0] = "ident-test"
lib-loghub-ident: debug: AGENT_UUID: generated = "018f3a5b-7c8d-7e9f-8a1b-2c3d4e5f6a7b"
lib-loghub-ident: debug: HOSTNAME: os.Hostname = "node01.example.com"
lib-loghub-ident: debug: WORKSPACE: fallback = "default"

As linhas saem antes do tratamento de uma eventual falha: no caminho de erro elas são a única pista sobre qual fonte alimentou cada campo já resolvido.

Avisos

Independentemente do modo debug, a biblioteca escreve em stderr um aviso sempre que descarta uma identidade persistida — um $DATADIR/machine_id ou $DATADIR/agent_uuid com conteúdo inválido, que a regra de regeneração manda substituir:

lib-loghub-ident: aviso: MACHINE_ID: /data/machine_id tinha conteúdo inválido (16 bytes, hash 8f3a1b0c9e7d4a2f) e será substituído (o aviso seguinte diz se foi regenerado ou restaurado de um registro)
lib-loghub-ident: aviso: MACHINE_ID: /data/machine_id foi REGERADO com valor novo (32 bytes, hash 5d41402abc4b2a76); a identidade desta máquina muda a partir de agora

São sempre duas linhas. Quando a regeneração já foi feita por um processo irmão (registro .machine_id.regen), a segunda diz foi RESTAURADO a partir do registro de regeneração, e a identidade volta a ser a anterior em vez de mudar. Um arquivo vazio recebe o mesmo tratamento (0 bytes).

Sem esse aviso, um agente voltaria com outro machine_id depois de um crash e apareceria no servidor Loghub como uma máquina nova, sem rastro para correlacionar.

Códigos de saída

Em qualquer falha, escreve lib-loghub-ident: <VARIÁVEL>: <motivo> em stderr e chama os.Exit(<código>).

Código Variável Motivo
100 DATADIR diretório necessário ausente, não é diretório, caminho relativo ou com controle, erro de I/O, ou symlink recusado
100 MACHINE_ID_FILE caminho informado está inacessível, relativo ou com controle, ou não é arquivo comum utilizável (diretório, fifo, device, > 4 KiB)
102 MACHINE_ID env não casa com ^[0-9a-f]{32}$
103 AGENT_NAME todas as fontes vazias (argv[0] saneou para vazio/./separador)
104 AGENT_NAME valor não casa com ^[a-z0-9._-]+$
105 AGENT_UUID falha na geração local de UUIDv7
106 AGENT_UUID gravação em $DATADIR/agent_uuid falhou
107 AGENT_UUID valor não casa com a regex de UUIDv7
108 HOSTNAME env vazia e os.Hostname() retornou erro
109 HOSTNAME valor não casa com ^[a-z0-9.-]+$
111 WORKSPACE valor não casa com ^[a-z0-9.-]+$
112 (geral) Initialize() chamado mais de uma vez
113 MACHINE_ID gravação em $DATADIR/machine_id falhou
114 MACHINE_ID falha na geração local do UUIDv7 base do machine-id

Em relação ao rascunho original: removidos os códigos 101 (machine-id é sempre regenerável) e 110 (workspace tem fallback default); adicionados o 113 (falha de gravação do machine_id) e o 114 (falha de geração do machine_id — o 105 é exclusivo do AGENT_UUID).

A linha de erro é a última que a biblioteca escreve; avisos e, em modo debug, linhas de diagnóstico podem precedê-la.

Exemplo

Veja skill/examples/minimal para um programa executável e skill/examples/ para as receitas de Docker e Kubernetes.

Skill para equipes e agentes de IA

A pasta skill/ é uma Agent Skill autocontida, chamada use-loghub-ident: integração, regras de uso, testes no projeto consumidor, receitas Docker/Kubernetes e diagnóstico de cada código de saída. É escrita para os desenvolvedores e os agentes de IA dos serviços que usam a biblioteca e não depende de nenhum outro arquivo deste repositório.

Instalar num projeto que usa o Claude Code, a partir de uma tag publicada:

V=0.5.2
mkdir -p .claude/skills/use-loghub-ident
curl -fsSL "https://github.com/patrickbrandao/go-loghub-ident/archive/refs/tags/v$V.tar.gz" \
  | tar -xz --strip-components=2 -C .claude/skills/use-loghub-ident "go-loghub-ident-$V/skill"
  • Para valer em todos os projetos de uma pessoa, use ~/.claude/skills/use-loghub-ident como destino. Em outras ferramentas compatíveis com o padrão Agent Skills, copie a pasta para o diretório de skills da ferramenta.
  • A pasta de destino precisa se chamar use-loghub-ident, o name da skill.
  • Commite a pasta no projeto consumidor: assim toda a equipe e o CI usam a mesma versão.

Gerar um .zip para upload no claude.ai ou distribuição interna, a partir de um checkout (o git archive leva só os arquivos versionados, sem binários compilados localmente):

git archive --format=zip --prefix=use-loghub-ident/ -o use-loghub-ident.zip v0.5.2:skill

A skill declara a versão da biblioteca que documenta (**Versão documentada:** no SKILL.md); ao atualizar a dependência num projeto, atualize a skill junto.

Licença

Ver LICENSE.

Documentation

Overview

Package loghubident resolve e expõe a identidade de um software dentro do ecossistema Loghub. É carregado uma única vez no boot do processo (Initialize) e disponibiliza seis valores imutáveis por meio de getters de pacote.

O objetivo principal é dar ao software que a utiliza uma identidade única — quem é este agente: AgentUUID (UUIDv7 gerado e persistido), AgentName e Hostname — e uma localização virtual — onde ele está: MachineID (nó físico ou virtual) e Workspace (tenant lógico). DataDir é suporte: o diretório onde as identidades geradas são persistidas, não uma identidade.

Os seis campos resolvidos são:

  • DataDir — diretório de dados persistente (env DATADIR ou /data)
  • MachineID — identificador da máquina (env MACHINE_ID, arquivo configurável via MACHINE_ID_FILE, $DATADIR/machine_id, ou gerado)
  • AgentName — nome do agente (env AGENT_NAME, $DATADIR/agent_name ou argv[0])
  • AgentUUID — UUIDv7 persistente (env AGENT_UUID, $DATADIR/agent_uuid ou gerado)
  • Hostname — hostname (env HOSTNAME ou os.Hostname)
  • Workspace — workspace/tenant (env WORKSPACE, $DATADIR/workspace ou "default")

Precedência geral de cada campo: variável de ambiente → arquivo de disco → fallback do sistema. Uma fonte ausente cai para a próxima; uma env presente mas inválida encerra o processo com o código do campo. Todos os valores são saneados (1ª linha, sem BOM, sem espaços nem caracteres de controle nas bordas) e normalizados para lowercase antes da validação — env e arquivo passam pelo mesmo pipeline.

As identidades geradas (MachineID e AgentUUID) são persistidas em $DATADIR com permissão 0644 garantida, gravação durável e atômica: processos que sobem ao mesmo tempo sobre o mesmo volume convergem para uma identidade única, e um crash no meio da escrita não deixa arquivo truncado. Descartar uma identidade persistida inválida sempre gera aviso em stderr.

O UUIDv7 é gerado localmente via github.com/patrickbrandao/go-loghub-uuidv7, sem dependência de /proc, sendo portanto multiplataforma.

Uso rápido

package main

import (
    "fmt"

    lhident "github.com/patrickbrandao/go-loghub-ident"
)

func main() {
    lhident.Initialize() // encerra o processo se algo falhar
    fmt.Println("agent:", lhident.AgentName(), lhident.AgentUUID())
}

Defina a variável de ambiente LOGHUB_IDENT_DEBUG para que Initialize escreva em stderr a origem e o valor final de cada um dos seis campos resolvidos.

Index

Constants

View Source
const DefaultDataDir = "/data"

DefaultDataDir é o caminho padrão do diretório de dados quando a variável de ambiente DATADIR está ausente ou vazia.

View Source
const DefaultMachineIDFile = "/etc/machine-id"

DefaultMachineIDFile é o caminho padrão do arquivo de machine-id quando a variável de ambiente MACHINE_ID_FILE está ausente ou vazia.

View Source
const DefaultWorkspace = "default"

DefaultWorkspace é o valor usado para WORKSPACE quando nenhuma fonte fornece um valor.

View Source
const EnvDebug = "LOGHUB_IDENT_DEBUG"

EnvDebug é a variável de ambiente que, quando definida (valor não vazio), ativa o log de diagnóstico em stderr durante Initialize.

Variables

This section is empty.

Functions

func AgentName added in v0.2.0

func AgentName() string

AgentName retorna o nome do agente.

func AgentUUID added in v0.2.0

func AgentUUID() string

AgentUUID retorna o UUIDv7 canônico do agente (com hífens).

func DataDir added in v0.2.0

func DataDir() string

DataDir retorna o diretório de dados resolvido.

func Hostname added in v0.2.0

func Hostname() string

Hostname retorna o hostname resolvido.

func Initialize

func Initialize()

Initialize resolve os seis campos de identidade a partir de variáveis de ambiente, arquivos e fallbacks do sistema, e os disponibiliza pelos getters de pacote (DataDir, MachineID, AgentName, AgentUUID, Hostname, Workspace).

Deve ser chamada UMA ÚNICA VEZ, no início de main(), ANTES de criar qualquer goroutine que vá ler os getters. Esse contrato estabelece a relação happens-before que torna a leitura posterior segura sem sincronização.

Em qualquer falha de resolução, escreve em stderr no formato

lib-loghub-ident: <VARIÁVEL>: <motivo>

e encerra o processo com o código correspondente (ver SPEC.md / README). A segunda chamada encerra com código 112.

Eventos operacionais — hoje, o descarte de uma identidade persistida que estava corrompida — são escritos em stderr SEMPRE, com o prefixo "lib-loghub-ident: aviso: ", independentemente do modo debug.

Se a variável de ambiente LOGHUB_IDENT_DEBUG estiver definida, escreve em stderr uma linha por campo indicando a origem e o valor final resolvido.

func IsInitialized added in v0.3.1

func IsInitialized() bool

IsInitialized informa se Initialize já concluiu com sucesso. Getters chamados antes disso devolvem "" (zero value) e não devem ser usados — ver SPEC §3.

Lê ready, e não initialized: initialized vira true no INÍCIO de Initialize (é a trava contra a segunda chamada), enquanto ready só vira true depois de apply, quando os getters já têm os valores finais.

func MachineID added in v0.2.0

func MachineID() string

MachineID retorna o identificador da máquina (32 hex, sem hífen).

func Workspace added in v0.2.0

func Workspace() string

Workspace retorna o workspace (tenant) resolvido.

Types

This section is empty.

Directories

Path Synopsis
Pacote tests contém a suíte de testes de integração da biblioteca go-loghub-ident: exercita a API pública (Initialize e os seis getters) contra o sistema operacional REAL, sem o fakeSystem.
Pacote tests contém a suíte de testes de integração da biblioteca go-loghub-ident: exercita a API pública (Initialize e os seis getters) contra o sistema operacional REAL, sem o fakeSystem.

Jump to

Keyboard shortcuts

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