• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

mendersoftware / mender / 1054626264

30 Oct 2023 10:27AM UTC coverage: 80.137% (-0.06%) from 80.194%
1054626264

push

gitlab-ci

kacf
chore: Add many missing error checks and exception harnesses.

Signed-off-by: Kristian Amlie <kristian.amlie@northern.tech>

100 of 100 new or added lines in 7 files covered. (100.0%)

6887 of 8594 relevant lines covered (80.14%)

9361.04 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

84.95
/mender-update/deployments/platform/boost_log/deployments.cpp
1
// Copyright 2023 Northern.tech AS
2
//
3
//    Licensed under the Apache License, Version 2.0 (the "License");
4
//    you may not use this file except in compliance with the License.
5
//    You may obtain a copy of the License at
6
//
7
//        http://www.apache.org/licenses/LICENSE-2.0
8
//
9
//    Unless required by applicable law or agreed to in writing, software
10
//    distributed under the License is distributed on an "AS IS" BASIS,
11
//    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
//    See the License for the specific language governing permissions and
13
//    limitations under the License.
14

15
#include <mender-update/deployments.hpp>
16

17
#include <algorithm>
18
#include <cctype>
19
#include <filesystem>
20
#include <string>
21

22
#include <boost/date_time/posix_time/posix_time.hpp>
23
#include <boost/log/attributes.hpp>
24
#include <boost/log/common.hpp>
25
#include <boost/log/sinks.hpp>
26
#include <boost/smart_ptr/shared_ptr.hpp>
27

28
#include <common/error.hpp>
29
#include <common/io.hpp>
30
#include <common/json.hpp>
31
#include <common/log.hpp>
32
#include <common/path.hpp>
33

34
namespace mender {
35
namespace update {
36
namespace deployments {
37

38
using namespace std;
39

40
namespace logging = boost::log;
41
namespace expr = boost::log::expressions;
42
namespace sinks = boost::log::sinks;
43

44
namespace fs = std::filesystem;
45

46
namespace error = mender::common::error;
47
namespace io = mender::common::io;
48
namespace json = mender::common::json;
49
namespace mlog = mender::common::log;
50
namespace path = mender::common::path;
51

52
static void JsonLogFormatter(logging::record_view const &rec, logging::formatting_ostream &strm) {
8,588✔
53
        strm << "{";
8,588✔
54

55
        auto val = logging::extract<boost::posix_time::ptime>("TimeStamp", rec);
8,588✔
56
        if (val) {
8,588✔
57
                strm << R"("timestamp":")"
58
                         << json::EscapeString(boost::posix_time::to_iso_extended_string(val.get())) << "Z"
25,764✔
59
                         << "\",";
8,588✔
60
        }
61

62
        auto level = logging::extract<mlog::LogLevel>("Severity", rec);
8,588✔
63
        if (level) {
8,588✔
64
                string lvl = mlog::ToStringLogLevel(level.get());
8,588✔
65
                strm << R"("level":")" << json::EscapeString(lvl) << "\",";
17,176✔
66
        }
67

68
        strm << R"("message":")" << json::EscapeString(*rec[expr::smessage]) << "\"}";
8,588✔
69
}
8,588✔
70

71
static const size_t kMaxExistingLogs = 5;
72
static const uintmax_t kLogsFreeSpaceRequired = 100 * 1024; // 100 KiB
73

74
error::Error DeploymentLog::PrepareLogDirectory() {
96✔
75
        try {
76
                return DoPrepareLogDirectory();
96✔
77
        } catch (fs::filesystem_error &e) {
×
78
                return error::Error(e.code().default_error_condition(), "Could not prepare log directory");
×
79
        }
80
}
81

