1 | /* |
2 | * Copyright Andrey Semashev 2007 - 2015. |
3 | * Distributed under the Boost Software License, Version 1.0. |
4 | * (See accompanying file LICENSE_1_0.txt or copy at |
5 | * http://www.boost.org/LICENSE_1_0.txt) |
6 | */ |
7 | /*! |
8 | * \file main.cpp |
9 | * \author Andrey Semashev |
10 | * \date 11.11.2007 |
11 | * |
12 | * \brief An example of in-depth library usage. See the library tutorial for expanded |
13 | * comments on this code. It may also be worthwhile reading the Wiki requirements page: |
14 | * http://www.crystalclearsoftware.com/cgi-bin/boost_wiki/wiki.pl?Boost.Logging |
15 | */ |
16 | |
17 | // #define BOOST_LOG_USE_CHAR |
18 | // #define BOOST_ALL_DYN_LINK 1 |
19 | // #define BOOST_LOG_DYN_LINK 1 |
20 | |
21 | #include <cassert> |
22 | #include <iostream> |
23 | #include <fstream> |
24 | #include <boost/smart_ptr/shared_ptr.hpp> |
25 | #include <boost/core/null_deleter.hpp> |
26 | #include <boost/date_time/posix_time/posix_time.hpp> |
27 | #include <boost/log/common.hpp> |
28 | #include <boost/log/expressions.hpp> |
29 | #include <boost/log/attributes.hpp> |
30 | #include <boost/log/sinks.hpp> |
31 | #include <boost/log/sources/logger.hpp> |
32 | #include <boost/log/utility/manipulators/add_value.hpp> |
33 | #include <boost/log/attributes/scoped_attribute.hpp> |
34 | #include <boost/log/support/date_time.hpp> |
35 | |
36 | namespace logging = boost::log; |
37 | namespace expr = boost::log::expressions; |
38 | namespace sinks = boost::log::sinks; |
39 | namespace attrs = boost::log::attributes; |
40 | namespace src = boost::log::sources; |
41 | namespace keywords = boost::log::keywords; |
42 | |
43 | using boost::shared_ptr; |
44 | |
45 | // Here we define our application severity levels. |
46 | enum severity_level |
47 | { |
48 | normal, |
49 | notification, |
50 | warning, |
51 | error, |
52 | critical |
53 | }; |
54 | |
55 | // The formatting logic for the severity level |
56 | template< typename CharT, typename TraitsT > |
57 | inline std::basic_ostream< CharT, TraitsT >& operator<< (std::basic_ostream< CharT, TraitsT >& strm, severity_level lvl) |
58 | { |
59 | static const char* const str[] = |
60 | { |
61 | "normal" , |
62 | "notification" , |
63 | "warning" , |
64 | "error" , |
65 | "critical" |
66 | }; |
67 | if (static_cast< std::size_t >(lvl) < (sizeof(str) / sizeof(*str))) |
68 | strm << str[lvl]; |
69 | else |
70 | strm << static_cast< int >(lvl); |
71 | return strm; |
72 | } |
73 | |
74 | int foo(src::logger& lg) |
75 | { |
76 | BOOST_LOG_FUNCTION(); |
77 | BOOST_LOG(lg) << "foo is being called" ; |
78 | return 10; |
79 | } |
80 | |
81 | int main(int argc, char* argv[]) |
82 | { |
83 | // This is a in-depth tutorial/example of Boost.Log usage |
84 | |
85 | // The first thing we have to do to get using the library is |
86 | // to set up the logging sinks - i.e. where the logs will be written to. |
87 | // Each sink is composed from frontend and backend. Frontend deals with |
88 | // general sink behavior, like filtering (see below) and threading model. |
89 | // Backend implements formatting and, actually, storing log records. |
90 | // Not every frontend/backend combinations are compatible (mostly because of |
91 | // threading models incompatibilities), but if they are not, the code will |
92 | // simply not compile. |
93 | |
94 | // For now we only create a text output sink: |
95 | typedef sinks::synchronous_sink< sinks::text_ostream_backend > text_sink; |
96 | shared_ptr< text_sink > pSink(new text_sink); |
97 | |
98 | // Here synchronous_sink is a sink frontend that performs thread synchronization |
99 | // before passing log records to the backend (the text_ostream_backend class). |
100 | // The backend formats each record and outputs it to one or several streams. |
101 | // This approach makes implementing backends a lot simpler, because you don't |
102 | // need to worry about multithreading. |
103 | |
104 | { |
105 | // The good thing about sink frontends is that they are provided out-of-box and |
106 | // take away thread-safety burden from the sink backend implementors. Even if you |
107 | // have to call a custom backend method, the frontend gives you a convenient way |
108 | // to do it in a thread safe manner. All you need is to acquire a locking pointer |
109 | // to the backend. |
110 | text_sink::locked_backend_ptr pBackend = pSink->locked_backend(); |
111 | |
112 | // Now, as long as pBackend lives, you may work with the backend without |
113 | // interference of other threads that might be trying to log. |
114 | |
115 | // Next we add streams to which logging records should be output |
116 | shared_ptr< std::ostream > pStream(&std::clog, boost::null_deleter()); |
117 | pBackend->add_stream(strm: pStream); |
118 | |
119 | // We can add more than one stream to the sink backend |
120 | shared_ptr< std::ofstream > pStream2(new std::ofstream("sample.log" )); |
121 | assert(pStream2->is_open()); |
122 | pBackend->add_stream(strm: pStream2); |
123 | } |
124 | |
125 | // Ok, we're ready to add the sink to the logging library |
126 | logging::core::get()->add_sink(s: pSink); |
127 | |
128 | // Now our logs will be written both to the console and to the file. |
129 | // Let's do a quick test and output something. We have to create a logger for this. |
130 | src::logger lg; |
131 | |
132 | // And output... |
133 | BOOST_LOG(lg) << "Hello, World!" ; |
134 | |
135 | // Nice, huh? That's pretty much equivalent to writing the string to both the file |
136 | // and the console. Now let's define the different way of formatting log records. |
137 | // Each logging record may have a number of attributes in addition to the |
138 | // message body itself. By setting up formatter we define which of them |
139 | // will be written to log and in what way they will look there. |
140 | pSink->set_formatter(expr::stream |
141 | << expr::attr< unsigned int >(name: "RecordID" ) // First an attribute "RecordID" is written to the log |
142 | << " [" << expr::format_date_time< boost::posix_time::ptime >(name: "TimeStamp" , format: "%d.%m.%Y %H:%M:%S.%f" ) |
143 | << "] [" << expr::attr< severity_level >(name: "Severity" ) |
144 | << "] [" << expr::attr< boost::posix_time::time_duration >(name: "Uptime" ) |
145 | << "] [" // then this delimiter separates it from the rest of the line |
146 | << expr::if_(cond: expr::has_attr(name: "Tag" )) |
147 | [ |
148 | expr::stream << expr::attr< std::string >(name: "Tag" ) // then goes another attribute named "Tag" |
149 | // Note here we explicitly stated that its type |
150 | // should be std::string. We could omit it just |
151 | // like we did it with the "RecordID", but in this case |
152 | // library would have to detect the actual attribute value |
153 | // type in run time which has the following consequences: |
154 | // - On the one hand, the attribute would have been output |
155 | // even if it has another type (not std::string). |
156 | // - On the other, this detection does not come for free |
157 | // and will result in performance decrease. |
158 | // |
159 | // In general it's better you to specify explicitly which |
160 | // type should an attribute have wherever it is possible. |
161 | // You may specify an MPL sequence of types if the attribute |
162 | // may have more than one type. And you will have to specify |
163 | // it anyway if the library is not familiar with it (see |
164 | // boost/log/utility/type_dispatch/standard_types.hpp for the list |
165 | // of the supported out-of-the-box types). |
166 | << "] [" // yet another delimiter |
167 | ] |
168 | << expr::format_named_scope(name: "Scope" , arg0: keywords::format = "%n" , arg1: keywords::iteration = expr::reverse) << "] " |
169 | << expr::smessage); // here goes the log record text |
170 | |
171 | /* |
172 | // There is an alternative way of specifying formatters |
173 | pSink->set_formatter( |
174 | expr::format("%1% @ %2% [%3%] >%4%< Scope: %5%: %6%") |
175 | % expr::attr< unsigned int >("RecordID") |
176 | % expr::format_date_time< boost::posix_time::ptime >("TimeStamp", "%d.%m.%Y %H:%M:%S.%f") |
177 | % expr::attr< boost::posix_time::time_duration >("Uptime") |
178 | % expr::attr< std::string >("Tag") |
179 | % expr::format_named_scope("Scope", keywords::format = "%n", keywords::iteration = expr::reverse, keywords::depth = 2) |
180 | % expr::smessage); |
181 | */ |
182 | |
183 | // Now the sink will output in the following format: |
184 | // 1 [Current time] [Tag value] Hello World! |
185 | // The output will be the same for all streams we add to the sink. If you want something different, |
186 | // you may create another sink for that purpose. |
187 | |
188 | // Now we're going to set up the attributes. |
189 | // Remember that "RecordID" attribute in the formatter? There is a counter |
190 | // attribute in the library that increments or decrements the value each time |
191 | // it is output. Let's create it with a starting value 1. |
192 | attrs::counter< unsigned int > RecordID(1); |
193 | |
194 | // Since we intend to count all logging records ever made by the application, |
195 | // this attribute should clearly be global. |
196 | logging::core::get()->add_global_attribute(name: "RecordID" , attr: RecordID); |
197 | |
198 | // And similarly add a time stamp |
199 | attrs::local_clock TimeStamp; |
200 | logging::core::get()->add_global_attribute(name: "TimeStamp" , attr: TimeStamp); |
201 | |
202 | // And an up time stopwatch |
203 | BOOST_LOG_SCOPED_THREAD_ATTR("Uptime" , attrs::timer()); |
204 | |
205 | // Attributes may have two other scopes: thread scope and source scope. Attributes of thread |
206 | // scope are output with each record made by the thread (regardless of the logger object), and |
207 | // attributes of the source scope are output with each record made by the logger. On output |
208 | // all attributes of global, thread and source scopes are merged into a one record and passed to |
209 | // the sinks as one view. There is no difference between attributes of different scopes from the |
210 | // sinks' perspective. |
211 | |
212 | // Let's also track the execution scope from which the records are made |
213 | attrs::named_scope Scope; |
214 | logging::core::get()->add_thread_attribute(name: "Scope" , attr: Scope); |
215 | |
216 | // We can mark the current execution scope now - it's the 'main' function |
217 | BOOST_LOG_FUNCTION(); |
218 | |
219 | // Let's try out the counter attribute and formatting |
220 | BOOST_LOG(lg) << "Some log line with a counter" ; |
221 | BOOST_LOG(lg) << "Another log line with the counter" ; |
222 | |
223 | // Ok, remember the "Tag" attribute we added in the formatter? It is absent in these |
224 | // two lines above, so it is empty in the output. Let's try to tag some log records with it. |
225 | { |
226 | BOOST_LOG_NAMED_SCOPE("Tagging scope" ); |
227 | |
228 | // Here we add a temporary attribute to the logger lg. |
229 | // Every log record being written in the current scope with logger lg |
230 | // will have a string attribute "Tag" with value "Tagged line" attached. |
231 | BOOST_LOG_SCOPED_LOGGER_TAG(lg, "Tag" , "Tagged line" ); |
232 | |
233 | // The above line is roughly equivalent to the following: |
234 | // attrs::constant< std::string > TagAttr("Tagged line"); |
235 | // logging::scoped_attribute _ = |
236 | // logging::add_scoped_logger_attribute(lg, "Tag", TagAttr); |
237 | |
238 | // Now these lines will be highlighted with the tag |
239 | BOOST_LOG(lg) << "Some tagged log line" ; |
240 | BOOST_LOG(lg) << "Another tagged log line" ; |
241 | } |
242 | |
243 | // And this line is not highlighted anymore |
244 | BOOST_LOG(lg) << "Now the tag is removed" ; |
245 | BOOST_LOG(lg) << logging::add_value(name: "Tag" , value: "Tagged line" ) << "Some lines can also be selectively tagged" ; |
246 | |
247 | // Now let's try to apply filtering to the output. Filtering is based on |
248 | // attributes being output with the record. One of the common filtering use cases |
249 | // is filtering based on the record severity level. We've already defined severity levels. |
250 | // Now we can set the filter. A filter is essentially a functor that returns |
251 | // boolean value that tells whether to write the record or not. |
252 | pSink->set_filter( |
253 | expr::attr< severity_level >(name: "Severity" ).or_default(def_val: normal) >= warning // Write all records with "warning" severity or higher |
254 | || expr::begins_with(attr: expr::attr< std::string >(name: "Tag" ).or_default(def_val: std::string()), substring: "IMPORTANT" )); // ...or specifically tagged |
255 | |
256 | // The "attr" placeholder here acts pretty much like the "attr" placeholder in formatters, except |
257 | // that it requires the attribute type (or types in MPL-sequence) to be specified. |
258 | // In case of a single std::string or std::wstring type of attribute the "attr" placeholder |
259 | // provides a number of extended predicates which include "begins_with", "ends_with", "contains" |
260 | // and "matches" (the last one performs RegEx matching). |
261 | // There are other placeholders to be used for filter composition in the "boost/log/filters" |
262 | // directory. Additionally, you are not restricted to them and may provide your own filtering |
263 | // functors. |
264 | |
265 | // It must be noted that filters may be applied on per-sink basis and/or globally. |
266 | // Above we set a filter for this particular sink. Had we another sink, the filter would |
267 | // not influence it. To set a global filter one should call the set_filter method of the |
268 | // logging system like that: |
269 | // logging::core::get()->set_filter(...); |
270 | |
271 | // Now, to set logging severity we could perfectly use our previously created logger "lg". |
272 | // But no make it more convenient and efficient there is a special extended logger class. |
273 | // Its implementation may serve as an example of extending basic library functionality. |
274 | // You may add your specific capabilities to the logger by deriving your class from it. |
275 | src::severity_logger< severity_level > slg; |
276 | |
277 | // These two lines test filtering based on severity |
278 | BOOST_LOG_SEV(slg, normal) << "A normal severity message, will not pass to the output" ; |
279 | BOOST_LOG_SEV(slg, error) << "An error severity message, will pass to the output" ; |
280 | |
281 | { |
282 | // Next we try if the second condition of the filter works |
283 | // We mark following lines with a tag |
284 | BOOST_LOG_SCOPED_THREAD_TAG("Tag" , "IMPORTANT MESSAGES" ); |
285 | |
286 | // We may omit the severity and use the shorter BOOST_LOG macro. The logger "slg" |
287 | // has the default severity that may be specified on its construction. We didn't |
288 | // do it, so it is 0 by default. Therefore this record will have "normal" severity. |
289 | // The only reason this record will be output is the "Tag" attribute we added above. |
290 | BOOST_LOG(slg) << "Some really urgent line" ; |
291 | } |
292 | |
293 | pSink->reset_filter(); |
294 | |
295 | // And moreover, it is possible to nest logging records. For example, this will |
296 | // be processed in the order of evaluation: |
297 | BOOST_LOG(lg) << "The result of foo is " << foo(lg); |
298 | |
299 | return 0; |
300 | } |
301 | |