1 | /* |
---|
2 | Copyright (c) 2006-2018 Elmar Pruesse <elmar.pruesse@ucdenver.edu> |
---|
3 | |
---|
4 | This file is part of SINA. |
---|
5 | SINA is free software: you can redistribute it and/or modify it under |
---|
6 | the terms of the GNU General Public License as published by the Free |
---|
7 | Software Foundation, either version 3 of the License, or (at your |
---|
8 | option) any later version. |
---|
9 | |
---|
10 | SINA is distributed in the hope that it will be useful, but WITHOUT ANY |
---|
11 | WARRANTY; without even the implied warranty of MERCHANTABILITY or |
---|
12 | FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
---|
13 | for more details. |
---|
14 | |
---|
15 | You should have received a copy of the GNU General Public License |
---|
16 | along with SINA. If not, see <http://www.gnu.org/licenses/>. |
---|
17 | |
---|
18 | Additional permission under GNU GPL version 3 section 7 |
---|
19 | |
---|
20 | If you modify SINA, or any covered work, by linking or combining it |
---|
21 | with components of ARB (or a modified version of that software), |
---|
22 | containing parts covered by the terms of the |
---|
23 | ARB-public-library-license, the licensors of SINA grant you additional |
---|
24 | permission to convey the resulting work. Corresponding Source for a |
---|
25 | non-source form of such a combination shall include the source code |
---|
26 | for the parts of ARB used as well as that of the covered work. |
---|
27 | */ |
---|
28 | |
---|
29 | #ifndef _PROGRESS_H_ |
---|
30 | #define _PROGRESS_H_ |
---|
31 | |
---|
32 | #include <mutex> |
---|
33 | #include <list> |
---|
34 | #include <unistd.h> |
---|
35 | #include <sys/ioctl.h> |
---|
36 | #include <signal.h> |
---|
37 | |
---|
38 | #include "spdlog/spdlog.h" |
---|
39 | #include "spdlog/fmt/bundled/chrono.h" |
---|
40 | #include "spdlog/sinks/ansicolor_sink.h" |
---|
41 | |
---|
42 | namespace sina { |
---|
43 | |
---|
44 | static const char* bar_syms_unicode[] = { |
---|
45 | " ", |
---|
46 | "\xE2\x96\x8F", "\xE2\x96\x8E", "\xE2\x96\x8D", "\xE2\x96\x8C", |
---|
47 | "\xE2\x96\x8B", "\xE2\x96\x8A", "\xE2\x96\x89", "\xE2\x96\x88" |
---|
48 | }; |
---|
49 | static const char* bar_syms_ascii[] = { |
---|
50 | " ", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "#" |
---|
51 | }; |
---|
52 | |
---|
53 | |
---|
54 | /* abstract base rendering progress bar |
---|
55 | * needs show_progress() filled in */ |
---|
56 | class base_progress { |
---|
57 | public: |
---|
58 | using clock_t = std::chrono::steady_clock; |
---|
59 | using timepoint_t = clock_t::time_point; |
---|
60 | using duration_t = clock_t::duration; |
---|
61 | |
---|
62 | base_progress(std::string desc="", unsigned int total=0, bool ascii=false) |
---|
63 | : _total(total), |
---|
64 | _desc(desc), |
---|
65 | _bar_syms(ascii?bar_syms_ascii:bar_syms_unicode), |
---|
66 | _nsyms(ascii?std::extent<decltype(bar_syms_ascii)>::value |
---|
67 | :std::extent<decltype(bar_syms_unicode)>::value) |
---|
68 | {} |
---|
69 | |
---|
70 | void restart(std::string desc="", unsigned int total=0) { |
---|
71 | _n = 0; |
---|
72 | _total = total; |
---|
73 | _desc = desc; |
---|
74 | show_progress(); |
---|
75 | } |
---|
76 | |
---|
77 | unsigned int count() { |
---|
78 | return _n; |
---|
79 | } |
---|
80 | |
---|
81 | base_progress& operator++() { |
---|
82 | update(); |
---|
83 | return *this; |
---|
84 | } |
---|
85 | |
---|
86 | void operator+=(unsigned int n) { |
---|
87 | update(n); |
---|
88 | } |
---|
89 | |
---|
90 | void set_total(unsigned int n) { |
---|
91 | _total = n; |
---|
92 | } |
---|
93 | |
---|
94 | unsigned int size() { |
---|
95 | return _total; |
---|
96 | } |
---|
97 | |
---|
98 | void format_bar_to(fmt::memory_buffer& buf, unsigned int width, float frac) { |
---|
99 | if (width == 0) { |
---|
100 | return; |
---|
101 | } |
---|
102 | buf.reserve(buf.size() + width * 3); |
---|
103 | auto it = std::back_inserter(buf); |
---|
104 | |
---|
105 | size_t complete = frac * width * _nsyms; |
---|
106 | size_t full_blocks = complete / _nsyms; |
---|
107 | size_t frac_block = complete % _nsyms; |
---|
108 | size_t fill_length = width - full_blocks; |
---|
109 | |
---|
110 | auto append_n = [&](size_t n, unsigned int idx) { |
---|
111 | size_t len_sym = strlen(_bar_syms[idx]); |
---|
112 | if (len_sym == 1) { |
---|
113 | std::fill_n(it, n, _bar_syms[idx][0]); |
---|
114 | } else { |
---|
115 | for (size_t i=0; i<n; i++) |
---|
116 | std::copy_n(_bar_syms[idx], len_sym, it); |
---|
117 | } |
---|
118 | }; |
---|
119 | |
---|
120 | append_n(full_blocks, _nsyms-1); |
---|
121 | if (frac_block) { |
---|
122 | append_n(1, frac_block); |
---|
123 | --fill_length; |
---|
124 | } |
---|
125 | append_n(fill_length, 0); |
---|
126 | } |
---|
127 | |
---|
128 | void update(unsigned int n=1) { |
---|
129 | _n += n; |
---|
130 | if (_n == _total) { |
---|
131 | std::lock_guard<std::mutex> lock(_mutex); |
---|
132 | show_progress(); |
---|
133 | return; |
---|
134 | } |
---|
135 | if (_n >= _last_print_n + _miniterations) { |
---|
136 | timepoint_t now = clock_t::now(); |
---|
137 | duration_t delta_time = now - _last_update; |
---|
138 | if (delta_time > _mininterval) { |
---|
139 | std::lock_guard<std::mutex> lock(_mutex); |
---|
140 | _last_update = now; |
---|
141 | _miniterations = (_n - _last_print_n) * _mininterval / delta_time; |
---|
142 | show_progress(now); |
---|
143 | } |
---|
144 | } |
---|
145 | } |
---|
146 | |
---|
147 | void render_progress(timepoint_t now, unsigned int width, fmt::memory_buffer& buf) { |
---|
148 | _last_print_n = _n; |
---|
149 | auto arg_desc = fmt::arg("desc", _desc); |
---|
150 | auto elapsed = now - _started_at; |
---|
151 | auto arg_elapsed = fmt::arg("elapsed", elapsed); |
---|
152 | auto arg_eol = fmt::arg("eol", term_eol); |
---|
153 | auto arg_n = fmt::arg("n", _n); |
---|
154 | |
---|
155 | if (_total == 0) { |
---|
156 | fmt::format_to(buf, nototal_fmt, arg_desc, arg_elapsed, arg_eol, arg_n); |
---|
157 | return; |
---|
158 | } |
---|
159 | |
---|
160 | float frac = (float) _n / _total; |
---|
161 | auto eta = elapsed * (1/frac -1); |
---|
162 | auto remaining = (frac > 0) ? elapsed * (1/frac - 1) : duration_t(0); |
---|
163 | |
---|
164 | fmt::memory_buffer right; |
---|
165 | |
---|
166 | float percent = frac * 100; |
---|
167 | auto arg_frac = fmt::arg("frac", percent); |
---|
168 | auto arg_total = fmt::arg("total", _total); |
---|
169 | auto arg_remain = fmt::arg("remaining", remaining); |
---|
170 | auto args = fmt::make_format_args(arg_desc, arg_frac, arg_n, arg_total, |
---|
171 | arg_elapsed, arg_remain, arg_eol); |
---|
172 | |
---|
173 | fmt::vformat_to(buf, lbar_fmt, args); |
---|
174 | fmt::vformat_to(right, rbar_fmt, args); |
---|
175 | |
---|
176 | int space_for_bar = width - buf.size() - right.size() + term_eol.size(); |
---|
177 | if (space_for_bar > 0) { |
---|
178 | format_bar_to(buf, space_for_bar, frac); |
---|
179 | } |
---|
180 | buf.reserve(buf.size() + right.size()); |
---|
181 | std::copy(right.begin(), right.end(), std::back_inserter(buf)); |
---|
182 | } |
---|
183 | |
---|
184 | virtual void show_progress(timepoint_t now=clock_t::now()) = 0; |
---|
185 | private: |
---|
186 | |
---|
187 | std::atomic<unsigned int> _n{0}; |
---|
188 | unsigned int _last_print_n{0}; |
---|
189 | unsigned int _total; |
---|
190 | std::string _desc; |
---|
191 | const char **_bar_syms; |
---|
192 | unsigned int _nsyms; |
---|
193 | timepoint_t _started_at{clock_t::now()}; |
---|
194 | timepoint_t _last_update{std::chrono::seconds(0)}; |
---|
195 | duration_t _mininterval{std::chrono::milliseconds(10)}; |
---|
196 | unsigned int _miniterations{1}; |
---|
197 | std::string _bar_tpl; |
---|
198 | std::mutex _mutex; |
---|
199 | |
---|
200 | const std::string term_erase_line = "\x1B[0K"; |
---|
201 | const std::string term_eol = "\n"; |
---|
202 | const std::string lbar_fmt = "{desc}: {frac:3.0f}% |"; |
---|
203 | const std::string rbar_fmt = "| {n}/{total} [{elapsed:%T} / {remaining:%T}]{eol}"; |
---|
204 | const std::string nototal_fmt = "{desc}: {n} [{elapsed:%T}]{eol}"; |
---|
205 | }; |
---|
206 | |
---|
207 | /* Progress monitor writing directly to a file */ |
---|
208 | class Progress final : public base_progress { |
---|
209 | public: |
---|
210 | Progress(std::string desc="", unsigned int total=0, bool ascii=false, |
---|
211 | FILE* file=stderr, unsigned int width=0) |
---|
212 | : base_progress(desc, total, ascii), |
---|
213 | _width(width), |
---|
214 | _file(file) |
---|
215 | |
---|
216 | { |
---|
217 | if (_width == 0) { |
---|
218 | update_term_width(); |
---|
219 | } |
---|
220 | } |
---|
221 | |
---|
222 | void update_term_width() { |
---|
223 | int fd = fileno(_file); |
---|
224 | struct winsize size; |
---|
225 | if (ioctl(fd, TIOCGWINSZ, &size) == 0) { |
---|
226 | _width = size.ws_col; |
---|
227 | } |
---|
228 | } |
---|
229 | |
---|
230 | void show_progress(timepoint_t now=clock_t::now()) override final { |
---|
231 | // called from base_progress when it decided the progress bar needs |
---|
232 | // to be printed again |
---|
233 | fmt::memory_buffer buf; |
---|
234 | render_progress(now, _width, buf); |
---|
235 | std::copy(term_move_up.begin(), term_move_up.end(), std::back_inserter(buf)); |
---|
236 | fwrite(buf.data(), 1, buf.size(), _file); |
---|
237 | fflush(_file); |
---|
238 | } |
---|
239 | const std::string term_move_up = "\x1B[A"; |
---|
240 | private: |
---|
241 | unsigned int _width; |
---|
242 | FILE* _file; |
---|
243 | }; |
---|
244 | |
---|
245 | /****** spdlog integration ****/ |
---|
246 | |
---|
247 | class status_line; |
---|
248 | |
---|
249 | class status_line_registry { |
---|
250 | public: |
---|
251 | virtual ~status_line_registry() {} |
---|
252 | |
---|
253 | void add_status_line(status_line *msg) { |
---|
254 | _status_lines.push_back(msg); |
---|
255 | } |
---|
256 | void remove_status_line(status_line *msg) { |
---|
257 | _status_lines.erase( |
---|
258 | std::remove(_status_lines.begin(), _status_lines.end(), msg), |
---|
259 | _status_lines.end()); |
---|
260 | } |
---|
261 | const std::vector<status_line*>& get_status_lines() { |
---|
262 | return _status_lines; |
---|
263 | } |
---|
264 | |
---|
265 | virtual void print_status_line_registry() = 0; |
---|
266 | |
---|
267 | private: |
---|
268 | std::vector<status_line*> _status_lines; |
---|
269 | int _change_since_last_print{0}; |
---|
270 | }; |
---|
271 | |
---|
272 | |
---|
273 | class status_line { |
---|
274 | public: |
---|
275 | status_line(std::shared_ptr<spdlog::logger> logger, |
---|
276 | spdlog::level::level_enum level) |
---|
277 | : _logger(logger), _level(level) |
---|
278 | { |
---|
279 | /* on creation, register it with all sinks that understand about us */ |
---|
280 | for (auto &sink : _logger->sinks()) { |
---|
281 | auto ptr = dynamic_cast<status_line_registry*>(sink.get()); |
---|
282 | if (ptr) { |
---|
283 | ptr->add_status_line(this); |
---|
284 | } |
---|
285 | } |
---|
286 | } |
---|
287 | |
---|
288 | ~status_line() { |
---|
289 | /* on deletion, deregister from all sinks */ |
---|
290 | for (auto &sink : _logger->sinks()) { |
---|
291 | auto ptr = dynamic_cast<status_line_registry*>(sink.get()); |
---|
292 | if (ptr) { |
---|
293 | ptr->remove_status_line(this); |
---|
294 | } |
---|
295 | } |
---|
296 | } |
---|
297 | |
---|
298 | /* trigger (re)print of this status line */ |
---|
299 | void trigger_print_status_lines() { |
---|
300 | for (auto &sink : _logger->sinks()) { |
---|
301 | if (sink->should_log(get_level())) { |
---|
302 | auto ptr = dynamic_cast<status_line_registry*>(sink.get()); |
---|
303 | if (ptr) { |
---|
304 | ptr->print_status_line_registry(); |
---|
305 | } |
---|
306 | } |
---|
307 | } |
---|
308 | } |
---|
309 | |
---|
310 | virtual void render_status_line(fmt::memory_buffer &buf, unsigned int width) = 0; |
---|
311 | |
---|
312 | static const char* magic_filename() { |
---|
313 | static const char* file = "PROGRESS MONITOR"; |
---|
314 | return file; |
---|
315 | } |
---|
316 | bool should_log() { |
---|
317 | return _logger->should_log(_level); |
---|
318 | } |
---|
319 | spdlog::level::level_enum get_level() { |
---|
320 | return _level; |
---|
321 | } |
---|
322 | |
---|
323 | void send_log_msg(fmt::memory_buffer &buf) { |
---|
324 | using spdlog::details::fmt_helper::to_string_view; |
---|
325 | spdlog::source_loc loc; |
---|
326 | loc.filename = magic_filename(); |
---|
327 | spdlog::details::log_msg msg{loc, &_logger->name(), _level, to_string_view(buf)}; |
---|
328 | for (auto &sink : _logger->sinks()) { |
---|
329 | if (sink->should_log(get_level())) { |
---|
330 | sink->log(msg); |
---|
331 | } |
---|
332 | } |
---|
333 | } |
---|
334 | |
---|
335 | private: |
---|
336 | std::shared_ptr<spdlog::logger> _logger; |
---|
337 | spdlog::level::level_enum _level{spdlog::level::off}; |
---|
338 | }; |
---|
339 | |
---|
340 | |
---|
341 | class sigwinch_mixin { |
---|
342 | protected: |
---|
343 | sigwinch_mixin(bool install) { |
---|
344 | if (install) { |
---|
345 | install_handler(); |
---|
346 | } |
---|
347 | instances().push_back(this); |
---|
348 | } |
---|
349 | virtual ~sigwinch_mixin() { |
---|
350 | instances().remove(this); |
---|
351 | } |
---|
352 | inline bool check_got_sigwinch() { |
---|
353 | if (got_sigwinch() != 0) { |
---|
354 | got_sigwinch() = 0; |
---|
355 | notify_instances(); |
---|
356 | } |
---|
357 | if (_needs_update) { |
---|
358 | _needs_update = 0; |
---|
359 | return true; |
---|
360 | } |
---|
361 | return false; |
---|
362 | } |
---|
363 | private: |
---|
364 | static sig_atomic_t& got_sigwinch() { |
---|
365 | static sig_atomic_t flag; |
---|
366 | return flag; |
---|
367 | } |
---|
368 | static std::list<sigwinch_mixin*>& instances() { |
---|
369 | static std::list<sigwinch_mixin*> list; |
---|
370 | return list; |
---|
371 | } |
---|
372 | static void handle_sigwinch(int) { |
---|
373 | got_sigwinch() = 1; |
---|
374 | } |
---|
375 | static void install_handler() { |
---|
376 | struct sigaction sa; |
---|
377 | memset(&sa, 0, sizeof(sa)); |
---|
378 | if (sigaction(SIGWINCH, nullptr, &sa)) { |
---|
379 | return; // failed |
---|
380 | } |
---|
381 | if (sa.sa_handler == handle_sigwinch) { |
---|
382 | return; // already installed |
---|
383 | } |
---|
384 | memset(&sa, 0, sizeof(sa)); |
---|
385 | sa.sa_handler = handle_sigwinch; |
---|
386 | sigfillset(&sa.sa_mask); // block other signals during handler |
---|
387 | if (sigaction(SIGWINCH, &sa, nullptr)) { |
---|
388 | return; // failed |
---|
389 | } |
---|
390 | } |
---|
391 | static void notify_instances() { |
---|
392 | //std::lock_guard<mutex_t> lock(super::mutex_); |
---|
393 | for (auto& instance : instances()) { |
---|
394 | instance->_needs_update = true; |
---|
395 | } |
---|
396 | } |
---|
397 | |
---|
398 | bool _needs_update{false}; |
---|
399 | }; |
---|
400 | |
---|
401 | |
---|
402 | template<typename TargetStream, class ConsoleMutex> |
---|
403 | class terminal_sink final |
---|
404 | : public spdlog::sinks::ansicolor_sink<TargetStream, ConsoleMutex>, |
---|
405 | public status_line_registry, public sigwinch_mixin { |
---|
406 | public: |
---|
407 | using super = spdlog::sinks::ansicolor_sink<TargetStream, ConsoleMutex>; |
---|
408 | using mutex_t = typename ConsoleMutex::mutex_t; |
---|
409 | |
---|
410 | terminal_sink() : super(), sigwinch_mixin(super::should_do_colors_) { |
---|
411 | if (super::should_do_colors_) { |
---|
412 | update_term_width(); |
---|
413 | } |
---|
414 | } |
---|
415 | ~terminal_sink() override = default; |
---|
416 | |
---|
417 | // normal log message coming in |
---|
418 | void log(const spdlog::details::log_msg &msg) override final { |
---|
419 | _print(&msg); |
---|
420 | } |
---|
421 | |
---|
422 | // update to status coming in |
---|
423 | void print_status_line_registry() override final { |
---|
424 | _print(nullptr); |
---|
425 | } |
---|
426 | |
---|
427 | void _print(const spdlog::details::log_msg *msg) { |
---|
428 | if (not super::should_do_colors_) { // not a tty |
---|
429 | if (msg != nullptr) { // got a msg |
---|
430 | super::log(*msg); // pass it upstream |
---|
431 | } |
---|
432 | return; |
---|
433 | } |
---|
434 | |
---|
435 | if (check_got_sigwinch()) { |
---|
436 | update_term_width(); |
---|
437 | } |
---|
438 | |
---|
439 | // render status lines |
---|
440 | fmt::memory_buffer status_text; |
---|
441 | int nlines = 0; |
---|
442 | for (auto line : get_status_lines()) { |
---|
443 | if (this->should_log(line->get_level())) { |
---|
444 | line->render_status_line(status_text, _ncols); |
---|
445 | ++ nlines; |
---|
446 | } |
---|
447 | } |
---|
448 | |
---|
449 | // move back up to last log line |
---|
450 | std::lock_guard<mutex_t> lock(super::mutex_); |
---|
451 | for (unsigned int i=0; i < _last_status_lines; ++i) { |
---|
452 | fwrite(term_move_up.data(), 1, term_move_up.size(), super::target_file_); |
---|
453 | } |
---|
454 | _last_status_lines = nlines; |
---|
455 | |
---|
456 | // print message if we have one and it's not an update for other sinks |
---|
457 | if (msg != nullptr && msg->source.filename != status_line::magic_filename()) { |
---|
458 | fwrite(term_clear_line.data(), 1, term_clear_line.size(), super::target_file_); |
---|
459 | super::log(*msg); |
---|
460 | } |
---|
461 | |
---|
462 | fwrite(status_text.data(), 1, status_text.size(), super::target_file_); |
---|
463 | fflush(super::target_file_); |
---|
464 | } |
---|
465 | |
---|
466 | void update_term_width() { |
---|
467 | int fd = fileno(super::target_file_); |
---|
468 | struct winsize size; |
---|
469 | if (ioctl(fd, TIOCGWINSZ, &size) == 0) { |
---|
470 | std::lock_guard<mutex_t> lock(super::mutex_); |
---|
471 | _ncols = size.ws_col; |
---|
472 | } |
---|
473 | } |
---|
474 | |
---|
475 | const std::string term_move_up = "\x1B[A"; |
---|
476 | const std::string term_clear_line = "\x1B[K"; |
---|
477 | private: |
---|
478 | unsigned int _ncols{60}; |
---|
479 | unsigned int _last_status_lines{0}; |
---|
480 | }; |
---|
481 | |
---|
482 | using terminal_stdout_sink_mt = terminal_sink<spdlog::details::console_stdout, spdlog::details::console_mutex>; |
---|
483 | using terminal_stdout_sink_st = terminal_sink<spdlog::details::console_stdout, spdlog::details::console_nullmutex>; |
---|
484 | using terminal_stderr_sink_mt = terminal_sink<spdlog::details::console_stderr, spdlog::details::console_mutex>; |
---|
485 | using terminal_stderr_sink_st = terminal_sink<spdlog::details::console_stderr, spdlog::details::console_nullmutex>; |
---|
486 | |
---|
487 | template<typename Factory = spdlog::default_factory> |
---|
488 | inline std::shared_ptr<spdlog::logger> stdout_terminal_mt(const std::string &logger_name) |
---|
489 | { |
---|
490 | return Factory::template create<terminal_stdout_sink_mt>(logger_name); |
---|
491 | } |
---|
492 | template<typename Factory = spdlog::default_factory> |
---|
493 | inline std::shared_ptr<spdlog::logger> stderr_terminal_mt(const std::string &logger_name) |
---|
494 | { |
---|
495 | return Factory::template create<terminal_stderr_sink_mt>(logger_name); |
---|
496 | } |
---|
497 | |
---|
498 | |
---|
499 | class logger_progress final : public base_progress, status_line { |
---|
500 | public: |
---|
501 | logger_progress(std::shared_ptr<spdlog::logger> logger, |
---|
502 | std::string desc="", unsigned int total=0, bool ascii=false, |
---|
503 | unsigned int /*width*/=0, |
---|
504 | spdlog::level::level_enum level=spdlog::level::warn |
---|
505 | ) |
---|
506 | : base_progress(desc, total, ascii), |
---|
507 | status_line(logger, level) |
---|
508 | { |
---|
509 | } |
---|
510 | ~logger_progress() { |
---|
511 | } |
---|
512 | |
---|
513 | void show_progress(timepoint_t now=clock_t::now()) override final { |
---|
514 | if (!should_log()) { |
---|
515 | // early quit if our logger has loglevel below ourselves |
---|
516 | return; |
---|
517 | } |
---|
518 | duration_t delta_time = now - _last_update; |
---|
519 | status_line::trigger_print_status_lines(); |
---|
520 | if (delta_time > _mininterval) { |
---|
521 | _last_update = now; |
---|
522 | |
---|
523 | fmt::memory_buffer buf; |
---|
524 | base_progress::render_progress(now, 60, buf); |
---|
525 | status_line::send_log_msg(buf); |
---|
526 | } |
---|
527 | } |
---|
528 | |
---|
529 | void render_status_line(fmt::memory_buffer &buf, unsigned int width) override final { |
---|
530 | base_progress::render_progress(clock_t::now(), width, buf); |
---|
531 | } |
---|
532 | |
---|
533 | private: |
---|
534 | timepoint_t _last_update{std::chrono::seconds(0)}; |
---|
535 | duration_t _mininterval{std::chrono::milliseconds(500)}; |
---|
536 | |
---|
537 | }; |
---|
538 | |
---|
539 | |
---|
540 | |
---|
541 | |
---|
542 | } // namespace sina |
---|
543 | |
---|
544 | |
---|
545 | #endif // _LOG_H_ |
---|
546 | /* |
---|
547 | Local Variables: |
---|
548 | mode:c++ |
---|
549 | c-file-style:"stroustrup" |
---|
550 | c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . 0)) |
---|
551 | indent-tabs-mode:nil |
---|
552 | fill-column:99 |
---|
553 | End: |
---|
554 | */ |
---|
555 | // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 : |
---|