1 #pragma once
2 
3 #include "additional_data.hpp"
4 #include "elog_entry.hpp"
5 
6 #include <phosphor-logging/log.hpp>
7 #include <sdeventplus/event.hpp>
8 #include <sdeventplus/source/event.hpp>
9 
10 #include <queue>
11 #include <tuple>
12 
13 namespace openpower::pels
14 {
15 
16 /**
17  * @class EventLogger
18  *
19  * This class handles creating OpenBMC event logs (and thus PELs) from
20  * within the PEL extension code.
21  *
22  * The function to actually create the event log is passed in via the
23  * constructor so that different functions can be used when testing.
24  *
25  * To create the event log, call log() with the appropriate arguments
26  * and the log will be created as soon as the flow gets back to the event
27  * loop.  If the queue isn't empty after a log is created, the next
28  * one will be scheduled to be created from the event loop again.
29  *
30  * This class does not allow new events to be added while inside the
31  * creation function, because if the code added an event log every time
32  * it tried to create one, it would do so infinitely.
33  */
34 class EventLogger
35 {
36   public:
37     using ADMap = std::map<std::string, std::string>;
38     using LogFunction = std::function<void(
39         const std::string&, phosphor::logging::Entry::Level, const ADMap&)>;
40 
41     static constexpr size_t msgPos = 0;
42     static constexpr size_t levelPos = 1;
43     static constexpr size_t adPos = 2;
44     using EventEntry = std::tuple<std::string, phosphor::logging::Entry::Level,
45                                   AdditionalData>;
46 
47     EventLogger() = delete;
48     ~EventLogger() = default;
49     EventLogger(const EventLogger&) = delete;
50     EventLogger& operator=(const EventLogger&) = delete;
51     EventLogger(EventLogger&&) = delete;
52     EventLogger& operator=(EventLogger&&) = delete;
53 
54     /**
55      * @brief Constructor
56      *
57      * @param[in] creator - The function to use to create the event log
58      */
59     explicit EventLogger(LogFunction creator) :
60         _event(sdeventplus::Event::get_default()), _creator(creator)
61     {}
62 
63     /**
64      * @brief Adds an event to the queue so that it will be created
65      *        as soon as the code makes it back to the event loop.
66      *
67      * Won't add it to the queue if already inside the create()
68      * callback.
69      *
70      * @param[in] message - The message property of the event log
71      * @param[in] severity - The severity level of the event log
72      * @param[in] ad - The additional data property of the event log
73      */
74     void log(const std::string& message,
75              phosphor::logging::Entry::Level severity, const AdditionalData& ad)
76     {
77         if (!_inEventCreation)
78         {
79             _eventsToCreate.emplace(message, severity, ad);
80 
81             if (!_eventSource)
82             {
83                 scheduleCreate();
84             }
85         }
86         else
87         {
88             phosphor::logging::log<phosphor::logging::level::INFO>(
89                 "Already in event create callback, skipping new create",
90                 phosphor::logging::entry("ERROR_NAME=%s", message.c_str()));
91         }
92     }
93 
94     /**
95      * @brief Returns the event log queue size.
96      *
97      * @return size_t - The queue size
98      */
99     size_t queueSize() const
100     {
101         return _eventsToCreate.size();
102     }
103 
104     /**
105      * @brief Schedules the create() function to run using the
106      *        'defer' sd_event source.
107      */
108     void scheduleCreate()
109     {
110         _eventSource = std::make_unique<sdeventplus::source::Defer>(
111             _event, std::bind(std::mem_fn(&EventLogger::create), this,
112                               std::placeholders::_1));
113     }
114 
115   private:
116     /**
117      * @brief Creates an event log and schedules the next one if
118      *        there is one.
119      *
120      * This gets called from the event loop by the sd_event code.
121      *
122      * @param[in] source - The event source object used
123      */
124     void create(sdeventplus::source::EventBase& /*source*/)
125     {
126         _eventSource.reset();
127 
128         if (_eventsToCreate.empty())
129         {
130             return;
131         }
132 
133         auto event = _eventsToCreate.front();
134         _eventsToCreate.pop();
135 
136         _inEventCreation = true;
137 
138         try
139         {
140             _creator(std::get<msgPos>(event), std::get<levelPos>(event),
141                      std::get<adPos>(event).getData());
142         }
143         catch (const std::exception& e)
144         {
145             phosphor::logging::log<phosphor::logging::level::ERR>(
146                 "EventLogger's create function threw an exception",
147                 phosphor::logging::entry("ERROR=%s", e.what()));
148         }
149 
150         _inEventCreation = false;
151 
152         if (!_eventsToCreate.empty())
153         {
154             scheduleCreate();
155         }
156     }
157 
158     /**
159      * @brief The sd_event object.
160      */
161     sdeventplus::Event _event;
162 
163     /**
164      * @brief The user supplied function to create the event log.
165      */
166     LogFunction _creator;
167 
168     /**
169      * @brief Keeps track of if an event is currently being created.
170      *
171      * Guards against creating new events while creating events.
172      */
173     bool _inEventCreation = false;
174 
175     /**
176      * @brief The event source object used for scheduling.
177      */
178     std::unique_ptr<sdeventplus::source::Defer> _eventSource;
179 
180     /**
181      * @brief The queue of event logs to create.
182      */
183     std::queue<EventEntry> _eventsToCreate;
184 };
185 
186 } // namespace openpower::pels
187