-
-
Notifications
You must be signed in to change notification settings - Fork 112
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Style Atmos Logger with Theme #1121
base: main
Are you sure you want to change the base?
Changes from 18 commits
317a096
da9600b
39cdea6
675b318
fdfb3bb
f8ce952
3abe383
3bd7849
00efab5
6113510
5a7f7a9
8634014
3efcee9
a3e053e
15acbf6
3201ab1
0c1eb4b
2dfcbd5
a9e4709
8f21c58
e615ef9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -86,4 +86,6 @@ const ( | |
AtmosYamlFuncEnv = "!env" | ||
|
||
TerraformDefaultWorkspace = "default" | ||
|
||
StandardFilePermissions = 0o644 | ||
) |
Original file line number | Diff line number | Diff line change | ||||||||
---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,147 @@ | ||||||||||
package logger | ||||||||||
|
||||||||||
import ( | ||||||||||
"os" | ||||||||||
|
||||||||||
"github.com/charmbracelet/lipgloss" | ||||||||||
log "github.com/charmbracelet/log" | ||||||||||
"github.com/cloudposse/atmos/pkg/ui/theme" | ||||||||||
) | ||||||||||
|
||||||||||
const TraceLevel log.Level = log.DebugLevel - 1 | ||||||||||
|
||||||||||
var helperLogger *log.Logger | ||||||||||
|
||||||||||
func init() { | ||||||||||
helperLogger = GetCharmLogger() | ||||||||||
} | ||||||||||
|
||||||||||
// GetCharmLogger returns a pre-configured Charmbracelet logger with Atmos styling. | ||||||||||
func GetCharmLogger() *log.Logger { | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is not getting an existing logger but creating a new one. |
||||||||||
styles := getAtmosLogStyles() | ||||||||||
logger := log.New(os.Stderr) | ||||||||||
logger.SetStyles(styles) | ||||||||||
return logger | ||||||||||
} | ||||||||||
|
||||||||||
// GetCharmLoggerWithOutput returns a pre-configured Charmbracelet logger with custom output. | ||||||||||
func GetCharmLoggerWithOutput(output *os.File) *log.Logger { | ||||||||||
Comment on lines
+27
to
+28
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is also creating a new logger |
||||||||||
styles := getAtmosLogStyles() | ||||||||||
logger := log.New(output) | ||||||||||
logger.SetStyles(styles) | ||||||||||
return logger | ||||||||||
} | ||||||||||
|
||||||||||
// getAtmosLogStyles returns custom styles for the Charmbracelet logger using Atmos theme colors. | ||||||||||
func getAtmosLogStyles() *log.Styles { | ||||||||||
styles := log.DefaultStyles() | ||||||||||
|
||||||||||
const ( | ||||||||||
paddingVertical = 0 | ||||||||||
paddingHorizontal = 1 | ||||||||||
) | ||||||||||
|
||||||||||
configureLogLevelStyles(styles, paddingVertical, paddingHorizontal) | ||||||||||
configureKeyStyles(styles) | ||||||||||
|
||||||||||
return styles | ||||||||||
} | ||||||||||
|
||||||||||
// configureLogLevelStyles configures the styles for different log levels. | ||||||||||
func configureLogLevelStyles(styles *log.Styles, paddingVertical, paddingHorizontal int) { | ||||||||||
const ( | ||||||||||
errorLevelLabel = "ERROR" | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||
warnLevelLabel = "WARN" | ||||||||||
infoLevelLabel = "INFO" | ||||||||||
debugLevelLabel = "DEBU" | ||||||||||
traceLevelLabel = "TRACE" | ||||||||||
Cerebrovinny marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||
) | ||||||||||
|
||||||||||
// Style `Error` log messages. | ||||||||||
styles.Levels[log.ErrorLevel] = lipgloss.NewStyle(). | ||||||||||
SetString(errorLevelLabel). | ||||||||||
Padding(paddingVertical, paddingHorizontal, paddingVertical, paddingHorizontal). | ||||||||||
Background(lipgloss.Color(theme.ColorPink)). | ||||||||||
Foreground(lipgloss.Color(theme.ColorWhite)) | ||||||||||
|
||||||||||
// Style `Warning` log messages. | ||||||||||
styles.Levels[log.WarnLevel] = lipgloss.NewStyle(). | ||||||||||
SetString(warnLevelLabel). | ||||||||||
Padding(paddingVertical, paddingHorizontal, paddingVertical, paddingHorizontal). | ||||||||||
Background(lipgloss.Color(theme.ColorPink)). | ||||||||||
Foreground(lipgloss.Color(theme.ColorDarkGray)) | ||||||||||
|
||||||||||
// Style `Info` log messages. | ||||||||||
styles.Levels[log.InfoLevel] = lipgloss.NewStyle(). | ||||||||||
SetString(infoLevelLabel). | ||||||||||
Padding(paddingVertical, paddingHorizontal, paddingVertical, paddingHorizontal). | ||||||||||
Background(lipgloss.Color(theme.ColorCyan)). | ||||||||||
Foreground(lipgloss.Color(theme.ColorDarkGray)) | ||||||||||
|
||||||||||
// Style `Debug` log messages. | ||||||||||
styles.Levels[log.DebugLevel] = lipgloss.NewStyle(). | ||||||||||
SetString(debugLevelLabel). | ||||||||||
Padding(paddingVertical, paddingHorizontal, paddingVertical, paddingHorizontal). | ||||||||||
Background(lipgloss.Color(theme.ColorBlue)). | ||||||||||
Foreground(lipgloss.Color(theme.ColorWhite)) | ||||||||||
|
||||||||||
// Style `Trace` log messages. | ||||||||||
styles.Levels[TraceLevel] = lipgloss.NewStyle(). | ||||||||||
SetString(traceLevelLabel). | ||||||||||
Padding(paddingVertical, paddingHorizontal, paddingVertical, paddingHorizontal). | ||||||||||
Background(lipgloss.Color(theme.ColorDarkGray)). | ||||||||||
Foreground(lipgloss.Color(theme.ColorWhite)) | ||||||||||
} | ||||||||||
|
||||||||||
// configureKeyStyles configures the styles for different log keys. | ||||||||||
func configureKeyStyles(styles *log.Styles) { | ||||||||||
const ( | ||||||||||
keyError = "error" | ||||||||||
keyComponent = "component" | ||||||||||
keyStack = "stack" | ||||||||||
keyDuration = "duration" | ||||||||||
) | ||||||||||
|
||||||||||
// Custom style for 'err' key | ||||||||||
styles.Keys[keyError] = lipgloss.NewStyle().Foreground(lipgloss.Color(theme.ColorPink)) | ||||||||||
styles.Values[keyError] = lipgloss.NewStyle().Bold(true) | ||||||||||
|
||||||||||
// Custom style for 'component' key | ||||||||||
styles.Keys[keyComponent] = lipgloss.NewStyle().Foreground(lipgloss.Color(theme.ColorPink)) | ||||||||||
|
||||||||||
// Custom style for 'stack' key | ||||||||||
styles.Keys[keyStack] = lipgloss.NewStyle().Foreground(lipgloss.Color(theme.ColorBlue)) | ||||||||||
|
||||||||||
// Custom style for 'duration' key | ||||||||||
styles.Keys[keyDuration] = lipgloss.NewStyle().Foreground(lipgloss.Color(theme.ColorGreen)) | ||||||||||
} | ||||||||||
|
||||||||||
// Error logs an error message with context. | ||||||||||
func Error(message string, keyvals ...interface{}) { | ||||||||||
helperLogger.Error(message, keyvals...) | ||||||||||
} | ||||||||||
|
||||||||||
// Warn logs a warning message with context. | ||||||||||
func Warn(message string, keyvals ...interface{}) { | ||||||||||
helperLogger.Warn(message, keyvals...) | ||||||||||
} | ||||||||||
|
||||||||||
// Info logs an informational message with context. | ||||||||||
func Info(message string, keyvals ...interface{}) { | ||||||||||
helperLogger.Info(message, keyvals...) | ||||||||||
} | ||||||||||
|
||||||||||
// Debug logs a debug message with context. | ||||||||||
func Debug(message string, keyvals ...interface{}) { | ||||||||||
helperLogger.Debug(message, keyvals...) | ||||||||||
} | ||||||||||
|
||||||||||
// Trace logs a trace message with context. | ||||||||||
func Trace(message string, keyvals ...interface{}) { | ||||||||||
helperLogger.Log(TraceLevel, message, keyvals...) | ||||||||||
} | ||||||||||
|
||||||||||
// Fatal logs an error message and exits with status code 1. | ||||||||||
func Fatal(message string, keyvals ...interface{}) { | ||||||||||
helperLogger.Fatal(message, keyvals...) | ||||||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
package logger | ||
|
||
import ( | ||
"os" | ||
"path/filepath" | ||
"testing" | ||
|
||
"github.com/charmbracelet/lipgloss" | ||
log "github.com/charmbracelet/log" | ||
"github.com/stretchr/testify/assert" | ||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
func TestGetCharmLogger(t *testing.T) { | ||
logger := GetCharmLogger() | ||
require.NotNil(t, logger, "Should return a non-nil logger") | ||
|
||
// These should not panic. | ||
assert.NotPanics(t, func() { | ||
logger.SetLevel(log.InfoLevel) | ||
logger.SetTimeFormat("") | ||
}) | ||
} | ||
|
||
func TestGetCharmLoggerWithOutput(t *testing.T) { | ||
tempDir := os.TempDir() | ||
logFile := filepath.Join(tempDir, "charm_test.log") | ||
defer os.Remove(logFile) | ||
|
||
f, err := os.Create(logFile) | ||
require.NoError(t, err, "Should create log file without error") | ||
|
||
logger := GetCharmLoggerWithOutput(f) | ||
require.NotNil(t, logger, "Should return a non-nil logger") | ||
|
||
logger.SetTimeFormat("") | ||
logger.Info("File test message") | ||
|
||
f.Close() | ||
|
||
data, err := os.ReadFile(logFile) | ||
require.NoError(t, err, "Should read log file without error") | ||
|
||
content := string(data) | ||
assert.Contains(t, content, "INFO", "Should have INFO level in file") | ||
assert.Contains(t, content, "File test message", "Should contain the message") | ||
} | ||
|
||
// Test the actual styling implementation. | ||
func TestCharmLoggerStylingDetails(t *testing.T) { | ||
styles := getAtmosLogStyles() | ||
|
||
assert.NotEqual(t, lipgloss.Style{}, styles.Levels[log.ErrorLevel], "ERROR level should have styling") | ||
assert.NotEqual(t, lipgloss.Style{}, styles.Levels[log.WarnLevel], "WARN level should have styling") | ||
assert.NotEqual(t, lipgloss.Style{}, styles.Levels[log.InfoLevel], "INFO level should have styling") | ||
assert.NotEqual(t, lipgloss.Style{}, styles.Levels[log.DebugLevel], "DEBUG level should have styling") | ||
|
||
assert.Contains(t, styles.Levels[log.ErrorLevel].Render("ERROR"), "ERROR", "ERROR label should be styled") | ||
assert.Contains(t, styles.Levels[log.WarnLevel].Render("WARN"), "WARN", "WARN label should be styled") | ||
assert.Contains(t, styles.Levels[log.InfoLevel].Render("INFO"), "INFO", "INFO label should be styled") | ||
assert.Contains(t, styles.Levels[log.DebugLevel].Render("DEBUG"), "DEBUG", "DEBUG label should be styled") | ||
|
||
assert.NotNil(t, styles.Keys["err"], "err key should have styling") | ||
assert.NotNil(t, styles.Values["err"], "err value should have styling") | ||
assert.NotNil(t, styles.Keys["component"], "component key should have styling") | ||
assert.NotNil(t, styles.Keys["stack"], "stack key should have styling") | ||
} | ||
|
||
func ExampleGetCharmLogger() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Where is this used? |
||
logger := GetCharmLogger() | ||
|
||
logger.SetTimeFormat("2006-01-02 15:04:05") | ||
logger.SetLevel(log.InfoLevel) | ||
|
||
logger.Info("User logged in", "user_id", "12345", "component", "auth") | ||
|
||
logger.Error("Failed to process request", | ||
"err", "connection timeout", | ||
"component", "api", | ||
"duration", "1.5s") | ||
|
||
logger.Warn("Resource utilization high", | ||
"component", "database", | ||
"stack", "prod-ue1", | ||
"usage", "95%") | ||
|
||
logger.Debug("Processing request", | ||
"request_id", "abc123", | ||
"component", "api", | ||
"endpoint", "/users", | ||
"method", "GET") | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.