@@ -6,8 +6,10 @@ import (
66 "log/slog"
77 "os"
88 "os/signal"
9+ "path/filepath"
910 "strings"
1011 "syscall"
12+ "time"
1113
1214 "github.com/coder/boundary"
1315 "github.com/coder/boundary/audit"
@@ -22,6 +24,7 @@ import (
2224type Config struct {
2325 AllowStrings []string
2426 LogLevel string
27+ LogDir string
2528 Unprivileged bool
2629}
2730
@@ -56,20 +59,26 @@ func BaseCommand() *serpent.Command {
5659 Short : "Network isolation tool for monitoring and restricting HTTP/HTTPS requests" ,
5760 Long : `boundary creates an isolated network environment for target processes, intercepting HTTP/HTTPS traffic through a transparent proxy that enforces user-defined allow rules.` ,
5861 Options : []serpent.Option {
59- serpent. Option {
62+ {
6063 Flag : "allow" ,
6164 Env : "BOUNDARY_ALLOW" ,
6265 Description : "Allow rule (repeatable). Format: \" pattern\" or \" METHOD[,METHOD] pattern\" ." ,
6366 Value : serpent .StringArrayOf (& config .AllowStrings ),
6467 },
65- serpent. Option {
68+ {
6669 Flag : "log-level" ,
6770 Env : "BOUNDARY_LOG_LEVEL" ,
6871 Description : "Set log level (error, warn, info, debug)." ,
6972 Default : "warn" ,
7073 Value : serpent .StringOf (& config .LogLevel ),
7174 },
72- serpent.Option {
75+ {
76+ Flag : "log-dir" ,
77+ Env : "BOUNDARY_LOG_DIR" ,
78+ Description : "Set a directory to write logs to rather than stderr." ,
79+ Value : serpent .StringOf (& config .LogDir ),
80+ },
81+ {
7382 Flag : "unprivileged" ,
7483 Env : "BOUNDARY_UNPRIVILEGED" ,
7584 Description : "Run in unprivileged mode (no network isolation, uses proxy environment variables)." ,
@@ -87,7 +96,11 @@ func BaseCommand() *serpent.Command {
8796func Run (ctx context.Context , config Config , args []string ) error {
8897 ctx , cancel := context .WithCancel (ctx )
8998 defer cancel ()
90- logger := setupLogging (config .LogLevel )
99+
100+ logger , err := setupLogging (config )
101+ if err != nil {
102+ return fmt .Errorf ("could not set up logging: %v" , err )
103+ }
91104 username , uid , gid , homeDir , configDir := util .GetUserInfo ()
92105
93106 // Get command arguments
@@ -204,9 +217,9 @@ func Run(ctx context.Context, config Config, args []string) error {
204217}
205218
206219// setupLogging creates a slog logger with the specified level
207- func setupLogging (logLevel string ) * slog.Logger {
220+ func setupLogging (config Config ) ( * slog.Logger , error ) {
208221 var level slog.Level
209- switch strings .ToLower (logLevel ) {
222+ switch strings .ToLower (config . LogLevel ) {
210223 case "error" :
211224 level = slog .LevelError
212225 case "warn" :
@@ -219,12 +232,34 @@ func setupLogging(logLevel string) *slog.Logger {
219232 level = slog .LevelWarn // Default to warn if invalid level
220233 }
221234
235+ logTarget := os .Stderr
236+
237+ if config .LogDir != "" {
238+ // Set up the logging directory if it doesn't exist yet
239+ if err := os .MkdirAll (config .LogDir , 0755 ); err != nil {
240+ return nil , fmt .Errorf ("could not set up log dir %s: %v" , config .LogDir , err )
241+ }
242+
243+ // Create a logfile (timestamp and pid to avoid race conditions with multiple boundary calls running)
244+ logFilePath := fmt .Sprintf ("boundary-%s-%d.log" ,
245+ time .Now ().Format ("2006-01-02_15-04-05" ),
246+ os .Getpid ())
247+
248+ logFile , err := os .Create (filepath .Join (config .LogDir , logFilePath ))
249+ if err != nil {
250+ return nil , fmt .Errorf ("could not create log file %s: %v" , logFilePath , err )
251+ }
252+
253+ // Set the log target to the file rather than stderr.
254+ logTarget = logFile
255+ }
256+
222257 // Create a standard slog logger with the appropriate level
223- handler := slog .NewTextHandler (os . Stderr , & slog.HandlerOptions {
258+ handler := slog .NewTextHandler (logTarget , & slog.HandlerOptions {
224259 Level : level ,
225260 })
226261
227- return slog .New (handler )
262+ return slog .New (handler ), nil
228263}
229264
230265// createJailer creates a new jail instance for the current platform
0 commit comments