mirror of
https://github.com/kidfromjupiter/nearby.git
synced 2026-09-14 14:46:12 -04:00
added native file logging
This commit is contained in:
@@ -0,0 +1,22 @@
|
||||
# QuickShare for Linux
|
||||
|
||||
## Native application logs
|
||||
|
||||
The QuickShare GUI writes its native Abseil diagnostics to:
|
||||
|
||||
```text
|
||||
$XDG_STATE_HOME/quickshare/logs/quickshare.log
|
||||
```
|
||||
|
||||
If `XDG_STATE_HOME` is not set to an absolute path, the directory defaults to
|
||||
`$HOME/.local/state/quickshare/logs`.
|
||||
|
||||
Records are stored exactly as Abseil emits them. The active log rotates at
|
||||
10 MiB, with `quickshare.log.1` through `quickshare.log.4` retaining the four
|
||||
previous files. The directory and its files are accessible only to the current
|
||||
user.
|
||||
|
||||
After file logging initializes, native Abseil diagnostics are not duplicated
|
||||
to stderr. If the file cannot be initialized or written, diagnostics fall back
|
||||
to stderr. Qt and QML messages keep their existing behavior and are not written
|
||||
to these files.
|
||||
@@ -1,4 +1,5 @@
|
||||
load("@rules_cc//cc:cc_library.bzl", "cc_library")
|
||||
load("@rules_cc//cc:cc_test.bzl", "cc_test")
|
||||
load("@rules_qt//:qt.bzl", "qt_cc_binary", "qt_cc_library", "qt_resource_via_qrc")
|
||||
load("@rules_shell//shell:sh_binary.bzl", "sh_binary")
|
||||
|
||||
@@ -67,6 +68,38 @@ qt_cc_library(
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "native_file_logging",
|
||||
srcs = ["native_file_logging.cc"],
|
||||
hdrs = ["native_file_logging.h"],
|
||||
deps = [
|
||||
"//sharing/linux:linux_sharing_platform",
|
||||
"@com_google_absl//absl/base:log_severity",
|
||||
"@com_google_absl//absl/log:globals",
|
||||
"@com_google_absl//absl/log:initialize",
|
||||
"@com_google_absl//absl/log:log_entry",
|
||||
"@com_google_absl//absl/log:log_sink",
|
||||
"@com_google_absl//absl/log:log_sink_registry",
|
||||
"@com_google_absl//absl/strings:string_view",
|
||||
],
|
||||
)
|
||||
|
||||
cc_test(
|
||||
name = "native_file_logging_test",
|
||||
size = "small",
|
||||
srcs = ["native_file_logging_test.cc"],
|
||||
deps = [
|
||||
":native_file_logging",
|
||||
"//sharing/linux:linux_sharing_platform",
|
||||
"@com_google_absl//absl/log",
|
||||
"@com_google_absl//absl/log:globals",
|
||||
"@com_google_absl//absl/log:initialize",
|
||||
"@com_google_absl//absl/log:log_sink",
|
||||
"@com_google_absl//absl/log:log_sink_registry",
|
||||
"@com_google_googletest//:gtest_main",
|
||||
],
|
||||
)
|
||||
|
||||
qt_cc_binary(
|
||||
name = "app",
|
||||
srcs = ["main.cc"],
|
||||
@@ -76,6 +109,7 @@ qt_cc_binary(
|
||||
deps = [
|
||||
":app_resources",
|
||||
":backend",
|
||||
":native_file_logging",
|
||||
"@rules_qt//:qt_core",
|
||||
"@rules_qt//:qt_gui",
|
||||
"@rules_qt//:qt_hdrs",
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#include <QQmlContext>
|
||||
#include "backend.h"
|
||||
#include "qobject.h"
|
||||
#include "sharing/linux/app/native_file_logging.h"
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -113,6 +114,8 @@ void InstallTerminationCleanup(QApplication& app) {
|
||||
} // namespace
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
std::unique_ptr<nearby::sharing::linux::NativeFileLogging> native_logging =
|
||||
nearby::sharing::linux::NativeFileLogging::Initialize();
|
||||
QApplication app(argc, argv);
|
||||
app.setApplicationName(QStringLiteral("QuickShare"));
|
||||
app.setDesktopFileName(QStringLiteral("quickshare"));
|
||||
|
||||
@@ -0,0 +1,213 @@
|
||||
// Copyright 2026 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "sharing/linux/app/native_file_logging.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/file.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <cstdio>
|
||||
#include <system_error>
|
||||
#include <utility>
|
||||
|
||||
#include "absl/log/globals.h"
|
||||
#include "absl/log/initialize.h"
|
||||
#include "absl/log/log_sink_registry.h"
|
||||
#include "absl/strings/string_view.h"
|
||||
#include "sharing/linux/platform/platform_util.h"
|
||||
|
||||
namespace nearby::sharing::linux {
|
||||
namespace {
|
||||
|
||||
constexpr char kLogFileName[] = "quickshare.log";
|
||||
constexpr char kLockFileName[] = ".quickshare.log.lock";
|
||||
|
||||
bool WriteAll(int fd, const char* data, size_t size) {
|
||||
while (size > 0) {
|
||||
const ssize_t written = write(fd, data, size);
|
||||
if (written < 0) {
|
||||
if (errno == EINTR) {
|
||||
continue;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
data += written;
|
||||
size -= static_cast<size_t>(written);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void WriteToStderr(absl::string_view message) {
|
||||
static_cast<void>(WriteAll(STDERR_FILENO, message.data(), message.size()));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
std::unique_ptr<NativeFileLogSink> NativeFileLogSink::Create(
|
||||
std::filesystem::path log_directory, size_t max_file_size, int file_count) {
|
||||
if (max_file_size == 0 || file_count < 1) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::error_code error;
|
||||
std::filesystem::create_directories(log_directory, error);
|
||||
if (error) {
|
||||
return nullptr;
|
||||
}
|
||||
std::filesystem::permissions(log_directory, std::filesystem::perms::owner_all,
|
||||
std::filesystem::perm_options::replace, error);
|
||||
if (error) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const std::filesystem::path lock_path = log_directory / kLockFileName;
|
||||
const int lock_fd =
|
||||
open(lock_path.c_str(), O_CREAT | O_RDWR | O_CLOEXEC | O_NOFOLLOW, 0600);
|
||||
if (lock_fd < 0) {
|
||||
return nullptr;
|
||||
}
|
||||
if (fchmod(lock_fd, 0600) != 0) {
|
||||
close(lock_fd);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const std::filesystem::path log_path = log_directory / kLogFileName;
|
||||
const int log_fd =
|
||||
open(log_path.c_str(),
|
||||
O_CREAT | O_WRONLY | O_APPEND | O_CLOEXEC | O_NOFOLLOW, 0600);
|
||||
if (log_fd < 0 || fchmod(log_fd, 0600) != 0) {
|
||||
if (log_fd >= 0) {
|
||||
close(log_fd);
|
||||
}
|
||||
close(lock_fd);
|
||||
return nullptr;
|
||||
}
|
||||
close(log_fd);
|
||||
|
||||
return std::unique_ptr<NativeFileLogSink>(new NativeFileLogSink(
|
||||
std::move(log_directory), lock_fd, max_file_size, file_count));
|
||||
}
|
||||
|
||||
NativeFileLogSink::NativeFileLogSink(std::filesystem::path log_directory,
|
||||
int lock_fd, size_t max_file_size,
|
||||
int file_count)
|
||||
: log_path_(std::move(log_directory) / kLogFileName),
|
||||
lock_fd_(lock_fd),
|
||||
max_file_size_(max_file_size),
|
||||
file_count_(file_count) {}
|
||||
|
||||
NativeFileLogSink::~NativeFileLogSink() {
|
||||
close(lock_fd_);
|
||||
}
|
||||
|
||||
void NativeFileLogSink::Send(const absl::LogEntry& entry) {
|
||||
const absl::string_view message =
|
||||
entry.text_message_with_prefix_and_newline();
|
||||
const bool flush = entry.log_severity() >= absl::LogSeverity::kError;
|
||||
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
if (flock(lock_fd_, LOCK_EX) != 0) {
|
||||
WriteFallback(message.data(), message.size());
|
||||
return;
|
||||
}
|
||||
|
||||
const bool success = RotateIfNeeded(message.size()) &&
|
||||
WriteRecord(message.data(), message.size(), flush);
|
||||
static_cast<void>(flock(lock_fd_, LOCK_UN));
|
||||
if (!success) {
|
||||
WriteFallback(message.data(), message.size());
|
||||
}
|
||||
}
|
||||
|
||||
bool NativeFileLogSink::RotateIfNeeded(size_t incoming_size) {
|
||||
struct stat status{};
|
||||
if (stat(log_path_.c_str(), &status) != 0) {
|
||||
return errno == ENOENT;
|
||||
}
|
||||
const size_t current_size =
|
||||
status.st_size > 0 ? static_cast<size_t>(status.st_size) : 0;
|
||||
if (current_size == 0 || (incoming_size <= max_file_size_ &&
|
||||
current_size <= max_file_size_ - incoming_size)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (int index = file_count_ - 1; index >= 1; --index) {
|
||||
const std::filesystem::path destination =
|
||||
log_path_.string() + "." + std::to_string(index);
|
||||
const std::filesystem::path source =
|
||||
index == 1 ? log_path_
|
||||
: std::filesystem::path(log_path_.string() + "." +
|
||||
std::to_string(index - 1));
|
||||
if (rename(source.c_str(), destination.c_str()) != 0 && errno != ENOENT) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool NativeFileLogSink::WriteRecord(const char* data, size_t size, bool flush) {
|
||||
const int fd =
|
||||
open(log_path_.c_str(),
|
||||
O_CREAT | O_WRONLY | O_APPEND | O_CLOEXEC | O_NOFOLLOW, 0600);
|
||||
if (fd < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool success = fchmod(fd, 0600) == 0 && WriteAll(fd, data, size);
|
||||
if (success && flush) {
|
||||
success = fsync(fd) == 0;
|
||||
}
|
||||
if (close(fd) != 0) {
|
||||
success = false;
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
void NativeFileLogSink::WriteFallback(const char* data, size_t size) {
|
||||
WriteToStderr(absl::string_view(data, size));
|
||||
}
|
||||
|
||||
std::unique_ptr<NativeFileLogging> NativeFileLogging::Initialize() {
|
||||
std::unique_ptr<NativeFileLogSink> sink =
|
||||
NativeFileLogSink::Create(internal::GetQuickShareLogPath().ToString());
|
||||
absl::InitializeLog();
|
||||
if (sink == nullptr) {
|
||||
WriteToStderr(
|
||||
"QuickShare: unable to initialize file logging; using "
|
||||
"stderr.\n");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
absl::AddLogSink(sink.get());
|
||||
const absl::LogSeverityAtLeast previous_threshold = absl::StderrThreshold();
|
||||
absl::SetStderrThreshold(absl::LogSeverityAtLeast::kInfinity);
|
||||
return std::unique_ptr<NativeFileLogging>(
|
||||
new NativeFileLogging(std::move(sink), previous_threshold));
|
||||
}
|
||||
|
||||
NativeFileLogging::NativeFileLogging(
|
||||
std::unique_ptr<NativeFileLogSink> sink,
|
||||
absl::LogSeverityAtLeast previous_stderr_threshold)
|
||||
: sink_(std::move(sink)),
|
||||
previous_stderr_threshold_(previous_stderr_threshold) {}
|
||||
|
||||
NativeFileLogging::~NativeFileLogging() {
|
||||
absl::SetStderrThreshold(previous_stderr_threshold_);
|
||||
absl::RemoveLogSink(sink_.get());
|
||||
}
|
||||
|
||||
} // namespace nearby::sharing::linux
|
||||
@@ -0,0 +1,82 @@
|
||||
// Copyright 2026 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef SHARING_LINUX_APP_NATIVE_FILE_LOGGING_H_
|
||||
#define SHARING_LINUX_APP_NATIVE_FILE_LOGGING_H_
|
||||
|
||||
#include <cstddef>
|
||||
#include <filesystem>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
|
||||
#include "absl/base/log_severity.h"
|
||||
#include "absl/log/log_entry.h"
|
||||
#include "absl/log/log_sink.h"
|
||||
|
||||
namespace nearby::sharing::linux {
|
||||
|
||||
class NativeFileLogSink final : public absl::LogSink {
|
||||
public:
|
||||
static constexpr size_t kDefaultMaxFileSize = 10 * 1024 * 1024;
|
||||
static constexpr int kDefaultFileCount = 5;
|
||||
|
||||
static std::unique_ptr<NativeFileLogSink> Create(
|
||||
std::filesystem::path log_directory,
|
||||
size_t max_file_size = kDefaultMaxFileSize,
|
||||
int file_count = kDefaultFileCount);
|
||||
|
||||
NativeFileLogSink(const NativeFileLogSink&) = delete;
|
||||
NativeFileLogSink& operator=(const NativeFileLogSink&) = delete;
|
||||
~NativeFileLogSink() override;
|
||||
|
||||
void Send(const absl::LogEntry& entry) override;
|
||||
|
||||
const std::filesystem::path& log_path() const { return log_path_; }
|
||||
|
||||
private:
|
||||
NativeFileLogSink(std::filesystem::path log_directory, int lock_fd,
|
||||
size_t max_file_size, int file_count);
|
||||
|
||||
bool RotateIfNeeded(size_t incoming_size);
|
||||
bool WriteRecord(const char* data, size_t size, bool flush);
|
||||
void WriteFallback(const char* data, size_t size);
|
||||
|
||||
std::filesystem::path log_path_;
|
||||
int lock_fd_;
|
||||
size_t max_file_size_;
|
||||
int file_count_;
|
||||
std::mutex mutex_;
|
||||
};
|
||||
|
||||
// Owns the process-wide Abseil sink registration. Initialize() must be called
|
||||
// exactly once, before any application components start logging.
|
||||
class NativeFileLogging final {
|
||||
public:
|
||||
static std::unique_ptr<NativeFileLogging> Initialize();
|
||||
|
||||
NativeFileLogging(const NativeFileLogging&) = delete;
|
||||
NativeFileLogging& operator=(const NativeFileLogging&) = delete;
|
||||
~NativeFileLogging();
|
||||
|
||||
private:
|
||||
NativeFileLogging(std::unique_ptr<NativeFileLogSink> sink,
|
||||
absl::LogSeverityAtLeast previous_stderr_threshold);
|
||||
|
||||
std::unique_ptr<NativeFileLogSink> sink_;
|
||||
absl::LogSeverityAtLeast previous_stderr_threshold_;
|
||||
};
|
||||
|
||||
} // namespace nearby::sharing::linux
|
||||
|
||||
#endif // SHARING_LINUX_APP_NATIVE_FILE_LOGGING_H_
|
||||
@@ -0,0 +1,265 @@
|
||||
// Copyright 2026 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "sharing/linux/app/native_file_logging.h"
|
||||
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <cstdlib>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/log/globals.h"
|
||||
#include "absl/log/initialize.h"
|
||||
#include "absl/log/log.h"
|
||||
#include "absl/log/log_sink.h"
|
||||
#include "absl/log/log_sink_registry.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "sharing/linux/platform/platform_util.h"
|
||||
|
||||
namespace nearby::sharing::linux {
|
||||
namespace {
|
||||
|
||||
class TemporaryDirectory {
|
||||
public:
|
||||
TemporaryDirectory() {
|
||||
std::string path =
|
||||
(std::filesystem::temp_directory_path() /
|
||||
("quickshare-logging-test-" + std::to_string(getpid()) + "-XXXXXX"))
|
||||
.string();
|
||||
path.push_back('\0');
|
||||
char* created = mkdtemp(path.data());
|
||||
EXPECT_NE(created, nullptr);
|
||||
if (created != nullptr) {
|
||||
path_ = created;
|
||||
}
|
||||
}
|
||||
|
||||
~TemporaryDirectory() {
|
||||
std::error_code error;
|
||||
std::filesystem::remove_all(path_, error);
|
||||
}
|
||||
|
||||
const std::filesystem::path& path() const { return path_; }
|
||||
|
||||
private:
|
||||
std::filesystem::path path_;
|
||||
};
|
||||
|
||||
class ScopedEnvironmentVariable {
|
||||
public:
|
||||
ScopedEnvironmentVariable(const char* name, std::optional<std::string> value)
|
||||
: name_(name) {
|
||||
const char* old_value = std::getenv(name);
|
||||
if (old_value != nullptr) {
|
||||
old_value_ = old_value;
|
||||
}
|
||||
if (value.has_value()) {
|
||||
setenv(name, value->c_str(), 1);
|
||||
} else {
|
||||
unsetenv(name);
|
||||
}
|
||||
}
|
||||
|
||||
~ScopedEnvironmentVariable() {
|
||||
if (old_value_.has_value()) {
|
||||
setenv(name_.c_str(), old_value_->c_str(), 1);
|
||||
} else {
|
||||
unsetenv(name_.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
std::string name_;
|
||||
std::optional<std::string> old_value_;
|
||||
};
|
||||
|
||||
class CaptureSink final : public absl::LogSink {
|
||||
public:
|
||||
void Send(const absl::LogEntry& entry) override {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
records_.emplace_back(entry.text_message_with_prefix_and_newline());
|
||||
}
|
||||
|
||||
std::vector<std::string> records() {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
return records_;
|
||||
}
|
||||
|
||||
private:
|
||||
std::mutex mutex_;
|
||||
std::vector<std::string> records_;
|
||||
};
|
||||
|
||||
void EnsureAbslLoggingInitialized() {
|
||||
static const bool initialized = [] {
|
||||
absl::InitializeLog();
|
||||
return true;
|
||||
}();
|
||||
static_cast<void>(initialized);
|
||||
}
|
||||
|
||||
std::string ReadFile(const std::filesystem::path& path) {
|
||||
std::ifstream stream(path, std::ios::binary);
|
||||
std::ostringstream contents;
|
||||
contents << stream.rdbuf();
|
||||
return contents.str();
|
||||
}
|
||||
|
||||
int PermissionBits(const std::filesystem::path& path) {
|
||||
struct stat status{};
|
||||
EXPECT_EQ(stat(path.c_str(), &status), 0);
|
||||
return status.st_mode & 0777;
|
||||
}
|
||||
|
||||
TEST(NativeFileLogSinkTest, PreservesOriginalAbseilRecord) {
|
||||
EnsureAbslLoggingInitialized();
|
||||
TemporaryDirectory temporary_directory;
|
||||
auto sink = NativeFileLogSink::Create(temporary_directory.path());
|
||||
ASSERT_NE(sink, nullptr);
|
||||
CaptureSink capture;
|
||||
absl::ScopedStderrThreshold suppress_stderr(
|
||||
absl::LogSeverityAtLeast::kInfinity);
|
||||
absl::AddLogSink(sink.get());
|
||||
absl::AddLogSink(&capture);
|
||||
|
||||
LOG(INFO) << "native-record-sentinel";
|
||||
|
||||
absl::RemoveLogSink(&capture);
|
||||
absl::RemoveLogSink(sink.get());
|
||||
const std::vector<std::string> records = capture.records();
|
||||
ASSERT_EQ(records.size(), size_t{1});
|
||||
EXPECT_EQ(ReadFile(sink->log_path()), records.front());
|
||||
}
|
||||
|
||||
TEST(NativeFileLogSinkTest, RotatesAndRetainsFiveFiles) {
|
||||
EnsureAbslLoggingInitialized();
|
||||
TemporaryDirectory temporary_directory;
|
||||
auto sink = NativeFileLogSink::Create(temporary_directory.path(), 256, 5);
|
||||
ASSERT_NE(sink, nullptr);
|
||||
absl::ScopedStderrThreshold suppress_stderr(
|
||||
absl::LogSeverityAtLeast::kInfinity);
|
||||
absl::AddLogSink(sink.get());
|
||||
|
||||
for (int index = 0; index < 8; ++index) {
|
||||
LOG(INFO) << "rotation-record-" << index << "-" << std::string(180, 'x');
|
||||
}
|
||||
|
||||
absl::RemoveLogSink(sink.get());
|
||||
EXPECT_TRUE(std::filesystem::exists(sink->log_path()));
|
||||
for (int index = 1; index <= 4; ++index) {
|
||||
EXPECT_TRUE(std::filesystem::exists(sink->log_path().string() + "." +
|
||||
std::to_string(index)));
|
||||
}
|
||||
EXPECT_FALSE(std::filesystem::exists(sink->log_path().string() + ".5"));
|
||||
}
|
||||
|
||||
TEST(NativeFileLogSinkTest, SerializesConcurrentRecords) {
|
||||
EnsureAbslLoggingInitialized();
|
||||
TemporaryDirectory temporary_directory;
|
||||
auto sink = NativeFileLogSink::Create(temporary_directory.path());
|
||||
ASSERT_NE(sink, nullptr);
|
||||
absl::ScopedStderrThreshold suppress_stderr(
|
||||
absl::LogSeverityAtLeast::kInfinity);
|
||||
absl::AddLogSink(sink.get());
|
||||
|
||||
std::vector<std::thread> threads;
|
||||
for (int thread = 0; thread < 4; ++thread) {
|
||||
threads.emplace_back([thread] {
|
||||
for (int record = 0; record < 20; ++record) {
|
||||
LOG(INFO) << "concurrent-record-" << thread << "-" << record;
|
||||
}
|
||||
});
|
||||
}
|
||||
for (std::thread& thread : threads) {
|
||||
thread.join();
|
||||
}
|
||||
|
||||
absl::RemoveLogSink(sink.get());
|
||||
const std::string contents = ReadFile(sink->log_path());
|
||||
size_t record_count = 0;
|
||||
size_t position = 0;
|
||||
while ((position = contents.find("concurrent-record-", position)) !=
|
||||
std::string::npos) {
|
||||
++record_count;
|
||||
position += 18;
|
||||
}
|
||||
EXPECT_EQ(record_count, size_t{80});
|
||||
}
|
||||
|
||||
TEST(NativeFileLogSinkTest, UsesPrivatePermissions) {
|
||||
TemporaryDirectory temporary_directory;
|
||||
const std::filesystem::path log_directory =
|
||||
temporary_directory.path() / "nested" / "logs";
|
||||
auto sink = NativeFileLogSink::Create(log_directory);
|
||||
ASSERT_NE(sink, nullptr);
|
||||
|
||||
EXPECT_EQ(PermissionBits(log_directory), 0700);
|
||||
EXPECT_EQ(PermissionBits(sink->log_path()), 0600);
|
||||
EXPECT_EQ(PermissionBits(log_directory / ".quickshare.log.lock"), 0600);
|
||||
}
|
||||
|
||||
TEST(NativeFileLogSinkTest, RejectsUnavailableDirectory) {
|
||||
TemporaryDirectory temporary_directory;
|
||||
const std::filesystem::path regular_file =
|
||||
temporary_directory.path() / "not-a-directory";
|
||||
{
|
||||
std::ofstream stream(regular_file);
|
||||
stream << "content";
|
||||
}
|
||||
|
||||
EXPECT_EQ(NativeFileLogSink::Create(regular_file), nullptr);
|
||||
}
|
||||
|
||||
TEST(LogPathTest, UsesAbsoluteXdgStateHome) {
|
||||
TemporaryDirectory temporary_directory;
|
||||
ScopedEnvironmentVariable state_home("XDG_STATE_HOME",
|
||||
temporary_directory.path().string());
|
||||
|
||||
EXPECT_EQ(internal::GetQuickShareLogPath().ToString(),
|
||||
(temporary_directory.path() / "quickshare" / "logs").string());
|
||||
}
|
||||
|
||||
TEST(LogPathTest, FallsBackToHomeForRelativeXdgStateHome) {
|
||||
TemporaryDirectory temporary_directory;
|
||||
ScopedEnvironmentVariable home("HOME", temporary_directory.path().string());
|
||||
ScopedEnvironmentVariable state_home("XDG_STATE_HOME", "relative/path");
|
||||
|
||||
EXPECT_EQ(
|
||||
internal::GetQuickShareLogPath().ToString(),
|
||||
(temporary_directory.path() / ".local" / "state" / "quickshare" / "logs")
|
||||
.string());
|
||||
}
|
||||
|
||||
TEST(LogPathTest, FallsBackToHomeWhenXdgStateHomeIsMissing) {
|
||||
TemporaryDirectory temporary_directory;
|
||||
ScopedEnvironmentVariable home("HOME", temporary_directory.path().string());
|
||||
ScopedEnvironmentVariable state_home("XDG_STATE_HOME", std::nullopt);
|
||||
|
||||
EXPECT_EQ(
|
||||
internal::GetQuickShareLogPath().ToString(),
|
||||
(temporary_directory.path() / ".local" / "state" / "quickshare" / "logs")
|
||||
.string());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace nearby::sharing::linux
|
||||
@@ -182,7 +182,7 @@ class LinuxDeviceInfo final : public nearby::api::DeviceInfo {
|
||||
return BuildPathFromBase("/tmp", {"Google Nearby"});
|
||||
}
|
||||
FilePath GetLogPath() const override {
|
||||
return GetLocalAppDataPath(FilePath("logs"));
|
||||
return GetQuickShareLogPath();
|
||||
}
|
||||
bool IsScreenLocked() const override { return false; }
|
||||
void RegisterScreenLockedListener(
|
||||
|
||||
@@ -50,6 +50,19 @@ FilePath BuildPathFromBase(const std::string& base,
|
||||
return FilePath(path.string());
|
||||
}
|
||||
|
||||
FilePath GetQuickShareLogPath() {
|
||||
const char* state_home = std::getenv("XDG_STATE_HOME");
|
||||
std::filesystem::path base_path;
|
||||
if (state_home != nullptr && *state_home != '\0' &&
|
||||
std::filesystem::path(state_home).is_absolute()) {
|
||||
base_path = state_home;
|
||||
} else {
|
||||
base_path =
|
||||
std::filesystem::path(GetHomeDirectory()) / ".local" / "state";
|
||||
}
|
||||
return FilePath((base_path / "quickshare" / "logs").string());
|
||||
}
|
||||
|
||||
std::optional<std::string> GetLanguageCode() {
|
||||
const char* lang = std::getenv("LANG");
|
||||
if (lang == nullptr || *lang == '\0') {
|
||||
|
||||
@@ -29,6 +29,7 @@ std::string GetEnvOrDefault(const char* key, std::string fallback);
|
||||
std::string GetHomeDirectory();
|
||||
FilePath BuildPathFromBase(const std::string& base,
|
||||
std::initializer_list<std::string> components);
|
||||
FilePath GetQuickShareLogPath();
|
||||
std::optional<std::string> GetLanguageCode();
|
||||
bool HasNonLoopbackInterface();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user