1 // Copyright 2010 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "base/vlog.h"
6
7 #include <stddef.h>
8
9 #include <ostream>
10 #include <string_view>
11 #include <utility>
12
13 #include "base/check_op.h"
14 #include "base/logging.h"
15 #include "base/strings/string_number_conversions.h"
16 #include "base/strings/string_split.h"
17 #include "base/strings/string_util.h"
18
19 namespace logging {
20
21 const int VlogInfo::kDefaultVlogLevel = 0;
22
VmodulePattern(const std::string & pattern)23 VlogInfo::VmodulePattern::VmodulePattern(const std::string& pattern)
24 : pattern(pattern),
25 vlog_level(VlogInfo::kDefaultVlogLevel),
26 match_target(MATCH_MODULE) {
27 // If the pattern contains a {forward,back} slash, we assume that
28 // it's meant to be tested against the entire __FILE__ string.
29 std::string::size_type first_slash = pattern.find_first_of("\\/");
30 if (first_slash != std::string::npos)
31 match_target = MATCH_FILE;
32 }
33
VmodulePattern()34 VlogInfo::VmodulePattern::VmodulePattern()
35 : vlog_level(VlogInfo::kDefaultVlogLevel), match_target(MATCH_MODULE) {}
36
37 // static
ParseVmoduleLevels(const std::string & vmodule_switch)38 std::vector<VlogInfo::VmodulePattern> VlogInfo::ParseVmoduleLevels(
39 const std::string& vmodule_switch) {
40 std::vector<VmodulePattern> vmodule_levels;
41 base::StringPairs kv_pairs;
42 if (!base::SplitStringIntoKeyValuePairs(vmodule_switch, '=', ',',
43 &kv_pairs)) {
44 DLOG(WARNING) << "Could not fully parse vmodule switch \"" << vmodule_switch
45 << "\"";
46 }
47 for (const auto& pair : kv_pairs) {
48 VmodulePattern pattern(pair.first);
49 if (!base::StringToInt(pair.second, &pattern.vlog_level)) {
50 DLOG(WARNING) << "Parsed vlog level for \"" << pair.first << "="
51 << pair.second << "\" as " << pattern.vlog_level;
52 }
53 vmodule_levels.push_back(pattern);
54 }
55 return vmodule_levels;
56 }
57
VlogInfo(const std::string & v_switch,const std::string & vmodule_switch,int * min_log_level)58 VlogInfo::VlogInfo(const std::string& v_switch,
59 const std::string& vmodule_switch,
60 int* min_log_level)
61 : vmodule_levels_(ParseVmoduleLevels(vmodule_switch)),
62 min_log_level_(min_log_level) {
63 DCHECK_NE(min_log_level, nullptr);
64
65 int vlog_level = 0;
66 if (!v_switch.empty()) {
67 if (base::StringToInt(v_switch, &vlog_level)) {
68 SetMaxVlogLevel(vlog_level);
69 } else {
70 DLOG(WARNING) << "Could not parse v switch \"" << v_switch << "\"";
71 }
72 }
73 }
74
75 VlogInfo::~VlogInfo() = default;
76
77 namespace {
78
79 // Given a path, returns the basename with the extension chopped off
80 // (and any -inl suffix). We avoid using FilePath to minimize the
81 // number of dependencies the logging system has.
GetModule(std::string_view file)82 std::string_view GetModule(std::string_view file) {
83 std::string_view module(file);
84 size_t last_slash_pos = module.find_last_of("\\/");
85 if (last_slash_pos != std::string_view::npos) {
86 module.remove_prefix(last_slash_pos + 1);
87 }
88 size_t extension_start = module.rfind('.');
89 module = module.substr(0, extension_start);
90 static const char kInlSuffix[] = "-inl";
91 static const int kInlSuffixLen = std::size(kInlSuffix) - 1;
92 if (base::EndsWith(module, kInlSuffix))
93 module.remove_suffix(kInlSuffixLen);
94 return module;
95 }
96
97 } // namespace
98
GetVlogLevel(std::string_view file) const99 int VlogInfo::GetVlogLevel(std::string_view file) const {
100 if (!vmodule_levels_.empty()) {
101 std::string_view module(GetModule(file));
102 for (const auto& it : vmodule_levels_) {
103 std::string_view target(
104 (it.match_target == VmodulePattern::MATCH_FILE) ? file : module);
105 if (MatchVlogPattern(target, it.pattern))
106 return it.vlog_level;
107 }
108 }
109 return GetMaxVlogLevel();
110 }
111
SetMaxVlogLevel(int level)112 void VlogInfo::SetMaxVlogLevel(int level) {
113 // Log severity is the negative verbosity.
114 *min_log_level_ = -level;
115 }
116
GetMaxVlogLevel() const117 int VlogInfo::GetMaxVlogLevel() const {
118 return -*min_log_level_;
119 }
120
VlogInfo(std::vector<VmodulePattern> vmodule_levels,int * min_log_level)121 VlogInfo::VlogInfo(std::vector<VmodulePattern> vmodule_levels,
122 int* min_log_level)
123 : vmodule_levels_(std::move(vmodule_levels)),
124 min_log_level_(min_log_level) {}
125
WithSwitches(const std::string & vmodule_switch) const126 VlogInfo* VlogInfo::WithSwitches(const std::string& vmodule_switch) const {
127 std::vector<VmodulePattern> vmodule_levels = vmodule_levels_;
128 std::vector<VmodulePattern> additional_vmodule_levels =
129 ParseVmoduleLevels(vmodule_switch);
130 vmodule_levels.insert(vmodule_levels.end(), additional_vmodule_levels.begin(),
131 additional_vmodule_levels.end());
132 return new VlogInfo(std::move(vmodule_levels), min_log_level_);
133 }
134
MatchVlogPattern(std::string_view string,std::string_view vlog_pattern)135 bool MatchVlogPattern(std::string_view string, std::string_view vlog_pattern) {
136 // The code implements the glob matching using a greedy approach described in
137 // https://research.swtch.com/glob.
138 size_t s = 0, nexts = 0;
139 size_t p = 0, nextp = 0;
140 size_t slen = string.size(), plen = vlog_pattern.size();
141 while (s < slen || p < plen) {
142 if (p < plen) {
143 switch (vlog_pattern[p]) {
144 // A slash (forward or back) must match a slash (forward or back).
145 case '/':
146 case '\\':
147 if (s < slen && (string[s] == '/' || string[s] == '\\')) {
148 p++, s++;
149 continue;
150 }
151 break;
152 // A '?' matches anything.
153 case '?':
154 if (s < slen) {
155 p++, s++;
156 continue;
157 }
158 break;
159 case '*':
160 nextp = p;
161 nexts = s + 1;
162 p++;
163 continue;
164 // Anything else must match literally.
165 default:
166 if (s < slen && string[s] == vlog_pattern[p]) {
167 p++, s++;
168 continue;
169 }
170 break;
171 }
172 }
173 // Mismatch - maybe restart.
174 if (0 < nexts && nexts <= slen) {
175 p = nextp;
176 s = nexts;
177 continue;
178 }
179 return false;
180 }
181 return true;
182 }
183
184 } // namespace logging
185