Skip to content

Commit f8100f1

Browse files
authored
Merge pull request #36 from Majorfi/feat/log-improvements
fix: improve log system and doc
2 parents 895bfdf + 35eff1e commit f8100f1

11 files changed

Lines changed: 630 additions & 11 deletions

File tree

README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,20 @@ API_KEY=your_immich_api_key
1111
API_URL=http://immich-server:2283/api
1212
RUN_MODE=cron
1313
CRON_INTERVAL=60
14+
# Optional: Enable file logging for persistent logs
15+
# LOG_FILE=/app/logs/immich-stack.log
1416
EOL
1517

1618
# Run with Docker (using Docker Hub)
1719
docker run -d --name immich-stack --env-file .env -v ./logs:/app/logs majorfi/immich-stack:latest
1820

1921
# Or using GitHub Container Registry
2022
docker run -d --name immich-stack --env-file .env -v ./logs:/app/logs ghcr.io/majorfi/immich-stack:latest
23+
24+
# View logs
25+
docker logs -f immich-stack
26+
27+
# If LOG_FILE is set, logs are also saved to ./logs/immich-stack.log
2128
```
2229

2330
## Documentation

cmd/config.go

Lines changed: 90 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"io"
1111
"os"
1212
"strconv"
13+
"strings"
1314
"time"
1415

1516
"github.com/joho/godotenv"
@@ -54,9 +55,30 @@ func configureLogger() *logrus.Logger {
5455
func configureLoggerWithOutput(output io.Writer) *logrus.Logger {
5556
logger := logrus.New()
5657

57-
// Set output if provided (for testing)
58+
// Set output - file logging if LOG_FILE is set, otherwise stdout
5859
if output != nil {
60+
// Testing mode - use provided output
5961
logger.SetOutput(output)
62+
} else if logFile := os.Getenv("LOG_FILE"); logFile != "" {
63+
// File logging enabled - write to both stdout and file
64+
if err := os.MkdirAll(utils.GetDir(logFile), 0755); err != nil {
65+
logger.Warnf("Failed to create log directory: %v, falling back to stdout only", err)
66+
logger.SetOutput(os.Stdout)
67+
} else {
68+
file, err := os.OpenFile(logFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
69+
if err != nil {
70+
logger.Warnf("Failed to open log file %s: %v, falling back to stdout only", logFile, err)
71+
logger.SetOutput(os.Stdout)
72+
} else {
73+
// Write to both stdout and file
74+
multiWriter := io.MultiWriter(os.Stdout, file)
75+
logger.SetOutput(multiWriter)
76+
logger.Infof("Logging to file: %s", logFile)
77+
}
78+
}
79+
} else {
80+
// Default to stdout only
81+
logger.SetOutput(os.Stdout)
6082
}
6183

6284
// Set log level - flag takes precedence over environment variable
@@ -100,6 +122,69 @@ type LoadEnvConfig struct {
100122
Error error
101123
}
102124

125+
/**************************************************************************************************
126+
** logStartupSummary logs a concise summary of the current configuration at startup.
127+
** Shows the resolved configuration values for all major settings.
128+
**
129+
** @param logger - Logger instance to output the summary
130+
**************************************************************************************************/
131+
func logStartupSummary(logger *logrus.Logger) {
132+
// Build summary based on format
133+
if format := os.Getenv("LOG_FORMAT"); format == "json" {
134+
logger.WithFields(logrus.Fields{
135+
"runMode": runMode,
136+
"cronInterval": cronInterval,
137+
"logLevel": logger.GetLevel().String(),
138+
"logFormat": "json",
139+
"logFile": os.Getenv("LOG_FILE"),
140+
"dryRun": dryRun,
141+
"replaceStacks": replaceStacks,
142+
"resetStacks": resetStacks,
143+
"withArchived": withArchived,
144+
"withDeleted": withDeleted,
145+
"removeSingleAssetStacks": removeSingleAssetStacks,
146+
"criteria": criteria,
147+
"parentFilenamePromote": parentFilenamePromote,
148+
"parentExtPromote": parentExtPromote,
149+
}).Info("Configuration loaded")
150+
} else {
151+
// Build human-readable summary
152+
var summary []string
153+
summary = append(summary, fmt.Sprintf("mode=%s", runMode))
154+
if runMode == "cron" {
155+
summary = append(summary, fmt.Sprintf("interval=%ds", cronInterval))
156+
}
157+
summary = append(summary, fmt.Sprintf("level=%s", logger.GetLevel().String()))
158+
summary = append(summary, fmt.Sprintf("format=%s", "text"))
159+
if logFile := os.Getenv("LOG_FILE"); logFile != "" {
160+
summary = append(summary, fmt.Sprintf("file=%s", logFile))
161+
}
162+
if dryRun {
163+
summary = append(summary, "dry-run=true")
164+
}
165+
if replaceStacks {
166+
summary = append(summary, "replace=true")
167+
}
168+
if resetStacks {
169+
summary = append(summary, "reset=true")
170+
}
171+
if withArchived {
172+
summary = append(summary, "archived=true")
173+
}
174+
if withDeleted {
175+
summary = append(summary, "deleted=true")
176+
}
177+
if removeSingleAssetStacks {
178+
summary = append(summary, "remove-single=true")
179+
}
180+
if criteria != "" {
181+
summary = append(summary, fmt.Sprintf("criteria=%s", criteria))
182+
}
183+
184+
logger.Infof("Starting with config: %s", strings.Join(summary, ", "))
185+
}
186+
}
187+
103188
/**************************************************************************************************
104189
** LoadEnvForTesting loads environment variables and validates configuration without calling Fatal().
105190
** Returns errors instead of terminating, allowing tests to verify error conditions.
@@ -183,6 +268,10 @@ func LoadEnvForTesting() LoadEnvConfig {
183268
parentExtPromote = envVal
184269
}
185270
}
271+
272+
// Log startup configuration summary
273+
logStartupSummary(logger)
274+
186275
return LoadEnvConfig{Logger: logger, Error: nil}
187276
}
188277

0 commit comments

Comments
 (0)