1 /*
2  *  Copyright (c) 2016, Facebook, Inc.
3  *  All rights reserved.
4  *
5  *  This source code is licensed under the BSD-style license found in the
6  *  LICENSE file in the root directory of this source tree. An additional grant
7  *  of patent rights can be found in the PATENTS file in the same directory.
8  */
9 
10 #ifndef FATAL_INCLUDE_fatal_log_log_h
11 #define FATAL_INCLUDE_fatal_log_log_h
12 
13 #include <fatal/preprocessor.h>
14 #include <fatal/time/time.h>
15 
16 #include <atomic>
17 #include <chrono>
18 #include <iostream>
19 #include <memory>
20 #include <ostream>
21 #include <type_traits>
22 #include <utility>
23 
24 #include <cstdlib>
25 
26 // for internal use only
27 
28 namespace fatal {
29 namespace log {
30 namespace detail {
31 namespace log_impl {
32 
33 template <typename TOut, typename TInfo>
34 struct logger {
35   using info = TInfo;
36 
37   struct writer {
writerlogger::writer38     explicit writer(TOut *out) noexcept: out_(out) {}
39 
40     writer(writer const &) = delete;
writerlogger::writer41     writer(writer &&rhs) noexcept: out_(rhs.out_) { rhs.out_ = nullptr; }
42 
43     template <typename T>
44     writer &operator <<(T &&value) & {
45       if (out_) {
46         *out_ << std::forward<T>(value);
47       }
48 
49       return *this;
50     }
51 
52     template <typename T>
53     writer &&operator <<(T &&value) && {
54       if (out_) {
55         *out_ << std::forward<T>(value);
56       }
57 
58       return std::move(*this);
59     }
60 
~writerlogger::writer61     ~writer() {
62       if (out_) {
63         *out_ << '\n';
64       }
65     }
66 
67   private:
68     TOut *out_;
69   };
70 
loggerlogger71   logger(TOut *out, source_info source) noexcept:
72     writer_(out),
73     source_(source)
74   {}
75 
76   logger(logger const &) = delete;
77   logger(logger &&rhs) = default;
78 
~loggerlogger79   ~logger() {
80     if (info::abort::value) {
81       std::abort();
82     }
83   }
84 
85   template <typename T>
86   writer operator <<(T &&value) {
87     writer_ << info::signature::value;
88 
89     if (info::show_level::value) {
90       writer_ << info::value;
91     }
92 
93     auto const now = std::chrono::duration_cast<std::chrono::nanoseconds>(
94       std::chrono::system_clock::now().time_since_epoch()
95     );
96 
97     // TODO: output date in an absolute format
98     time::pretty_print(
99       writer_ << " [" << source_.file() << ':' << source_.line() << "] at ",
100       now
101     ) << ": " << std::forward<T>(value);
102 
103     return std::move(writer_);
104   }
105 
106 private:
107   writer writer_;
108   source_info source_;
109 };
110 
111 using level_t = unsigned;
112 
113 template <typename TCategory, level_t Level>
114 struct log_level {
setlog_level115   static void set(level_t level) {
116     value() = level;
117   }
118 
getlog_level119   static level_t get() {
120     return value();
121   }
122 
123 private:
valuelog_level124   static std::atomic<level_t> &value() {
125     static std::atomic<level_t> instance(Level);
126     return instance;
127   }
128 };
129 
130 template <
131   typename TCategory,
132   level_t Level,
133   char Signature,
134   bool ShowLevel,
135   bool Abort = false
136 >
137 struct level_info:
138   std::integral_constant<level_t, Level>
139 {
140   using category = TCategory;
141   using show_level = std::integral_constant<bool, ShowLevel>;
142   using signature = std::integral_constant<char, Signature>;
143   using abort = std::integral_constant<bool, Abort>;
144 };
145 
146 struct log_tag {};
147 struct verbose_tag {};
148 
149 using level_FATAL = level_info<log_tag, 0, 'F', false, true>;
150 using level_CRITICAL = level_info<log_tag, 1, 'C', false>;
151 using level_ERROR = level_info<log_tag, 2, 'E', false>;
152 using level_WARNING = level_info<log_tag, 3, 'W', false>;
153 using level_INFO = level_info<log_tag, 4, 'I', false>;
154 
155 template <level_t Level>
156 using level_verbose = level_info<verbose_tag, Level, 'V', true>;
157 
158 template <typename> struct by_category;
159 
160 template <>
161 struct by_category<log_tag> {
162   using level = detail::log_impl::log_level<
163     detail::log_impl::log_tag,
164     detail::log_impl::level_INFO::value
165   >;
166 };
167 
168 template <>
169 struct by_category<verbose_tag> {
170   using level = detail::log_impl::log_level<
171     detail::log_impl::verbose_tag, 0
172   >;
173 };
174 
175 } // namespace log_impl {
176 } // namespace detail {
177 
178 struct null_logger {
179   template <typename T>
180   null_logger const &operator <<(T &&) const { return *this; }
181 };
182 
183 using level = detail::log_impl::by_category<
184   detail::log_impl::log_tag
185 >::level;
186 
187 using v_level = detail::log_impl::by_category<
188   detail::log_impl::verbose_tag
189 >::level;
190 
191 // TODO: ADD THE ABILITY TO TURN VERBOSE LOGGING ON AND OFF
192 template <typename TInfo>
193 log::detail::log_impl::logger<std::ostream, TInfo> log(source_info source) {
194   return log::detail::log_impl::logger<std::ostream, TInfo>(
195     TInfo::value <= detail::log_impl::by_category<
196       typename TInfo::category
197     >::level::get()
198       ? std::addressof(std::cerr)
199       : nullptr,
200     source
201   );
202 }
203 
204 } // namespace log {
205 
206 #define FATAL_LOG(Level) \
207   ::fatal::log::log<::fatal::log::detail::log_impl::level_##Level>( \
208     FATAL_SOURCE_INFO() \
209   )
210 
211 #define FATAL_VLOG(Level) \
212   ::fatal::log::log<::fatal::log::detail::log_impl::level_verbose<Level>>( \
213     FATAL_SOURCE_INFO() \
214   )
215 
216 #ifdef NDEBUG
217 # define FATAL_DLOG(Level) ::fatal::log::null_logger()
218 # define FATAL_DVLOG(Level) ::fatal::log::null_logger()
219 #else // NDEBUG
220 # define FATAL_DLOG(Level) FATAL_LOG(Level)
221 # define FATAL_DVLOG(Level) FATAL_VLOG(Level)
222 #endif // NDEBUG
223 
224 } // namespace fatal {
225 
226 #endif // FATAL_INCLUDE_fatal_log_log_h
227