1 /** @file logfilter.cpp  Log entry filter.
2  *
3  * @authors Copyright © 2014-2017 Jaakko Keränen <jaakko.keranen@iki.fi>
4  *
5  * @par License
6  * LGPL: http://www.gnu.org/licenses/lgpl.html
7  *
8  * <small>This program is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU Lesser General Public License as published by
10  * the Free Software Foundation; either version 3 of the License, or (at your
11  * option) any later version. This program is distributed in the hope that it
12  * will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
13  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
14  * General Public License for more details. You should have received a copy of
15  * the GNU Lesser General Public License along with this program; if not, see:
16  * http://www.gnu.org/licenses</small>
17  */
18 
19 #include "de/LogFilter"
20 
21 namespace de {
22 
23 namespace internal {
24     enum FilterId {
25         GenericFilter,
26         ResourceFilter,
27         MapFilter,
28         ScriptFilter,
29         GLFilter,
30         AudioFilter,
31         InputFilter,
32         NetworkFilter,
33         NUM_FILTERS
34     };
35     static char const *subRecName[NUM_FILTERS] = { // for Config
36         "generic",
37         "resource",
38         "map",
39         "script",
40         "gl",
41         "audio",
42         "input",
43         "network"
44     };
45 }
46 
47 using namespace internal;
48 
49 /**
50  * Filter for determining which log entries will be put in the log buffer.
51  */
DENG2_PIMPL_NOREF(LogFilter)52 DENG2_PIMPL_NOREF(LogFilter)
53 {
54     /// Filtering information for a domain.
55     struct Filter {
56         int domainBit;
57         LogEntry::Level minLevel;
58         bool allowDev;
59 
60         Filter()
61             : domainBit(LogEntry::GenericBit)
62             , minLevel(LogEntry::Message)
63             , allowDev(false)
64         {}
65 
66         inline bool checkContextBit(duint32 md) const
67         {
68             return (md & (1 << domainBit)) != 0;
69         }
70 
71         void read(Record const &rec)
72         {
73             minLevel = LogEntry::Level(dint(rec["minLevel"].value().asNumber()));
74             allowDev = rec["allowDev"].value().isTrue();
75         }
76 
77         void write(Record &rec) const
78         {
79             rec.set("minLevel", dint(minLevel));
80             rec.set("allowDev", allowDev);
81         }
82     };
83 
84     Filter filterByContext[NUM_FILTERS];
85 
86     Impl()
87     {
88         for (int i = 0; i < NUM_FILTERS; ++i)
89         {
90             filterByContext[i].domainBit = LogEntry::FirstDomainBit + i;
91         }
92     }
93 
94     bool isLogEntryAllowed(duint32 md) const
95     {
96         // Multiple contexts allowed, in which case if any one passes,
97         // the entry is allowed.
98         for (uint i = 0; i < NUM_FILTERS; ++i)
99         {
100             Filter const &ftr = filterByContext[i];
101             if (ftr.checkContextBit(md))
102             {
103                 if ((md & LogEntry::Dev) && !ftr.allowDev) continue; // No devs.
104                 if (ftr.minLevel <= int(md & LogEntry::LevelMask))
105                 {
106                     // Pass due to entry level being enabled.
107                     return true;
108                 }
109                 if ((md & LogEntry::Interactive) && i == ScriptFilter)
110                 {
111                     // Interactive script entries pass.
112                     return true;
113                 }
114             }
115         }
116         return false;
117     }
118 
119     LogEntry::Level minLevel(duint32 md) const
120     {
121         int lev = LogEntry::HighestLogLevel + 1;
122         for (uint i = 0; i < NUM_FILTERS; ++i)
123         {
124             Filter const &ftr = filterByContext[i];
125             if (ftr.checkContextBit(md))
126             {
127                 lev = de::min(lev, int(ftr.minLevel));
128             }
129         }
130         return LogEntry::Level(lev);
131     }
132 
133     bool allowDev(duint32 md) const
134     {
135         for (uint i = 0; i < NUM_FILTERS; ++i)
136         {
137             Filter const &ftr = filterByContext[i];
138             if (ftr.checkContextBit(md))
139             {
140                 if (ftr.allowDev) return true;
141             }
142         }
143         return false;
144     }
145 
146     void setAllowDev(duint32 md, bool allow)
147     {
148         for (uint i = 0; i < NUM_FILTERS; ++i)
149         {
150             Filter &ftr = filterByContext[i];
151             if (ftr.checkContextBit(md))
152             {
153                 ftr.allowDev = allow;
154             }
155         }
156     }
157 
158     void setMinLevel(duint32 md, LogEntry::Level level)
159     {
160         for (uint i = 0; i < NUM_FILTERS; ++i)
161         {
162             Filter &ftr = filterByContext[i];
163             if (ftr.checkContextBit(md))
164             {
165                 ftr.minLevel = level;
166             }
167         }
168     }
169 
170     void read(Record const &rec)
171     {
172         try
173         {
174             for (uint i = 0; i < NUM_FILTERS; ++i)
175             {
176                 filterByContext[i].read(rec.subrecord(subRecName[i]));
177             }
178         }
179         catch (Error const &er)
180         {
181             LOGDEV_WARNING("Failed to read filter from record: %s\nThe record is:\n%s")
182                     << er.asText() << rec.asText();
183 
184             LOG_WARNING("Log filter reset to defaults");
185             *this = Impl(); // Reset.
186         }
187     }
188 
189     void write(Record &rec) const
190     {
191         for (uint i = 0; i < NUM_FILTERS; ++i)
192         {
193             // Reuse existing subrecords.
194             if (!rec.hasSubrecord(subRecName[i]))
195             {
196                 rec.add(subRecName[i], new Record);
197             }
198             filterByContext[i].write(rec.subrecord(subRecName[i]));
199         }
200     }
201 };
202 
LogFilter()203 LogFilter::LogFilter() : d(new Impl)
204 {}
205 
isLogEntryAllowed(duint32 metadata) const206 bool LogFilter::isLogEntryAllowed(duint32 metadata) const
207 {
208     DENG2_ASSERT(metadata & LogEntry::DomainMask); // must have a domain
209     return d->isLogEntryAllowed(metadata);
210 }
211 
setAllowDev(duint32 md,bool allow)212 void LogFilter::setAllowDev(duint32 md, bool allow)
213 {
214     d->setAllowDev(md, allow);
215 }
216 
setMinLevel(duint32 md,LogEntry::Level level)217 void LogFilter::setMinLevel(duint32 md, LogEntry::Level level)
218 {
219     d->setMinLevel(md, level);
220 }
221 
allowDev(duint32 md) const222 bool LogFilter::allowDev(duint32 md) const
223 {
224     return d->allowDev(md);
225 }
226 
minLevel(duint32 md) const227 LogEntry::Level LogFilter::minLevel(duint32 md) const
228 {
229     return d->minLevel(md);
230 }
231 
read(Record const & rec)232 void LogFilter::read(Record const &rec)
233 {
234     d->read(rec);
235 }
236 
write(Record & rec) const237 void LogFilter::write(Record &rec) const
238 {
239     d->write(rec);
240 }
241 
domainRecordName(LogEntry::Context domain)242 String LogFilter::domainRecordName(LogEntry::Context domain)
243 {
244     for (int i = LogEntry::FirstDomainBit; i <= LogEntry::LastDomainBit; ++i)
245     {
246         if (domain & (1 << i)) return subRecName[i - LogEntry::FirstDomainBit];
247     }
248     return "";
249 }
250 
251 } // namespace de
252