Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 56 additions & 28 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,13 +133,28 @@ if err != nil {
}
```

## V3 - Destino unificado (arquivo ou Graylog)
A V3 é o destino unificado da toolkit. O serviço escolhe entre **arquivo** (mesmo layout do V2) e **GELF UDP** (Graylog) configurando `DestinationConfig.Protocol`. A API do `Logger` não muda — serviços que ainda usam V1 (default) ou V2 seguem funcionando sem alteração.
## V3 - Destino unificado (roteador por subject)
A V3 é o destino unificado da toolkit. Internamente roteia cada chamada
`LogSubject` por **subject**:

A V3 é a forma recomendada daqui em diante. V1 e V2 continuam disponíveis para retrocompatibilidade enquanto serviços migram.
- **Subjects "level-like"** — `test`, `debug`, `info`, `warn`, `error`,
`fatal`, ou seja, a saída de `logger.Info/.Warn/.Error/...` — vão para o
**destino de rede** (Graylog UDP/GELF) quando configurado; caso
contrário, caem em arquivo.
- **Subjects de domínio** — qualquer outra string passada a `LogSubject`
— vão **sempre** para arquivo, no mesmo layout do V2
(`<basePath>/<service>/<subject>/<subject>.log`).

### Modo arquivo (`ProtocolFile`)
Comportamento idêntico ao V2: layout `<basePath>/<service>/<level>/<level>.log`, mesma JSON shape (`message`, `level`, `source`, `line`, `trace`, `timestamp`).
Roteamento é **exclusivo** (um subject vai para um único destino) e
**fail-loud**: se o destino de rede está configurado mas a inicialização
falha, o caller recebe o erro — não há fallback automático para arquivo.

A V3 é a forma recomendada daqui em diante. V1 e V2 continuam disponíveis
para retrocompatibilidade enquanto serviços migram.

### Modo arquivo-only
Comportamento idêntico ao V2: layout `<basePath>/<service>/<subject>/<subject>.log`,
mesma JSON shape (`message`, `level`, `source`, `line`, `trace`, `timestamp`).
```go
import (
mchlogtoolkitgo "github.com/gaudiumsoftware/mchlogtoolkitgo"
Expand All @@ -148,9 +163,7 @@ import (
)

func main() {
if err := mchlogcorev3.Configure(mchlogcorev3.DestinationConfig{
Protocol: mchlogcorev3.ProtocolFile,
}); err != nil {
if err := mchlogcorev3.Configure(mchlogcorev3.DestinationConfig{}); err != nil {
panic(err)
}
mchlogcore.SetVersion(mchlogcore.V3)
Expand All @@ -161,8 +174,9 @@ func main() {
}
```

### Modo Graylog UDP (`ProtocolGraylogUDP`)
Para `dev`/`qa` que centralizam logs no Graylog em vez de arquivo local:
### Modo roteador (arquivo + Graylog UDP)
Para serviços que querem level-logs no Graylog mantendo eventos de
domínio em disco:
```go
import (
"os"
Expand All @@ -174,30 +188,37 @@ import (

func main() {
if err := mchlogcorev3.Configure(mchlogcorev3.DestinationConfig{
Protocol: mchlogcorev3.ProtocolGraylogUDP,
Addr: "graylog.dev.internal:12201",
Source: "payments-api-qa-" + os.Getenv("POD_NAME"),
// DisableGZIP: true, // opcional, default = compressão habilitada
Network: &mchlogcorev3.NetworkConfig{
Type: mchlogcorev3.NetworkGraylogUDP,
Addr: "graylog.dev.internal:12201",
Source: "payments-api-qa-" + os.Getenv("POD_NAME"),
// DisableGZIP: true, // opcional, default = compressão habilitada
},
// NetworkSubjects: []string{"meu_subject_custom"}, // opcional
}); err != nil {
panic(err)
}
mchlogcore.SetVersion(mchlogcore.V3)

logger, _ := mchlogtoolkitgo.NewLogger("payments-api", "debug")
logger.Initialize()
logger.Info("aplicação iniciada e ouvindo na porta 80")
logger.Info("aplicação iniciada e ouvindo na porta 80") // → Graylog
// Eventos de domínio continuam indo para arquivo:
// mchlogcorev3.MchLog.LogSubject("meu_evento_dominio", payload, nil)
}
```

### Campos do `DestinationConfig`
| Campo | Obrigatório quando… | Descrição |
|---------------|--------------------------------|--------------------------------------------------------------------------------------|
| `Protocol` | — | `ProtocolFile` (default) ou `ProtocolGraylogUDP`. |
| `Addr` | `Protocol = ProtocolGraylogUDP`| Endereço do Graylog no formato `host:porta`. |
| `Source` | `Protocol = ProtocolGraylogUDP`| Valor do campo GELF `host` (coluna `source` no Graylog). **Fornecido pelo serviço** — a toolkit não autodetecta. Ex.: `payments-api-qa-pod-7f8d2`. Use `mchlogcorev3.DefaultSource()` se quiser apenas o hostname. |
| `DisableGZIP` | nunca (opcional) | Default `false` (gzip habilitado). Aplica só ao `ProtocolGraylogUDP`. |

### Como aparece no Graylog (modo `ProtocolGraylogUDP`)
| Campo | Obrigatório quando… | Descrição |
|------------------------|----------------------------------|--------------------------------------------------------------------------------------|
| `Network` | nunca (opcional) | Quando `nil`, todos os subjects vão para arquivo. Quando definido, subjects level-like vão para esse destino. |
| `Network.Type` | `Network != nil` | Único valor suportado hoje: `NetworkGraylogUDP`. |
| `Network.Addr` | `Type = NetworkGraylogUDP` | Endereço do Graylog no formato `host:porta`. |
| `Network.Source` | `Type = NetworkGraylogUDP` | Valor do campo GELF `host` (coluna `source` no Graylog). **Fornecido pelo serviço** — a toolkit não autodetecta. Ex.: `payments-api-qa-pod-7f8d2`. Use `mchlogcorev3.DefaultSource()` se quiser apenas o hostname. |
| `Network.DisableGZIP` | nunca (opcional) | Default `false` (gzip habilitado). |
| `NetworkSubjects` | requer `Network != nil` | Lista extra de subjects que devem ir para o destino de rede em vez de arquivo. Match exato, case-sensitive. **Cuidado:** subjects usados por healthchecks ou probes (qualquer caller que chame `GetFileNameFromStreamName` esperando um caminho de arquivo) devem ficar fora desta lista. |

### Como aparece no Graylog
| GELF field | Origem | Coluna/campo no Graylog |
|---------------------|----------------------------------------------|-------------------------|
| `host` | `cfg.Source` | `source` (default) |
Expand All @@ -214,16 +235,23 @@ Exemplos de busca:
- `log_id:payments-api-mchlog-info` — equivale ao arquivo `INFO`.
- `source:*-qa-*` — todos os pods de QA (env embutido em `Source` pelo caller).

### Falhas de envio (modo `ProtocolGraylogUDP`)
### Falhas de envio (subjects roteados ao Graylog)
UDP é fire-and-forget. Se o destino estiver inacessível, a toolkit
**descarta a mensagem silenciosamente** e emite no máximo **uma linha em
`stderr` a cada 60s** (`mchlogcorev3: GELF UDP send failed: ...`).
Não há fallback automático para arquivo.
Eventos de domínio em arquivo seguem sendo gravados normalmente — file é
fonte de verdade para dados persistentes.

### Quando usar cada modo
- **Produção**: `ProtocolFile` (ou seguir em V1/V2). Arquivos persistidos em `/applog/<service>/...` são a fonte de verdade.
- **Dev/QA**: `ProtocolGraylogUDP` para concentrar logs no Graylog.
- **Produção** e qualquer ambiente que precise rastreabilidade de
eventos de domínio: configure `Network` para receber level-logs no
Graylog e mantenha o file destination (sempre presente) gravando os
eventos de domínio.
- **Sem Graylog (legado/local)**: `Configure(DestinationConfig{})` —
modo arquivo-only, idêntico ao V2.

### Migração de V1/V2 para V3
Trocar `mchlogcore.SetVersion(mchlogcore.V2)` por `Configure(DestinationConfig{Protocol: ProtocolFile}) + SetVersion(V3)` mantém o comportamento bit-a-bit (mesmo layout, mesma JSON shape).
Trocar `mchlogcore.SetVersion(mchlogcore.V2)` por
`Configure(DestinationConfig{}) + SetVersion(V3)` mantém o comportamento
bit-a-bit (mesmo layout, mesma JSON shape).
V1 e V2 seguem disponíveis até a próxima onda de migração.
7 changes: 4 additions & 3 deletions mchlogcore/mchlog.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,9 +108,10 @@ var MchLog LogType

// InitializeMchLog inicializa o destino selecionado com o caminho dado.
// Em todos os destinos o path tem a forma "<basePath>/<service>/":
// - V1, V2 e V3-ProtocolFile usam o caminho como diretório base de arquivos.
// - V3-ProtocolGraylogUDP usa o último segmento apenas para extrair
// o nome do serviço; o destino real é cfg.Addr.
// - V1 e V2 usam o caminho como diretório base de arquivos.
// - V3 usa o caminho como diretório base do file impl (sempre presente)
// e também extrai o nome do serviço do último segmento para uso no
// destino de rede (quando configurado em DestinationConfig.Network).
func InitializeMchLog(path string) {
var versionName string
var initErr error
Expand Down
8 changes: 5 additions & 3 deletions mchlogcore/v3_dispatch_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,11 @@ func TestSetVersionV3DispatchesToGraylog(t *testing.T) {
defer conn.Close()

if err := mchlogcorev3.Configure(mchlogcorev3.DestinationConfig{
Protocol: mchlogcorev3.ProtocolGraylogUDP,
Addr: addr,
Source: "pod-1",
Network: &mchlogcorev3.NetworkConfig{
Type: mchlogcorev3.NetworkGraylogUDP,
Addr: addr,
Source: "pod-1",
},
}); err != nil {
t.Fatalf("Configure: %v", err)
}
Expand Down
143 changes: 95 additions & 48 deletions mchlogcorev3/config.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,23 @@
// Package mchlogcorev3 é o destino unificado da toolkit. Suporta múltiplos
// protocolos selecionados por DestinationConfig.Protocol:
// Package mchlogcorev3 é o destino unificado da toolkit. O V3 sempre
// roteia chamadas LogSubject por um routerDestination que combina dois
// impls: file (sempre presente) e network (opcional).
//
// - ProtocolFile: grava em arquivo no mesmo layout do mchlogcorev2
// (<basePath>/<service>/<level>/<level>.log) e mesma JSON shape.
// - ProtocolGraylogUDP: envia em formato GELF via UDP para o Graylog.
// Regra de roteamento (exata, case-sensitive):
//
// Novos protocolos (graylog-tcp, syslog, splunk-hec, etc.) podem ser
// adicionados expondo novos valores de Protocol e a implementação
// correspondente; a API pública não muda.
// - subjects "level-like" da toolkit (test, debug, info, warn, error,
// fatal) → network impl, se configurado; caso contrário, file.
// - subjects estendidos via DestinationConfig.NetworkSubjects → mesma
// regra dos level-like.
// - qualquer outro subject (eventos de domínio, ex.: historico_posicao_taxi,
// log_posicao_alterada, etc.) → file impl.
//
// O file impl usa o mesmo layout do mchlogcorev2
// (<basePath>/<service>/<subject>/<subject>.log) e a mesma JSON shape.
//
// Atualmente o único tipo de network suportado é Graylog UDP (GELF).
// Novos transportes (graylog-tcp, syslog, splunk-hec, etc.) podem ser
// adicionados expondo novos valores de NetworkType e a implementação
// correspondente; a API pública (LogSubject) não muda.
package mchlogcorev3

import (
Expand All @@ -16,67 +26,90 @@ import (
"sync"
)

// Protocol identifica o destino efetivo usado para persistir/enviar logs.
type Protocol string
// NetworkType identifica o transporte do impl de rede.
type NetworkType string

const (
// ProtocolFile grava logs em arquivo. Layout e JSON shape são os
// mesmos do mchlogcorev2; o caller controla o caminho via
// Logger.SetPath (ou usa o default /applog/).
ProtocolFile Protocol = "file"

// ProtocolGraylogUDP envia logs em formato GELF via UDP.
ProtocolGraylogUDP Protocol = "graylog-udp"
// NetworkGraylogUDP envia logs em formato GELF via UDP.
NetworkGraylogUDP NetworkType = "graylog-udp"
)

// DestinationConfig agrupa todos os parâmetros aceitos pelo V3. Os campos
// relevantes dependem de Protocol — campos de outros protocolos são
// ignorados pela validação.
type DestinationConfig struct {
// Protocol seleciona o destino. Default: ProtocolFile.
Protocol Protocol
// NetworkConfig descreve o destino de rede opcional. Quando presente em
// DestinationConfig, subjects "level-like" (e os explicitamente listados
// em NetworkSubjects) são enviados por aqui em vez de gravados em disco.
type NetworkConfig struct {
// Type seleciona o transporte. Hoje só NetworkGraylogUDP.
Type NetworkType

// Addr é o endereço do destino no formato "host:porta".
// Obrigatório quando Protocol = ProtocolGraylogUDP.
Addr string

// Source é o valor gravado no campo GELF "host" (coluna "source"
// no Graylog). Obrigatório quando Protocol = ProtocolGraylogUDP.
// Fornecido pelo serviço (a toolkit não autodetecta).
// no Graylog). Fornecido pelo serviço (a toolkit não autodetecta).
Source string

// DisableGZIP desabilita a compressão GZIP do GELF UDP. Default
// (zero value) = GZIP habilitado. Aplica apenas a ProtocolGraylogUDP.
// (zero value) = GZIP habilitado.
DisableGZIP bool
}

// DestinationConfig agrupa os parâmetros aceitos pelo V3.
//
// Zero value (Network==nil, NetworkSubjects==nil) configura o V3 em modo
// "file-only": todos os subjects vão para arquivo no mesmo layout do V2.
type DestinationConfig struct {
// Network é opcional. Quando nil, todos os subjects vão para arquivo.
// Quando definido, subjects level-like (e os listados em
// NetworkSubjects) vão por aqui; o restante continua em arquivo.
Network *NetworkConfig

// NetworkSubjects estende a whitelist default de subjects roteados
// para o network impl. A whitelist default é o conjunto fixo de
// levels da toolkit (test, debug, info, warn, error, fatal).
// Match é exato e case-sensitive. Strings vazias são ignoradas.
//
// Só faz sentido com Network != nil. Configure rejeita o contrário.
NetworkSubjects []string
}

var (
cfgMu sync.RWMutex
activeCfg DestinationConfig
configured bool
)

// Configure normaliza e armazena a configuração que será usada pelo
// destino. Aplica default a Protocol e valida os campos obrigatórios
// para o protocolo selecionado.
// destino. Valida os campos obrigatórios para o transporte de rede
// selecionado (quando presente).
//
// A NetworkConfig recebida é copiada antes do armazenamento, então o
// caller pode mutar/descartar a struct após o retorno.
func Configure(cfg DestinationConfig) error {
if cfg.Protocol == "" {
cfg.Protocol = ProtocolFile
if cfg.Network != nil {
netCfg := *cfg.Network
switch netCfg.Type {
case "":
return errors.New("mchlogcorev3: Network.Type is required when Network is set")
case NetworkGraylogUDP:
if netCfg.Addr == "" {
return errors.New("mchlogcorev3: Network.Addr is required for NetworkGraylogUDP")
}
if netCfg.Source == "" {
return errors.New("mchlogcorev3: Network.Source is required for NetworkGraylogUDP (caller-provided)")
}
default:
return errors.New("mchlogcorev3: unknown Network.Type: " + string(netCfg.Type))
}
cfg.Network = &netCfg
} else if len(cfg.NetworkSubjects) > 0 {
return errors.New("mchlogcorev3: NetworkSubjects requires Network to be set")
}

switch cfg.Protocol {
case ProtocolFile:
// arquivo: nada obrigatório aqui; o path vem via Logger.SetPath
// e o nome do serviço via NewLogger.
case ProtocolGraylogUDP:
if cfg.Addr == "" {
return errors.New("mchlogcorev3: Addr is required for ProtocolGraylogUDP")
}
if cfg.Source == "" {
return errors.New("mchlogcorev3: Source is required for ProtocolGraylogUDP (caller-provided)")
}
default:
return errors.New("mchlogcorev3: unknown Protocol: " + string(cfg.Protocol))
if len(cfg.NetworkSubjects) > 0 {
// Copia o slice para isolar mutações posteriores no caller.
dup := make([]string, len(cfg.NetworkSubjects))
copy(dup, cfg.NetworkSubjects)
cfg.NetworkSubjects = dup
}

cfgMu.Lock()
Expand All @@ -86,13 +119,27 @@ func Configure(cfg DestinationConfig) error {
return nil
}

// ActiveConfig retorna uma cópia da configuração ativa. Útil para
// testes e para o destino ler os parâmetros já normalizados.
// ActiveConfig retorna uma cópia profunda da configuração ativa. Útil
// para testes e para o destino ler os parâmetros já normalizados.
// Antes de Configure ser chamado, devolve um DestinationConfig zero-valued.
//
// O ponteiro Network e o slice NetworkSubjects são duplicados, então
// callers podem mutar o resultado livremente sem afetar o estado interno
// (simetria com Configure, que também duplica ambos na entrada).
func ActiveConfig() DestinationConfig {
cfgMu.RLock()
defer cfgMu.RUnlock()
return activeCfg
out := activeCfg
if activeCfg.Network != nil {
n := *activeCfg.Network
out.Network = &n
}
if len(activeCfg.NetworkSubjects) > 0 {
dup := make([]string, len(activeCfg.NetworkSubjects))
copy(dup, activeCfg.NetworkSubjects)
out.NetworkSubjects = dup
}
return out
}

// IsConfigured indica se Configure já foi chamado com sucesso.
Expand All @@ -105,7 +152,7 @@ func IsConfigured() bool {
// DefaultSource é um helper para callers que não querem compor o Source
// manualmente. Devolve o hostname do sistema (os.Hostname) ou "unknown"
// caso a chamada falhe ou retorne string vazia. Útil apenas para
// ProtocolGraylogUDP.
// NetworkGraylogUDP.
func DefaultSource() string {
if h, err := os.Hostname(); err == nil && h != "" {
return h
Expand Down
Loading
Loading