@@ -11,6 +11,7 @@ import (
11
11
"strconv"
12
12
"strings"
13
13
"syscall"
14
+ "time"
14
15
15
16
"github.com/coder/boundary"
16
17
"github.com/coder/boundary/audit"
@@ -24,6 +25,7 @@ import (
24
25
type Config struct {
25
26
AllowStrings []string
26
27
LogLevel string
28
+ LogDir string
27
29
Unprivileged bool
28
30
}
29
31
@@ -58,20 +60,26 @@ func BaseCommand() *serpent.Command {
58
60
Short : "Network isolation tool for monitoring and restricting HTTP/HTTPS requests" ,
59
61
Long : `boundary creates an isolated network environment for target processes, intercepting HTTP/HTTPS traffic through a transparent proxy that enforces user-defined allow rules.` ,
60
62
Options : []serpent.Option {
61
- serpent. Option {
63
+ {
62
64
Flag : "allow" ,
63
65
Env : "BOUNDARY_ALLOW" ,
64
66
Description : "Allow rule (repeatable). Format: \" pattern\" or \" METHOD[,METHOD] pattern\" ." ,
65
67
Value : serpent .StringArrayOf (& config .AllowStrings ),
66
68
},
67
- serpent. Option {
69
+ {
68
70
Flag : "log-level" ,
69
71
Env : "BOUNDARY_LOG_LEVEL" ,
70
72
Description : "Set log level (error, warn, info, debug)." ,
71
73
Default : "warn" ,
72
74
Value : serpent .StringOf (& config .LogLevel ),
73
75
},
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
+ {
75
83
Flag : "unprivileged" ,
76
84
Env : "BOUNDARY_UNPRIVILEGED" ,
77
85
Description : "Run in unprivileged mode (no network isolation, uses proxy environment variables)." ,
@@ -89,7 +97,10 @@ func BaseCommand() *serpent.Command {
89
97
func Run (ctx context.Context , config Config , args []string ) error {
90
98
ctx , cancel := context .WithCancel (ctx )
91
99
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
+ }
93
104
username , uid , gid , homeDir , configDir := getUserInfo ()
94
105
95
106
// Get command arguments
@@ -242,9 +253,9 @@ func getUserInfo() (string, int, int, string, string) {
242
253
}
243
254
244
255
// setupLogging creates a slog logger with the specified level
245
- func setupLogging (logLevel string ) * slog.Logger {
256
+ func setupLogging (config Config ) ( * slog.Logger , error ) {
246
257
var level slog.Level
247
- switch strings .ToLower (logLevel ) {
258
+ switch strings .ToLower (config . LogLevel ) {
248
259
case "error" :
249
260
level = slog .LevelError
250
261
case "warn" :
@@ -257,12 +268,34 @@ func setupLogging(logLevel string) *slog.Logger {
257
268
level = slog .LevelWarn // Default to warn if invalid level
258
269
}
259
270
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
+
260
293
// Create a standard slog logger with the appropriate level
261
- handler := slog .NewTextHandler (os . Stderr , & slog.HandlerOptions {
294
+ handler := slog .NewTextHandler (logTarget , & slog.HandlerOptions {
262
295
Level : level ,
263
296
})
264
297
265
- return slog .New (handler )
298
+ return slog .New (handler ), nil
266
299
}
267
300
268
301
// getCurrentUserInfo gets information for the current user
0 commit comments