diff --git a/moxygen/mlog/CMakeLists.txt b/moxygen/mlog/CMakeLists.txt index c30a0b31..6ea1d0c8 100644 --- a/moxygen/mlog/CMakeLists.txt +++ b/moxygen/mlog/CMakeLists.txt @@ -50,3 +50,13 @@ moxygen_add_library(moxygen_mlog_file_mlogger_factory moxygen_mlog_file_mlogger moxygen_mlog_mlogger_factory ) + +moxygen_add_library(moxygen_mlog_sampling_mlogger_factory + SRCS + SamplingMLoggerFactory.cpp + EXPORTED_DEPS + moxygen_mlog_mlogger_factory + Folly::folly_random +) + +add_subdirectory(test) diff --git a/moxygen/mlog/FileMLogger.cpp b/moxygen/mlog/FileMLogger.cpp index c3017ad9..ec1056a5 100644 --- a/moxygen/mlog/FileMLogger.cpp +++ b/moxygen/mlog/FileMLogger.cpp @@ -5,19 +5,56 @@ */ #include "moxygen/mlog/FileMLogger.h" +#include #include +#include #include namespace moxygen { +std::optional FileMLogger::derivePath() const { + if (dir_) { + if (!dcid_) { + XLOG(WARN) << "Skipping mlog output: dcid must be set when dir is configured"; + return std::nullopt; + } + const auto dcidHex = dcid_->hex(); + if (dcidHex.empty()) { + XLOG(WARN) + << "Skipping mlog output: dcid must have a non-empty hex representation when dir is configured"; + return std::nullopt; + } + return *dir_ + "/" + dcidHex + ".mlog"; + } + return path_; +} + void FileMLogger::outputLogs() { - std::ofstream fileObj(path_); + auto path = derivePath(); + if (!path) { + return; + } + + // Pre-format logs on the calling thread (where formatLog() is safe to + // call), then execute the write lambda either inline or via the executor. + std::vector serialized; + serialized.reserve(logs_.size()); for (const auto& log : logs_) { - auto obj = formatLog(log); - std::string jsonLog = folly::toPrettyJson(obj); - fileObj << jsonLog << std::endl; + serialized.push_back(folly::toPrettyJson(formatLog(log))); + } + + auto write = [p = std::move(*path), lines = std::move(serialized)]() { + std::ofstream fileObj(p); + for (const auto& line : lines) { + fileObj << line << '\n'; + } + }; + + if (writeExecutor_) { + writeExecutor_->add(std::move(write)); + } else { + write(); } - fileObj.close(); } } // namespace moxygen diff --git a/moxygen/mlog/FileMLogger.h b/moxygen/mlog/FileMLogger.h index 1fb2fa46..3b8189de 100644 --- a/moxygen/mlog/FileMLogger.h +++ b/moxygen/mlog/FileMLogger.h @@ -6,6 +6,10 @@ #pragma once +#include +#include +#include +#include #include "moxygen/mlog/MLogger.h" namespace moxygen { @@ -14,6 +18,12 @@ namespace moxygen { * FileMLogger is a concrete MLogger implementation that outputs logs to a file. * It inherits from the abstract MLogger base class and implements outputLogs() * to write formatted JSON logs to the path specified via setPath(). + * + * If a write executor is set via setWriteExecutor(), outputLogs() will + * pre-format logs on the calling thread and schedule the file write + * asynchronously on that executor. + * + * If a directory is set via setDir(), setPath() will prepend it automatically. */ class FileMLogger : public MLogger { public: @@ -21,10 +31,37 @@ class FileMLogger : public MLogger { ~FileMLogger() override = default; /** - * Outputs all accumulated logs to the file specified by path_. + * Set an executor for asynchronous file writes. + */ + void setWriteExecutor(std::shared_ptr executor) { + writeExecutor_ = std::move(executor); + } + + /** + * Set an output directory + */ + void setDir(std::string dir) { + dir_ = std::move(dir); + } + + /** + * Outputs all accumulated logs to the file specified by path_ or + * auto-derived path (if dir is set). * Logs are formatted as pretty-printed JSON. */ void outputLogs() override; + + private: + /** + * Derives the output file path. If dir is set, dcid with a non-empty hex + * representation is required and the path is {dir}/{dcid_hex}.mlog. + * Otherwise returns the explicit path_. Returns nullopt when logging should + * be skipped. + */ + std::optional derivePath() const; + + std::shared_ptr writeExecutor_; + std::optional dir_; }; } // namespace moxygen diff --git a/moxygen/mlog/FileMLoggerFactory.h b/moxygen/mlog/FileMLoggerFactory.h index ef6a0345..8c5c5cbc 100644 --- a/moxygen/mlog/FileMLoggerFactory.h +++ b/moxygen/mlog/FileMLoggerFactory.h @@ -6,27 +6,55 @@ #pragma once +#include #include +#include +#include #include #include "moxygen/mlog/MLoggerFactory.h" namespace moxygen { -// Creates an MLogger that writes to a file +// Creates FileMLogger instances per session. +// Optionally forwards a write executor and/or output directory to each created +// logger. class FileMLoggerFactory : public MLoggerFactory { public: - FileMLoggerFactory(const std::string& path, VantagePoint vantagePoint) - : path_(path), vantagePoint_(vantagePoint) {} + explicit FileMLoggerFactory(VantagePoint vantagePoint = VantagePoint::SERVER) + : vantagePoint_(vantagePoint) {} + + explicit FileMLoggerFactory( + std::string path, + VantagePoint vantagePoint = VantagePoint::SERVER) + : vantagePoint_(vantagePoint), path_(std::move(path)) {} std::shared_ptr createMLogger() override { auto logger = std::make_shared(vantagePoint_); - logger->setPath(path_); + if (writeExecutor_) { + logger->setWriteExecutor(writeExecutor_); + } + if (path_) { + logger->setPath(*path_); + } + if (dir_) { + logger->setDir(*dir_); + } return logger; } + void setWriteExecutor(std::shared_ptr executor) { + writeExecutor_ = std::move(executor); + } + + void setDir(std::string dir) { + dir_ = std::move(dir); + } + private: - std::string path_; VantagePoint vantagePoint_; + std::shared_ptr writeExecutor_; + std::optional path_; + std::optional dir_; }; } // namespace moxygen diff --git a/moxygen/mlog/SamplingMLoggerFactory.cpp b/moxygen/mlog/SamplingMLoggerFactory.cpp new file mode 100644 index 00000000..926213a0 --- /dev/null +++ b/moxygen/mlog/SamplingMLoggerFactory.cpp @@ -0,0 +1,47 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * This source code is licensed under the Apache 2.0 license found in the + * LICENSE file in the root directory of this source tree. + */ + +#include "moxygen/mlog/SamplingMLoggerFactory.h" + +namespace moxygen { + +SamplingMLoggerFactory::SamplingMLoggerFactory( + std::shared_ptr inner, + float sampleRate) + : inner_(std::move(inner)), sampleRate_(sampleRate) { + // Validate and normalize sampleRate to valid range [0.0, 1.0] + if (sampleRate_ <= 0.0f) { + sampleRate_ = 0.0f; + bucketSize_ = 0; // Sentinel: no sampling + } else if (sampleRate_ >= 1.0f) { + sampleRate_ = 1.0f; + bucketSize_ = 1; // All sessions logged + } else { + // bucketSize = how many sessions per one logged session, e.g. 0.01 -> 100 + bucketSize_ = static_cast(1.0f / sampleRate_); + if (bucketSize_ == 0) { + bucketSize_ = 1; // guard against rounding to zero for very high rates + } + } +} + +std::shared_ptr SamplingMLoggerFactory::createMLogger() { + if (bucketSize_ == 0) { + // No sampling: sampleRate was <= 0 + return nullptr; + } + if (bucketSize_ == 1) { + // Log all: sampleRate was >= 1 + return inner_->createMLogger(); + } + // folly::Random::oneIn() is thread-safe and bucketSize_ is always > 1 here. + if (folly::Random::oneIn(bucketSize_)) { + return inner_->createMLogger(); + } + return nullptr; +} + +} // namespace moxygen diff --git a/moxygen/mlog/SamplingMLoggerFactory.h b/moxygen/mlog/SamplingMLoggerFactory.h new file mode 100644 index 00000000..5ee6b8ec --- /dev/null +++ b/moxygen/mlog/SamplingMLoggerFactory.h @@ -0,0 +1,35 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * This source code is licensed under the Apache 2.0 license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +#include +#include +#include + +namespace moxygen { + +// Wraps a MLoggerFactory and applies probabilistic sampling. +// For each createMLogger() call, returns a logger with probability sampleRate, +// or nullptr otherwise (meaning the session is not logged). +// +// THREAD SAFETY: Uses folly::Random::oneIn() which relies on ThreadLocalPRNG +// and is safe for concurrent calls. +class SamplingMLoggerFactory : public MLoggerFactory { + public: + SamplingMLoggerFactory( + std::shared_ptr inner, + float sampleRate); + + std::shared_ptr createMLogger() override; + + private: + std::shared_ptr inner_; + float sampleRate_; + uint32_t bucketSize_; // ceil(1 / sampleRate_) +}; + +} // namespace moxygen diff --git a/moxygen/mlog/test/CMakeLists.txt b/moxygen/mlog/test/CMakeLists.txt new file mode 100644 index 00000000..39997361 --- /dev/null +++ b/moxygen/mlog/test/CMakeLists.txt @@ -0,0 +1,22 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# This source code is licensed under the Apache 2.0 license found in the +# LICENSE file in the root directory of this source tree. + +if(NOT BUILD_TESTS) + return() +endif() + +moxygen_add_test(TARGET MLoggerTests + PREFIX "MLogger" + SOURCES + FileMLoggerTest.cpp + FileMLoggerFactoryTest.cpp + SamplingMLoggerFactoryTest.cpp + DEPENDS + moxygen::moxygen_mlog_file_mlogger + moxygen::moxygen_mlog_file_mlogger_factory + moxygen::moxygen_mlog_sampling_mlogger_factory + Folly::folly_executors_manual_executor + mvfst::mvfst_codec_types + GTest::gtest_main +) diff --git a/moxygen/mlog/test/FileMLoggerFactoryTest.cpp b/moxygen/mlog/test/FileMLoggerFactoryTest.cpp new file mode 100644 index 00000000..7bce819d --- /dev/null +++ b/moxygen/mlog/test/FileMLoggerFactoryTest.cpp @@ -0,0 +1,146 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * This source code is licensed under the Apache 2.0 license found in the + * LICENSE file in the root directory of this source tree. + */ + +#include +#include + +#include +#include +#include +#include + +namespace fs = std::filesystem; + +namespace moxygen { + +class FileMLoggerFactoryTest : public ::testing::Test { + protected: + void SetUp() override { + dir_ = fs::path(testing::TempDir()) / "mlog_factory_test"; + fs::create_directories(dir_); + } + + void TearDown() override { + fs::remove_all(dir_); + } + + fs::path dir_; +}; + +// --------------------------------------------------------------------------- +// Constructor tests +// --------------------------------------------------------------------------- + +TEST_F(FileMLoggerFactoryTest, PathCtor_CreatesLoggerThatWritesToGivenPath) { + const auto kFactoryOutputFile = "factory_out.mlog"; + auto path = (dir_ / kFactoryOutputFile).string(); + FileMLoggerFactory factory(path); + + auto logger = factory.createMLogger(); + ASSERT_NE(logger, nullptr); + + logger->outputLogs(); + EXPECT_TRUE(fs::exists(path)); +} + +// --------------------------------------------------------------------------- +// setDir propagation +// --------------------------------------------------------------------------- + +// Factory propagates dir to loggers: outputLogs() should use dcid-derived filename +TEST_F(FileMLoggerFactoryTest, SetDir_PropagatesDirToLogger) { + const auto kFile = "11223344.mlog"; + const std::vector kCid = {0x11, 0x22, 0x33, 0x44}; + FileMLoggerFactory factory; + factory.setDir(dir_.string()); + + auto logger = factory.createMLogger(); + ASSERT_NE(logger, nullptr); + + // Give the logger a known dcid so derivePath produces a predictable filename + logger->setDcid( + quic::ConnectionId::createAndMaybeCrash(kCid)); + logger->outputLogs(); + + EXPECT_TRUE(fs::exists(dir_ / kFile)); +} + +// Multiple loggers from the same factory each get their own dir +TEST_F(FileMLoggerFactoryTest, SetDir_EachLoggerGetsOwnFile) { + const auto kLoggerAFile = "55667788.mlog"; + const auto kLoggerBFile = "99aabbcc.mlog"; + const std::vector kLoggerACid = {0x55, 0x66, 0x77, 0x88}; + const std::vector kLoggerBCid = {0x99, 0xAA, 0xBB, 0xCC}; + FileMLoggerFactory factory; + factory.setDir(dir_.string()); + + auto loggerA = factory.createMLogger(); + auto loggerB = factory.createMLogger(); + ASSERT_NE(loggerA, nullptr); + ASSERT_NE(loggerB, nullptr); + + loggerA->setDcid(quic::ConnectionId::createAndMaybeCrash(kLoggerACid)); + loggerB->setDcid(quic::ConnectionId::createAndMaybeCrash(kLoggerBCid)); + loggerA->outputLogs(); + loggerB->outputLogs(); + + EXPECT_TRUE(fs::exists(dir_ / kLoggerAFile)); + EXPECT_TRUE(fs::exists(dir_ / kLoggerBFile)); +} + +// --------------------------------------------------------------------------- +// setWriteExecutor propagation +// --------------------------------------------------------------------------- + +TEST_F(FileMLoggerFactoryTest, SetWriteExecutor_PropagatesAsyncBehavior) { + const auto kAsyncFactoryFile = "async_factory.mlog"; + auto executor = std::make_shared(); + auto path = (dir_ / kAsyncFactoryFile).string(); + + FileMLoggerFactory factory(path); + factory.setWriteExecutor(executor); + + auto logger = factory.createMLogger(); + ASSERT_NE(logger, nullptr); + + logger->outputLogs(); + // Not yet written — executor not drained + EXPECT_FALSE(fs::exists(path)); + + executor->drain(); + EXPECT_TRUE(fs::exists(path)); +} + +// Each logger created by the factory shares the same executor +TEST_F(FileMLoggerFactoryTest, SetWriteExecutor_SharedAcrossLoggers) { + const auto kAsyncLoggerAFile = "ddeeff00.mlog"; + const auto kAsyncLoggerBFile = "aabbccdd.mlog"; + const std::vector kAsyncLoggerACid = {0xDD, 0xEE, 0xFF, 0x00}; + const std::vector kAsyncLoggerBCid = {0xAA, 0xBB, 0xCC, 0xDD}; + auto executor = std::make_shared(); + FileMLoggerFactory factory; + factory.setDir(dir_.string()); + factory.setWriteExecutor(executor); + + auto loggerA = factory.createMLogger(); + auto loggerB = factory.createMLogger(); + + loggerA->setDcid(quic::ConnectionId::createAndMaybeCrash(kAsyncLoggerACid)); + loggerB->setDcid(quic::ConnectionId::createAndMaybeCrash(kAsyncLoggerBCid)); + + loggerA->outputLogs(); + loggerB->outputLogs(); + + EXPECT_FALSE(fs::exists(dir_ / kAsyncLoggerAFile)); + EXPECT_FALSE(fs::exists(dir_ / kAsyncLoggerBFile)); + + executor->drain(); + + EXPECT_TRUE(fs::exists(dir_ / kAsyncLoggerAFile)); + EXPECT_TRUE(fs::exists(dir_ / kAsyncLoggerBFile)); +} + +} // namespace moxygen diff --git a/moxygen/mlog/test/FileMLoggerTest.cpp b/moxygen/mlog/test/FileMLoggerTest.cpp new file mode 100644 index 00000000..204aabc0 --- /dev/null +++ b/moxygen/mlog/test/FileMLoggerTest.cpp @@ -0,0 +1,145 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * This source code is licensed under the Apache 2.0 license found in the + * LICENSE file in the root directory of this source tree. + */ + +#include + +#include +#include +#include +#include +#include + +namespace fs = std::filesystem; + +namespace moxygen { + +class FileMLoggerTest : public ::testing::Test { + protected: + void SetUp() override { + // Each test gets an isolated temp dir derived from testing::TempDir() + dir_ = fs::path(testing::TempDir()) / "mlog_test"; + fs::create_directories(dir_); + } + + void TearDown() override { + fs::remove_all(dir_); + } + + // Helper: create a ConnectionId from fixed bytes + static quic::ConnectionId makeCid(std::vector bytes) { + return quic::ConnectionId::createAndMaybeCrash(std::move(bytes)); + } + + fs::path dir_; +}; + +// --------------------------------------------------------------------------- +// Sync write tests +// --------------------------------------------------------------------------- + +TEST_F(FileMLoggerTest, SyncWrite_CreatesFile) { + const auto kSyncOutputFile = "sync_out.mlog"; + auto path = (dir_ / kSyncOutputFile).string(); + FileMLogger logger(VantagePoint::SERVER); + logger.setPath(path); + logger.outputLogs(); + + EXPECT_TRUE(fs::exists(path)); +} + +TEST_F(FileMLoggerTest, SyncWrite_ErrorOnBadPath) { + // Write to a path whose parent dir does not exist — should not throw + FileMLogger logger(VantagePoint::SERVER); + logger.setPath("/nonexistent_dir_xyz/out.mlog"); + EXPECT_NO_THROW(logger.outputLogs()); +} + +// --------------------------------------------------------------------------- +// Async write tests +// --------------------------------------------------------------------------- + +TEST_F(FileMLoggerTest, AsyncWrite_FileNotCreatedBeforeDrain) { + const auto kAsyncOutputFile = "async_out.mlog"; + auto executor = std::make_shared(); + auto path = (dir_ / kAsyncOutputFile).string(); + + FileMLogger logger(VantagePoint::SERVER); + logger.setPath(path); + logger.setWriteExecutor(executor); + logger.outputLogs(); + + // Task is enqueued but not yet run + EXPECT_FALSE(fs::exists(path)); + + executor->drain(); + EXPECT_TRUE(fs::exists(path)); +} + +TEST_F(FileMLoggerTest, AsyncWrite_ErrorOnBadPathDoesNotThrow) { + auto executor = std::make_shared(); + FileMLogger logger(VantagePoint::SERVER); + logger.setPath("/nonexistent_dir_xyz/out.mlog"); + logger.setWriteExecutor(executor); + logger.outputLogs(); + EXPECT_NO_THROW(executor->drain()); +} + +// --------------------------------------------------------------------------- +// derivePath tests (verified through outputLogs file creation) +// --------------------------------------------------------------------------- + +// When dir + dcid set: output is {dir}/{dcid_hex}.mlog +TEST_F(FileMLoggerTest, DerivePath_DcidTakesPrecedence) { + const auto kDcidFile = "12345678.mlog"; + const std::vector kTestDcid = {0x12, 0x34, 0x56, 0x78}; + FileMLogger logger(VantagePoint::SERVER); + logger.setDir(dir_.string()); + logger.setDcid(makeCid(kTestDcid)); + + logger.outputLogs(); + + EXPECT_TRUE(fs::exists(dir_ / kDcidFile)); +} + +TEST_F(FileMLoggerTest, DerivePath_EmptyDcidSkipsLogging) { + FileMLogger logger(VantagePoint::SERVER); + logger.setDir(dir_.string()); + logger.setDcid(quic::ConnectionId::createZeroLength()); + + EXPECT_NO_THROW(logger.outputLogs()); + EXPECT_TRUE(fs::is_empty(dir_)); +} + +// When no dir set: output is the explicit path regardless of any cids +TEST_F(FileMLoggerTest, DerivePath_NoDirUsesExplicitPath) { + const auto kNoOpFile = "nodir_test.mlog"; + const std::vector kTestCidSimple = {0x01, 0x02}; + auto path = (dir_ / kNoOpFile).string(); + FileMLogger logger(VantagePoint::SERVER); + logger.setPath(path); + logger.setDcid(makeCid(kTestCidSimple)); // dcid present but dir is not set + + logger.outputLogs(); + + // Should write to the explicit path, not a dcid-named file + EXPECT_TRUE(fs::exists(path)); + EXPECT_FALSE(fs::exists(dir_ / "dcid_derived.mlog")); +} + +// When dir set + dcid set: output is {dir}/{dcid_hex}.mlog (srcCid ignored) +TEST_F(FileMLoggerTest, DerivePath_DcidTakesPrecedenceOverSrcCid) { + const auto kDcidPrecedenceFile = "aabbccdd.mlog"; + const std::vector kTestCidDead = {0xAA, 0xBB, 0xCC, 0xDD}; + FileMLogger logger(VantagePoint::SERVER); + logger.setDir(dir_.string()); + logger.setDcid(makeCid(kTestCidDead)); + + logger.outputLogs(); + + EXPECT_TRUE(fs::exists(dir_ / kDcidPrecedenceFile)); +} + +} // namespace moxygen diff --git a/moxygen/mlog/test/SamplingMLoggerFactoryTest.cpp b/moxygen/mlog/test/SamplingMLoggerFactoryTest.cpp new file mode 100644 index 00000000..a98f2322 --- /dev/null +++ b/moxygen/mlog/test/SamplingMLoggerFactoryTest.cpp @@ -0,0 +1,71 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * This source code is licensed under the Apache 2.0 license found in the + * LICENSE file in the root directory of this source tree. + */ + +#include +#include +#include + +#include +#include + +namespace moxygen { + +// --------------------------------------------------------------------------- +// Minimal spy factory: counts createMLogger() calls, returns real loggers +// --------------------------------------------------------------------------- + +class SpyMLoggerFactory : public MLoggerFactory { + public: + int callCount{0}; + + std::shared_ptr createMLogger() override; +}; + +// Minimal no-op MLogger for testing — outputLogs() does nothing +class NullMLogger : public MLogger { + public: + explicit NullMLogger() : MLogger(VantagePoint::SERVER) {} + void outputLogs() override {} +}; + +std::shared_ptr SpyMLoggerFactory::createMLogger() { + ++callCount; + return std::make_shared(); +} + +// --------------------------------------------------------------------------- +// Tests +// --------------------------------------------------------------------------- + +class SamplingMLoggerFactoryTest : public ::testing::Test { + protected: + std::shared_ptr inner_ = + std::make_shared(); +}; + +// rate <= 0: always returns nullptr, inner never called +TEST_F(SamplingMLoggerFactoryTest, ZeroRate_AlwaysReturnsNull) { + constexpr float kZeroRate = 0.0f; + constexpr int kBasicTrials = 100; + SamplingMLoggerFactory factory(inner_, kZeroRate); + for (int i = 0; i < kBasicTrials; ++i) { + EXPECT_EQ(factory.createMLogger(), nullptr); + } + EXPECT_EQ(inner_->callCount, 0); +} + +// rate >= 1.0: always returns a logger, inner always called +TEST_F(SamplingMLoggerFactoryTest, FullRate_AlwaysReturnsLogger) { + constexpr float kFullRate = 1.0f; + constexpr int kBasicTrials = 100; + SamplingMLoggerFactory factory(inner_, kFullRate); + for (int i = 0; i < kBasicTrials; ++i) { + EXPECT_NE(factory.createMLogger(), nullptr); + } + EXPECT_EQ(inner_->callCount, kBasicTrials); +} + +} // namespace moxygen diff --git a/moxygen/openmoq/transport/pico/MoQPicoServerBase.cpp b/moxygen/openmoq/transport/pico/MoQPicoServerBase.cpp index d02dc0eb..a338b4b0 100644 --- a/moxygen/openmoq/transport/pico/MoQPicoServerBase.cpp +++ b/moxygen/openmoq/transport/pico/MoQPicoServerBase.cpp @@ -466,6 +466,16 @@ void MoQPicoServerBase::onNewConnectionImpl(void* vcnx) { onWebTransportCreated(*webTransport); auto moqSession = createSession(webTransport, executor_); + if (mLoggerFactory_) { + auto logger = createLogger(); + auto picoCid = picoquic_get_local_cnxid(cnx); + if (picoCid.id_len > 0) { + logger->setDcid( + quic::ConnectionId::createAndMaybeCrash( + std::vector(picoCid.id, picoCid.id + picoCid.id_len))); + } + moqSession->setLogger(logger); + } webTransport->setHandler(moqSession.get()); const char* alpn = picoquic_tls_get_negotiated_alpn(cnx); @@ -564,6 +574,16 @@ int MoQPicoServerBase::onWebTransportConnectImpl( // Create MoQSession auto moqSession = createSession(webTransport, executor_); + if (mLoggerFactory_) { + auto logger = createLogger(); + auto picoCid = picoquic_get_local_cnxid(cnx); + if (picoCid.id_len > 0) { + logger->setDcid( + quic::ConnectionId::createAndMaybeCrash( + std::vector(picoCid.id, picoCid.id + picoCid.id_len))); + } + moqSession->setLogger(logger); + } webTransport->setHandler(moqSession.get()); // Set path from the HTTP/3 CONNECT request. Authority is not set because