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 6 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
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 @@ -17,7 +17,9 @@

package org.photonvision.common.logging;

import edu.wpi.first.util.RuntimeDetector;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.text.ParseException;
import java.text.SimpleDateFormat;
Expand All @@ -29,9 +31,40 @@
import org.photonvision.common.dataflow.DataChangeService;
import org.photonvision.common.dataflow.events.OutgoingUIEvent;
import org.photonvision.common.util.TimedTaskManager;
import org.photonvision.jni.QueuedFileLogger;

@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 KernelLogListener 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 void addKlongListener() {
mcm001 marked this conversation as resolved.
Show resolved Hide resolved
klogListener = new KernelLogListener();
}

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 +83,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 +120,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 Expand Up @@ -288,6 +298,38 @@ private static String convertStackTraceToString(Throwable throwable) {
}
}

private static class KernelLogListener {
QueuedFileLogger listener = null;
Logger logger = new Logger(KernelLogListener.class, LogGroup.General);

public KernelLogListener() {
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");
}

TimedTaskManager.getInstance().addTask("outputPrintk", this::outputNewPrintks, 1000);
}

public void outputNewPrintks() {
for (var msg : listener.getNewlines()) {
logger.log(msg, LogLevel.DEBUG);
}
}
}

private interface LogAppender {
void log(String message, LogLevel level);

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
4 changes: 4 additions & 0 deletions photon-server/src/main/java/org/photonvision/Main.java
Original file line number Diff line number Diff line change
Expand Up @@ -437,6 +437,10 @@ public static void main(String[] args) {
Logger.setLevel(LogGroup.General, logLevel);
logger.info("Logging initialized in debug mode.");

// after native libraries are loaded :(
Logger.addKlongListener();
mcm001 marked this conversation as resolved.
Show resolved Hide resolved

// 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);
}
103 changes: 103 additions & 0 deletions photon-targeting/src/main/native/jni/FileLoggerExtrasJNI.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
/*
* 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/>.
*/

#include <functional>
#include <string>
#include <vector>

#include <wpi/FileLogger.h>

#include "jni_utils.h"
#include "org_photonvision_jni_QueuedFileLogger.h"

struct QueuedFileLogger {
// ew ew ew ew ew ew ew ew
std::vector<char> m_data{};

std::mutex m_mutex;

wpi::FileLogger logger;

explicit QueuedFileLogger(std::string_view file)
: logger{file, std::bind(&QueuedFileLogger::callback, this,
std::placeholders::_1)} {
// fmt::println("Watching {}", file);
}

void callback(std::string_view newline) {
std::lock_guard lock{m_mutex};
// fmt::println("FileLogger got: {}", newline);
m_data.insert(m_data.end(), newline.begin(), newline.end());
}

std::vector<char> SwapData() {
std::vector<char> ret;
{
std::lock_guard lock{m_mutex};
ret.swap(m_data);
}

return ret;
}
};

extern "C" {

/*
* Class: org_photonvision_jni_QueuedFileLogger
* Method: create
* Signature: (Ljava/lang/String;)J
*/
JNIEXPORT jlong JNICALL
Java_org_photonvision_jni_QueuedFileLogger_create
(JNIEnv* env, jclass, jstring name)
{
const char* c_name{env->GetStringUTFChars(name, 0)};
std::string cpp_name{c_name};
jlong ret{reinterpret_cast<jlong>(new QueuedFileLogger(cpp_name))};
env->ReleaseStringUTFChars(name, c_name);
return ret;
}

/*
* Class: org_photonvision_jni_QueuedFileLogger
* Method: destroy
* Signature: (J)V
*/
JNIEXPORT void JNICALL
Java_org_photonvision_jni_QueuedFileLogger_destroy
(JNIEnv*, jclass, jlong handle)
{
CHECK_PTR(handle);
delete reinterpret_cast<QueuedFileLogger*>(handle);
}

/*
* Class: org_photonvision_jni_QueuedFileLogger
* Method: getNewLines
* Signature: (J)Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL
Java_org_photonvision_jni_QueuedFileLogger_getNewLines
(JNIEnv* env, jclass, jlong handle)
{
CHECK_PTR_RETURN(handle, nullptr);
QueuedFileLogger* logger = reinterpret_cast<QueuedFileLogger*>(handle);

return env->NewStringUTF(logger->SwapData().data());
}
} // extern "C"
12 changes: 1 addition & 11 deletions photon-targeting/src/main/native/jni/TimeSyncClientJNI.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,21 +20,11 @@
#include <cstdio>
#include <string>

#include "jni_utils.h"
#include "net/TimeSyncClient.h"

using namespace wpi::tsp;

#define CHECK_PTR(ptr) \
if (!ptr) { \
fmt::println("Got invalid pointer?? {}:{}", __FILE__, __LINE__); \
return; \
}
#define CHECK_PTR_RETURN(ptr, default) \
if (!ptr) { \
fmt::println("Got invalid pointer?? {}:{}", __FILE__, __LINE__); \
return default; \
}

/**
* Finds a class and keeps it as a global reference.
*
Expand Down
Loading
Loading