From 48624f028f5c189447416ee5aa0d51448d375c07 Mon Sep 17 00:00:00 2001 From: gabrielferreira-gaudium Date: Thu, 5 Feb 2026 15:54:52 -0300 Subject: [PATCH 1/8] alter mchlog.go to use /.log file names and no log rotation --- mchlogcore/mchlog.go | 226 +++++++++++-------------------------------- 1 file changed, 55 insertions(+), 171 deletions(-) diff --git a/mchlogcore/mchlog.go b/mchlogcore/mchlog.go index 926c6c8..5217929 100644 --- a/mchlogcore/mchlog.go +++ b/mchlogcore/mchlog.go @@ -4,7 +4,6 @@ import ( "encoding/json" "errors" "fmt" - "net" "os" "path/filepath" "reflect" @@ -12,7 +11,6 @@ import ( "time" "github.com/rs/zerolog" - "github.com/rs/zerolog/log" ) /* Exemplo de uso do mchlogcore. @@ -27,7 +25,7 @@ mchlogcore.MchLog.LogSubject("teste", m, nil) ou mchlogcore.MchLog.LogSubject("teste", []byte(m), nil) // output: {"somekey":{"id":"123"},"testmsg":"some msg","tick":10,"time":"2020-11-09T12:05:19+01:00","data_hora":"2022-10-12 19:11:33 UTC"} -// no arquivo ./etc/log/teste/teste-169.254.215.202-2022101222.log +// no arquivo ./etc/log/teste/teste.log // Origem do log é um mapa onde a chave é uma string e o tipo do valor "dinâmico" (interface{}). // A chave TEM que ser string, o valor pode ser string, int ou float. // O tipo do valor pode ser estático também (string, por exemplo). @@ -36,7 +34,7 @@ h["mapa1"] = "aaa" h["mapa2"] = 22 mchlogcore.MchLog.LogSubject("teste", h, nil) // output: {"mapa1":"aaa","mapa2":22,"data_hora":"2022-10-12 19:11:33 UTC"} -// no arquivo ./etc/log/teste/teste-169.254.215.202-2022101222.log +// no arquivo ./etc/log/teste/teste.log // Origem do log é um array/slice de tipo dinâmico. // O tipo do array/slice pode ser estático, nesse caso só pode ser string. // Começando do indice zero, os elementos pares são as "chaves" e @@ -46,155 +44,96 @@ a = append(a, "key_array1", 22, "key_array2") a = append(a, "55") mchlogcore.MchLog.LogSubject("teste", a, nil) // output: {"key_array1":22,"key_array2":"55","data_hora":"2022-10-12 19:11:33 UTC"} -// no arquivo ./etc/log/teste/teste-169.254.215.202-2022101222.log +// no arquivo ./etc/log/teste/teste.log // Usando o log do exemplo anterior, desta vez enviando um erro. mchlogcore.MchLog.LogSubject("teste", a, errors.New("isto é um erro forçado")) // output: {"key_array1":22,"key_array2":"55","error":"isto é um erro forçado","data_hora":"2022-10-12 19:11:33 UTC"} -// no arquivo ./etc/log/err_teste/err_teste-169.254.215.202-2022101222.log +// no arquivo ./etc/log/err_teste/err_teste.log */ const ( - ccLogDataHora string = "timestamp" //chave ref. ao timestamp, gravada automaticamente no json de log - ccDateTimeMask string = "2006010215" //YYYYMMDDHH - timestamp que define a rotação dos arquivos de log - ccLogFileSuffix string = ".log" //sufixo do arquivo de log - ccLogErrPrefixSubject string = "err_" //prefixo do arquivo de log de erro + ccLogDataHora string = "timestamp" //chave ref. ao timestamp, gravada automaticamente no json de log + ccLogFileSuffix string = ".log" //sufixo do arquivo de log + ccLogErrPrefixSubject string = "err_" //prefixo do arquivo de log de erro ) // LogType utiliza a biblioteca 'zerolog' para persistir log em arquivo. type LogType struct { - //key = subject; value = obj do tipo fileLogType - mapLogger sync.Map + mapLogger sync.Map // cache de loggers para evitar abrir/fechar os arquivos de log repetidamente path string - ip string } -type fileLogType struct { - filename string - file *os.File - err error - logger *zerolog.Logger -} - -// closeFile tenta fechar o arquivo de instrumentação, caso esteja aberto -func (flt *fileLogType) closeFile() { - if flt.file != nil { - _ = flt.file.Close() - } -} - -func (flt *fileLogType) mkDir() error { - var err error - dirName := filepath.Dir(flt.filename) - if _, err = os.Stat(dirName); os.IsNotExist(err) { - err = os.MkdirAll(dirName, os.ModePerm) - } - return err -} - -// fileStreamType é usado na comunicação por canal -type fileStreamType struct { - filename string - subject string - chReturn chan fileLogType -} - -var _chLog chan fileStreamType - // MchLog é o objeto de acesso ao método LogSubject, // que efetivamente escreve o log. var MchLog LogType -// InitializeMchLog inicia os procedimentos para persistir -// logs em arquivo pelo objeto MchLog. +// InitializeMchLog configura o diretório base e as regras de formatação do zerolog. func InitializeMchLog(path string) { - _chLog = make(chan fileStreamType) zerolog.TimestampFieldName = ccLogDataHora zerolog.TimeFieldFormat = "2006-01-02 15:04:05" - zerolog.TimestampFunc = func() time.Time { - loc, _ := time.LoadLocation("UTC") - return time.Now().In(loc) + return time.Now().UTC() } MchLog.path = filepath.FromSlash(path) - MchLog.ip = getLocalIP() - - go func() { - for filestream := range _chLog { - var fileLog fileLogType - var ok bool - var obj any - - if obj, ok = MchLog.mapLogger.Load(filestream.subject); ok { - fileLog = obj.(fileLogType) - } - - // se o registro não foi encontrado no mapa de controle ou - // o nome do arquivo mudou, ele será gerado e registrado no mapa. - if !ok || (filestream.filename != fileLog.filename) { - fileLog.closeFile() // se houver arquivo aberto ele será fechado. - fileLog = fileLogType{filename: filestream.filename} - - // se não houver diretório, que seja criado. - // se o diretório existir nada faz e não retorna erro. - if fileLog.err = fileLog.mkDir(); fileLog.err == nil { - // criar arquivo de log com o nome passado pelo canal - fileLog.file, fileLog.err = os.OpenFile(filestream.filename, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0666) - if fileLog.err == nil { - logg := log.With().Logger().Output(fileLog.file) - fileLog.logger = &logg - MchLog.mapLogger.Store(filestream.subject, fileLog) - } - } - } - - filestream.chReturn <- fileLog - } - }() } -// LogSubject grava no arquivo de log o conteúdo (content) -// enviado no parâmetro, que pode ser um json, map ou array. -// Um subdiretório será criado com o nome do subject e parte -// do nome do arquivo também conterá o parâmetro subject. -// Se o parâmetro errLog estiver preenchido, o subject -// receberá o prefix "err_", consequentemente a pasta e -// o nome do arquivo correspondente. O descritivo do erro -// fará parte do json gravado no arquivo de log -// ascendStackFrame é um parâmetro opcional (default = 1) -// que indica onde procurar a linha de código que deu origem -// à mensagem de log, em caso de errLog!=nil. +// LogSubject grava no arquivo de log o conteúdo (content) enviado no parâmetro, que pode ser um json, map ou array. +// Um subdiretório será criado com um arquivo de log dentro, ambos com o nome do subject: /.log +// Se o parâmetro errLog estiver preenchido, o subject receberá o prefix "err_", e consequentemente a pasta e o nome do arquivo correspondente. +// O descritivo do erro fará parte do json gravado no arquivo de log +// ascendStackFrame é um parâmetro opcional (default = 1) que indica onde procurar a linha de código que deu origem à mensagem de log, em caso de errLog!=nil. func (l *LogType) LogSubject(subject string, content any, errLog error, ascendStackFrame ...int) { if subject == "" { return } - var err error - var logger *zerolog.Logger - var event *zerolog.Event - var skip = 1 - - // o subject de um log de erro recebe um prefixo correspondente + // Se for erro, adiciona o prefixo "err_" no subject + // Ex.: se subject for "init", cria err_init/err_init.log if errLog != nil { subject = ccLogErrPrefixSubject + subject } - if len(ascendStackFrame) > 0 { - skip = ascendStackFrame[0] - } + var logger *zerolog.Logger - if logger, err = l.checkFile(subject); err == nil { - if event, err = l.getJSONLogger(logger, content); err == nil { - if errLog == nil { - event.Send() - } else { - event.Caller(skip).Err(errLog).Send() - } + // Tenta recuperar o logger do cache (evita abrir o arquivo novamente) + if obj, ok := l.mapLogger.Load(subject); ok { + logger = obj.(*zerolog.Logger) + } else { + // Se não estiver no cache, abre o arquivo e inicializa o logger + filename := filepath.Join(l.path, subject, subject+ccLogFileSuffix) + + // Garante que a árvore de diretórios existe + _ = os.MkdirAll(filepath.Dir(filename), 0755) + + // Abre o arquivo para escrita ao final (append). Cria se não existir. + f, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0664) + if err != nil { + return } + + // Importante: Não fechamos o arquivo (f.Close) aqui pois ele será + // gerenciado pelo objeto logger que ficará no cache durante a vida do processo. + newLogger := zerolog.New(f).With().Timestamp().Logger() + logger = &newLogger + l.mapLogger.Store(subject, logger) + } + + // Processa o conteúdo e gera o evento de log + event, err := l.getJSONLogger(logger, content) + if err != nil { + return + } + + skip := 1 + if len(ascendStackFrame) > 0 { + skip = ascendStackFrame[0] } - if err != nil { // mostrar log de erro na saída default do zerolog (console) - log.Log().Msg("LogType.LogSubject: " + subject + " - " + err.Error()) + if errLog == nil { + event.Send() + } else { + event.Caller(skip).Err(errLog).Send() } } @@ -268,62 +207,7 @@ func (l *LogType) getJSONLogger(logger *zerolog.Logger, content any) (*zerolog.E return event, err } -// checkFile trata da rotação dos arquivos de log, dependendo -// da máscara do timestamp definido na constante ccDateTimeMask. -func (l *LogType) checkFile(subject string) (*zerolog.Logger, error) { - var flt fileLogType - - filename := l.GetFileNameFromStreamName(subject) - obj, ok := l.mapLogger.Load(subject) - - if ok { - flt = obj.(fileLogType) - } - - if !ok || (filename != flt.filename) { - //criando canal de retorno dinamicamente e passando - //para a estrutura que será enviada para o canal de - //manipulação do arquivo - chret := make(chan fileLogType) - _chLog <- fileStreamType{filename: filename, subject: subject, chReturn: chret} - flt = <-chret - } - - return flt.logger, flt.err -} - -// getFileNameFromStreamName monta o diretório completo com o nome do arquivo +// GetFileNameFromStreamName mantido apenas para compatibilidade com os testes unitários func (l *LogType) GetFileNameFromStreamName(subject string) string { - loc, _ := time.LoadLocation("UTC") - dataHora := time.Now().In(loc).Format(ccDateTimeMask) - - ip := l.ip - if ip != "" { - ip = "-" + ip - } - - logFile := filepath.Join(l.path, subject, subject+ip+"-"+dataHora+ccLogFileSuffix) - return filepath.FromSlash(logFile) -} - -// GetIP retorna o IP onde o log está rodando -func (l *LogType) GetIP() string { - return l.ip -} - -// getLocalIP retorna o endereço local do IP, desconsiderando o loopback -func getLocalIP() string { - addrs, err := net.InterfaceAddrs() - if err != nil { - return "" - } - for _, address := range addrs { - // check the address type and if it is not a loopback the display it - if ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() { - if ipnet.IP.To4() != nil { - return ipnet.IP.String() - } - } - } - return "" + return filepath.Join(l.path, subject, subject+ccLogFileSuffix) } From 5041d0eb216ffdd2a42846604cc1daaf4247954b Mon Sep 17 00:00:00 2001 From: gabrielferreira-gaudium Date: Fri, 6 Feb 2026 10:23:03 -0300 Subject: [PATCH 2/8] separate mchlog implementations in V1 and v2 --- mchlogcore/mchlog.go | 212 ------------------------- mchlogcoreV1/mchlogV1.go | 329 +++++++++++++++++++++++++++++++++++++++ mchlogcoreV2/mchlogV2.go | 213 +++++++++++++++++++++++++ 3 files changed, 542 insertions(+), 212 deletions(-) create mode 100644 mchlogcoreV1/mchlogV1.go create mode 100644 mchlogcoreV2/mchlogV2.go diff --git a/mchlogcore/mchlog.go b/mchlogcore/mchlog.go index 5217929..d8ece06 100644 --- a/mchlogcore/mchlog.go +++ b/mchlogcore/mchlog.go @@ -1,213 +1 @@ package mchlogcore - -import ( - "encoding/json" - "errors" - "fmt" - "os" - "path/filepath" - "reflect" - "sync" - "time" - - "github.com/rs/zerolog" -) - -/* Exemplo de uso do mchlogcore. -// Primeiro o pacote precisa ser inicializado com o -// caminho a gravar os arquivos de log: -mchlogcore.InitializeMchLog("./etc/log") -// O pacote mchlogcore contém o objeto público MchLog e -// o único método acessível: LogSubject() -// Origem do log é uma string ou []byte em formato de json (tem que ser um json válido) -m := fmt.Sprintf("{\"somekey\":{\"id\":\"123\"},\"tick\":%d,\"time\":\"2020-11-09T12:05:19+01:00\",\"testmsg\":\"some msg\"}", 10) -mchlogcore.MchLog.LogSubject("teste", m, nil) -ou -mchlogcore.MchLog.LogSubject("teste", []byte(m), nil) -// output: {"somekey":{"id":"123"},"testmsg":"some msg","tick":10,"time":"2020-11-09T12:05:19+01:00","data_hora":"2022-10-12 19:11:33 UTC"} -// no arquivo ./etc/log/teste/teste.log -// Origem do log é um mapa onde a chave é uma string e o tipo do valor "dinâmico" (interface{}). -// A chave TEM que ser string, o valor pode ser string, int ou float. -// O tipo do valor pode ser estático também (string, por exemplo). -h := make(map[string]any) -h["mapa1"] = "aaa" -h["mapa2"] = 22 -mchlogcore.MchLog.LogSubject("teste", h, nil) -// output: {"mapa1":"aaa","mapa2":22,"data_hora":"2022-10-12 19:11:33 UTC"} -// no arquivo ./etc/log/teste/teste.log -// Origem do log é um array/slice de tipo dinâmico. -// O tipo do array/slice pode ser estático, nesse caso só pode ser string. -// Começando do indice zero, os elementos pares são as "chaves" e -// os ímpares os valores, que podem ser string, int ou float. -var a []any -a = append(a, "key_array1", 22, "key_array2") -a = append(a, "55") -mchlogcore.MchLog.LogSubject("teste", a, nil) -// output: {"key_array1":22,"key_array2":"55","data_hora":"2022-10-12 19:11:33 UTC"} -// no arquivo ./etc/log/teste/teste.log -// Usando o log do exemplo anterior, desta vez enviando um erro. -mchlogcore.MchLog.LogSubject("teste", a, errors.New("isto é um erro forçado")) -// output: {"key_array1":22,"key_array2":"55","error":"isto é um erro forçado","data_hora":"2022-10-12 19:11:33 UTC"} -// no arquivo ./etc/log/err_teste/err_teste.log -*/ - -const ( - ccLogDataHora string = "timestamp" //chave ref. ao timestamp, gravada automaticamente no json de log - ccLogFileSuffix string = ".log" //sufixo do arquivo de log - ccLogErrPrefixSubject string = "err_" //prefixo do arquivo de log de erro -) - -// LogType utiliza a biblioteca 'zerolog' para persistir log em arquivo. -type LogType struct { - mapLogger sync.Map // cache de loggers para evitar abrir/fechar os arquivos de log repetidamente - path string -} - -// MchLog é o objeto de acesso ao método LogSubject, -// que efetivamente escreve o log. -var MchLog LogType - -// InitializeMchLog configura o diretório base e as regras de formatação do zerolog. -func InitializeMchLog(path string) { - zerolog.TimestampFieldName = ccLogDataHora - zerolog.TimeFieldFormat = "2006-01-02 15:04:05" - zerolog.TimestampFunc = func() time.Time { - return time.Now().UTC() - } - - MchLog.path = filepath.FromSlash(path) -} - -// LogSubject grava no arquivo de log o conteúdo (content) enviado no parâmetro, que pode ser um json, map ou array. -// Um subdiretório será criado com um arquivo de log dentro, ambos com o nome do subject: /.log -// Se o parâmetro errLog estiver preenchido, o subject receberá o prefix "err_", e consequentemente a pasta e o nome do arquivo correspondente. -// O descritivo do erro fará parte do json gravado no arquivo de log -// ascendStackFrame é um parâmetro opcional (default = 1) que indica onde procurar a linha de código que deu origem à mensagem de log, em caso de errLog!=nil. -func (l *LogType) LogSubject(subject string, content any, errLog error, ascendStackFrame ...int) { - if subject == "" { - return - } - - // Se for erro, adiciona o prefixo "err_" no subject - // Ex.: se subject for "init", cria err_init/err_init.log - if errLog != nil { - subject = ccLogErrPrefixSubject + subject - } - - var logger *zerolog.Logger - - // Tenta recuperar o logger do cache (evita abrir o arquivo novamente) - if obj, ok := l.mapLogger.Load(subject); ok { - logger = obj.(*zerolog.Logger) - } else { - // Se não estiver no cache, abre o arquivo e inicializa o logger - filename := filepath.Join(l.path, subject, subject+ccLogFileSuffix) - - // Garante que a árvore de diretórios existe - _ = os.MkdirAll(filepath.Dir(filename), 0755) - - // Abre o arquivo para escrita ao final (append). Cria se não existir. - f, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0664) - if err != nil { - return - } - - // Importante: Não fechamos o arquivo (f.Close) aqui pois ele será - // gerenciado pelo objeto logger que ficará no cache durante a vida do processo. - newLogger := zerolog.New(f).With().Timestamp().Logger() - logger = &newLogger - l.mapLogger.Store(subject, logger) - } - - // Processa o conteúdo e gera o evento de log - event, err := l.getJSONLogger(logger, content) - if err != nil { - return - } - - skip := 1 - if len(ascendStackFrame) > 0 { - skip = ascendStackFrame[0] - } - - if errLog == nil { - event.Send() - } else { - event.Caller(skip).Err(errLog).Send() - } -} - -// getJSONLogger retorna o evento de log correspondente -// pronto para ser persistido no arquivo. -func (l *LogType) getJSONLogger(logger *zerolog.Logger, content any) (*zerolog.Event, error) { - var err error - var event *zerolog.Event - var ok bool - - v := reflect.ValueOf(content) - switch v.Kind() { - case reflect.Map: - var m map[string]any - if m, ok = content.(map[string]any); !ok { - m = make(map[string]any) - keys := v.MapKeys() - for _, k := range keys { - va := v.MapIndex(k) - switch va.Kind() { - case reflect.String: - m[k.String()] = va.String() - case reflect.Int, reflect.Int64, reflect.Int32: - m[k.String()] = va.Int() - case reflect.Float64, reflect.Float32: - m[k.String()] = va.Float() - } - } - } - event = logger.Log().Fields(m) - - case reflect.Slice, reflect.Array: - var arrb []byte - if arrb, ok = content.([]byte); ok { - var m map[string]any - if err = json.Unmarshal(arrb, &m); err == nil { - event = logger.Log().Fields(m) - } - } else { - var arr []any - if arr, ok = content.([]any); !ok { - tam := v.Len() - for i := 0; i < tam; i++ { - va := v.Index(i) - switch va.Kind() { - case reflect.String: - arr = append(arr, va.String()) - case reflect.Int, reflect.Int64, reflect.Int32: - arr = append(arr, va.Int()) - case reflect.Float64, reflect.Float32: - arr = append(arr, va.Float()) - } - } - } - event = logger.Log().Fields(arr) - } - case reflect.String: - s := content.(string) - var m map[string]any - if err = json.Unmarshal([]byte(s), &m); err == nil { - event = logger.Log().Fields(m) - } - default: - err = errors.New("") - } - - if err != nil { - err = fmt.Errorf("tipo inválido do conteúdo do log: %v.\nEsperados map, json em formato de string ou []byte", v.Type()) - } - - return event, err -} - -// GetFileNameFromStreamName mantido apenas para compatibilidade com os testes unitários -func (l *LogType) GetFileNameFromStreamName(subject string) string { - return filepath.Join(l.path, subject, subject+ccLogFileSuffix) -} diff --git a/mchlogcoreV1/mchlogV1.go b/mchlogcoreV1/mchlogV1.go new file mode 100644 index 0000000..cac9fbf --- /dev/null +++ b/mchlogcoreV1/mchlogV1.go @@ -0,0 +1,329 @@ +package mchlogcoreV1 + +import ( + "encoding/json" + "errors" + "fmt" + "net" + "os" + "path/filepath" + "reflect" + "sync" + "time" + + "github.com/rs/zerolog" + "github.com/rs/zerolog/log" +) + +/* Exemplo de uso do mchlogcore. +// Primeiro o pacote precisa ser inicializado com o +// caminho a gravar os arquivos de log: +mchlogcore.InitializeMchLog("./etc/log") +// O pacote mchlogcore contém o objeto público MchLog e +// o único método acessível: LogSubject() +// Origem do log é uma string ou []byte em formato de json (tem que ser um json válido) +m := fmt.Sprintf("{\"somekey\":{\"id\":\"123\"},\"tick\":%d,\"time\":\"2020-11-09T12:05:19+01:00\",\"testmsg\":\"some msg\"}", 10) +mchlogcore.MchLog.LogSubject("teste", m, nil) +ou +mchlogcore.MchLog.LogSubject("teste", []byte(m), nil) +// output: {"somekey":{"id":"123"},"testmsg":"some msg","tick":10,"time":"2020-11-09T12:05:19+01:00","data_hora":"2022-10-12 19:11:33 UTC"} +// no arquivo ./etc/log/teste/teste-169.254.215.202-2022101222.log +// Origem do log é um mapa onde a chave é uma string e o tipo do valor "dinâmico" (interface{}). +// A chave TEM que ser string, o valor pode ser string, int ou float. +// O tipo do valor pode ser estático também (string, por exemplo). +h := make(map[string]any) +h["mapa1"] = "aaa" +h["mapa2"] = 22 +mchlogcore.MchLog.LogSubject("teste", h, nil) +// output: {"mapa1":"aaa","mapa2":22,"data_hora":"2022-10-12 19:11:33 UTC"} +// no arquivo ./etc/log/teste/teste-169.254.215.202-2022101222.log +// Origem do log é um array/slice de tipo dinâmico. +// O tipo do array/slice pode ser estático, nesse caso só pode ser string. +// Começando do indice zero, os elementos pares são as "chaves" e +// os ímpares os valores, que podem ser string, int ou float. +var a []any +a = append(a, "key_array1", 22, "key_array2") +a = append(a, "55") +mchlogcore.MchLog.LogSubject("teste", a, nil) +// output: {"key_array1":22,"key_array2":"55","data_hora":"2022-10-12 19:11:33 UTC"} +// no arquivo ./etc/log/teste/teste-169.254.215.202-2022101222.log +// Usando o log do exemplo anterior, desta vez enviando um erro. +mchlogcore.MchLog.LogSubject("teste", a, errors.New("isto é um erro forçado")) +// output: {"key_array1":22,"key_array2":"55","error":"isto é um erro forçado","data_hora":"2022-10-12 19:11:33 UTC"} +// no arquivo ./etc/log/err_teste/err_teste-169.254.215.202-2022101222.log +*/ + +const ( + ccLogDataHora string = "timestamp" //chave ref. ao timestamp, gravada automaticamente no json de log + ccDateTimeMask string = "2006010215" //YYYYMMDDHH - timestamp que define a rotação dos arquivos de log + ccLogFileSuffix string = ".log" //sufixo do arquivo de log + ccLogErrPrefixSubject string = "err_" //prefixo do arquivo de log de erro +) + +// LogType utiliza a biblioteca 'zerolog' para persistir log em arquivo. +type LogType struct { + //key = subject; value = obj do tipo fileLogType + mapLogger sync.Map + path string + ip string +} + +type fileLogType struct { + filename string + file *os.File + err error + logger *zerolog.Logger +} + +// closeFile tenta fechar o arquivo de instrumentação, caso esteja aberto +func (flt *fileLogType) closeFile() { + if flt.file != nil { + _ = flt.file.Close() + } +} + +func (flt *fileLogType) mkDir() error { + var err error + dirName := filepath.Dir(flt.filename) + if _, err = os.Stat(dirName); os.IsNotExist(err) { + err = os.MkdirAll(dirName, os.ModePerm) + } + return err +} + +// fileStreamType é usado na comunicação por canal +type fileStreamType struct { + filename string + subject string + chReturn chan fileLogType +} + +var _chLog chan fileStreamType + +// MchLog é o objeto de acesso ao método LogSubject, +// que efetivamente escreve o log. +var MchLog LogType + +// InitializeMchLog inicia os procedimentos para persistir +// logs em arquivo pelo objeto MchLog. +func InitializeMchLog(path string) { + _chLog = make(chan fileStreamType) + zerolog.TimestampFieldName = ccLogDataHora + zerolog.TimeFieldFormat = "2006-01-02 15:04:05" + + zerolog.TimestampFunc = func() time.Time { + loc, _ := time.LoadLocation("UTC") + return time.Now().In(loc) + } + + MchLog.path = filepath.FromSlash(path) + MchLog.ip = getLocalIP() + + go func() { + for filestream := range _chLog { + var fileLog fileLogType + var ok bool + var obj any + + if obj, ok = MchLog.mapLogger.Load(filestream.subject); ok { + fileLog = obj.(fileLogType) + } + + // se o registro não foi encontrado no mapa de controle ou + // o nome do arquivo mudou, ele será gerado e registrado no mapa. + if !ok || (filestream.filename != fileLog.filename) { + fileLog.closeFile() // se houver arquivo aberto ele será fechado. + fileLog = fileLogType{filename: filestream.filename} + + // se não houver diretório, que seja criado. + // se o diretório existir nada faz e não retorna erro. + if fileLog.err = fileLog.mkDir(); fileLog.err == nil { + // criar arquivo de log com o nome passado pelo canal + fileLog.file, fileLog.err = os.OpenFile(filestream.filename, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0666) + if fileLog.err == nil { + logg := log.With().Logger().Output(fileLog.file) + fileLog.logger = &logg + MchLog.mapLogger.Store(filestream.subject, fileLog) + } + } + } + + filestream.chReturn <- fileLog + } + }() +} + +// LogSubject grava no arquivo de log o conteúdo (content) +// enviado no parâmetro, que pode ser um json, map ou array. +// Um subdiretório será criado com o nome do subject e parte +// do nome do arquivo também conterá o parâmetro subject. +// Se o parâmetro errLog estiver preenchido, o subject +// receberá o prefix "err_", consequentemente a pasta e +// o nome do arquivo correspondente. O descritivo do erro +// fará parte do json gravado no arquivo de log +// ascendStackFrame é um parâmetro opcional (default = 1) +// que indica onde procurar a linha de código que deu origem +// à mensagem de log, em caso de errLog!=nil. +func (l *LogType) LogSubject(subject string, content any, errLog error, ascendStackFrame ...int) { + if subject == "" { + return + } + + var err error + var logger *zerolog.Logger + var event *zerolog.Event + var skip = 1 + + // o subject de um log de erro recebe um prefixo correspondente + if errLog != nil { + subject = ccLogErrPrefixSubject + subject + } + + if len(ascendStackFrame) > 0 { + skip = ascendStackFrame[0] + } + + if logger, err = l.checkFile(subject); err == nil { + if event, err = l.getJSONLogger(logger, content); err == nil { + if errLog == nil { + event.Send() + } else { + event.Caller(skip).Err(errLog).Send() + } + } + } + + if err != nil { // mostrar log de erro na saída default do zerolog (console) + log.Log().Msg("LogType.LogSubject: " + subject + " - " + err.Error()) + } +} + +// getJSONLogger retorna o evento de log correspondente +// pronto para ser persistido no arquivo. +func (l *LogType) getJSONLogger(logger *zerolog.Logger, content any) (*zerolog.Event, error) { + var err error + var event *zerolog.Event + var ok bool + + v := reflect.ValueOf(content) + switch v.Kind() { + case reflect.Map: + var m map[string]any + if m, ok = content.(map[string]any); !ok { + m = make(map[string]any) + keys := v.MapKeys() + for _, k := range keys { + va := v.MapIndex(k) + switch va.Kind() { + case reflect.String: + m[k.String()] = va.String() + case reflect.Int, reflect.Int64, reflect.Int32: + m[k.String()] = va.Int() + case reflect.Float64, reflect.Float32: + m[k.String()] = va.Float() + } + } + } + event = logger.Log().Fields(m) + + case reflect.Slice, reflect.Array: + var arrb []byte + if arrb, ok = content.([]byte); ok { + var m map[string]any + if err = json.Unmarshal(arrb, &m); err == nil { + event = logger.Log().Fields(m) + } + } else { + var arr []any + if arr, ok = content.([]any); !ok { + tam := v.Len() + for i := 0; i < tam; i++ { + va := v.Index(i) + switch va.Kind() { + case reflect.String: + arr = append(arr, va.String()) + case reflect.Int, reflect.Int64, reflect.Int32: + arr = append(arr, va.Int()) + case reflect.Float64, reflect.Float32: + arr = append(arr, va.Float()) + } + } + } + event = logger.Log().Fields(arr) + } + case reflect.String: + s := content.(string) + var m map[string]any + if err = json.Unmarshal([]byte(s), &m); err == nil { + event = logger.Log().Fields(m) + } + default: + err = errors.New("") + } + + if err != nil { + err = fmt.Errorf("tipo inválido do conteúdo do log: %v.\nEsperados map, json em formato de string ou []byte", v.Type()) + } + + return event, err +} + +// checkFile trata da rotação dos arquivos de log, dependendo +// da máscara do timestamp definido na constante ccDateTimeMask. +func (l *LogType) checkFile(subject string) (*zerolog.Logger, error) { + var flt fileLogType + + filename := l.GetFileNameFromStreamName(subject) + obj, ok := l.mapLogger.Load(subject) + + if ok { + flt = obj.(fileLogType) + } + + if !ok || (filename != flt.filename) { + //criando canal de retorno dinamicamente e passando + //para a estrutura que será enviada para o canal de + //manipulação do arquivo + chret := make(chan fileLogType) + _chLog <- fileStreamType{filename: filename, subject: subject, chReturn: chret} + flt = <-chret + } + + return flt.logger, flt.err +} + +// getFileNameFromStreamName monta o diretório completo com o nome do arquivo +func (l *LogType) GetFileNameFromStreamName(subject string) string { + loc, _ := time.LoadLocation("UTC") + dataHora := time.Now().In(loc).Format(ccDateTimeMask) + + ip := l.ip + if ip != "" { + ip = "-" + ip + } + + logFile := filepath.Join(l.path, subject, subject+ip+"-"+dataHora+ccLogFileSuffix) + return filepath.FromSlash(logFile) +} + +// GetIP retorna o IP onde o log está rodando +func (l *LogType) GetIP() string { + return l.ip +} + +// getLocalIP retorna o endereço local do IP, desconsiderando o loopback +func getLocalIP() string { + addrs, err := net.InterfaceAddrs() + if err != nil { + return "" + } + for _, address := range addrs { + // check the address type and if it is not a loopback the display it + if ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() { + if ipnet.IP.To4() != nil { + return ipnet.IP.String() + } + } + } + return "" +} diff --git a/mchlogcoreV2/mchlogV2.go b/mchlogcoreV2/mchlogV2.go new file mode 100644 index 0000000..ebd1c38 --- /dev/null +++ b/mchlogcoreV2/mchlogV2.go @@ -0,0 +1,213 @@ +package mchlogcoreV2 + +import ( + "encoding/json" + "errors" + "fmt" + "os" + "path/filepath" + "reflect" + "sync" + "time" + + "github.com/rs/zerolog" +) + +/* Exemplo de uso do mchlogcore. +// Primeiro o pacote precisa ser inicializado com o +// caminho a gravar os arquivos de log: +mchlogcore.InitializeMchLog("./etc/log") +// O pacote mchlogcore contém o objeto público MchLog e +// o único método acessível: LogSubject() +// Origem do log é uma string ou []byte em formato de json (tem que ser um json válido) +m := fmt.Sprintf("{\"somekey\":{\"id\":\"123\"},\"tick\":%d,\"time\":\"2020-11-09T12:05:19+01:00\",\"testmsg\":\"some msg\"}", 10) +mchlogcore.MchLog.LogSubject("teste", m, nil) +ou +mchlogcore.MchLog.LogSubject("teste", []byte(m), nil) +// output: {"somekey":{"id":"123"},"testmsg":"some msg","tick":10,"time":"2020-11-09T12:05:19+01:00","data_hora":"2022-10-12 19:11:33 UTC"} +// no arquivo ./etc/log/teste/teste.log +// Origem do log é um mapa onde a chave é uma string e o tipo do valor "dinâmico" (interface{}). +// A chave TEM que ser string, o valor pode ser string, int ou float. +// O tipo do valor pode ser estático também (string, por exemplo). +h := make(map[string]any) +h["mapa1"] = "aaa" +h["mapa2"] = 22 +mchlogcore.MchLog.LogSubject("teste", h, nil) +// output: {"mapa1":"aaa","mapa2":22,"data_hora":"2022-10-12 19:11:33 UTC"} +// no arquivo ./etc/log/teste/teste.log +// Origem do log é um array/slice de tipo dinâmico. +// O tipo do array/slice pode ser estático, nesse caso só pode ser string. +// Começando do indice zero, os elementos pares são as "chaves" e +// os ímpares os valores, que podem ser string, int ou float. +var a []any +a = append(a, "key_array1", 22, "key_array2") +a = append(a, "55") +mchlogcore.MchLog.LogSubject("teste", a, nil) +// output: {"key_array1":22,"key_array2":"55","data_hora":"2022-10-12 19:11:33 UTC"} +// no arquivo ./etc/log/teste/teste.log +// Usando o log do exemplo anterior, desta vez enviando um erro. +mchlogcore.MchLog.LogSubject("teste", a, errors.New("isto é um erro forçado")) +// output: {"key_array1":22,"key_array2":"55","error":"isto é um erro forçado","data_hora":"2022-10-12 19:11:33 UTC"} +// no arquivo ./etc/log/err_teste/err_teste.log +*/ + +const ( + ccLogDataHora string = "timestamp" //chave ref. ao timestamp, gravada automaticamente no json de log + ccLogFileSuffix string = ".log" //sufixo do arquivo de log + ccLogErrPrefixSubject string = "err_" //prefixo do arquivo de log de erro +) + +// LogType utiliza a biblioteca 'zerolog' para persistir log em arquivo. +type LogType struct { + mapLogger sync.Map // cache de loggers para evitar abrir/fechar os arquivos de log repetidamente + path string +} + +// MchLog é o objeto de acesso ao método LogSubject, +// que efetivamente escreve o log. +var MchLog LogType + +// InitializeMchLog configura o diretório base e as regras de formatação do zerolog. +func InitializeMchLog(path string) { + zerolog.TimestampFieldName = ccLogDataHora + zerolog.TimeFieldFormat = "2006-01-02 15:04:05" + zerolog.TimestampFunc = func() time.Time { + return time.Now().UTC() + } + + MchLog.path = filepath.FromSlash(path) +} + +// LogSubject grava no arquivo de log o conteúdo (content) enviado no parâmetro, que pode ser um json, map ou array. +// Um subdiretório será criado com um arquivo de log dentro, ambos com o nome do subject: /.log +// Se o parâmetro errLog estiver preenchido, o subject receberá o prefix "err_", e consequentemente a pasta e o nome do arquivo correspondente. +// O descritivo do erro fará parte do json gravado no arquivo de log +// ascendStackFrame é um parâmetro opcional (default = 1) que indica onde procurar a linha de código que deu origem à mensagem de log, em caso de errLog!=nil. +func (l *LogType) LogSubject(subject string, content any, errLog error, ascendStackFrame ...int) { + if subject == "" { + return + } + + // Se for erro, adiciona o prefixo "err_" no subject + // Ex.: se subject for "init", cria err_init/err_init.log + if errLog != nil { + subject = ccLogErrPrefixSubject + subject + } + + var logger *zerolog.Logger + + // Tenta recuperar o logger do cache (evita abrir o arquivo novamente) + if obj, ok := l.mapLogger.Load(subject); ok { + logger = obj.(*zerolog.Logger) + } else { + // Se não estiver no cache, abre o arquivo e inicializa o logger + filename := filepath.Join(l.path, subject, subject+ccLogFileSuffix) + + // Garante que a árvore de diretórios existe + _ = os.MkdirAll(filepath.Dir(filename), 0755) + + // Abre o arquivo para escrita ao final (append). Cria se não existir. + f, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0664) + if err != nil { + return + } + + // Importante: Não fechamos o arquivo (f.Close) aqui pois ele será + // gerenciado pelo objeto logger que ficará no cache durante a vida do processo. + newLogger := zerolog.New(f).With().Timestamp().Logger() + logger = &newLogger + l.mapLogger.Store(subject, logger) + } + + // Processa o conteúdo e gera o evento de log + event, err := l.getJSONLogger(logger, content) + if err != nil { + return + } + + skip := 1 + if len(ascendStackFrame) > 0 { + skip = ascendStackFrame[0] + } + + if errLog == nil { + event.Send() + } else { + event.Caller(skip).Err(errLog).Send() + } +} + +// getJSONLogger retorna o evento de log correspondente +// pronto para ser persistido no arquivo. +func (l *LogType) getJSONLogger(logger *zerolog.Logger, content any) (*zerolog.Event, error) { + var err error + var event *zerolog.Event + var ok bool + + v := reflect.ValueOf(content) + switch v.Kind() { + case reflect.Map: + var m map[string]any + if m, ok = content.(map[string]any); !ok { + m = make(map[string]any) + keys := v.MapKeys() + for _, k := range keys { + va := v.MapIndex(k) + switch va.Kind() { + case reflect.String: + m[k.String()] = va.String() + case reflect.Int, reflect.Int64, reflect.Int32: + m[k.String()] = va.Int() + case reflect.Float64, reflect.Float32: + m[k.String()] = va.Float() + } + } + } + event = logger.Log().Fields(m) + + case reflect.Slice, reflect.Array: + var arrb []byte + if arrb, ok = content.([]byte); ok { + var m map[string]any + if err = json.Unmarshal(arrb, &m); err == nil { + event = logger.Log().Fields(m) + } + } else { + var arr []any + if arr, ok = content.([]any); !ok { + tam := v.Len() + for i := 0; i < tam; i++ { + va := v.Index(i) + switch va.Kind() { + case reflect.String: + arr = append(arr, va.String()) + case reflect.Int, reflect.Int64, reflect.Int32: + arr = append(arr, va.Int()) + case reflect.Float64, reflect.Float32: + arr = append(arr, va.Float()) + } + } + } + event = logger.Log().Fields(arr) + } + case reflect.String: + s := content.(string) + var m map[string]any + if err = json.Unmarshal([]byte(s), &m); err == nil { + event = logger.Log().Fields(m) + } + default: + err = errors.New("") + } + + if err != nil { + err = fmt.Errorf("tipo inválido do conteúdo do log: %v.\nEsperados map, json em formato de string ou []byte", v.Type()) + } + + return event, err +} + +// GetFileNameFromStreamName mantido apenas para compatibilidade com os testes unitários +func (l *LogType) GetFileNameFromStreamName(subject string) string { + return filepath.Join(l.path, subject, subject+ccLogFileSuffix) +} From dca74b28cd5a5c5f32bff3f06c89ae60939aa51d Mon Sep 17 00:00:00 2001 From: gabrielferreira-gaudium Date: Fri, 6 Feb 2026 10:56:17 -0300 Subject: [PATCH 3/8] add configuration to select V1 or V2 --- mchlogcore/mchlog.go | 69 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/mchlogcore/mchlog.go b/mchlogcore/mchlog.go index d8ece06..a63cede 100644 --- a/mchlogcore/mchlog.go +++ b/mchlogcore/mchlog.go @@ -1 +1,70 @@ package mchlogcore + +import ( + "github.com/gaudiumsoftware/mchlogtoolkitgo/mchlogcoreV1" + "github.com/gaudiumsoftware/mchlogtoolkitgo/mchlogcoreV2" +) + +// LogVersion is a type to define which version of the logger to use +type LogVersion int + +const ( + // V1 refers to the first version of the logger, which includes IP in filename and uses a channel-based approach + V1 LogVersion = iota + // V2 refers to the second version of the logger, which has a simpler file structure without IP or timestamps in names + V2 +) + +var currentVersion = V1 + +// SetVersion chooses which version to use (V1 or V2). +// This should ideally be called before InitializeMchLog. +func SetVersion(v LogVersion) { + currentVersion = v +} + +// LogType is the facade structure that delegates calls to either V1 or V2 implementation +type LogType struct{} + +// LogSubject records the content to the log file using the selected version +func (l *LogType) LogSubject(subject string, content any, errLog error, ascendStackFrame ...int) { + if currentVersion == V1 { + mchlogcoreV1.MchLog.LogSubject(subject, content, errLog, ascendStackFrame...) + } else { + mchlogcoreV2.MchLog.LogSubject(subject, content, errLog, ascendStackFrame...) + } +} + +// GetFileNameFromStreamName returns the log file path for the given subject +func (l *LogType) GetFileNameFromStreamName(subject string) string { + if currentVersion == V1 { + return mchlogcoreV1.MchLog.GetFileNameFromStreamName(subject) + } else { + return mchlogcoreV2.MchLog.GetFileNameFromStreamName(subject) + } +} + +// GetIP returns the IP where the log is running (only available in V1, returns empty for V2) +func (l *LogType) GetIP() string { + if currentVersion == V1 { + return mchlogcoreV1.MchLog.GetIP() + } + return "" +} + +// MchLog is the global instance of the log facade +var MchLog LogType + +// InitializeMchLog initializes the selected version's backend with the given path +func InitializeMchLog(path string) { + versionName := "V1" + if currentVersion == V1 { + mchlogcoreV1.InitializeMchLog(path) + } else { + versionName = "V2" + mchlogcoreV2.InitializeMchLog(path) + } + + // The first log in info should be the version of the logger (v1 or v2) + MchLog.LogSubject("info", map[string]string{"message": "MchLogToolkit initialized", "version": versionName}, nil) +} From af78d4a4c6dc58653a3f2427c2094a68cca2bdee Mon Sep 17 00:00:00 2001 From: gabrielferreira-gaudium Date: Mon, 9 Feb 2026 09:28:30 -0300 Subject: [PATCH 4/8] change log file creation to 644 --- mchlogcoreV2/mchlogV2.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mchlogcoreV2/mchlogV2.go b/mchlogcoreV2/mchlogV2.go index ebd1c38..3cfebd7 100644 --- a/mchlogcoreV2/mchlogV2.go +++ b/mchlogcoreV2/mchlogV2.go @@ -107,7 +107,7 @@ func (l *LogType) LogSubject(subject string, content any, errLog error, ascendSt _ = os.MkdirAll(filepath.Dir(filename), 0755) // Abre o arquivo para escrita ao final (append). Cria se não existir. - f, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0664) + f, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644) if err != nil { return } From 6912264abc5d4a6a80d89d53ab9edb86af65b9cb Mon Sep 17 00:00:00 2001 From: gabrielferreira-gaudium Date: Mon, 9 Feb 2026 09:39:13 -0300 Subject: [PATCH 5/8] logs via zerolog when there's an error in LogSubject --- mchlogcoreV2/mchlogV2.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/mchlogcoreV2/mchlogV2.go b/mchlogcoreV2/mchlogV2.go index 3cfebd7..7cdef6b 100644 --- a/mchlogcoreV2/mchlogV2.go +++ b/mchlogcoreV2/mchlogV2.go @@ -11,6 +11,7 @@ import ( "time" "github.com/rs/zerolog" + "github.com/rs/zerolog/log" ) /* Exemplo de uso do mchlogcore. @@ -109,6 +110,7 @@ func (l *LogType) LogSubject(subject string, content any, errLog error, ascendSt // Abre o arquivo para escrita ao final (append). Cria se não existir. f, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644) if err != nil { + log.Error().Err(err).Str("filename", filename).Interface("conteudo", subject).Msg("mchlogcoreV2: falha ao criar e abrir arquivo de log") return } @@ -122,6 +124,7 @@ func (l *LogType) LogSubject(subject string, content any, errLog error, ascendSt // Processa o conteúdo e gera o evento de log event, err := l.getJSONLogger(logger, content) if err != nil { + log.Error().Err(err).Interface("conteudo", subject).Msg("mchlogcoreV2: falha ao processar conteúdo do log") return } From 0741d9073f5a0c4e24bf9500165cfa02d5d52ace Mon Sep 17 00:00:00 2001 From: gabrielferreira-gaudium Date: Mon, 9 Feb 2026 10:36:44 -0300 Subject: [PATCH 6/8] changes requested in PR --- mchlogcore/mchlog.go | 20 ++++++++++---------- mchlogcoreV1/mchlogV1.go | 2 +- mchlogcoreV2/mchlogV2.go | 6 +++--- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/mchlogcore/mchlog.go b/mchlogcore/mchlog.go index a63cede..16e32e8 100644 --- a/mchlogcore/mchlog.go +++ b/mchlogcore/mchlog.go @@ -1,8 +1,8 @@ package mchlogcore import ( - "github.com/gaudiumsoftware/mchlogtoolkitgo/mchlogcoreV1" - "github.com/gaudiumsoftware/mchlogtoolkitgo/mchlogcoreV2" + "github.com/gaudiumsoftware/mchlogtoolkitgo/mchlogcorev1" + "github.com/gaudiumsoftware/mchlogtoolkitgo/mchlogcorev2" ) // LogVersion is a type to define which version of the logger to use @@ -29,25 +29,25 @@ type LogType struct{} // LogSubject records the content to the log file using the selected version func (l *LogType) LogSubject(subject string, content any, errLog error, ascendStackFrame ...int) { if currentVersion == V1 { - mchlogcoreV1.MchLog.LogSubject(subject, content, errLog, ascendStackFrame...) + mchlogcorev1.MchLog.LogSubject(subject, content, errLog, ascendStackFrame...) } else { - mchlogcoreV2.MchLog.LogSubject(subject, content, errLog, ascendStackFrame...) + mchlogcorev2.MchLog.LogSubject(subject, content, errLog, ascendStackFrame...) } } // GetFileNameFromStreamName returns the log file path for the given subject func (l *LogType) GetFileNameFromStreamName(subject string) string { if currentVersion == V1 { - return mchlogcoreV1.MchLog.GetFileNameFromStreamName(subject) - } else { - return mchlogcoreV2.MchLog.GetFileNameFromStreamName(subject) + return mchlogcorev1.MchLog.GetFileNameFromStreamName(subject) } + + return mchlogcorev2.MchLog.GetFileNameFromStreamName(subject) } // GetIP returns the IP where the log is running (only available in V1, returns empty for V2) func (l *LogType) GetIP() string { if currentVersion == V1 { - return mchlogcoreV1.MchLog.GetIP() + return mchlogcorev1.MchLog.GetIP() } return "" } @@ -59,10 +59,10 @@ var MchLog LogType func InitializeMchLog(path string) { versionName := "V1" if currentVersion == V1 { - mchlogcoreV1.InitializeMchLog(path) + mchlogcorev1.InitializeMchLog(path) } else { versionName = "V2" - mchlogcoreV2.InitializeMchLog(path) + mchlogcorev2.InitializeMchLog(path) } // The first log in info should be the version of the logger (v1 or v2) diff --git a/mchlogcoreV1/mchlogV1.go b/mchlogcoreV1/mchlogV1.go index cac9fbf..508e945 100644 --- a/mchlogcoreV1/mchlogV1.go +++ b/mchlogcoreV1/mchlogV1.go @@ -1,4 +1,4 @@ -package mchlogcoreV1 +package mchlogcorev1 import ( "encoding/json" diff --git a/mchlogcoreV2/mchlogV2.go b/mchlogcoreV2/mchlogV2.go index 7cdef6b..e15fd3d 100644 --- a/mchlogcoreV2/mchlogV2.go +++ b/mchlogcoreV2/mchlogV2.go @@ -1,4 +1,4 @@ -package mchlogcoreV2 +package mchlogcorev2 import ( "encoding/json" @@ -110,7 +110,7 @@ func (l *LogType) LogSubject(subject string, content any, errLog error, ascendSt // Abre o arquivo para escrita ao final (append). Cria se não existir. f, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644) if err != nil { - log.Error().Err(err).Str("filename", filename).Interface("conteudo", subject).Msg("mchlogcoreV2: falha ao criar e abrir arquivo de log") + log.Error().Err(err).Str("filename", filename).Interface("conteudo", subject).Msg("mchlogcorev2: falha ao criar e abrir arquivo de log") return } @@ -124,7 +124,7 @@ func (l *LogType) LogSubject(subject string, content any, errLog error, ascendSt // Processa o conteúdo e gera o evento de log event, err := l.getJSONLogger(logger, content) if err != nil { - log.Error().Err(err).Interface("conteudo", subject).Msg("mchlogcoreV2: falha ao processar conteúdo do log") + log.Error().Err(err).Interface("conteudo", subject).Msg("mchlogcorev2: falha ao processar conteúdo do log") return } From c96ea8fc14fb7364beb7a58b3a0ffbbb836c822c Mon Sep 17 00:00:00 2001 From: gabrielferreira-gaudium Date: Mon, 9 Feb 2026 10:50:49 -0300 Subject: [PATCH 7/8] move GetJSONLogger to mchlogcommon --- mchlogcommon/mchlogcommon.go | 80 ++++++++++++++++++++++++++++++++++++ mchlogcoreV1/mchlogV1.go | 77 +--------------------------------- mchlogcoreV2/mchlogV2.go | 77 +--------------------------------- 3 files changed, 84 insertions(+), 150 deletions(-) create mode 100644 mchlogcommon/mchlogcommon.go diff --git a/mchlogcommon/mchlogcommon.go b/mchlogcommon/mchlogcommon.go new file mode 100644 index 0000000..697271f --- /dev/null +++ b/mchlogcommon/mchlogcommon.go @@ -0,0 +1,80 @@ +package mchlogcommon + +import ( + "encoding/json" + "errors" + "fmt" + "reflect" + + "github.com/rs/zerolog" +) + +// GetJSONLogger retorna o evento de log correspondente +// pronto para ser persistido no arquivo. +func GetJSONLogger(logger *zerolog.Logger, content any) (*zerolog.Event, error) { + var err error + var event *zerolog.Event + var ok bool + + v := reflect.ValueOf(content) + switch v.Kind() { + case reflect.Map: + var m map[string]any + if m, ok = content.(map[string]any); !ok { + m = make(map[string]any) + keys := v.MapKeys() + for _, k := range keys { + va := v.MapIndex(k) + switch va.Kind() { + case reflect.String: + m[k.String()] = va.String() + case reflect.Int, reflect.Int64, reflect.Int32: + m[k.String()] = va.Int() + case reflect.Float64, reflect.Float32: + m[k.String()] = va.Float() + } + } + } + event = logger.Log().Fields(m) + + case reflect.Slice, reflect.Array: + var arrb []byte + if arrb, ok = content.([]byte); ok { + var m map[string]any + if err = json.Unmarshal(arrb, &m); err == nil { + event = logger.Log().Fields(m) + } + } else { + var arr []any + if arr, ok = content.([]any); !ok { + tam := v.Len() + for i := 0; i < tam; i++ { + va := v.Index(i) + switch va.Kind() { + case reflect.String: + arr = append(arr, va.String()) + case reflect.Int, reflect.Int64, reflect.Int32: + arr = append(arr, va.Int()) + case reflect.Float64, reflect.Float32: + arr = append(arr, va.Float()) + } + } + } + event = logger.Log().Fields(arr) + } + case reflect.String: + s := content.(string) + var m map[string]any + if err = json.Unmarshal([]byte(s), &m); err == nil { + event = logger.Log().Fields(m) + } + default: + err = errors.New("") + } + + if err != nil { + err = fmt.Errorf("tipo inválido do conteúdo do log: %v.\nEsperados map, json em formato de string ou []byte", v.Type()) + } + + return event, err +} diff --git a/mchlogcoreV1/mchlogV1.go b/mchlogcoreV1/mchlogV1.go index 508e945..db3e4fc 100644 --- a/mchlogcoreV1/mchlogV1.go +++ b/mchlogcoreV1/mchlogV1.go @@ -1,16 +1,13 @@ package mchlogcorev1 import ( - "encoding/json" - "errors" - "fmt" "net" "os" "path/filepath" - "reflect" "sync" "time" + "github.com/gaudiumsoftware/mchlogtoolkitgo/mchlogcommon" "github.com/rs/zerolog" "github.com/rs/zerolog/log" ) @@ -184,7 +181,7 @@ func (l *LogType) LogSubject(subject string, content any, errLog error, ascendSt } if logger, err = l.checkFile(subject); err == nil { - if event, err = l.getJSONLogger(logger, content); err == nil { + if event, err = mchlogcommon.GetJSONLogger(logger, content); err == nil { if errLog == nil { event.Send() } else { @@ -198,76 +195,6 @@ func (l *LogType) LogSubject(subject string, content any, errLog error, ascendSt } } -// getJSONLogger retorna o evento de log correspondente -// pronto para ser persistido no arquivo. -func (l *LogType) getJSONLogger(logger *zerolog.Logger, content any) (*zerolog.Event, error) { - var err error - var event *zerolog.Event - var ok bool - - v := reflect.ValueOf(content) - switch v.Kind() { - case reflect.Map: - var m map[string]any - if m, ok = content.(map[string]any); !ok { - m = make(map[string]any) - keys := v.MapKeys() - for _, k := range keys { - va := v.MapIndex(k) - switch va.Kind() { - case reflect.String: - m[k.String()] = va.String() - case reflect.Int, reflect.Int64, reflect.Int32: - m[k.String()] = va.Int() - case reflect.Float64, reflect.Float32: - m[k.String()] = va.Float() - } - } - } - event = logger.Log().Fields(m) - - case reflect.Slice, reflect.Array: - var arrb []byte - if arrb, ok = content.([]byte); ok { - var m map[string]any - if err = json.Unmarshal(arrb, &m); err == nil { - event = logger.Log().Fields(m) - } - } else { - var arr []any - if arr, ok = content.([]any); !ok { - tam := v.Len() - for i := 0; i < tam; i++ { - va := v.Index(i) - switch va.Kind() { - case reflect.String: - arr = append(arr, va.String()) - case reflect.Int, reflect.Int64, reflect.Int32: - arr = append(arr, va.Int()) - case reflect.Float64, reflect.Float32: - arr = append(arr, va.Float()) - } - } - } - event = logger.Log().Fields(arr) - } - case reflect.String: - s := content.(string) - var m map[string]any - if err = json.Unmarshal([]byte(s), &m); err == nil { - event = logger.Log().Fields(m) - } - default: - err = errors.New("") - } - - if err != nil { - err = fmt.Errorf("tipo inválido do conteúdo do log: %v.\nEsperados map, json em formato de string ou []byte", v.Type()) - } - - return event, err -} - // checkFile trata da rotação dos arquivos de log, dependendo // da máscara do timestamp definido na constante ccDateTimeMask. func (l *LogType) checkFile(subject string) (*zerolog.Logger, error) { diff --git a/mchlogcoreV2/mchlogV2.go b/mchlogcoreV2/mchlogV2.go index e15fd3d..0b3e5ee 100644 --- a/mchlogcoreV2/mchlogV2.go +++ b/mchlogcoreV2/mchlogV2.go @@ -1,15 +1,12 @@ package mchlogcorev2 import ( - "encoding/json" - "errors" - "fmt" "os" "path/filepath" - "reflect" "sync" "time" + "github.com/gaudiumsoftware/mchlogtoolkitgo/mchlogcommon" "github.com/rs/zerolog" "github.com/rs/zerolog/log" ) @@ -122,7 +119,7 @@ func (l *LogType) LogSubject(subject string, content any, errLog error, ascendSt } // Processa o conteúdo e gera o evento de log - event, err := l.getJSONLogger(logger, content) + event, err := mchlogcommon.GetJSONLogger(logger, content) if err != nil { log.Error().Err(err).Interface("conteudo", subject).Msg("mchlogcorev2: falha ao processar conteúdo do log") return @@ -140,76 +137,6 @@ func (l *LogType) LogSubject(subject string, content any, errLog error, ascendSt } } -// getJSONLogger retorna o evento de log correspondente -// pronto para ser persistido no arquivo. -func (l *LogType) getJSONLogger(logger *zerolog.Logger, content any) (*zerolog.Event, error) { - var err error - var event *zerolog.Event - var ok bool - - v := reflect.ValueOf(content) - switch v.Kind() { - case reflect.Map: - var m map[string]any - if m, ok = content.(map[string]any); !ok { - m = make(map[string]any) - keys := v.MapKeys() - for _, k := range keys { - va := v.MapIndex(k) - switch va.Kind() { - case reflect.String: - m[k.String()] = va.String() - case reflect.Int, reflect.Int64, reflect.Int32: - m[k.String()] = va.Int() - case reflect.Float64, reflect.Float32: - m[k.String()] = va.Float() - } - } - } - event = logger.Log().Fields(m) - - case reflect.Slice, reflect.Array: - var arrb []byte - if arrb, ok = content.([]byte); ok { - var m map[string]any - if err = json.Unmarshal(arrb, &m); err == nil { - event = logger.Log().Fields(m) - } - } else { - var arr []any - if arr, ok = content.([]any); !ok { - tam := v.Len() - for i := 0; i < tam; i++ { - va := v.Index(i) - switch va.Kind() { - case reflect.String: - arr = append(arr, va.String()) - case reflect.Int, reflect.Int64, reflect.Int32: - arr = append(arr, va.Int()) - case reflect.Float64, reflect.Float32: - arr = append(arr, va.Float()) - } - } - } - event = logger.Log().Fields(arr) - } - case reflect.String: - s := content.(string) - var m map[string]any - if err = json.Unmarshal([]byte(s), &m); err == nil { - event = logger.Log().Fields(m) - } - default: - err = errors.New("") - } - - if err != nil { - err = fmt.Errorf("tipo inválido do conteúdo do log: %v.\nEsperados map, json em formato de string ou []byte", v.Type()) - } - - return event, err -} - // GetFileNameFromStreamName mantido apenas para compatibilidade com os testes unitários func (l *LogType) GetFileNameFromStreamName(subject string) string { return filepath.Join(l.path, subject, subject+ccLogFileSuffix) From 86c212001f7deaed7584e8764887c430d989afc1 Mon Sep 17 00:00:00 2001 From: gabrielferreira-gaudium Date: Mon, 9 Feb 2026 10:57:03 -0300 Subject: [PATCH 8/8] rename files to lowercase v1 and v2 --- mchlogcoreV1/mchlogV1.go => mchlogcorev1/mchlogv1.go | 0 mchlogcoreV2/mchlogV2.go => mchlogcorev2/mchlogv2.go | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename mchlogcoreV1/mchlogV1.go => mchlogcorev1/mchlogv1.go (100%) rename mchlogcoreV2/mchlogV2.go => mchlogcorev2/mchlogv2.go (100%) diff --git a/mchlogcoreV1/mchlogV1.go b/mchlogcorev1/mchlogv1.go similarity index 100% rename from mchlogcoreV1/mchlogV1.go rename to mchlogcorev1/mchlogv1.go diff --git a/mchlogcoreV2/mchlogV2.go b/mchlogcorev2/mchlogv2.go similarity index 100% rename from mchlogcoreV2/mchlogV2.go rename to mchlogcorev2/mchlogv2.go