diff --git a/backend/cmd/main.go b/backend/cmd/main.go index 3aa50883b..1cdbafefc 100644 --- a/backend/cmd/main.go +++ b/backend/cmd/main.go @@ -75,7 +75,10 @@ func main() { updateFactory := update_factory.NewFactory(boardToPackets) // <--- logger ---> - loggerHandler, subloggers := setUpLogger(config) + loggerHandler, subloggers, err := setUpLogger(config, adj.Commit) + if err != nil { + trace.Fatal().Err(err).Msg("setting up logger") + } // <-- connections & upgrader --> connections := make(chan *websocket.Client) diff --git a/backend/cmd/orchestrator.go b/backend/cmd/orchestrator.go index e395060df..53e9f6cac 100644 --- a/backend/cmd/orchestrator.go +++ b/backend/cmd/orchestrator.go @@ -134,16 +134,17 @@ func createLookupTables( createBoardToPackets(podData) } -func setUpLogger(config config.Config) (*logger.Logger, abstraction.SubloggersMap) { +func setUpLogger(config config.Config, commitHash string) (*logger.Logger, abstraction.SubloggersMap, error) { var subloggers = abstraction.SubloggersMap{ data_logger.Name: data_logger.NewLogger(), order_logger.Name: order_logger.NewLogger(), } - logger.ConfigureLogger(config.Logging.TimeUnit, config.Logging.LoggingPath) + err := logger.ConfigureLogger(config.Logging.TimeUnit, config.Logging.LoggingPath, commitHash) + loggerHandler := logger.NewLogger(subloggers, trace.Logger) - return loggerHandler, subloggers + return loggerHandler, subloggers, err } diff --git a/backend/pkg/adj/adj.go b/backend/pkg/adj/adj.go index 81c79ce13..8a496f423 100644 --- a/backend/pkg/adj/adj.go +++ b/backend/pkg/adj/adj.go @@ -35,7 +35,7 @@ func getRepoPath() string { } func NewADJ(AdjSettings config.Adj) (ADJ, error) { - infoRaw, boardsRaw, err := downloadADJ(AdjSettings) + commitHash, infoRaw, boardsRaw, err := downloadADJ(AdjSettings) if err != nil { return ADJ{}, err } @@ -84,31 +84,38 @@ func NewADJ(AdjSettings config.Adj) (ADJ, error) { adj := ADJ{ Info: info, Boards: boards, + Commit: commitHash, } return adj, nil } -func downloadADJ(AdjSettings config.Adj) (json.RawMessage, json.RawMessage, error) { - updateRepo(AdjSettings.Branch) +func downloadADJ(AdjSettings config.Adj) (string, json.RawMessage, json.RawMessage, error) { + commitHash, err := updateRepo(AdjSettings.Branch) + if err != nil { + return "local", nil, nil, err + } + + // If not downloading use local ADJ + if commitHash == "" { + commitHash = "local" + } // After downloading adj apply adj validator if AdjSettings.Validate { - Validate() - } info, err := os.ReadFile(RepoPath + "general_info.json") if err != nil { - return nil, nil, err + return commitHash, nil, nil, err } boardsList, err := os.ReadFile(RepoPath + "boards.json") if err != nil { - return nil, nil, err + return commitHash, nil, nil, err } - return info, boardsList, nil + return commitHash, info, boardsList, nil } diff --git a/backend/pkg/adj/git.go b/backend/pkg/adj/git.go index 2feb7b6bb..ab17f1f28 100644 --- a/backend/pkg/adj/git.go +++ b/backend/pkg/adj/git.go @@ -15,14 +15,15 @@ import ( // connectivity). If the remote branch is accessible, the local repository is completely // removed and replaced with a clean, shallow clone of that branch. // If the remote is not accessible, the existing local repository is left untouched. +// returns commit an error if any operation fails, otherwise returns nil -func updateRepo(AdjBranch string) error { +func updateRepo(AdjBranch string) (string, error) { var err error - + var commitHash string if AdjBranch == "" { // Makes use of user's custom ADJ trace.Info().Msg("No ADJ branch specified. Using local ADJ.") - return nil + return "", nil } else { trace.Info().Msgf("Updating local ADJ repository to match remote branch '%s'", AdjBranch) cloneOptions := &git.CloneOptions{ @@ -37,7 +38,7 @@ func updateRepo(AdjBranch string) error { // Remove previous failed cloning attempts if err = os.RemoveAll(tempPath); err != nil { - return err + return "", err } // Try to import the ADJ to the temp directory @@ -45,30 +46,44 @@ func updateRepo(AdjBranch string) error { if err != nil { // If the clone fails, work with the local ADJ trace.Info().Msgf("Warning: Could not clone ADJ branch '%s' from remote. Working with local ADJ. Error: %v", AdjBranch, err) - return nil + + return "", nil } // If the clone is succesful, delete the temp files if err = os.RemoveAll(tempPath); err != nil { - return err + return "", err } // After checking that the repo is accessible, clone or update (overwrite) the local ADJ repo if _, err = os.Stat(RepoPath); os.IsNotExist(err) { - _, err = git.PlainClone(RepoPath, false, cloneOptions) + repo, err := git.PlainClone(RepoPath, false, cloneOptions) + if err != nil { + return "", err + } + // log the commit + ref, err := repo.Head() if err != nil { - return err + return "", err } + commitHash = ref.Hash().String() + } else { if err = os.RemoveAll(RepoPath); err != nil { - return err + return "", err + } + repo, err := git.PlainClone(RepoPath, false, cloneOptions) + if err != nil { + return "", err } - _, err = git.PlainClone(RepoPath, false, cloneOptions) + // log the commit + ref, err := repo.Head() if err != nil { - return err + return "", err } + commitHash = ref.Hash().String() } } - return nil + return commitHash, nil } diff --git a/backend/pkg/adj/models.go b/backend/pkg/adj/models.go index 53fe950fd..27c01030c 100644 --- a/backend/pkg/adj/models.go +++ b/backend/pkg/adj/models.go @@ -8,6 +8,7 @@ import ( type ADJ struct { Info Info Boards map[string]Board + Commit string } type InfoJSON struct { diff --git a/backend/pkg/logger/logger.go b/backend/pkg/logger/logger.go index 2f607ae07..511d2e1db 100644 --- a/backend/pkg/logger/logger.go +++ b/backend/pkg/logger/logger.go @@ -2,6 +2,8 @@ package logger import ( + "encoding/json" + "os" "path" "sync" "sync/atomic" @@ -41,6 +43,12 @@ var Timestamp = time.Now() // StartAppTimestamp is the time in which the app was started var StartAppTimestamp = time.Now() +// CommitHash is the commit hash of the current version of ADJ +var CommitHash string + +// TimestampUnit is the unit of time used for the logger timestamps +var TimestampUnit TimeUnit + var BasePath = path.Join("logger", StartAppTimestamp.Format(TimestampFormat)) func (Logger) HandlerName() string { return HandlerName } @@ -93,6 +101,13 @@ func (logger *Logger) Start() error { logger.trace.Info().Msg("started") + // Write logger settings to a JSON file in the sublogger directory + err := WriteLoggerSettings(path.Join(BasePath, Timestamp.Format(TimestampFormat), "logger_settings.json")) + if err != nil { + logger.trace.Warn().Stack().Err(err).Msg("write logger settings") + return err + } + if logger.onStart != nil { logger.onStart() } @@ -127,22 +142,6 @@ func (logger *Logger) PullRecord(request abstraction.LoggerRequest) (abstraction panic("PullRecord") - // logger.trace. - // Trace(). - // Type("request", request). - // Msg("request") - - // loggerChecked, ok := logger.subloggers[request.Name()] - // if !ok { - // logger.trace. - // Warn(). - // Type("request", request). - // Str("name", string(request.Name())). - // Msg("no subloggger found for request") - - // return nil, ErrLoggerNotFound{request.Name()} - // } - // return loggerChecked.PullRecord(request) } func (logger *Logger) Stop() error { @@ -173,11 +172,53 @@ func (logger *Logger) Stop() error { } // ConfigureLogger configures the logger attributes before initializing it. -func ConfigureLogger(unit TimeUnit, basePath string) { +func ConfigureLogger(unit TimeUnit, basePath string, commitHash string) error { // Start the sublogger SetFormatTimestamp(unit) + // Set unit for logger timestamps + TimestampUnit = unit + + // Set commit hash + CommitHash = commitHash + // Update base Path BasePath = path.Join(basePath, "logger", StartAppTimestamp.Format(TimestampFormat)) + + err := WriteLoggerSettings(path.Join(BasePath, "others", "logger_settings.json")) + if err != nil { + return err + } + + return nil + +} + +/****************** +* Logger Settings * +*******************/ + +// JSON with adj commit and unit of time for logger settings +type LoggerSettings struct { + AdjCommitHash string `json:"adj_commit_hash"` + TimeUnit TimeUnit `json:"time_unit"` + Time string `json:"date"` +} + +// WriteLoggerSettings writes the logger settings to a JSON file in the logger directory +func WriteLoggerSettings(path string) error { + settings := LoggerSettings{ + AdjCommitHash: CommitHash, + TimeUnit: TimestampUnit, + Time: Timestamp.Format(TimestampFormat), + } + + settingsBytes, err := json.Marshal(settings) + if err != nil { + return err + } + + return os.WriteFile(path, settingsBytes, 0644) + } diff --git a/backend/pkg/logger/order/logger.go b/backend/pkg/logger/order/logger.go index 06fd9a494..8330ed704 100644 --- a/backend/pkg/logger/order/logger.go +++ b/backend/pkg/logger/order/logger.go @@ -6,12 +6,11 @@ import ( "path" "time" - loggerbase "github.com/HyperloopUPV-H8/h9-backend/pkg/logger/base" - trace "github.com/rs/zerolog/log" - "github.com/HyperloopUPV-H8/h9-backend/pkg/abstraction" "github.com/HyperloopUPV-H8/h9-backend/pkg/logger" + loggerbase "github.com/HyperloopUPV-H8/h9-backend/pkg/logger/base" "github.com/HyperloopUPV-H8/h9-backend/pkg/logger/file" + trace "github.com/rs/zerolog/log" ) const ( @@ -86,12 +85,18 @@ func (sublogger *Logger) PushRecord(record abstraction.LoggerRecord) error { } } - err := sublogger.writer.Write([]string{ + // Convert the values of the packet to JSON + jsonValues, err := orderRecord.Packet.GetValuesAsJSON() + if err != nil { + return err + } + + err = sublogger.writer.Write([]string{ fmt.Sprint(logger.FormatTimestamp(orderRecord.Packet.Timestamp()) - sublogger.StartTime), orderRecord.From, orderRecord.To, fmt.Sprint(orderRecord.Packet.Id()), - fmt.Sprint(orderRecord.Packet.GetValues()), + string(jsonValues), orderRecord.Timestamp.Format(time.RFC3339), }) sublogger.writer.Flush() diff --git a/backend/pkg/transport/packet/data/packet.go b/backend/pkg/transport/packet/data/packet.go index 75178e0a6..2be29288c 100644 --- a/backend/pkg/transport/packet/data/packet.go +++ b/backend/pkg/transport/packet/data/packet.go @@ -1,6 +1,7 @@ package data import ( + "encoding/json" "sync" "time" @@ -37,7 +38,6 @@ var packetPool = sync.Pool{ }, } - // NewPacketWithValues creates a new data packet with the given values func NewPacketWithValues(id abstraction.PacketId, values map[ValueName]Value, enabled map[ValueName]bool) *Packet { return &Packet{ @@ -69,6 +69,34 @@ func (packet *Packet) GetValues() map[ValueName]Value { return packet.values } +// numericValuer is implemented by NumericValue[N] for any numeric N. +type numericValuer interface { + Value() float64 +} + +// GetValuesAsJSON returns the values of the packet as a JSON object +func (packet *Packet) GetValuesAsJSON() ([]byte, error) { + out := make(map[string]any, len(packet.values)) + + for k, v := range packet.values { + switch x := v.(type) { + case BooleanValue: + out[string(k)] = x.Value() + case EnumValue: + out[string(k)] = string(x.Variant()) + default: + if nv, ok := v.(numericValuer); ok { + out[string(k)] = nv.Value() + } else { + out[string(k)] = nil + } + } + } + + return json.Marshal(out) +} + +// SetTimestamp sets the timestamp of the packet to the given time func (packet *Packet) SetTimestamp(timestamp time.Time) *Packet { packet.timestamp = timestamp return packet