From d036d44af66a8bc94a5a07817018d72634229ef6 Mon Sep 17 00:00:00 2001 From: Gold856 <117957790+Gold856@users.noreply.github.com> Date: Sat, 17 Aug 2024 03:45:16 -0400 Subject: [PATCH] [wpiutil, wpilib] Add FileLogger and log console to DataLog --- .../src/main/native/cpp/DataLogManager.cpp | 33 +++++++++ .../src/main/native/include/DataLogManager.h | 12 ++++ ntcoreffi/src/main/native/symbols.txt | 1 + .../src/main/native/cpp/DataLogManager.cpp | 31 ++++++++ .../main/native/include/frc/DataLogManager.h | 6 ++ .../edu/wpi/first/wpilibj/DataLogManager.java | 34 +++++++++ .../java/edu/wpi/first/util/FileLogger.java | 32 +++++++++ .../java/edu/wpi/first/util/WPIUtilJNI.java | 19 +++++ wpiutil/src/main/native/cpp/FileLogger.cpp | 37 ++++++++++ .../src/main/native/cpp/jni/WPIUtilJNI.cpp | 39 ++++++++++ .../src/main/native/include/wpi/FileLogger.h | 71 +++++++++++++++++++ 11 files changed, 315 insertions(+) create mode 100644 wpiutil/src/main/java/edu/wpi/first/util/FileLogger.java create mode 100644 wpiutil/src/main/native/cpp/FileLogger.cpp create mode 100644 wpiutil/src/main/native/include/wpi/FileLogger.h diff --git a/ntcoreffi/src/main/native/cpp/DataLogManager.cpp b/ntcoreffi/src/main/native/cpp/DataLogManager.cpp index d8543482fe6..d2cf3f3b491 100644 --- a/ntcoreffi/src/main/native/cpp/DataLogManager.cpp +++ b/ntcoreffi/src/main/native/cpp/DataLogManager.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -191,6 +192,8 @@ struct Thread final : public wpi::SafeThread { void StartNTLog(); void StopNTLog(); + void StartConsoleLog(); + void StopConsoleLog(); std::string m_logDir; bool m_filenameOverride; @@ -198,6 +201,8 @@ struct Thread final : public wpi::SafeThread { bool m_ntLoggerEnabled = false; NT_DataLogger m_ntEntryLogger = 0; NT_ConnectionDataLogger m_ntConnLogger = 0; + bool m_consoleLoggerEnabled = false; + wpi::FileLogger m_consoleLogger; wpi::log::StringLogEntry m_messageLog; }; @@ -452,6 +457,20 @@ void Thread::StopNTLog() { } } +void Thread::StartConsoleLog() { + if (!m_consoleLoggerEnabled) { + m_consoleLoggerEnabled = true; + m_consoleLogger = {"FRC_UserProgram.log", m_log, "output"}; + } +} + +void Thread::StopConsoleLog() { + if (m_consoleLoggerEnabled) { + m_consoleLoggerEnabled = false; + m_consoleLogger = {}; + } +} + Instance::Instance(std::string_view dir, std::string_view filename, double period) { // Delete all previously existing FRC_TBD_*.wpilog files. These only exist @@ -516,6 +535,16 @@ void DataLogManager::LogNetworkTables(bool enabled) { } } +void DataLogManager::LogConsoleOutput(bool enabled) { + if (auto thr = GetInstance().owner.GetThread()) { + if (enabled) { + thr->StartConsoleLog(); + } else if (!enabled) { + thr->StopConsoleLog(); + } + } +} + void DataLogManager::SignalNewDSDataOccur() { wpi::SetSignalObject(DriverStation::gNewDataEvent); } @@ -546,6 +575,10 @@ void DLM_LogNetworkTables(int enabled) { DataLogManager::LogNetworkTables(enabled); } +void DLM_LogConsoleOutput(int enabled) { + DataLogManager::LogConsoleOutput(enabled); +} + void DLM_SignalNewDSDataOccur(void) { DataLogManager::SignalNewDSDataOccur(); } diff --git a/ntcoreffi/src/main/native/include/DataLogManager.h b/ntcoreffi/src/main/native/include/DataLogManager.h index 79d73eaaec7..c0bd6e2b07a 100644 --- a/ntcoreffi/src/main/native/include/DataLogManager.h +++ b/ntcoreffi/src/main/native/include/DataLogManager.h @@ -89,6 +89,11 @@ class DataLogManager final { */ static void LogNetworkTables(bool enabled); + /** + * Enable or disable logging of the console output. Defaults to enabled. + * @param enabled true to enable, false to disable + */ + static void LogConsoleOutput(bool enabled); /** * Signal new DS data is available. */ @@ -152,6 +157,13 @@ const char* DLM_GetLogDir(void); */ void DLM_LogNetworkTables(int enabled); + + /** + * Enable or disable logging of the console output. Defaults to enabled. + * @param enabled true to enable, false to disable + */ +void DLM_LogConsoleOutput(int enabled); + /** * Signal new DS data is available. */ diff --git a/ntcoreffi/src/main/native/symbols.txt b/ntcoreffi/src/main/native/symbols.txt index aeacd0f849e..15668d45773 100644 --- a/ntcoreffi/src/main/native/symbols.txt +++ b/ntcoreffi/src/main/native/symbols.txt @@ -1,6 +1,7 @@ DLM_GetLog DLM_GetLogDir DLM_Log +DLM_LogConsoleOutput DLM_LogNetworkTables DLM_SignalNewDSDataOccur DLM_Start diff --git a/wpilibc/src/main/native/cpp/DataLogManager.cpp b/wpilibc/src/main/native/cpp/DataLogManager.cpp index dc9d3d73875..c72a40912c8 100644 --- a/wpilibc/src/main/native/cpp/DataLogManager.cpp +++ b/wpilibc/src/main/native/cpp/DataLogManager.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -37,6 +38,8 @@ struct Thread final : public wpi::SafeThread { void StartNTLog(); void StopNTLog(); + void StartConsoleLog(); + void StopConsoleLog(); std::string m_logDir; bool m_filenameOverride; @@ -44,6 +47,8 @@ struct Thread final : public wpi::SafeThread { bool m_ntLoggerEnabled = false; NT_DataLogger m_ntEntryLogger = 0; NT_ConnectionDataLogger m_ntConnLogger = 0; + bool m_consoleLoggerEnabled = false; + wpi::FileLogger m_consoleLogger; wpi::log::StringLogEntry m_messageLog; }; @@ -109,10 +114,12 @@ Thread::Thread(std::string_view dir, std::string_view filename, double period) m_log{dir, MakeLogFilename(filename), period}, m_messageLog{m_log, "messages"} { StartNTLog(); + StartConsoleLog(); } Thread::~Thread() { StopNTLog(); + StopConsoleLog(); } void Thread::Main() { @@ -297,6 +304,20 @@ void Thread::StopNTLog() { } } +void Thread::StartConsoleLog() { + if (!m_consoleLoggerEnabled) { + m_consoleLoggerEnabled = true; + m_consoleLogger = {"FRC_UserProgram.log", m_log, "output"}; + } +} + +void Thread::StopConsoleLog() { + if (m_consoleLoggerEnabled) { + m_consoleLoggerEnabled = false; + m_consoleLogger = {}; + } +} + Instance::Instance(std::string_view dir, std::string_view filename, double period) { // Delete all previously existing FRC_TBD_*.wpilog files. These only exist @@ -360,3 +381,13 @@ void DataLogManager::LogNetworkTables(bool enabled) { } } } + +void DataLogManager::LogConsoleOutput(bool enabled) { + if (auto thr = GetInstance().owner.GetThread()) { + if (enabled) { + thr->StartConsoleLog(); + } else if (!enabled) { + thr->StopConsoleLog(); + } + } +} diff --git a/wpilibc/src/main/native/include/frc/DataLogManager.h b/wpilibc/src/main/native/include/frc/DataLogManager.h index f171cd380a6..a624b5a0120 100644 --- a/wpilibc/src/main/native/include/frc/DataLogManager.h +++ b/wpilibc/src/main/native/include/frc/DataLogManager.h @@ -85,6 +85,12 @@ class DataLogManager final { * @param enabled true to enable, false to disable */ static void LogNetworkTables(bool enabled); + + /** + * Enable or disable logging of the console output. Defaults to enabled. + * @param enabled true to enable, false to disable + */ + static void LogConsoleOutput(bool enabled); }; } // namespace frc diff --git a/wpilibj/src/main/java/edu/wpi/first/wpilibj/DataLogManager.java b/wpilibj/src/main/java/edu/wpi/first/wpilibj/DataLogManager.java index 3b6bbd61c6e..86285d2e285 100644 --- a/wpilibj/src/main/java/edu/wpi/first/wpilibj/DataLogManager.java +++ b/wpilibj/src/main/java/edu/wpi/first/wpilibj/DataLogManager.java @@ -5,6 +5,7 @@ package edu.wpi.first.wpilibj; import edu.wpi.first.networktables.NetworkTableInstance; +import edu.wpi.first.util.FileLogger; import edu.wpi.first.util.WPIUtilJNI; import edu.wpi.first.util.concurrent.Event; import edu.wpi.first.util.datalog.DataLog; @@ -52,6 +53,8 @@ public final class DataLogManager { private static boolean m_ntLoggerEnabled = true; private static int m_ntEntryLogger; private static int m_ntConnLogger; + private static boolean m_consoleLoggerEnabled = true; + private static FileLogger m_consoleLogger; private static StringLogEntry m_messageLog; // if less than this much free space, delete log files until there is this much free space @@ -121,6 +124,10 @@ public static synchronized void start(String dir, String filename, double period if (m_ntLoggerEnabled) { startNtLog(); } + // Log console output + if (m_consoleLoggerEnabled) { + startConsoleLog(); + } } else if (m_stopped) { m_log.setFilename(makeLogFilename(filename)); m_log.resume(); @@ -205,6 +212,25 @@ public static synchronized void logNetworkTables(boolean enabled) { } } + /** + * Enable or disable logging of the console output. Defaults to enabled. + * + * @param enabled true to enable, false to disable + */ + public static synchronized void logConsoleOutput(boolean enabled) { + boolean wasEnabled = m_consoleLoggerEnabled; + m_consoleLoggerEnabled = enabled; + if (m_log == null) { + start(); + return; + } + if (enabled && !wasEnabled) { + startConsoleLog(); + } else if (!enabled && wasEnabled) { + stopConsoleLog(); + } + } + private static String makeLogDir(String dir) { if (!dir.isEmpty()) { return dir; @@ -266,6 +292,14 @@ private static void stopNtLog() { NetworkTableInstance.stopConnectionDataLog(m_ntConnLogger); } + private static void startConsoleLog() { + m_consoleLogger = new FileLogger("FRC_UserProgram.log", m_log, "console"); + } + + private static void stopConsoleLog() { + m_consoleLogger.close(); + } + private static void logMain() { // based on free disk space, scan for "old" FRC_*.wpilog files and remove { diff --git a/wpiutil/src/main/java/edu/wpi/first/util/FileLogger.java b/wpiutil/src/main/java/edu/wpi/first/util/FileLogger.java new file mode 100644 index 00000000000..5515090f99b --- /dev/null +++ b/wpiutil/src/main/java/edu/wpi/first/util/FileLogger.java @@ -0,0 +1,32 @@ +// Copyright (c) FIRST and other WPILib contributors. +// Open Source Software; you can modify and/or share it under the terms of +// the WPILib BSD license file in the root directory of this project. + +package edu.wpi.first.util; + +import edu.wpi.first.util.datalog.DataLog; + +/** + * A class version of `tail -f`, otherwise known as `tail -f` at home. Watches a file and puts the + * data into a data log. Only works on Linux-based platforms. + */ +public class FileLogger implements AutoCloseable { + private final long m_impl; + + /** + * Construct a FileLogger. When the specified file is modified, appended data will be appended to + * the specified data log. + * + * @param file The path to the file. + * @param log A data log. + * @param key The log key to append data to. + */ + public FileLogger(String file, DataLog log, String key) { + m_impl = WPIUtilJNI.createFileLogger(file, log, key); + } + + @Override + public void close() { + WPIUtilJNI.freeFileLogger(m_impl); + } +} diff --git a/wpiutil/src/main/java/edu/wpi/first/util/WPIUtilJNI.java b/wpiutil/src/main/java/edu/wpi/first/util/WPIUtilJNI.java index 58e69c34bc2..db11a4b196e 100644 --- a/wpiutil/src/main/java/edu/wpi/first/util/WPIUtilJNI.java +++ b/wpiutil/src/main/java/edu/wpi/first/util/WPIUtilJNI.java @@ -4,6 +4,7 @@ package edu.wpi.first.util; +import edu.wpi.first.util.datalog.DataLog; import java.io.IOException; import java.nio.ByteBuffer; import java.util.concurrent.atomic.AtomicBoolean; @@ -217,6 +218,24 @@ public static native boolean waitForObjectTimeout(int handle, double timeout) public static native int[] waitForObjectsTimeout(int[] handles, double timeout) throws InterruptedException; + /** + * Create a native FileLogger. When the specified file is modified, appended data will be appended + * to the specified data log. + * + * @param file path to the file + * @param log data log + * @param key log key to append data to + * @return The FileLogger handle. + */ + public static native long createFileLogger(String file, DataLog log, String key); + + /** + * Free a native FileLogger. This causes the FileLogger to stop appending data to the log. + * + * @param fileTail The FileLogger handle. + */ + public static native void freeFileLogger(long fileTail); + /** Utility class. */ protected WPIUtilJNI() {} } diff --git a/wpiutil/src/main/native/cpp/FileLogger.cpp b/wpiutil/src/main/native/cpp/FileLogger.cpp new file mode 100644 index 00000000000..ca43a3334a4 --- /dev/null +++ b/wpiutil/src/main/native/cpp/FileLogger.cpp @@ -0,0 +1,37 @@ +// Copyright (c) FIRST and other WPILib contributors. +// Open Source Software; you can modify and/or share it under the terms of +// the WPILib BSD license file in the root directory of this project. + +#include "wpi/FileLogger.h" + +namespace wpi { +FileLogger::FileLogger(std::string_view file, + std::function callback) +#ifdef __linux__ + : m_fileHandle{open(file.data(), O_RDONLY)}, + m_inotifyHandle{inotify_init()}, + m_inotifyWatchHandle{ + inotify_add_watch(m_inotifyHandle, file.data(), IN_MODIFY)}, + m_thread{[=, this] { + char buf[4000]; + struct inotify_event ev; + int len = 0; + while ((len = read(m_inotifyHandle, &ev, sizeof(ev))) > 0) { + int bufLen = 0; + if ((bufLen = read(m_fileHandle, buf, sizeof(buf)) > 0)) { + callback(std::string_view{buf, bufLen}); + } + } + }} +#endif +{ +} +FileLogger::~FileLogger() { +#ifdef __linux__ + inotify_rm_watch(m_inotifyHandle, m_inotifyWatchHandle); + close(m_inotifyHandle); + close(m_fileHandle); + m_thread.join(); +#endif +} +} // namespace wpi diff --git a/wpiutil/src/main/native/cpp/jni/WPIUtilJNI.cpp b/wpiutil/src/main/native/cpp/jni/WPIUtilJNI.cpp index 54b6da26712..15b78958acd 100644 --- a/wpiutil/src/main/native/cpp/jni/WPIUtilJNI.cpp +++ b/wpiutil/src/main/native/cpp/jni/WPIUtilJNI.cpp @@ -7,6 +7,8 @@ #include #include "edu_wpi_first_util_WPIUtilJNI.h" +#include "wpi/DataLog.h" +#include "wpi/FileLogger.h" #include "wpi/RawFrame.h" #include "wpi/Synchronization.h" #include "wpi/jni_util.h" @@ -414,4 +416,41 @@ Java_edu_wpi_first_util_WPIUtilJNI_setRawFrameInfo f->pixelFormat = pixelFormat; } +/* + * Class: edu_wpi_first_util_WPIUtilJNI + * Method: createFileLogger + * Signature: (Ljava/lang/String;JLjava/lang/String;)J + */ +JNIEXPORT jlong JNICALL +Java_edu_wpi_first_util_WPIUtilJNI_createFileLogger + (JNIEnv* env, jclass, jstring file, jlong log, jstring key) +{ + if (!file) { + wpi::ThrowNullPointerException(env, "file is null"); + return 0; + } + auto* f = reinterpret_cast(log); + if (!f) { + wpi::ThrowNullPointerException(env, "log is null"); + return 0; + } + if (!key) { + wpi::ThrowNullPointerException(env, "key is null"); + return 0; + } + return reinterpret_cast( + new wpi::FileLogger{JStringRef{env, file}, *f, JStringRef{env, key}}); +} + +/* + * Class: edu_wpi_first_util_WPIUtilJNI + * Method: freeFileLogger + * Signature: (J)V + */ +JNIEXPORT void JNICALL +Java_edu_wpi_first_util_WPIUtilJNI_freeFileLogger + (JNIEnv* env, jclass, jlong fileTail) +{ + delete reinterpret_cast(fileTail); +} } // extern "C" diff --git a/wpiutil/src/main/native/include/wpi/FileLogger.h b/wpiutil/src/main/native/include/wpi/FileLogger.h new file mode 100644 index 00000000000..c6c136969be --- /dev/null +++ b/wpiutil/src/main/native/include/wpi/FileLogger.h @@ -0,0 +1,71 @@ +// Copyright (c) FIRST and other WPILib contributors. +// Open Source Software; you can modify and/or share it under the terms of +// the WPILib BSD license file in the root directory of this project. + +#pragma once + +#ifdef __linux__ +#include +#include +#include +#endif + +#include +#include +#include +#include + +#include "wpi/DataLog.h" + +namespace wpi { +/** + * A class version of `tail -f`, otherwise known as `tail -f` at home. Watches + * a file and puts the data somewhere else. Only works on Linux-based platforms. + */ +class FileLogger { + public: + FileLogger() = default; + /** + * Construct a FileLogger. When the specified file is modified, the callback + * will be called with the appended changes. + * + * @param file The path to the file. + * @param callback A callback that accepts the appended file data. + */ + FileLogger(std::string_view file, + std::function callback); + + /** + * Construct a FileLogger. When the specified file is modified, appended data + * will be appended to the specified data log. + * + * @param file The path to the file. + * @param log A data log. + * @param key The log key to append data to. + */ + FileLogger(std::string_view file, log::DataLog& log, std::string_view key) + : FileLogger(file, [entry = log.Start(key, "string"), + &log](std::string_view data) { + log.AppendString(entry, data, 0); + }) {} + FileLogger(FileLogger&& fileLogger) = default; + FileLogger& operator=(FileLogger&& rhs) { +#ifdef __linux__ + m_fileHandle = rhs.m_fileHandle; + m_inotifyHandle = rhs.m_inotifyHandle; + m_inotifyWatchHandle = rhs.m_inotifyWatchHandle; + m_thread = std::move(rhs.m_thread); +#endif + return *this; + } + ~FileLogger(); + + private: +#ifdef __linux__ + int m_fileHandle; + int m_inotifyHandle; + int m_inotifyWatchHandle; + std::thread m_thread; +#endif +}; +} // namespace wpi