teeLoggerToGatewayLog adds a JSON sink at gatewayStatePath("gateway.log") to cli.logger so the daemon's structured logs land in the file /gateway start advertises (in addition to app.log). Returns a closer for the file, or nil when it can't be opened (logging then stays app.log-only — no false promi
()
| 471 | // when it can't be opened (logging then stays app.log-only — no false promise |
| 472 | // is broken because the tee simply didn't attach). |
| 473 | func (cli *ChatCLI) teeLoggerToGatewayLog() func() { |
| 474 | path := gatewayStatePath("gateway.log") |
| 475 | _ = os.MkdirAll(filepath.Dir(path), 0o750) |
| 476 | f, err := os.OpenFile(path, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0o600) // #nosec G304 -- daemon-scoped |
| 477 | if err != nil { |
| 478 | cli.logger.Warn("gateway: could not open gateway.log for tee", zap.Error(err)) |
| 479 | return nil |
| 480 | } |
| 481 | encCfg := zap.NewProductionEncoderConfig() |
| 482 | encCfg.EncodeTime = zapcore.ISO8601TimeEncoder |
| 483 | enc := zapcore.NewJSONEncoder(encCfg) |
| 484 | extra := zapcore.NewCore(enc, zapcore.AddSync(f), zapcore.InfoLevel) |
| 485 | cli.logger = cli.logger.WithOptions(zap.WrapCore(func(c zapcore.Core) zapcore.Core { |
| 486 | return zapcore.NewTee(c, extra) |
| 487 | })) |
| 488 | return func() { _ = f.Close() } |
| 489 | } |
| 490 | |
| 491 | // gatewayAgentFunc returns an AgentFunc that runs each inbound message through |
| 492 | // the real (unattended) coder ReAct loop, streaming a short filtered action |