82
error::Error DeploymentLog::DoPrepareLogDirectory() {
96✔
83
        fs::path dir_path(data_store_dir_);
192✔
84
        bool created = fs::create_directories(dir_path);
96✔
85
        if (created) {
96✔
86
                // should basically never happen, but if we happened to create the
87
                // directory, it's empty and thus well-prepared
88
                return error::NoError;
×
89
        }
90

91
        vector<string> old_logs;
96✔
92
        for (auto const &entry : fs::directory_iterator {dir_path}) {
1,018✔
93
                fs::path file_path = entry.path();
737✔
94
                if (!fs::is_regular_file(file_path)) {
730✔
95
                        continue;
723✔
96
                }
97

98
                string file_name = file_path.filename().string();
606✔
99

100
                if (file_name == LogFileName()) {
606✔
101
                        // this log file will be (re)used, leave it alone
102
                        continue;
36✔
103
                }
104

105
                if ((file_name.find("deployments.") != 0)
570✔
106
                        || (file_name.substr(file_name.size() - 4) != ".log")) {
581✔
107
                        continue;
559✔
108
                }
109

110
                // expected file name: deployments.NNNN.ID.log
111
                // "deployments.".size() == 12
112
                auto second_dot_pos = file_name.find('.', 12);
11✔
113
                auto last_dot_pos = file_name.find_last_of('.');
114
                if ((second_dot_pos == string::npos) || (last_dot_pos == string::npos)
15✔
115
                        || (second_dot_pos == last_dot_pos) || (second_dot_pos != 16)
10✔
116
                        || any_of(file_name.cbegin() + 12, file_name.cbegin() + second_dot_pos, [](char c) {
18✔
117
                                   return !isdigit(c);
7✔
118
                           })) {
119
                        mlog::Warning("Old deployment log with a malformed file name found: " + file_name);
4✔
120
                        continue;
4✔
121
                }
122

123
                old_logs.push_back(file_name);
7✔
124
        }
125
        std::sort(old_logs.begin(), old_logs.end());
96✔
126

127
        error_code ec;
96✔
128
        fs::space_info space_info = fs::space(dir_path, ec);
96✔
129
        if (ec) {
96✔
130
                return error::Error(
131
                        ec.default_error_condition(), "Failed to check free space for log files");
×
132
        }
133

134
        while ((old_logs.size() > 0)
135
                   && ((space_info.available < kLogsFreeSpaceRequired)
97✔
136
                           || (old_logs.size() > (kMaxExistingLogs - 1)))) {
4✔
137
                auto last_log_file = old_logs[old_logs.size() - 1];
1✔
138
                old_logs.pop_back();
1✔
139
                if (!fs::remove(dir_path / last_log_file, ec) && ec) {
1✔
140
                        return error::Error(
141
                                ec.default_error_condition(),
×
142
                                "Failed to remove old log file '" + last_log_file + "'");
×
143
                }
144
                if (space_info.available < kLogsFreeSpaceRequired) {
1✔
145
                        space_info = fs::space(dir_path, ec);
×
146
                        if (ec) {
×
147
                                return error::Error(
148
                                        ec.default_error_condition(), "Failed to check free space for log files");
×
149
                        }
150
                }
151
        }
152

153
        // now let's make sure old logs have an increasing index starting with 0001
154
        for (ssize_t i = old_logs.size() - 1; i >= 0; i--) {
102✔
155
                // "deployments.".size() == 12
156
                auto second_dot_pos = old_logs[i].find('.', 12);
6✔
157
                auto last_dot_pos = old_logs[i].find_last_of('.');
158

159
                // should never happen due the filter above when populating old_logs and
160
                // due to how these files are named
161
                assert(second_dot_pos != string::npos);
162
                assert(last_dot_pos != string::npos);
163
                assert(second_dot_pos != last_dot_pos);
164

165
                string deployment_id;
166
                if ((second_dot_pos == string::npos) || (last_dot_pos == string::npos)) {
6✔
167
                        deployment_id = "unknown_deployment";
×
168
                } else {
169
                        deployment_id =
170
                                old_logs[i].substr(second_dot_pos + 1, (last_dot_pos - second_dot_pos - 1));
12✔
171
                }
172
                stringstream ss;
12✔
173
                ss << "deployments.";
6✔
174
                ss << setfill('0') << setw(4) << to_string(i + 1);
6✔
175
                ss << "." + deployment_id;
6✔
176
                ss << ".log";
6✔
177

178
                string new_name = ss.str();
179
                fs::rename(dir_path / old_logs[i], dir_path / new_name, ec);
6✔
180
                if (ec) {
6✔
181
                        return error::Error(
182
                                ec.default_error_condition(),
×
183
                                "Failed to rename old log file '" + old_logs[i] + "'");
×
184
                }
185
        }
186

187
        return error::NoError;
96✔
188
}
189

190
error::Error DeploymentLog::BeginLogging() {
96✔
191
        auto err = PrepareLogDirectory();
96✔
192
        if (err != error::NoError) {
96✔
193
                return err;
×
194
        }
195

196
        auto ex_ofstr = io::OpenOfstream(LogFilePath(), true);
192✔
197
        if (!ex_ofstr) {
96✔
198
                return ex_ofstr.error();
×
199
        }
200

201
        auto log_stream = boost::make_shared<std::ofstream>(std::move(ex_ofstr.value()));
96✔
202
        sink_ = boost::make_shared<text_sink>();
192✔
203
        sink_->set_formatter(&JsonLogFormatter);
96✔
204
        sink_->locked_backend()->add_stream(log_stream);
192✔
205
        sink_->locked_backend()->auto_flush(true);
96✔
206

207
        logging::core::get()->add_sink(sink_);
288✔
208

209
        return error::NoError;
96✔
210
}
211

212
error::Error DeploymentLog::FinishLogging() {
96✔
213
        logging::core::get()->remove_sink(sink_);
288✔
214
        sink_.reset();
96✔
215
        return error::NoError;
96✔
216
}
217

218
string DeploymentLog::LogFileName() {
775✔
219
        return "deployments.0000." + id_ + ".log";
1,550✔
220
}
221

222
string DeploymentLog::LogFilePath() {
169✔
223
        return path::Join(data_store_dir_, LogFileName());
338✔
224
}
225

226
} // namespace deployments
227
} // namespace update
228
} // namespace mender
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc