Documentation Index Fetch the complete documentation index at: https://mintlify.com/raystack/salt/llms.txt
Use this file to discover all available pages before exploring further.
The log package provides a simple, consistent logging interface with support for multiple backend implementations including Logrus and a no-op logger for testing.
Features
Simple Interface : Consistent API across different implementations
Structured Logging : Key-value pairs for better log parsing
Multiple Backends : Logrus, no-op, and extensible for custom loggers
Log Levels : Debug, Info, Warn, Error, and Fatal
Configurable : Control log level, output format, and destination
Installation
go get github.com/raystack/salt/log
Logger Interface
type Logger interface {
Debug ( msg string , args ... interface {})
Info ( msg string , args ... interface {})
Warn ( msg string , args ... interface {})
Error ( msg string , args ... interface {})
Fatal ( msg string , args ... interface {})
Level () string
Writer () io . Writer
}
All log methods take a message string followed by alternating key-value pairs:
logger . Info ( "user logged in" , "user_id" , 123 , "ip_address" , "192.168.1.1" )
Logrus Implementation
Creating a Logrus Logger
func NewLogrus ( opts ... Option ) * Logrus
Example:
package main
import (
" github.com/raystack/salt/log "
)
func main () {
logger := log . NewLogrus (
log . LogrusWithLevel ( "info" ),
)
logger . Info ( "application started" )
}
Logrus Options
LogrusWithLevel
func LogrusWithLevel ( level string ) Option
Sets the log level. Valid values: debug, info, warn, error, fatal, panic.
Example:
logger := log . NewLogrus (
log . LogrusWithLevel ( "debug" ),
)
logger . Debug ( "detailed debug information" , "key" , "value" )
LogrusWithWriter
func LogrusWithWriter ( writer io . Writer ) Option
Sets the output destination for logs.
Example:
import " os "
logger := log . NewLogrus (
log . LogrusWithWriter ( os . Stderr ),
)
Write to file:
file , err := os . OpenFile ( "app.log" , os . O_CREATE | os . O_WRONLY | os . O_APPEND , 0666 )
if err != nil {
panic ( err )
}
defer file . Close ()
logger := log . NewLogrus (
log . LogrusWithWriter ( file ),
)
func LogrusWithFormatter ( f logrus . Formatter ) Option
Customizes the log output format.
JSON Format:
import " github.com/sirupsen/logrus "
logger := log . NewLogrus (
log . LogrusWithFormatter ( & logrus . JSONFormatter {}),
)
Custom Formatter:
type PlainFormatter struct {}
func ( p * PlainFormatter ) Format ( entry * logrus . Entry ) ([] byte , error ) {
return [] byte ( entry . Message + " \n " ), nil
}
logger := log . NewLogrus (
log . LogrusWithFormatter ( & PlainFormatter {}),
)
Log Levels
Debug
Detailed information for debugging:
logger . Debug ( "processing request" , "request_id" , "abc123" , "user_id" , 456 )
Info
General informational messages:
logger . Info ( "server started" , "port" , 8080 , "environment" , "production" )
Warn
Warning messages for potentially harmful situations:
logger . Warn ( "rate limit approaching" , "current" , 95 , "limit" , 100 )
Error
Error messages for serious problems:
if err != nil {
logger . Error ( "failed to save user" , "error" , err , "user_id" , userID )
}
Fatal
Critical errors that cause application exit:
if err := connectDB (); err != nil {
logger . Fatal ( "cannot connect to database" , "error" , err )
// Application exits here
}
No-op Logger
For testing or when logging is not needed:
func NewNoop ( opts ... Option ) * Noop
Example:
logger := log . NewNoop ()
// These calls do nothing
logger . Info ( "message" )
logger . Error ( "error message" )
Useful in tests:
func TestMyFunction ( t * testing . T ) {
logger := log . NewNoop ()
// Test code that uses logger
result := myFunction ( logger )
assert . Equal ( t , expected , result )
}
Structured Logging
The logger uses key-value pairs for structured logging:
timeTaken := time . Duration ( time . Second * 1 )
logger . Info ( "request processed" ,
"method" , "GET" ,
"path" , "/api/users" ,
"duration" , timeTaken ,
"status" , 200 ,
)
Output (JSON format):
{
"level" : "info" ,
"msg" : "request processed" ,
"method" : "GET" ,
"path" : "/api/users" ,
"duration" : 1000000000 ,
"status" : 200 ,
"time" : "2026-03-04T10:30:45Z"
}
Complete Example
package main
import (
" fmt "
" os "
" time "
" github.com/raystack/salt/log "
" github.com/sirupsen/logrus "
)
func main () {
// Configure logger
logger := log . NewLogrus (
log . LogrusWithLevel ( "info" ),
log . LogrusWithFormatter ( & logrus . JSONFormatter {
TimestampFormat : time . RFC3339 ,
PrettyPrint : false ,
}),
)
logger . Info ( "application starting" ,
"version" , "1.0.0" ,
"environment" , os . Getenv ( "ENV" ),
)
// Simulate some work
start := time . Now ()
processData ( logger )
duration := time . Since ( start )
logger . Info ( "application completed" ,
"duration" , duration . Seconds (),
)
}
func processData ( logger log . Logger ) {
logger . Debug ( "starting data processing" )
items := [] string { "item1" , "item2" , "item3" }
for i , item := range items {
logger . Debug ( "processing item" ,
"index" , i ,
"item" , item ,
)
time . Sleep ( 100 * time . Millisecond )
}
logger . Info ( "data processing complete" ,
"items_processed" , len ( items ),
)
}
Integration with Config
package main
import (
" github.com/raystack/salt/config "
" github.com/raystack/salt/log "
)
type Config struct {
Log LogConfig `mapstructure:"log"`
}
type LogConfig struct {
Level string `mapstructure:"level" default:"info"`
}
func main () {
// Load configuration
loader := config . NewLoader (
config . WithFile ( "config.yaml" ),
)
cfg := & Config {}
if err := loader . Load ( cfg ); err != nil {
panic ( err )
}
// Create logger with configured level
logger := log . NewLogrus (
log . LogrusWithLevel ( cfg . Log . Level ),
)
logger . Info ( "logger initialized" , "level" , logger . Level ())
}
HTTP Middleware Example
package main
import (
" net/http "
" time "
" github.com/raystack/salt/log "
)
func loggingMiddleware ( logger log . Logger , next http . Handler ) http . Handler {
return http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
start := time . Now ()
// Create a response writer wrapper to capture status code
wrapped := & responseWriter { ResponseWriter : w , statusCode : 200 }
// Call the next handler
next . ServeHTTP ( wrapped , r )
// Log the request
duration := time . Since ( start )
logger . Info ( "http request" ,
"method" , r . Method ,
"path" , r . URL . Path ,
"status" , wrapped . statusCode ,
"duration_ms" , duration . Milliseconds (),
"remote_addr" , r . RemoteAddr ,
)
})
}
type responseWriter struct {
http . ResponseWriter
statusCode int
}
func ( rw * responseWriter ) WriteHeader ( code int ) {
rw . statusCode = code
rw . ResponseWriter . WriteHeader ( code )
}
func main () {
logger := log . NewLogrus ( log . LogrusWithLevel ( "info" ))
mux := http . NewServeMux ()
mux . HandleFunc ( "/api/users" , func ( w http . ResponseWriter , r * http . Request ) {
w . Write ([] byte ( "users" ))
})
handler := loggingMiddleware ( logger , mux )
http . ListenAndServe ( ":8080" , handler )
}
Error Logging with Context
func processOrder ( logger log . Logger , orderID string ) error {
logger . Debug ( "processing order" , "order_id" , orderID )
// Simulate processing
err := validateOrder ( orderID )
if err != nil {
logger . Error ( "order validation failed" ,
"order_id" , orderID ,
"error" , err ,
"step" , "validation" ,
)
return err
}
err = chargePayment ( orderID )
if err != nil {
logger . Error ( "payment processing failed" ,
"order_id" , orderID ,
"error" , err ,
"step" , "payment" ,
)
return err
}
logger . Info ( "order processed successfully" , "order_id" , orderID )
return nil
}
Best Practices
Use Appropriate Log Levels
Choose the right level for each message: logger . Debug ( "detailed debug info" ) // Development only
logger . Info ( "user action" ) // Important events
logger . Warn ( "deprecated API used" ) // Warnings
logger . Error ( "failed operation" , err ) // Errors
logger . Fatal ( "critical failure" , err ) // Application exit
Always Use Key-Value Pairs
Provide context with structured data: logger . Info ( "user action" ,
"action" , "login" ,
"user_id" , userID ,
"ip" , ipAddress ,
)
Never log passwords, tokens, or PII: // Bad
logger . Info ( "login" , "password" , password )
// Good
logger . Info ( "login attempt" , "user_id" , userID )
Use JSON Format in Production