Files
golib/logger/log.go
nabbar 3837f0b2bb Improvements, test & documentatons (2025-12 #1)
[file/bandwidth]
- ADD documentation: add enhanced README and TESTING guidelines
- ADD tests: complete test suites with benchmarks, concurrency, and edge cases

[file/perm]
- ADD documentation: add enhanced README and TESTING guidelines
- ADD tests: complete test suites with benchmarks, concurrency, and edge cases
- ADD function to parse form "rwx-wxr-x" or "-rwx-w-r-x"
- ADD function to ParseFileMode to convert os.FileMode to file.Perm

[file/progress]
- ADD documentation: add enhanced README and TESTING guidelines
- ADD tests: complete test suites with benchmarks, concurrency, and edge cases

[ioutils/...]
- UPDATE documentation: update enhanced README and TESTING guidelines
- UPDATE tests: complete test suites with benchmarks, concurrency, and edge cases

[logger/...]
- UPDATE documentation: update enhanced README and TESTING guidelines
- ADD documentation: add enhanced README and TESTING guidelines for sub
  packages
- UPDATE tests: complete test suites with benchmarks, concurrency, and edge cases
- UPDATE config: remove FileBufferSize from OptionFile (rework hookfile)
- UPDATE fields: expose Store function in interface
- REWORK hookfile: rework package, use aggregator to allow multi write and
  single file
- FIX hookstderr: fix bug with NonColorable
- FIX hookstdout: fix bug with NonColorable
- FIX hookwriter: fix bug with NonColorable

[network/protocol]
- ADD function IsTCP, IsUDP, IsUnixLike to check type of protocol

[runner]
- FIX typo

[socket]
- UPDATE documentation: update enhanced README and TESTING guidelines
- ADD documentation: add enhanced README and TESTING guidelines for sub
  packages
- UPDATE tests: complete test suites with benchmarks, concurrency, and edge cases
- REWORK server: use context compatible io.reader, io.writer, io.closer
  instead of reader / writer
- REWORK server: simplify, optimize server
- REMOVE reader, writer type
- ADD context: add new interface in root socket interface to expose
  context interface that extend context, io reader/writer/closer,
dediacted function to server (IsConnected, ...)
2025-12-02 02:56:20 +01:00

302 lines
10 KiB
Go

/*
* MIT License
*
* Copyright (c) 2025 Nicolas JUHEL
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*
*/
package logger
import (
"fmt"
"time"
logent "github.com/nabbar/golib/logger/entry"
logfld "github.com/nabbar/golib/logger/fields"
loglvl "github.com/nabbar/golib/logger/level"
)
// Debug logs a message at Debug level with optional data and formatted arguments.
// Debug messages are for detailed diagnostic information useful during development.
//
// Parameters:
// - message: Format string for the log message (supports fmt.Sprintf formatting)
// - data: Optional structured data (map, struct, etc.) to include in the log
// - args: Optional arguments for message formatting (used with fmt.Sprintf)
//
// Example:
//
// logger.Debug("Processing request for user %s", userData, "john.doe")
func (o *logger) Debug(message string, data interface{}, args ...interface{}) {
if o == nil {
return
}
o.newEntry(loglvl.DebugLevel, fmt.Sprintf(message, args...), nil, nil, data).Log()
}
// Info logs a message at Info level with optional data and formatted arguments.
// Info messages are for general informational messages about normal operations.
//
// Parameters:
// - message: Format string for the log message (supports fmt.Sprintf formatting)
// - data: Optional structured data (map, struct, etc.) to include in the log
// - args: Optional arguments for message formatting (used with fmt.Sprintf)
//
// Example:
//
// logger.Info("Application started on port %d", nil, 8080)
func (o *logger) Info(message string, data interface{}, args ...interface{}) {
if o == nil {
return
}
o.newEntry(loglvl.InfoLevel, fmt.Sprintf(message, args...), nil, nil, data).Log()
}
// Warning logs a message at Warning level with optional data and formatted arguments.
// Warning messages indicate potentially harmful situations that should be reviewed.
//
// Parameters:
// - message: Format string for the log message (supports fmt.Sprintf formatting)
// - data: Optional structured data (map, struct, etc.) to include in the log
// - args: Optional arguments for message formatting (used with fmt.Sprintf)
//
// Example:
//
// logger.Warning("Slow query detected: %dms", nil, queryTime)
func (o *logger) Warning(message string, data interface{}, args ...interface{}) {
if o == nil {
return
}
o.newEntry(loglvl.WarnLevel, fmt.Sprintf(message, args...), nil, nil, data).Log()
}
// Error logs a message at Error level with optional data and formatted arguments.
// Error messages indicate failures that prevent normal operation but don't crash the application.
//
// Parameters:
// - message: Format string for the log message (supports fmt.Sprintf formatting)
// - data: Optional structured data (map, struct, etc.) to include in the log
// - args: Optional arguments for message formatting (used with fmt.Sprintf)
//
// Example:
//
// logger.Error("Failed to process request", err, requestID)
func (o *logger) Error(message string, data interface{}, args ...interface{}) {
if o == nil {
return
}
o.newEntry(loglvl.ErrorLevel, fmt.Sprintf(message, args...), nil, nil, data).Log()
}
// Fatal logs a message at Fatal level and terminates the application with os.Exit(1).
// Use Fatal only for critical errors that prevent the application from continuing.
//
// WARNING: This method calls os.Exit(1) after logging. Deferred functions will NOT run.
//
// Parameters:
// - message: Format string for the log message (supports fmt.Sprintf formatting)
// - data: Optional structured data (map, struct, etc.) to include in the log
// - args: Optional arguments for message formatting (used with fmt.Sprintf)
//
// Example:
//
// logger.Fatal("Cannot connect to database", nil)
func (o *logger) Fatal(message string, data interface{}, args ...interface{}) {
if o == nil {
return
}
o.newEntry(loglvl.FatalLevel, fmt.Sprintf(message, args...), nil, nil, data).Log()
}
// Panic logs a message at Panic level and calls panic() with the message.
// Use Panic for errors that should trigger panic recovery mechanisms.
//
// WARNING: This method triggers a panic after logging. Use recover() to catch it.
//
// Parameters:
// - message: Format string for the log message (supports fmt.Sprintf formatting)
// - data: Optional structured data (map, struct, etc.) to include in the log
// - args: Optional arguments for message formatting (used with fmt.Sprintf)
//
// Example:
//
// logger.Panic("Invalid state detected", stateData)
func (o *logger) Panic(message string, data interface{}, args ...interface{}) {
if o == nil {
return
}
o.newEntry(loglvl.PanicLevel, fmt.Sprintf(message, args...), nil, nil, data).Log()
}
// LogDetails logs a message with complete control over all entry parameters.
// This is the most flexible logging method, allowing you to specify level, message,
// data, errors, and custom fields all at once.
//
// Parameters:
// - lvl: Log level for this entry
// - message: Format string for the log message (supports fmt.Sprintf formatting)
// - data: Optional structured data to include in the log
// - err: Optional slice of errors to include in the log
// - fields: Optional additional fields to merge with default fields
// - args: Optional arguments for message formatting (used with fmt.Sprintf)
//
// Example:
//
// errs := []error{err1, err2}
// fields := logfld.New(ctx).Add("request_id", "123")
// logger.LogDetails(loglvl.ErrorLevel, "Request failed", nil, errs, fields)
func (o *logger) LogDetails(lvl loglvl.Level, message string, data interface{}, err []error, fields logfld.Fields, args ...interface{}) {
if o == nil {
return
}
o.newEntry(lvl, fmt.Sprintf(message, args...), err, fields, data).Log()
}
// CheckError logs an error if any error is provided, otherwise optionally logs success.
// This is a convenience method for error checking patterns.
//
// Parameters:
// - lvlKO: Log level to use if errors are present
// - lvlOK: Log level to use if no errors (use NilLevel to skip logging success)
// - message: Message to log
// - err: Optional errors to check
//
// Returns:
// - true if any error was provided (and logged), false otherwise
//
// Example:
//
// if logger.CheckError(loglvl.ErrorLevel, loglvl.InfoLevel, "Operation completed", err) {
// return // Error was logged
// }
// // Success was logged at InfoLevel
func (o *logger) CheckError(lvlKO, lvlOK loglvl.Level, message string, err ...error) bool {
if o == nil {
return false
}
ent := o.newEntry(lvlKO, message, err, nil, nil)
return ent.Check(lvlOK)
}
// Entry creates a log entry that can be customized before logging.
// This method returns an Entry interface allowing you to add fields, set context,
// and control when the entry is actually logged.
//
// Parameters:
// - lvl: Log level for this entry
// - message: Format string for the log message (supports fmt.Sprintf formatting)
// - args: Optional arguments for message formatting (used with fmt.Sprintf)
//
// Returns:
// - logent.Entry: An entry that can be customized with fields before calling Log()
//
// Example:
//
// entry := logger.Entry(loglvl.InfoLevel, "User action: %s", action)
// entry.FieldAdd("user_id", userID)
// entry.FieldAdd("timestamp", time.Now())
// entry.Log()
func (o *logger) Entry(lvl loglvl.Level, message string, args ...interface{}) logent.Entry {
return o.newEntry(lvl, fmt.Sprintf(message, args...), nil, nil, nil)
}
// Access creates an HTTP access log entry in standard format.
// This method generates a log entry following the Common Log Format (CLF) extended with latency.
// The entry is created at Info level with message-only mode (no stack traces or extra fields).
//
// Parameters:
// - remoteAddr: Client IP address
// - remoteUser: Authenticated user name (use "" for anonymous)
// - localtime: Request timestamp
// - latency: Request processing duration
// - method: HTTP method (GET, POST, etc.)
// - request: Request path
// - proto: HTTP protocol version (HTTP/1.1, HTTP/2, etc.)
// - status: HTTP status code
// - size: Response size in bytes
//
// Returns:
// - logent.Entry: A clean entry formatted for access logging
//
// Example:
//
// logger.Access(
// "192.168.1.1", "john.doe",
// time.Now(), 150*time.Millisecond,
// "GET", "/api/users", "HTTP/1.1",
// 200, 1024,
// ).Log()
func (o *logger) Access(remoteAddr, remoteUser string, localtime time.Time, latency time.Duration, method, request, proto string, status int, size int64) logent.Entry {
var msg = fmt.Sprintf("%s - %s [%s] [%s] \"%s %s %s\" %d %d", remoteAddr, remoteUser, localtime.Format(time.RFC1123Z), latency.String(), method, request, proto, status, size)
return o.newEntryClean(msg)
}
func (o *logger) newEntry(lvl loglvl.Level, message string, err []error, fields logfld.Fields, data interface{}) logent.Entry {
if o == nil {
return logent.New(loglvl.NilLevel)
}
var (
fct = o.getLogrus
ent = logent.New(lvl)
frm = o.getCaller()
stk = o.getStack()
fld = o.GetFields()
)
if fld != nil {
ent.FieldSet(fld.Clone())
}
// prevent overflow
var uif uint64
if frm.Line <= 0 {
uif = 0
} else {
uif = uint64(frm.Line)
}
ent.ErrorSet(err)
ent.DataSet(data)
ent.SetLogger(fct)
ent.SetEntryContext(time.Now(), stk, frm.Function, frm.File, uif, message)
ent.FieldMerge(fields)
return ent
}
func (o *logger) newEntryClean(message string) logent.Entry {
if o == nil {
return logent.New(loglvl.NilLevel)
}
return o.newEntry(loglvl.InfoLevel, message, nil, nil, nil).SetMessageOnly(true)
}