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.