Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions logging.m
Original file line number Diff line number Diff line change
Expand Up @@ -206,4 +206,9 @@ void IPALoggingInit(const char *subsystem) {
}

g_logSandbox = [logsDir stringByAppendingPathComponent:filename];
#ifndef FINAL_RELEASE
// Let the log server replay the tail of this file to clients that connect
// after launch — they see history instead of only the live stream.
IPALogServerSetReplayPath(g_logSandbox);
#endif
}
7 changes: 7 additions & 0 deletions logserver.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,13 @@
// subsequent calls after the first are silent no-ops.
void IPALogServerStart(uint16_t port);

// Register the sandbox log file path for replay-on-connect.
// Must be called after IPALogServerStart(). When a new client connects,
// the server tails the last IPA_LOG_REPLAY_BYTES (default 100 KB) of this
// file and sends them before switching to the live stream. Safe to call
// with nil to disable replay.
void IPALogServerSetReplayPath(NSString *path);

// Broadcast a log line to all connected clients. Called from IPALog().
// line must be non-nil; the implementation appends a trailing newline if
// absent. Returns immediately when no clients are connected.
Expand Down
61 changes: 61 additions & 0 deletions logserver.m
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,12 @@
#define LOG_SERVER_RECV_CHUNK 256
#define LOG_SERVER_QUEUE_DEPTH_MAX 256

// How many bytes from the end of the log file to replay to a new client.
// Aligned up to the next newline boundary so no partial lines are sent.
#ifndef IPA_LOG_REPLAY_BYTES
#define IPA_LOG_REPLAY_BYTES (100 * 1024)
#endif

// ---------------------------------------------------------------------------
// Module state.
// ---------------------------------------------------------------------------
Expand All @@ -53,6 +59,10 @@
static _Atomic uint32_t g_dropped = 0;
static _Atomic uint32_t g_pending = 0;

// Log file path for replay-on-connect. Set via IPALogServerSetReplayPath().
// Accessed on g_queue only.
static NSString *g_replayPath = nil;

// ---------------------------------------------------------------------------
// Socket helpers.
// ---------------------------------------------------------------------------
Expand Down Expand Up @@ -85,6 +95,49 @@ static BOOL ls_send_all(int fd, const uint8_t *buf, size_t len) {
return YES;
}

// ---------------------------------------------------------------------------
// Replay history — send the tail of the log file to a freshly connected
// client, then return so the caller can add it to the live-stream set.
// Runs on g_queue so g_replayPath access is safe.
// ---------------------------------------------------------------------------
static void ls_replay_to(int fd) {
if (!g_replayPath) return;

int fileFd = open(g_replayPath.UTF8String, O_RDONLY | O_NONBLOCK);
if (fileFd < 0) return;

off_t fileSize = lseek(fileFd, 0, SEEK_END);
if (fileSize <= 0) { close(fileFd); return; }

// Start IPA_LOG_REPLAY_BYTES before EOF (or at BOF if file is smaller).
off_t startOff = (fileSize > (off_t)IPA_LOG_REPLAY_BYTES)
? fileSize - (off_t)IPA_LOG_REPLAY_BYTES
: 0;

if (lseek(fileFd, startOff, SEEK_SET) < 0) { close(fileFd); return; }

// Skip forward to the next newline so we don't send a partial first line.
if (startOff > 0) {
char ch;
while (read(fileFd, &ch, 1) == 1 && ch != '\n') {}
}

// Stream the rest to the client in chunks.
uint8_t buf[4096];
ssize_t n;
while ((n = read(fileFd, buf, sizeof(buf))) > 0) {
const uint8_t *p = buf;
size_t rem = (size_t)n;
while (rem > 0) {
ssize_t sent = send(fd, p, rem, 0);
if (sent <= 0) { close(fileFd); return; }
p += sent;
rem -= (size_t)sent;
}
Comment on lines +131 to +136
}
close(fileFd);
}

// ---------------------------------------------------------------------------
// Client management — must be called on g_queue.
// ---------------------------------------------------------------------------
Expand All @@ -96,6 +149,9 @@ static void ls_add_client(int fd) {
close(fd);
return;
}
// Replay history before admitting to the live stream so the client sees
// events that happened before it connected.
ls_replay_to(fd);
g_clients[g_clientCount++] = fd;
Comment on lines +152 to 155
IPALog([NSString stringWithFormat:
@"[LOGSVR] client connected fd=%d (%d/%d)",
Expand Down Expand Up @@ -148,6 +204,11 @@ static void ls_handle_accept(void) {
// ---------------------------------------------------------------------------
// Public API.
// ---------------------------------------------------------------------------
void IPALogServerSetReplayPath(NSString *path) {
if (!g_queue) return;
dispatch_async(g_queue, ^{ g_replayPath = [path copy]; });
}

void IPALogServerStart(uint16_t port) {
if (g_listenFd >= 0) return; // already running

Expand Down