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

mendersoftware / mender / 2271300743

19 Jan 2026 11:42AM UTC coverage: 81.376% (+1.7%) from 79.701%
2271300743

push

gitlab-ci

web-flow
Merge pull request #1879 from lluiscampos/MEN-8687-ci-debian-updates

MEN-8687: Update Debian base images for CI jobs

8791 of 10803 relevant lines covered (81.38%)

20310.08 hits per line

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

83.33
/src/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) {
12,661✔
53
        strm << "{";
12,661✔
54

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

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

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

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

74
error::Error DeploymentLog::PrepareLogDirectory() {
98✔
75
        try {
76
                return DoPrepareLogDirectory();
98✔
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() {
98✔
83
        fs::path dir_path(data_store_dir_);
98✔
84

85
        // `create_directories` may fail on some platforms with an error if the destination exists
86
        // as a symlink, even if the target is a valid directory. So let's check for existence
87
        // first.
88
        if (not fs::exists(dir_path)) {
98✔
89
                // should rarely happen, but if we happened to create the directory, it's empty and
90
                // thus well-prepared
91
                fs::create_directories(dir_path);
×
92
                return error::NoError;
×
93
        }
94

95
        vector<string> old_logs;
96
        for (auto const &entry : fs::directory_iterator {dir_path}) {
1,311✔
97
                fs::path file_path = entry.path();
1,017✔
98
                if (!fs::is_regular_file(file_path)) {
1,017✔
99
                        continue;
217✔
100
                }
101

102
                string file_name = file_path.filename().string();
800✔
103

104
                if (file_name == LogFileName()) {
800✔
105
                        // this log file will be (re)used, leave it alone
106
                        continue;
37✔
107
                }
108

109
                if ((file_name.find("deployments.") != 0)
763✔
110
                        || (file_name.substr(file_name.size() - 4) != ".log")) {
773✔
111
                        continue;
753✔
112
                }
113

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

127
                old_logs.push_back(file_name);
6✔
128
        }
1,017✔
129
        std::sort(old_logs.begin(), old_logs.end());
98✔
130

131
        error_code ec;
98✔
132
        fs::space_info space_info = fs::space(dir_path, ec);
98✔
133
        if (ec) {
98✔
134
                return error::Error(
135
                        ec.default_error_condition(), "Failed to check free space for log files");
×
136
        }
137

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

157
        // now let's make sure old logs have an increasing index starting with 0001
158
        for (ssize_t i = old_logs.size() - 1; i >= 0; i--) {
103✔
159
                // "deployments.".size() == 12
160
                auto second_dot_pos = old_logs[i].find('.', 12);
5✔
161
                auto last_dot_pos = old_logs[i].find_last_of('.');
162

163
                // should never happen due the filter above when populating old_logs and
164
                // due to how these files are named
165
                assert(second_dot_pos != string::npos);
166
                assert(last_dot_pos != string::npos);
167
                assert(second_dot_pos != last_dot_pos);
168

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

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

191
        return error::NoError;
98✔
192
}
98✔
193

194
error::Error DeploymentLog::BeginLogging() {
98✔
195
        auto err = PrepareLogDirectory();
98✔
196
        if (err != error::NoError) {
98✔
197
                return err;
×
198
        }
199

200
        auto ex_ofstr = io::OpenOfstream(LogFilePath(), true);
196✔
201
        if (!ex_ofstr) {
98✔
202
                return ex_ofstr.error();
×
203
        }
204

205
        auto log_stream = boost::make_shared<std::ofstream>(std::move(ex_ofstr.value()));
98✔
206
        sink_ = boost::make_shared<text_sink>();
196✔
207
        sink_->set_formatter(&JsonLogFormatter);
98✔
208
        sink_->locked_backend()->add_stream(log_stream);
196✔
209
        sink_->locked_backend()->auto_flush(true);
98✔
210

211
        logging::core::get()->add_sink(sink_);
294✔
212

213
        return error::NoError;
98✔
214
}
215

216
error::Error DeploymentLog::FinishLogging() {
98✔
217
        logging::core::get()->remove_sink(sink_);
294✔
218
        sink_.reset();
98✔
219
        return error::NoError;
98✔
220
}
221

222
string DeploymentLog::LogFileName() {
973✔
223
        return "deployments.0000." + id_ + ".log";
1,946✔
224
}
225

226
string DeploymentLog::LogFilePath() {
173✔
227
        return path::Join(data_store_dir_, LogFileName());
346✔
228
}
229

230
} // namespace deployments
231
} // namespace update
232
} // 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

© 2026 Coveralls, Inc