blob: 071b93198675fe01578bdbbd6249be05b09e22ea [file] [log] [blame]
// Copyright 2019 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package syslog
import (
"context"
"errors"
"fmt"
"io"
"golang.org/x/crypto/ssh"
"go.fuchsia.dev/fuchsia/tools/lib/logger"
"go.fuchsia.dev/fuchsia/tools/lib/runner"
"go.fuchsia.dev/fuchsia/tools/net/sshutil"
)
// The program on fuchsia used to stream system logs through a shell, not to be confused
// with the zircon host tool "loglistener" (no underscore) used to stream zircon-level logs to host.
const logListener = "/bin/log_listener"
// Syslogger streams systems logs from a Fuchsia instance.
type Syslogger struct {
r sshRunner
}
type sshRunner interface {
Run(context.Context, []string, io.Writer, io.Writer) error
Reconnect(context.Context) error
Close() error
}
// NewSyslogger creates a new Syslogger, given an SSH session with a Fuchsia instance.
func NewSyslogger(client *ssh.Client, config *ssh.ClientConfig) *Syslogger {
return &Syslogger{r: runner.NewSSHRunner(client, config)}
}
// Stream writes system logs to a given writer; it blocks until the stream is
// is terminated or a Done is signaled. The syslogger streams from the very
// beggining of the system's uptime.
func (s *Syslogger) Stream(ctx context.Context, output io.Writer) error {
for {
cmd := []string{logListener}
// Note: Fuchsia's log_listener does not write to stderr.
err := s.r.Run(ctx, cmd, output, nil)
// We need not attempt to reconnect if the context was canceled or if we
// hit an error unrelated to the connection.
if err == nil || ctx.Err() != nil || !errors.Is(err, sshutil.ConnectionError) {
return err
}
logger.Errorf(ctx, "syslog: SSH client unresponsive; will attempt to reconnect and continue streaming: %v", err)
if err := s.r.Reconnect(ctx); err != nil {
err = fmt.Errorf("syslog: failed to refresh SSH client: %v", err)
return err
}
io.WriteString(output, "\n\n<< SYSLOG STREAM INTERRUPTED; RECONNECTING NOW >>\n\n")
}
}
// Close tidies up the system logging session with the corresponding fuchsia instance.
func (s *Syslogger) Close() error {
return s.r.Close()
}