001// Copyright (c) FIRST and other WPILib contributors. 002// Open Source Software; you can modify and/or share it under the terms of 003// the WPILib BSD license file in the root directory of this project. 004 005package edu.wpi.first.epilogue.logging; 006 007import edu.wpi.first.epilogue.CustomLoggerFor; 008import edu.wpi.first.epilogue.logging.errors.ErrorHandler; 009import edu.wpi.first.util.sendable.Sendable; 010import edu.wpi.first.util.sendable.SendableBuilder; 011import java.util.LinkedHashMap; 012import java.util.Map; 013 014/** 015 * Base class for class-specific generated loggers. Loggers are generated at compile time by the 016 * Epilogue annotation processor and are used at runtime for zero-overhead data logging. Users may 017 * also declare custom loggers, annotated with {@link CustomLoggerFor @CustomLoggerFor}, for 018 * Epilogue to pull in during compile time to use for logging third party types. 019 * 020 * @param <T> the type of data supported by the logger 021 */ 022@SuppressWarnings("unused") // Used by generated subclasses 023public abstract class ClassSpecificLogger<T> { 024 private final Class<T> m_clazz; 025 // TODO: This will hold onto Sendables that are otherwise no longer referenced by a robot program. 026 // Determine if that's a concern 027 // Linked hashmap to maintain insert order 028 private final Map<Sendable, SendableBuilder> m_sendables = new LinkedHashMap<>(); 029 030 private boolean m_disabled = false; 031 032 /** 033 * Instantiates the logger. 034 * 035 * @param clazz the Java class of objects that can be logged 036 */ 037 protected ClassSpecificLogger(Class<T> clazz) { 038 this.m_clazz = clazz; 039 } 040 041 /** 042 * Updates an object's fields in a data log. 043 * 044 * @param backend the backend to update 045 * @param object the object to update in the log 046 */ 047 protected abstract void update(EpilogueBackend backend, T object); 048 049 /** 050 * Attempts to update the data log. Will do nothing if the logger is {@link #disable() disabled}. 051 * 052 * @param backend the backend to log data to 053 * @param object the data object to log 054 * @param errorHandler the handler to use if logging raised an exception 055 */ 056 @SuppressWarnings("PMD.AvoidCatchingGenericException") 057 public final void tryUpdate(EpilogueBackend backend, T object, ErrorHandler errorHandler) { 058 if (m_disabled) { 059 return; 060 } 061 062 try { 063 update(backend, object); 064 } catch (Exception e) { 065 errorHandler.handle(e, this); 066 } 067 } 068 069 /** 070 * Checks if this logger has been disabled. 071 * 072 * @return true if this logger has been disabled by {@link #disable()}, false if not 073 */ 074 public final boolean isDisabled() { 075 return m_disabled; 076 } 077 078 /** Disables this logger. Any log calls made while disabled will be ignored. */ 079 public final void disable() { 080 m_disabled = true; 081 } 082 083 /** Reenables this logger after being disabled. Has no effect if the logger is already enabled. */ 084 public final void reenable() { 085 m_disabled = false; 086 } 087 088 /** 089 * Gets the type of the data this logger accepts. 090 * 091 * @return the logged data type 092 */ 093 public final Class<T> getLoggedType() { 094 return m_clazz; 095 } 096 097 /** 098 * Logs a sendable type. 099 * 100 * @param backend the backend to log data into 101 * @param sendable the sendable object to log 102 */ 103 protected void logSendable(EpilogueBackend backend, Sendable sendable) { 104 if (sendable == null) { 105 return; 106 } 107 108 if (m_sendables.containsKey(sendable)) { 109 m_sendables.get(sendable).update(); 110 } else { 111 var builder = new LogBackedSendableBuilder(backend); 112 sendable.initSendable(builder); 113 m_sendables.put(sendable, builder); 114 builder.update(); 115 } 116 } 117}