@@ -11,6 +11,7 @@ import (
1111 "strconv"
1212 "strings"
1313 "syscall"
14+ "time"
1415
1516 "github.com/coder/boundary"
1617 "github.com/coder/boundary/audit"
@@ -24,6 +25,7 @@ import (
2425type Config struct {
2526 AllowStrings []string
2627 LogLevel string
28+ LogDir string
2729 Unprivileged bool
2830}
2931
@@ -58,20 +60,26 @@ func BaseCommand() *serpent.Command {
5860 Short : "Network isolation tool for monitoring and restricting HTTP/HTTPS requests" ,
5961 Long : `boundary creates an isolated network environment for target processes, intercepting HTTP/HTTPS traffic through a transparent proxy that enforces user-defined allow rules.` ,
6062 Options : []serpent.Option {
61- serpent. Option {
63+ {
6264 Flag : "allow" ,
6365 Env : "BOUNDARY_ALLOW" ,
6466 Description : "Allow rule (repeatable). Format: \" pattern\" or \" METHOD[,METHOD] pattern\" ." ,
6567 Value : serpent .StringArrayOf (& config .AllowStrings ),
6668 },
67- serpent. Option {
69+ {
6870 Flag : "log-level" ,
6971 Env : "BOUNDARY_LOG_LEVEL" ,
7072 Description : "Set log level (error, warn, info, debug)." ,
7173 Default : "warn" ,
7274 Value : serpent .StringOf (& config .LogLevel ),
7375 },
74- serpent.Option {
76+ {
77+ Flag : "log-dir" ,
78+ Env : "BOUNDARY_LOG_DIR" ,
79+ Description : "Set a directory to write logs to rather than stderr." ,
80+ Value : serpent .StringOf (& config .LogDir ),
81+ },
82+ {
7583 Flag : "unprivileged" ,
7684 Env : "BOUNDARY_UNPRIVILEGED" ,
7785 Description : "Run in unprivileged mode (no network isolation, uses proxy environment variables)." ,
@@ -89,7 +97,10 @@ func BaseCommand() *serpent.Command {
8997func Run (ctx context.Context , config Config , args []string ) error {
9098 ctx , cancel := context .WithCancel (ctx )
9199 defer cancel ()
92- logger := setupLogging (config .LogLevel )
100+ logger , err := setupLogging (config )
101+ if err != nil {
102+ return fmt .Errorf ("could not set up logging: %v" , err )
103+ }
93104 username , uid , gid , homeDir , configDir := getUserInfo ()
94105
95106 // Get command arguments
@@ -242,9 +253,9 @@ func getUserInfo() (string, int, int, string, string) {
242253}
243254
244255// setupLogging creates a slog logger with the specified level
245- func setupLogging (logLevel string ) * slog.Logger {
256+ func setupLogging (config Config ) ( * slog.Logger , error ) {
246257 var level slog.Level
247- switch strings .ToLower (logLevel ) {
258+ switch strings .ToLower (config . LogLevel ) {
248259 case "error" :
249260 level = slog .LevelError
250261 case "warn" :
@@ -257,12 +268,34 @@ func setupLogging(logLevel string) *slog.Logger {
257268 level = slog .LevelWarn // Default to warn if invalid level
258269 }
259270
271+ logTarget := os .Stderr
272+
273+ if config .LogDir != "" {
274+ // Set up the logging directory if it doesn't exist yet
275+ if err := os .MkdirAll (config .LogDir , 0755 ); err != nil {
276+ return nil , fmt .Errorf ("could not set up log dir %s: %v" , config .LogDir , err )
277+ }
278+
279+ // Create a logfile (timestamp and pid to avoid race conditions with multiple boundary calls running)
280+ logFilePath := fmt .Sprintf ("boundary-%s-%d.log" ,
281+ time .Now ().Format ("2006-01-02_15-04-05" ),
282+ os .Getpid ())
283+
284+ logFile , err := os .Create (filepath .Join (config .LogDir , logFilePath ))
285+ if err != nil {
286+ return nil , fmt .Errorf ("could not create log file %s: %v" , logFilePath , err )
287+ }
288+
289+ // Set the log target to the file rather than stderr.
290+ logTarget = logFile
291+ }
292+
260293 // Create a standard slog logger with the appropriate level
261- handler := slog .NewTextHandler (os . Stderr , & slog.HandlerOptions {
294+ handler := slog .NewTextHandler (logTarget , & slog.HandlerOptions {
262295 Level : level ,
263296 })
264297
265- return slog .New (handler )
298+ return slog .New (handler ), nil
266299}
267300
268301// getCurrentUserInfo gets information for the current user
0 commit comments