Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create FileLogger JNI #1517

Merged
merged 7 commits into from
Nov 7, 2024
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/*
* Copyright (C) Photon Vision.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

package org.photonvision.common.logging;

import edu.wpi.first.util.RuntimeDetector;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import org.photonvision.common.util.TimedTaskManager;
import org.photonvision.jni.QueuedFileLogger;

/**
* Listens for and reproduces Linux kernel logs, from /var/log/kern.log, into the Photon logger
* ecosystem
*/
public class KernelLogLogger {
private static KernelLogLogger INSTANCE;

public static KernelLogLogger getInstance() {
if (INSTANCE == null) {
INSTANCE = new KernelLogLogger();
}
return INSTANCE;
}

QueuedFileLogger listener = null;
Logger logger = new Logger(KernelLogLogger.class, LogGroup.General);

public KernelLogLogger() {
if (RuntimeDetector.isLinux()) {
logger.info("Listening for klogs on /var/log/dmesg ! Boot logs:");

try {
var bootlog = Files.readAllLines(Path.of("/var/log/dmesg"));
for (var line : bootlog) {
logger.log(line, LogLevel.DEBUG);
}
} catch (IOException e) {
logger.error("Couldn't read /var/log/dmesg - not printing boot logs");
}

listener = new QueuedFileLogger("/var/log/kern.log");
} else {
System.out.println("NOT for klogs");
}

// arbitrary frequency to grab logs. The underlying native buffer will grow unbounded without
// this, lol
TimedTaskManager.getInstance().addTask("outputPrintk", this::outputNewPrintks, 1000);
}

public void outputNewPrintks() {
for (var msg : listener.getNewlines()) {
// We currently set all logs to debug regardless of their actual level
logger.log(msg, LogLevel.DEBUG);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,5 @@ public enum LogGroup {
Config,
CSCore,
NetworkTables,
System,
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,34 @@
import org.photonvision.common.dataflow.events.OutgoingUIEvent;
import org.photonvision.common.util.TimedTaskManager;

@SuppressWarnings("unused")
/** TODO: get rid of static {} blocks and refactor to singleton pattern */
public class Logger {
private static final HashMap<LogGroup, LogLevel> levelMap = new HashMap<>();
private static final List<LogAppender> currentAppenders = new ArrayList<>();

private static final UILogAppender uiLogAppender = new UILogAppender();

// // TODO why's the logger care about this? split it out
// private static KernelLogLogger klogListener = null;

static {
levelMap.put(LogGroup.Camera, LogLevel.INFO);
levelMap.put(LogGroup.General, LogLevel.INFO);
levelMap.put(LogGroup.WebServer, LogLevel.INFO);
levelMap.put(LogGroup.Data, LogLevel.INFO);
levelMap.put(LogGroup.VisionModule, LogLevel.INFO);
levelMap.put(LogGroup.Config, LogLevel.INFO);
levelMap.put(LogGroup.CSCore, LogLevel.TRACE);
levelMap.put(LogGroup.NetworkTables, LogLevel.DEBUG);
levelMap.put(LogGroup.System, LogLevel.DEBUG);

currentAppenders.add(new ConsoleLogAppender());
currentAppenders.add(uiLogAppender);
addFileAppender(PathManager.getInstance().getLogPath());

cleanLogs(PathManager.getInstance().getLogsDir());
}

public static final String ANSI_RESET = "\u001B[0m";
public static final String ANSI_BLACK = "\u001B[30m";
public static final String ANSI_RED = "\u001B[31m";
Expand All @@ -50,8 +76,6 @@ public class Logger {
private static final List<Pair<String, LogLevel>> uiBacklog = new ArrayList<>();
private static boolean connected = false;

private static final UILogAppender uiLogAppender = new UILogAppender();

private final String className;
private final LogGroup group;

Expand Down Expand Up @@ -89,27 +113,6 @@ public static String format(
return builder.toString();
}

private static final HashMap<LogGroup, LogLevel> levelMap = new HashMap<>();
private static final List<LogAppender> currentAppenders = new ArrayList<>();

static {
levelMap.put(LogGroup.Camera, LogLevel.INFO);
levelMap.put(LogGroup.General, LogLevel.INFO);
levelMap.put(LogGroup.WebServer, LogLevel.INFO);
levelMap.put(LogGroup.Data, LogLevel.INFO);
levelMap.put(LogGroup.VisionModule, LogLevel.INFO);
levelMap.put(LogGroup.Config, LogLevel.INFO);
levelMap.put(LogGroup.CSCore, LogLevel.TRACE);
levelMap.put(LogGroup.NetworkTables, LogLevel.DEBUG);
}

static {
currentAppenders.add(new ConsoleLogAppender());
currentAppenders.add(uiLogAppender);
addFileAppender(PathManager.getInstance().getLogPath());
cleanLogs(PathManager.getInstance().getLogsDir());
}

@SuppressWarnings("ResultOfMethodCallIgnored")
public static void addFileAppender(Path logFilePath) {
var file = logFilePath.toFile();
Expand Down
3 changes: 3 additions & 0 deletions photon-server/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ apply from: "${rootDir}/shared/common.gradle"
dependencies {
implementation project(':photon-core')

// Zip
implementation 'org.zeroturnaround:zt-zip:1.14'

// Needed for Javalin Runtime Logging
implementation "org.slf4j:slf4j-simple:2.0.7"
}
Expand Down
5 changes: 5 additions & 0 deletions photon-server/src/main/java/org/photonvision/Main.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
import org.photonvision.common.hardware.HardwareManager;
import org.photonvision.common.hardware.PiVersion;
import org.photonvision.common.hardware.Platform;
import org.photonvision.common.logging.KernelLogLogger;
import org.photonvision.common.logging.LogGroup;
import org.photonvision.common.logging.LogLevel;
import org.photonvision.common.logging.Logger;
Expand Down Expand Up @@ -437,6 +438,10 @@ public static void main(String[] args) {
Logger.setLevel(LogGroup.General, logLevel);
logger.info("Logging initialized in debug mode.");

// Add Linux kernel log->Photon logger
KernelLogLogger.getInstance();

// Add CSCore->Photon logger
PvCSCoreLogger.getInstance();

logger.debug("Loading ConfigManager...");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
import org.photonvision.vision.calibration.CameraCalibrationCoefficients;
import org.photonvision.vision.camera.CameraQuirk;
import org.photonvision.vision.processes.VisionModuleManager;
import org.zeroturnaround.zip.ZipUtil;

public class RequestHandler {
// Treat all 2XX calls as "INFO"
Expand Down Expand Up @@ -422,20 +423,34 @@ public static void onLogExportRequest(Context ctx) {
try {
ShellExec shell = new ShellExec();
var tempPath = Files.createTempFile("photonvision-journalctl", ".txt");
shell.executeBashCommand("journalctl -u photonvision.service > " + tempPath.toAbsolutePath());
var tempPath2 = Files.createTempFile("photonvision-kernelogs", ".txt");
shell.executeBashCommand(
"journalctl -u photonvision.service > "
+ tempPath.toAbsolutePath()
+ " && journalctl -k > "
+ tempPath2.toAbsolutePath());

while (!shell.isOutputCompleted()) {
// TODO: add timeout
}

if (shell.getExitCode() == 0) {
// Wrote to the temp file! Add it to the ctx
var stream = new FileInputStream(tempPath.toFile());
ctx.contentType("text/plain");
ctx.header("Content-Disposition", "attachment; filename=\"photonvision-journalctl.txt\"");
ctx.status(200);
// Wrote to the temp file! Zip and yeet it to the client

var out = Files.createTempFile("photonvision-logs", "zip").toFile();

try {
ZipUtil.packEntries(new File[] {tempPath.toFile(), tempPath2.toFile()}, out);
} catch (Exception e) {
e.printStackTrace();
}

var stream = new FileInputStream(out);
ctx.contentType("application/zip");
ctx.header("Content-Disposition", "attachment; filename=\"photonvision-logs.zip\"");
ctx.result(stream);
logger.info("Uploading settings with size " + stream.available());
ctx.status(200);
logger.info("Outputting log ZIP with size " + stream.available());
} else {
ctx.status(500);
ctx.result("The journalctl service was unable to export logs");
Expand Down
3 changes: 3 additions & 0 deletions photon-targeting/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -220,3 +220,6 @@ nativeConfig.dependencies.add wpilibTools.deps.wpilib("wpimath")
nativeConfig.dependencies.add wpilibTools.deps.wpilib("wpinet")
nativeConfig.dependencies.add wpilibTools.deps.wpilib("ntcore")
nativeConfig.dependencies.add wpilibTools.deps.wpilib("hal")
nativeConfig.dependencies.add wpilibTools.deps.wpilib("cscore")
nativeConfig.dependencies.add wpilibTools.deps.wpilibOpenCv("frc" + openCVYear, wpi.versions.opencvVersion.get())
nativeConfig.dependencies.add wpilibTools.deps.wpilib("apriltag")
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
* Copyright (C) Photon Vision.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

package org.photonvision.jni;

public class QueuedFileLogger {
long m_handle = 0;

public QueuedFileLogger(String path) {
m_handle = QueuedFileLogger.create(path);
}

public String[] getNewlines() {
String newBuffer = null;

synchronized (this) {
if (m_handle == 0) {
System.err.println("QueuedFileLogger use after free");
return new String[0];
}

newBuffer = QueuedFileLogger.getNewLines(m_handle);
}

if (newBuffer == null) {
return new String[0];
}

return newBuffer.split("\n");
}

public void stop() {
synchronized (this) {
if (m_handle != 0) {
QueuedFileLogger.destroy(m_handle);
m_handle = 0;
}
}
}

private static native long create(String path);

private static native void destroy(long handle);

private static native String getNewLines(long handle);
}
Loading
Loading