1635de8c6SMatt Spinler #pragma once
2635de8c6SMatt Spinler 
3c674510eSMatt Spinler #include "utility.hpp"
4c674510eSMatt Spinler 
5635de8c6SMatt Spinler #include <unistd.h>
6635de8c6SMatt Spinler 
7635de8c6SMatt Spinler #include <nlohmann/json.hpp>
8635de8c6SMatt Spinler #include <phosphor-logging/log.hpp>
9635de8c6SMatt Spinler 
10635de8c6SMatt Spinler #include <cassert>
11635de8c6SMatt Spinler #include <ctime>
12635de8c6SMatt Spinler #include <filesystem>
13*fbf4703fSPatrick Williams #include <format>
14635de8c6SMatt Spinler #include <fstream>
15635de8c6SMatt Spinler #include <iomanip>
16635de8c6SMatt Spinler #include <sstream>
17635de8c6SMatt Spinler #include <string>
18635de8c6SMatt Spinler #include <vector>
19635de8c6SMatt Spinler 
20635de8c6SMatt Spinler namespace phosphor::fan
21635de8c6SMatt Spinler {
22635de8c6SMatt Spinler 
23635de8c6SMatt Spinler /**
24635de8c6SMatt Spinler  * @class Logger
25635de8c6SMatt Spinler  *
26635de8c6SMatt Spinler  * A simple logging class that stores log messages in a vector along
27635de8c6SMatt Spinler  * with their timestamp.  When a messaged is logged, it will also be
28635de8c6SMatt Spinler  * written to the journal.
29635de8c6SMatt Spinler  *
30635de8c6SMatt Spinler  * A saveToTempFile() function will write the log entries as JSON to
31635de8c6SMatt Spinler  * a temporary file, so they can be added to event logs.
32635de8c6SMatt Spinler  *
33635de8c6SMatt Spinler  * The maximum number of entries to keep is specified in the
34635de8c6SMatt Spinler  * constructor, and after that is hit the oldest entry will be
35635de8c6SMatt Spinler  * removed when a new one is added.
36635de8c6SMatt Spinler  */
37635de8c6SMatt Spinler class Logger
38635de8c6SMatt Spinler {
39635de8c6SMatt Spinler   public:
40635de8c6SMatt Spinler     // timestamp, message
41635de8c6SMatt Spinler     using LogEntry = std::tuple<std::string, std::string>;
42635de8c6SMatt Spinler 
43635de8c6SMatt Spinler     enum Priority
44635de8c6SMatt Spinler     {
45635de8c6SMatt Spinler         error,
4627f6b686SMatt Spinler         info,
4727f6b686SMatt Spinler         quiet
48635de8c6SMatt Spinler     };
49635de8c6SMatt Spinler 
50635de8c6SMatt Spinler     Logger() = delete;
51635de8c6SMatt Spinler     ~Logger() = default;
52635de8c6SMatt Spinler     Logger(const Logger&) = default;
53635de8c6SMatt Spinler     Logger& operator=(const Logger&) = default;
54635de8c6SMatt Spinler     Logger(Logger&&) = default;
55635de8c6SMatt Spinler     Logger& operator=(Logger&&) = default;
56635de8c6SMatt Spinler 
57635de8c6SMatt Spinler     /**
58635de8c6SMatt Spinler      * @brief Constructor
59635de8c6SMatt Spinler      *
60635de8c6SMatt Spinler      * @param[in] maxEntries - The maximum number of log entries
61635de8c6SMatt Spinler      *                         to keep.
62635de8c6SMatt Spinler      */
Logger(size_t maxEntries)63635de8c6SMatt Spinler     explicit Logger(size_t maxEntries) : _maxEntries(maxEntries)
64635de8c6SMatt Spinler     {
65635de8c6SMatt Spinler         assert(maxEntries != 0);
66635de8c6SMatt Spinler     }
67635de8c6SMatt Spinler 
68635de8c6SMatt Spinler     /**
69635de8c6SMatt Spinler      * @brief Places an entry in the log and writes it to the journal.
70635de8c6SMatt Spinler      *
71635de8c6SMatt Spinler      * @param[in] message - The log message
72635de8c6SMatt Spinler      *
73635de8c6SMatt Spinler      * @param[in] priority - The priority for the journal
74635de8c6SMatt Spinler      */
log(const std::string & message,Priority priority=Logger::info)75635de8c6SMatt Spinler     void log(const std::string& message, Priority priority = Logger::info)
76635de8c6SMatt Spinler     {
77635de8c6SMatt Spinler         if (priority == Logger::error)
78635de8c6SMatt Spinler         {
79635de8c6SMatt Spinler             phosphor::logging::log<phosphor::logging::level::ERR>(
80635de8c6SMatt Spinler                 message.c_str());
81635de8c6SMatt Spinler         }
8227f6b686SMatt Spinler         else if (priority != Logger::quiet)
83635de8c6SMatt Spinler         {
84635de8c6SMatt Spinler             phosphor::logging::log<phosphor::logging::level::INFO>(
85635de8c6SMatt Spinler                 message.c_str());
86635de8c6SMatt Spinler         }
87635de8c6SMatt Spinler 
88635de8c6SMatt Spinler         if (_entries.size() == _maxEntries)
89635de8c6SMatt Spinler         {
90635de8c6SMatt Spinler             _entries.erase(_entries.begin());
91635de8c6SMatt Spinler         }
92635de8c6SMatt Spinler 
93635de8c6SMatt Spinler         // Generate a timestamp
94635de8c6SMatt Spinler         auto t = std::time(nullptr);
95635de8c6SMatt Spinler         auto tm = *std::localtime(&t);
96635de8c6SMatt Spinler 
97635de8c6SMatt Spinler         // e.g. Sep 22 19:56:32
98635de8c6SMatt Spinler         auto timestamp = std::put_time(&tm, "%b %d %H:%M:%S");
99635de8c6SMatt Spinler 
100635de8c6SMatt Spinler         std::ostringstream stream;
101635de8c6SMatt Spinler         stream << timestamp;
102635de8c6SMatt Spinler         _entries.emplace_back(stream.str(), message);
103635de8c6SMatt Spinler     }
104635de8c6SMatt Spinler 
105635de8c6SMatt Spinler     /**
106635de8c6SMatt Spinler      * @brief Returns the entries in a JSON array
107635de8c6SMatt Spinler      *
108635de8c6SMatt Spinler      * @return JSON
109635de8c6SMatt Spinler      */
getLogs() const110635de8c6SMatt Spinler     const nlohmann::json getLogs() const
111635de8c6SMatt Spinler     {
112635de8c6SMatt Spinler         return _entries;
113635de8c6SMatt Spinler     }
114635de8c6SMatt Spinler 
115635de8c6SMatt Spinler     /**
116c674510eSMatt Spinler      * @brief Writes the data to a temporary file and returns the path
117635de8c6SMatt Spinler      *        to it.
118635de8c6SMatt Spinler      *
119635de8c6SMatt Spinler      * Uses a temp file because the only use case for this is for sending
120635de8c6SMatt Spinler      * in to an event log where a temp file makes sense, and frankly it
121635de8c6SMatt Spinler      * was just easier to encapsulate everything here.
122635de8c6SMatt Spinler      *
123635de8c6SMatt Spinler      * @return path - The path to the file.
124635de8c6SMatt Spinler      */
saveToTempFile()125635de8c6SMatt Spinler     std::filesystem::path saveToTempFile()
126635de8c6SMatt Spinler     {
127635de8c6SMatt Spinler         using namespace std::literals::string_literals;
128635de8c6SMatt Spinler 
129635de8c6SMatt Spinler         char tmpFile[] = "/tmp/loggertemp.XXXXXX";
130c674510eSMatt Spinler         util::FileDescriptor fd{mkstemp(tmpFile)};
131c674510eSMatt Spinler         if (fd() == -1)
132635de8c6SMatt Spinler         {
133635de8c6SMatt Spinler             throw std::runtime_error{"mkstemp failed!"};
134635de8c6SMatt Spinler         }
135635de8c6SMatt Spinler 
136635de8c6SMatt Spinler         std::filesystem::path path{tmpFile};
137635de8c6SMatt Spinler 
138c674510eSMatt Spinler         for (const auto& [time, message] : _entries)
139635de8c6SMatt Spinler         {
140*fbf4703fSPatrick Williams             auto line = std::format("{}: {}\n", time, message);
141c674510eSMatt Spinler             auto rc = write(fd(), line.data(), line.size());
142c674510eSMatt Spinler             if (rc == -1)
143c674510eSMatt Spinler             {
144c674510eSMatt Spinler                 auto e = errno;
145*fbf4703fSPatrick Williams                 auto msg = std::format(
146c674510eSMatt Spinler                     "Could not write to temp file {} errno {}", tmpFile, e);
147c674510eSMatt Spinler                 log(msg, Logger::error);
148c674510eSMatt Spinler                 throw std::runtime_error{msg};
149c674510eSMatt Spinler             }
150635de8c6SMatt Spinler         }
151635de8c6SMatt Spinler 
152635de8c6SMatt Spinler         return std::filesystem::path{tmpFile};
153635de8c6SMatt Spinler     }
154635de8c6SMatt Spinler 
155635de8c6SMatt Spinler     /**
156635de8c6SMatt Spinler      * @brief Deletes all log entries
157635de8c6SMatt Spinler      */
clear()158635de8c6SMatt Spinler     void clear()
159635de8c6SMatt Spinler     {
160635de8c6SMatt Spinler         _entries.clear();
161635de8c6SMatt Spinler     }
162635de8c6SMatt Spinler 
163635de8c6SMatt Spinler   private:
164635de8c6SMatt Spinler     /**
165635de8c6SMatt Spinler      * @brief The maximum number of entries to hold
166635de8c6SMatt Spinler      */
167635de8c6SMatt Spinler     const size_t _maxEntries;
168635de8c6SMatt Spinler 
169635de8c6SMatt Spinler     /**
170635de8c6SMatt Spinler      * @brief The vector of <timestamp, message> entries
171635de8c6SMatt Spinler      */
172635de8c6SMatt Spinler     std::vector<LogEntry> _entries;
173635de8c6SMatt Spinler };
174635de8c6SMatt Spinler 
175635de8c6SMatt Spinler } // namespace phosphor::fan
176