summaryrefslogtreecommitdiffstats
path: root/server/src/log.cppm
blob: 7c4214401615d8a7e736eaf6db7a236d766a82ca (about) (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
module;

// Seems like ADL for std::quoted is broken with
//   import std;
// Might be because the _Quoted_string object is defined in
// std::__detail, which is not exported by the module.
#include <iomanip>

export module routemon:log;

import std;

namespace routemon::log {

export enum class level : std::uint8_t {
  debug,
  info,
  warn,
  error,
};

namespace {

auto operator<<(std::ostream& os, level lvl) -> std::ostream&
{
  switch (lvl)
  {
  case level::debug:
    os << "dbg";
    break;
  case level::info:
    os << "inf";
    break;
  case level::warn:
    os << "wrn";
    break;
  case level::error:
    os << "err";
    break;
  }
  return os;
}

} // namespace

export class sink
{
  std::atomic<enum level> lvl_;
  std::ostream& os_ = std::cout;

  struct tmp_message
  {
    level lvl;
    std::string_view component;
    std::string_view txt;
    std::map<std::string, std::string> const& attrs;
  };

  auto write(tmp_message msg) -> void
  {
    auto sos = std::osyncstream{os_};
    sos << "[" << msg.lvl;
    if (!msg.component.empty())
      sos << " " << msg.component;
    sos << "] " << msg.txt;
    for (auto const& [k, v] : msg.attrs)
    {
      sos << " " << k << "=" << std::quoted(v);
    }
    sos << '\n';
  }

  explicit sink(level lvl) : lvl_{lvl} {}

  friend auto make_sink(level lvl) -> std::shared_ptr<sink>;
  friend class logger;

public:
  [[nodiscard]] auto level() const -> enum level { return lvl_; }

  auto set_level(enum level lvl) -> void { lvl_ = lvl; }
};

export auto make_sink(level lvl) -> std::shared_ptr<sink>
{
  return std::shared_ptr<sink>{new sink{lvl}};
}

export class logger
{
  std::shared_ptr<sink> sink_;
  std::string component_;
  std::map<std::string, std::string> attrs_;

  template <log::level lvl>
  auto log_at(std::string_view fmt, std::format_args args) -> logger&
  {
    if (sink_->level() <= lvl)
      sink_->write(
        sink::tmp_message{
          .lvl = lvl,
          .component = component_,
          .txt = std::vformat(fmt, args),
          .attrs = attrs_,
        });
    return *this;
  }

public:
  explicit logger(std::shared_ptr<sink> const& sink) : sink_{sink}
  {
    if (!sink)
    {
      throw std::invalid_argument{"logger sink may not be null"};
    }
  }

  [[nodiscard]] auto sub(std::string_view component) const -> logger
  {
    auto l = *this;
    if (l.component_.empty())
    {
      l.component_ = component;
    }
    else
    {
      l.component_ += ".";
      l.component_ += component;
    }
    return l;
  }

  [[nodiscard]] auto with(std::string const& k, std::string&& v) const -> logger
  {
    auto l = *this;
    l.attrs_[k] = std::move(v);
    return l;
  }

  [[nodiscard]] auto with(std::string const& k, std::string_view v) const
    -> logger
  {
    return with(k, std::string{v});
  }

  template <class... Args>
  auto debug(std::format_string<Args...> fmt, Args&&... args) -> logger&
  {
    return log_at<level::debug>(fmt.get(), std::make_format_args(args...));
  }

  template <class... Args>
  auto info(std::format_string<Args...> fmt, Args&&... args) -> logger&
  {
    return log_at<level::info>(fmt.get(), std::make_format_args(args...));
  }

  template <class... Args>
  auto warn(std::format_string<Args...> fmt, Args&&... args) -> logger&
  {
    return log_at<level::warn>(fmt.get(), std::make_format_args(args...));
  }

  template <class... Args>
  auto error(std::format_string<Args...> fmt, Args&&... args) -> logger&
  {
    return log_at<level::error>(fmt.get(), std::make_format_args(args...));
  }
};

} // namespace routemon::log