]> git.giacomolli.uk - onechat/commitdiff
Stamp daemon log lines only outside the journal
authorAnderson A. Giacomolli <redacted>
Mon, 9 Nov 2026 13:19:42 +0000 (10:19 -0300)
committerAnderson A. Giacomolli <redacted>
Mon, 9 Nov 2026 13:19:42 +0000 (10:19 -0300)
The daemon makes the logger of each part in one place. Under the
journal, which sets JOURNAL_STREAM and adds its own time, the lines have
no time stamp. Otherwise every line has the standard time stamp, the
onechat lines too, so that no line has two time stamps and none has
none.

The start line names each value: the socket, the model and its effort,
the directory where the commands run, whether WhatsApp is linked, and
the number of active schedules.

Issue: e4acfcb

daemon.go
daemon_test.go
schedule.go

index f7ed315745ab37033573a0f6dc122cf6abd304ab..4c0f466d4d0b04b5a9b2d05e541947e0fb3b2117 100644 (file)
--- a/daemon.go
+++ b/daemon.go
@@ -29,7 +29,8 @@ const (
 // the clients on the Unix socket in the data directory until ctx ends. When
 // telegram.json exists, it also runs the Telegram bot. It also keeps a linked
 // WhatsApp device connected. It holds the lock of the token file, so it is the
 // the clients on the Unix socket in the data directory until ctx ends. When
 // telegram.json exists, it also runs the Telegram bot. It also keeps a linked
 // WhatsApp device connected. It holds the lock of the token file, so it is the
-// only process that refreshes the tokens and opens the WhatsApp session.
+// only process that refreshes the tokens and opens the WhatsApp session. Its
+// log lines go to out, through the loggers of daemonLogger.
 func daemon(ctx context.Context, out io.Writer) (err error) {
        dir, err := dataDir()
        if err != nil {
 func daemon(ctx context.Context, out io.Writer) (err error) {
        dir, err := dataDir()
        if err != nil {
@@ -64,7 +65,8 @@ func daemon(ctx context.Context, out io.Writer) (err error) {
        }()
 
        whatsappSetDeviceProps()
        }()
 
        whatsappSetDeviceProps()
-       whatsapp, err := openWhatsApp(ctx, dir, time.Now, log.New(out, "whatsapp: ", log.LstdFlags), whatsappNewClient)
+       logger := daemonLogger(out, "onechat: ")
+       whatsapp, err := openWhatsApp(ctx, dir, time.Now, daemonLogger(out, "whatsapp: "), whatsappNewClient)
        if err != nil {
                return err
        }
        if err != nil {
                return err
        }
@@ -108,18 +110,17 @@ func daemon(ctx context.Context, out io.Writer) (err error) {
                return err
        }
 
                return err
        }
 
-       _, err = fmt.Fprintf(out, "onechat: %s on %s, %s at %s effort, in %s\n", agent.config.Name, listener.Addr(), agent.config.Model, agent.config.Effort, agent.config.Workdir)
-       if err != nil {
-               return errors.Join(err, listener.Close())
+       linked := "WhatsApp is not linked"
+       if whatsapp.isLinked() {
+               linked = "WhatsApp is linked"
        }
 
        }
 
+       logger.Printf("%s is ready: socket %s, model %s at %s effort, commands run in %s, %s, active schedules: %d", agent.config.Name, listener.Addr(), agent.config.Model, agent.config.Effort, agent.config.Workdir, linked, schedules.Len())
+
        var telegram *TelegramBot
        if hasBot {
        var telegram *TelegramBot
        if hasBot {
-               telegram = newTelegramBot(newTelegramAPI(telegramAPIURL, botSettings.Token), botSettings.Owner, filepath.Join(agent.dir, telegramOffsetFileName), log.New(out, "telegram: ", log.LstdFlags))
-               _, err = fmt.Fprintln(out, "onechat: the Telegram bot waits for messages of its owner")
-               if err != nil {
-                       return errors.Join(err, listener.Close())
-               }
+               telegram = newTelegramBot(newTelegramAPI(telegramAPIURL, botSettings.Token), botSettings.Owner, filepath.Join(agent.dir, telegramOffsetFileName), daemonLogger(out, "telegram: "))
+               logger.Print("the Telegram bot waits for messages of its owner")
        }
 
        return daemonServe(ctx, listener, telegram, whatsapp, schedules, func(notify func(Event)) (*TurnLoop, error) {
        }
 
        return daemonServe(ctx, listener, telegram, whatsapp, schedules, func(notify func(Event)) (*TurnLoop, error) {
@@ -421,6 +422,18 @@ func daemonRunRequest(l *TurnLoop, whatsapp *WhatsApp, request SocketRequest, an
        }
 }
 
        }
 }
 
+// daemonLogger returns the logger of the part of the daemon with prefix, such
+// as "telegram: ", on out. The journal adds the time to each line and sets
+// JOURNAL_STREAM for a service, so then the lines have no time stamp. Otherwise
+// each line has the standard time stamp.
+func daemonLogger(out io.Writer, prefix string) *log.Logger {
+       if os.Getenv("JOURNAL_STREAM") != "" {
+               return log.New(out, prefix, 0)
+       }
+
+       return log.New(out, prefix, log.LstdFlags)
+}
+
 // durationText returns d for a log line: rounded to the second, or to the
 // millisecond when it is shorter than a second.
 func durationText(d time.Duration) string {
 // durationText returns d for a log line: rounded to the second, or to the
 // millisecond when it is shorter than a second.
 func durationText(d time.Duration) string {
index 8bc8c6e9c3f290a5af3579556e55792ccc305360..9f457927124674c505a377fdc6b264c1630be245 100644 (file)
@@ -98,6 +98,35 @@ func TestDaemonWritesNoFileForALongSocketPath(t *testing.T) {
        require.Empty(t, entries)
 }
 
        require.Empty(t, entries)
 }
 
+func TestDaemonLogHasATimeStampOnlyOutsideTheJournal(t *testing.T) {
+       testCases := []struct {
+               name    string
+               journal string
+               line    string
+       }{
+               {
+                       name:    "the journal adds the time itself",
+                       journal: "8:12345",
+                       line:    `^onechat: the agent is ready\n$`,
+               },
+               {
+                       name:    "a terminal gets the time in the line",
+                       journal: "",
+                       line:    `^onechat: \d{4}/\d\d/\d\d \d\d:\d\d:\d\d the agent is ready\n$`,
+               },
+       }
+
+       for _, tc := range testCases {
+               t.Run(tc.name, func(t *testing.T) {
+                       t.Setenv("JOURNAL_STREAM", tc.journal)
+
+                       var out strings.Builder
+                       daemonLogger(&out, "onechat: ").Print("the agent is ready")
+                       require.Regexp(t, tc.line, out.String())
+               })
+       }
+}
+
 // daemonStartTest serves a new agent on a socket in a temporary directory and
 // returns the socket path. The commands of the agent run in a temporary
 // directory.
 // daemonStartTest serves a new agent on a socket in a temporary directory and
 // returns the socket path. The commands of the agent run in a temporary
 // directory.
index d27274bfe416f589b58ff5e0b047e3df129f1246..f3dcfe247d720b08d6d60f9b2ea27fe15c52905f 100644 (file)
@@ -209,6 +209,14 @@ func (s *Schedules) List() string {
        return strings.Join(lines, "\n")
 }
 
        return strings.Join(lines, "\n")
 }
 
+// Len returns the number of schedules.
+func (s *Schedules) Len() int {
+       s.mutex.Lock()
+       defer s.mutex.Unlock()
+
+       return len(s.file.Schedules)
+}
+
 // Remove removes the schedule id and returns the answer of the tool.
 func (s *Schedules) Remove(id int) (string, error) {
        s.mutex.Lock()
 // Remove removes the schedule id and returns the answer of the tool.
 func (s *Schedules) Remove(id int) (string, error) {
        s.mutex.Lock()