This is an automated email from the ASF dual-hosted git repository.

bneradt pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/trafficserver.git


The following commit(s) were added to refs/heads/master by this push:
     new 83ec495a12 Remove virtual dispatch from LogData (#13123)
83ec495a12 is described below

commit 83ec495a12b5e8a8795c2feb43289532b5531de7
Author: Brian Neradt <[email protected]>
AuthorDate: Wed Apr 29 14:28:23 2026 -0500

    Remove virtual dispatch from LogData (#13123)
    
    This addresses a performance regression added by #13059.
    
    Malformed pre-transaction logging introduced an extra virtual data
    interface on the normal access log path. That made every completed
    transaction pay for indirection that is only needed for rare
    protocol-layer failures.
    
    This replaces the virtual hierarchy with a concrete composed
    TransactionLogData wrapper. This keeps LogAccess using one data object
    while routing the common HttpSM path through direct getters and falling
    back to owned pre-transaction data only when no HttpSM exists.
---
 include/proxy/PreTransactionLogData.h            |  151 +--
 include/proxy/http/CompletedTransactionLogData.h |  208 ----
 include/proxy/logging/Log.h                      |    2 +-
 include/proxy/logging/LogAccess.h                |    4 +-
 include/proxy/logging/TransactionLogData.h       |  632 +++---------
 src/proxy/ProxyTransaction.cc                    |    4 +-
 src/proxy/http/CMakeLists.txt                    |    1 -
 src/proxy/http/CompletedTransactionLogData.cc    |  837 ----------------
 src/proxy/http/HttpBodyFactory.cc                |    6 +-
 src/proxy/http/HttpSM.cc                         |   10 +-
 src/proxy/logging/CMakeLists.txt                 |   12 +-
 src/proxy/logging/TransactionLogData.cc          | 1117 ++++++++++++++++++++++
 src/proxy/logging/unit-tests/test_LogAccess.cc   |   10 +-
 13 files changed, 1284 insertions(+), 1710 deletions(-)

diff --git a/include/proxy/PreTransactionLogData.h 
b/include/proxy/PreTransactionLogData.h
index 9d65e66761..0ec03c646b 100644
--- a/include/proxy/PreTransactionLogData.h
+++ b/include/proxy/PreTransactionLogData.h
@@ -24,7 +24,9 @@
 
 #pragma once
 
-#include "proxy/logging/TransactionLogData.h"
+#include "proxy/Milestones.h"
+#include "proxy/hdrs/HTTP.h"
+#include "tscore/ink_inet.h"
 
 #include <string>
 
@@ -36,160 +38,21 @@
  * copied request and session metadata needed to emit a best-effort
  * transaction log entry for those failures.
  *
- * Unlike TransactionLogData (which reads from a live HttpSM), this class
- * owns its milestones, addresses, and strings because the originating
- * stream is about to be destroyed.
+ * This class owns its milestones, addresses, and strings because the
+ * originating stream is about to be destroyed.
  */
-class PreTransactionLogData : public TransactionLogData
+class PreTransactionLogData
 {
 public:
   PreTransactionLogData() = default;
 
-  ~PreTransactionLogData() override
+  ~PreTransactionLogData()
   {
     if (owned_client_request.valid()) {
       owned_client_request.destroy();
     }
   }
 
-  // ===== Milestones =====
-
-  TransactionMilestones const *
-  get_milestones() const override
-  {
-    return &owned_milestones;
-  }
-
-  // ===== Headers =====
-
-  HTTPHdr *
-  get_client_request() const override
-  {
-    if (owned_client_request.valid()) {
-      return const_cast<HTTPHdr *>(&owned_client_request);
-    }
-    return nullptr;
-  }
-
-  // ===== Client request URL / path =====
-
-  const char *
-  get_client_req_url_str() const override
-  {
-    return owned_url.empty() ? nullptr : owned_url.c_str();
-  }
-  int
-  get_client_req_url_len() const override
-  {
-    return static_cast<int>(owned_url.size());
-  }
-  const char *
-  get_client_req_url_path_str() const override
-  {
-    return owned_path.empty() ? nullptr : owned_path.c_str();
-  }
-  int
-  get_client_req_url_path_len() const override
-  {
-    return static_cast<int>(owned_path.size());
-  }
-
-  // ===== Client addressing =====
-
-  sockaddr const *
-  get_client_addr() const override
-  {
-    return &owned_client_addr.sa;
-  }
-  sockaddr const *
-  get_client_src_addr() const override
-  {
-    return &owned_client_src_addr.sa;
-  }
-  sockaddr const *
-  get_client_dst_addr() const override
-  {
-    return &owned_client_dst_addr.sa;
-  }
-  uint16_t
-  get_client_port() const override
-  {
-    return m_client_port;
-  }
-
-  // ===== Squid codes =====
-
-  SquidLogCode
-  get_log_code() const override
-  {
-    return m_log_code;
-  }
-  SquidHitMissCode
-  get_hit_miss_code() const override
-  {
-    return m_hit_miss_code;
-  }
-  SquidHierarchyCode
-  get_hier_code() const override
-  {
-    return m_hier_code;
-  }
-
-  // ===== Transaction identifiers =====
-
-  int64_t
-  get_connection_id() const override
-  {
-    return m_connection_id;
-  }
-  int
-  get_transaction_id() const override
-  {
-    return m_transaction_id;
-  }
-
-  // ===== Protocol info =====
-
-  const char *
-  get_client_protocol() const override
-  {
-    return owned_client_protocol_str.empty() ? nullptr : 
owned_client_protocol_str.c_str();
-  }
-
-  // ===== Connection flags =====
-
-  bool
-  get_client_connection_is_ssl() const override
-  {
-    return m_client_connection_is_ssl;
-  }
-
-  // ===== Server transaction count =====
-
-  int64_t
-  get_server_transact_count() const override
-  {
-    return m_server_transact_count;
-  }
-
-  // ===== Fallback fields for pre-transaction logging =====
-
-  std::string_view
-  get_method() const override
-  {
-    return owned_method;
-  }
-  std::string_view
-  get_scheme() const override
-  {
-    return owned_scheme;
-  }
-  std::string_view
-  get_client_protocol_str() const override
-  {
-    return owned_client_protocol_str;
-  }
-
   // ===== Owned backing storage (public for ProxyTransaction to populate). 
=====
 
   HTTPHdr               owned_client_request;
diff --git a/include/proxy/http/CompletedTransactionLogData.h 
b/include/proxy/http/CompletedTransactionLogData.h
deleted file mode 100644
index e8593d4220..0000000000
--- a/include/proxy/http/CompletedTransactionLogData.h
+++ /dev/null
@@ -1,208 +0,0 @@
-/** @file
-
-  CompletedTransactionLogData populates TransactionLogData from a live HttpSM.
-
-  @section license License
-
-  Licensed to the Apache Software Foundation (ASF) under one
-  or more contributor license agreements.  See the NOTICE file
-  distributed with this work for additional information
-  regarding copyright ownership.  The ASF licenses this file
-  to you 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
-
-      http://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.
- */
-
-#pragma once
-
-#include "proxy/logging/TransactionLogData.h"
-
-class HttpSM;
-
-/** Provide TransactionLogData from a live HttpSM via virtual getters.
- *
- * Each getter reads directly from m_http_sm on demand, avoiding the need to
- * copy all fields upfront. The HttpSM must outlive this object.
- */
-class CompletedTransactionLogData : public TransactionLogData
-{
-public:
-  /** Construct from a live HttpSM.
-   *
-   * @param[in] sm The HttpSM for the completing transaction.
-   */
-  explicit CompletedTransactionLogData(HttpSM *sm);
-
-  void *http_sm_for_plugins() const override;
-
-  // ===== Milestones =====
-  TransactionMilestones const *get_milestones() const override;
-
-  // ===== Headers =====
-  HTTPHdr *get_client_request() const override;
-  HTTPHdr *get_proxy_response() const override;
-  HTTPHdr *get_proxy_request() const override;
-  HTTPHdr *get_server_response() const override;
-  HTTPHdr *get_cache_response() const override;
-
-  // ===== Client request URL / path =====
-  const char *get_client_req_url_str() const override;
-  int         get_client_req_url_len() const override;
-  const char *get_client_req_url_path_str() const override;
-  int         get_client_req_url_path_len() const override;
-
-  // ===== Proxy response content-type / reason =====
-  char *get_proxy_resp_content_type_str() const override;
-  int   get_proxy_resp_content_type_len() const override;
-  char *get_proxy_resp_reason_phrase_str() const override;
-  int   get_proxy_resp_reason_phrase_len() const override;
-
-  // ===== Unmapped URL =====
-  char *get_unmapped_url_str() const override;
-  int   get_unmapped_url_len() const override;
-
-  // ===== Cache lookup URL =====
-  char *get_cache_lookup_url_str() const override;
-  int   get_cache_lookup_url_len() const override;
-
-  // ===== Client addressing =====
-  sockaddr const *get_client_addr() const override;
-  sockaddr const *get_client_src_addr() const override;
-  sockaddr const *get_client_dst_addr() const override;
-  sockaddr const *get_verified_client_addr() const override;
-  uint16_t        get_client_port() const override;
-
-  // ===== Server addressing =====
-  sockaddr const *get_server_src_addr() const override;
-  sockaddr const *get_server_dst_addr() const override;
-  sockaddr const *get_server_info_dst_addr() const override;
-  const char     *get_server_name() const override;
-
-  // ===== Squid codes =====
-  SquidLogCode       get_log_code() const override;
-  SquidSubcode       get_subcode() const override;
-  SquidHitMissCode   get_hit_miss_code() const override;
-  SquidHierarchyCode get_hier_code() const override;
-
-  // ===== Byte counters =====
-  int64_t get_client_request_body_bytes() const override;
-  int64_t get_client_response_hdr_bytes() const override;
-  int64_t get_client_response_body_bytes() const override;
-  int64_t get_server_request_body_bytes() const override;
-  int64_t get_server_response_body_bytes() const override;
-  int64_t get_cache_response_body_bytes() const override;
-  int64_t get_cache_response_hdr_bytes() const override;
-
-  // ===== Transaction identifiers =====
-  int64_t get_sm_id() const override;
-  int64_t get_connection_id() const override;
-  int     get_transaction_id() const override;
-  int     get_transaction_priority_weight() const override;
-  int     get_transaction_priority_dependence() const override;
-
-  // ===== Plugin info =====
-  int64_t     get_plugin_id() const override;
-  const char *get_plugin_tag() const override;
-
-  // ===== Protocol info =====
-  const char *get_client_protocol() const override;
-  const char *get_server_protocol() const override;
-  const char *get_client_sec_protocol() const override;
-  const char *get_client_cipher_suite() const override;
-  const char *get_client_curve() const override;
-  const char *get_client_security_group() const override;
-  int         get_client_alpn_id() const override;
-
-  // ===== SNI =====
-  const char *get_sni_server_name() const override;
-
-  // ===== Connection flags =====
-  bool get_client_tcp_reused() const override;
-  bool get_client_connection_is_ssl() const override;
-  bool get_client_ssl_reused() const override;
-  bool get_is_internal() const override;
-  bool get_server_connection_is_ssl() const override;
-  bool get_server_ssl_reused() const override;
-  int  get_server_connection_provided_cert() const override;
-  int  get_client_provided_cert() const override;
-
-  // ===== Server transaction count =====
-  int64_t get_server_transact_count() const override;
-
-  // ===== Finish status =====
-  int get_client_finish_status_code() const override;
-  int get_proxy_finish_status_code() const override;
-
-  // ===== Error codes =====
-  const char *get_client_rx_error_code() const override;
-  const char *get_client_tx_error_code() const override;
-
-  // ===== MPTCP =====
-  std::optional<bool> get_mptcp_state() const override;
-
-  // ===== Misc transaction state =====
-  in_port_t get_incoming_port() const override;
-  int       get_orig_scheme() const override;
-  int64_t   get_congestion_control_crat() const override;
-
-  // ===== Cache state =====
-  int get_cache_write_code() const override;
-  int get_cache_transform_write_code() const override;
-  int get_cache_open_read_tries() const override;
-  int get_cache_open_write_tries() const override;
-  int get_max_cache_open_write_retries() const override;
-
-  // ===== Retry attempts =====
-  int64_t get_simple_retry_attempts() const override;
-  int64_t get_unavailable_retry_attempts() const override;
-  int64_t get_retry_attempts_saved() const override;
-
-  // ===== Status plugin entry name =====
-  std::string_view get_http_return_code_setter_name() const override;
-
-  // ===== Proxy Protocol =====
-  int              get_pp_version() const override;
-  sockaddr const  *get_pp_src_addr() const override;
-  sockaddr const  *get_pp_dst_addr() const override;
-  std::string_view get_pp_authority() const override;
-  std::string_view get_pp_tls_cipher() const override;
-  std::string_view get_pp_tls_version() const override;
-  std::string_view get_pp_tls_group() const override;
-
-  // ===== Server response Transfer-Encoding =====
-  std::string_view get_server_response_transfer_encoding() const override;
-
-private:
-  HttpSM *m_http_sm;
-
-  // Cached values for fields that require computation or string formatting.
-  mutable char m_client_rx_error_code[10] = {'-', '\0'};
-  mutable char m_client_tx_error_code[10] = {'-', '\0'};
-  mutable bool m_error_codes_formatted    = false;
-
-  // Cached URL string pointers (computed on first access).
-  mutable const char *m_client_req_url_str      = nullptr;
-  mutable int         m_client_req_url_len      = 0;
-  mutable const char *m_client_req_url_path_str = nullptr;
-  mutable int         m_client_req_url_path_len = 0;
-  mutable bool        m_url_cached              = false;
-
-  // Cached content-type pointers (computed on first access).
-  mutable char *m_proxy_resp_content_type_str  = nullptr;
-  mutable int   m_proxy_resp_content_type_len  = 0;
-  mutable char *m_proxy_resp_reason_phrase_str = nullptr;
-  mutable int   m_proxy_resp_reason_phrase_len = 0;
-  mutable bool  m_content_type_cached          = false;
-
-  void cache_url_strings() const;
-  void cache_content_type() const;
-  void format_error_codes() const;
-};
diff --git a/include/proxy/logging/Log.h b/include/proxy/logging/Log.h
index 1a7c81114f..20019bf6ce 100644
--- a/include/proxy/logging/Log.h
+++ b/include/proxy/logging/Log.h
@@ -43,7 +43,7 @@
   @section example Example usage of the API
 
   @code
-  // Populate a LogData (e.g. TransactionLogData or PreTransactionLogData), 
then:
+  // Populate a TransactionLogData, then:
   LogAccess entry(data);
   int ret = Log::access(&entry);
   @endcode
diff --git a/include/proxy/logging/LogAccess.h 
b/include/proxy/logging/LogAccess.h
index 7701944df5..e449b674b2 100644
--- a/include/proxy/logging/LogAccess.h
+++ b/include/proxy/logging/LogAccess.h
@@ -123,8 +123,8 @@ public:
    * The caller retains ownership of @a data, which must outlive the
    * synchronous Log::access() call that marshals this entry.
    *
-   * @param[in] data Populated TransactionLogData (CompletedTransactionLogData
-   *                 or PreTransactionLogData).
+   * @param[in] data Populated TransactionLogData for a completed or
+   *                 pre-transaction entry.
    */
   explicit LogAccess(TransactionLogData &data);
 
diff --git a/include/proxy/logging/TransactionLogData.h 
b/include/proxy/logging/TransactionLogData.h
index 99248614dd..0c1eefd53a 100644
--- a/include/proxy/logging/TransactionLogData.h
+++ b/include/proxy/logging/TransactionLogData.h
@@ -1,6 +1,6 @@
 /** @file
 
-  Base class providing the data interface for access log entries.
+  Concrete data accessor for access log entries.
 
   @section license License
 
@@ -30,569 +30,193 @@
 #include <optional>
 #include <string_view>
 
-class HTTPHdr;
+class HttpSM;
+class PreTransactionLogData;
 
-/** Abstract base for the data backing a single access log entry.
+/** Provide access-log data from either a completed HttpSM or pre-transaction 
storage.
  *
- * Subclasses provide data from the appropriate source via virtual getters:
- *   - CompletedTransactionLogData reads from HttpSM (defined in the http
- *     module) for transactions that completed normally.
- *   - PreTransactionLogData returns owned storage (defined in the proxy
- *     module), for requests that never create an HttpSM.
- *
- * LogAccess reads only from this interface, so the logging module has no
- * compile-time dependency on the http module.
+ * The common completed-transaction path reads directly from @c HttpSM.  The
+ * rare pre-transaction path reads from @c PreTransactionLogData, which owns
+ * copied request/session state for malformed requests rejected before HttpSM
+ * creation.
  */
 class TransactionLogData
 {
 public:
-  virtual ~TransactionLogData() = default;
-
-  /** Return the HttpSM pointer for plugin custom marshal functions.
-   *
-   * Only TransactionLogData provides a non-null value.  Pre-transaction
-   * entries have no HttpSM, so plugins receive nullptr.
-   *
-   * @return An opaque pointer to the HttpSM, or nullptr.
-   */
-  virtual void *
-  http_sm_for_plugins() const
-  {
-    return nullptr;
-  }
+  explicit TransactionLogData(HttpSM *sm);
+  explicit TransactionLogData(PreTransactionLogData const &pre_data);
 
-  // ===== Milestones =====
+  void *http_sm_for_plugins() const;
 
-  virtual TransactionMilestones const *
-  get_milestones() const
-  {
-    return nullptr;
-  }
+  // ===== Milestones =====
+  TransactionMilestones const *get_milestones() const;
 
   // ===== Headers =====
-
-  virtual HTTPHdr *
-  get_client_request() const
-  {
-    return nullptr;
-  }
-  virtual HTTPHdr *
-  get_proxy_response() const
-  {
-    return nullptr;
-  }
-  virtual HTTPHdr *
-  get_proxy_request() const
-  {
-    return nullptr;
-  }
-  virtual HTTPHdr *
-  get_server_response() const
-  {
-    return nullptr;
-  }
-  virtual HTTPHdr *
-  get_cache_response() const
-  {
-    return nullptr;
-  }
+  HTTPHdr *get_client_request() const;
+  HTTPHdr *get_proxy_response() const;
+  HTTPHdr *get_proxy_request() const;
+  HTTPHdr *get_server_response() const;
+  HTTPHdr *get_cache_response() const;
 
   // ===== Client request URL / path =====
-
-  virtual const char *
-  get_client_req_url_str() const
-  {
-    return nullptr;
-  }
-  virtual int
-  get_client_req_url_len() const
-  {
-    return 0;
-  }
-  virtual const char *
-  get_client_req_url_path_str() const
-  {
-    return nullptr;
-  }
-  virtual int
-  get_client_req_url_path_len() const
-  {
-    return 0;
-  }
+  const char *get_client_req_url_str() const;
+  int         get_client_req_url_len() const;
+  const char *get_client_req_url_path_str() const;
+  int         get_client_req_url_path_len() const;
 
   // ===== Proxy response content-type / reason =====
-
-  virtual char *
-  get_proxy_resp_content_type_str() const
-  {
-    return nullptr;
-  }
-  virtual int
-  get_proxy_resp_content_type_len() const
-  {
-    return 0;
-  }
-  virtual char *
-  get_proxy_resp_reason_phrase_str() const
-  {
-    return nullptr;
-  }
-  virtual int
-  get_proxy_resp_reason_phrase_len() const
-  {
-    return 0;
-  }
+  char *get_proxy_resp_content_type_str() const;
+  int   get_proxy_resp_content_type_len() const;
+  char *get_proxy_resp_reason_phrase_str() const;
+  int   get_proxy_resp_reason_phrase_len() const;
 
   // ===== Unmapped URL =====
-
-  virtual char *
-  get_unmapped_url_str() const
-  {
-    return nullptr;
-  }
-  virtual int
-  get_unmapped_url_len() const
-  {
-    return 0;
-  }
+  char *get_unmapped_url_str() const;
+  int   get_unmapped_url_len() const;
 
   // ===== Cache lookup URL =====
-
-  virtual char *
-  get_cache_lookup_url_str() const
-  {
-    return nullptr;
-  }
-  virtual int
-  get_cache_lookup_url_len() const
-  {
-    return 0;
-  }
+  char *get_cache_lookup_url_str() const;
+  int   get_cache_lookup_url_len() const;
 
   // ===== Client addressing =====
-
-  virtual sockaddr const *
-  get_client_addr() const
-  {
-    return nullptr;
-  }
-  virtual sockaddr const *
-  get_client_src_addr() const
-  {
-    return nullptr;
-  }
-  virtual sockaddr const *
-  get_client_dst_addr() const
-  {
-    return nullptr;
-  }
-  virtual sockaddr const *
-  get_verified_client_addr() const
-  {
-    return nullptr;
-  }
-  virtual uint16_t
-  get_client_port() const
-  {
-    return 0;
-  }
+  sockaddr const *get_client_addr() const;
+  sockaddr const *get_client_src_addr() const;
+  sockaddr const *get_client_dst_addr() const;
+  sockaddr const *get_verified_client_addr() const;
+  uint16_t        get_client_port() const;
 
   // ===== Server addressing =====
-
-  virtual sockaddr const *
-  get_server_src_addr() const
-  {
-    return nullptr;
-  }
-  virtual sockaddr const *
-  get_server_dst_addr() const
-  {
-    return nullptr;
-  }
-  virtual sockaddr const *
-  get_server_info_dst_addr() const
-  {
-    return nullptr;
-  }
-  virtual const char *
-  get_server_name() const
-  {
-    return nullptr;
-  }
+  sockaddr const *get_server_src_addr() const;
+  sockaddr const *get_server_dst_addr() const;
+  sockaddr const *get_server_info_dst_addr() const;
+  const char     *get_server_name() const;
 
   // ===== Squid codes =====
-
-  virtual SquidLogCode
-  get_log_code() const
-  {
-    return SquidLogCode::EMPTY;
-  }
-  virtual SquidSubcode
-  get_subcode() const
-  {
-    return SquidSubcode::EMPTY;
-  }
-  virtual SquidHitMissCode
-  get_hit_miss_code() const
-  {
-    return SQUID_MISS_NONE;
-  }
-  virtual SquidHierarchyCode
-  get_hier_code() const
-  {
-    return SquidHierarchyCode::NONE;
-  }
+  SquidLogCode       get_log_code() const;
+  SquidSubcode       get_subcode() const;
+  SquidHitMissCode   get_hit_miss_code() const;
+  SquidHierarchyCode get_hier_code() const;
 
   // ===== Byte counters =====
-
-  virtual int64_t
-  get_client_request_body_bytes() const
-  {
-    return 0;
-  }
-  virtual int64_t
-  get_client_response_hdr_bytes() const
-  {
-    return 0;
-  }
-  virtual int64_t
-  get_client_response_body_bytes() const
-  {
-    return 0;
-  }
-  virtual int64_t
-  get_server_request_body_bytes() const
-  {
-    return 0;
-  }
-  virtual int64_t
-  get_server_response_body_bytes() const
-  {
-    return 0;
-  }
-  virtual int64_t
-  get_cache_response_body_bytes() const
-  {
-    return 0;
-  }
-  virtual int64_t
-  get_cache_response_hdr_bytes() const
-  {
-    return 0;
-  }
+  int64_t get_client_request_body_bytes() const;
+  int64_t get_client_response_hdr_bytes() const;
+  int64_t get_client_response_body_bytes() const;
+  int64_t get_server_request_body_bytes() const;
+  int64_t get_server_response_body_bytes() const;
+  int64_t get_cache_response_body_bytes() const;
+  int64_t get_cache_response_hdr_bytes() const;
 
   // ===== Transaction identifiers =====
-
-  virtual int64_t
-  get_sm_id() const
-  {
-    return 0;
-  }
-  virtual int64_t
-  get_connection_id() const
-  {
-    return 0;
-  }
-  virtual int
-  get_transaction_id() const
-  {
-    return 0;
-  }
-  virtual int
-  get_transaction_priority_weight() const
-  {
-    return 0;
-  }
-  virtual int
-  get_transaction_priority_dependence() const
-  {
-    return 0;
-  }
+  int64_t get_sm_id() const;
+  int64_t get_connection_id() const;
+  int     get_transaction_id() const;
+  int     get_transaction_priority_weight() const;
+  int     get_transaction_priority_dependence() const;
 
   // ===== Plugin info =====
-
-  virtual int64_t
-  get_plugin_id() const
-  {
-    return 0;
-  }
-  virtual const char *
-  get_plugin_tag() const
-  {
-    return nullptr;
-  }
+  int64_t     get_plugin_id() const;
+  const char *get_plugin_tag() const;
 
   // ===== Protocol info =====
-
-  virtual const char *
-  get_client_protocol() const
-  {
-    return nullptr;
-  }
-  virtual const char *
-  get_server_protocol() const
-  {
-    return nullptr;
-  }
-  virtual const char *
-  get_client_sec_protocol() const
-  {
-    return nullptr;
-  }
-  virtual const char *
-  get_client_cipher_suite() const
-  {
-    return nullptr;
-  }
-  virtual const char *
-  get_client_curve() const
-  {
-    return nullptr;
-  }
-  virtual const char *
-  get_client_security_group() const
-  {
-    return nullptr;
-  }
-  virtual int
-  get_client_alpn_id() const
-  {
-    return -1;
-  }
+  const char *get_client_protocol() const;
+  const char *get_server_protocol() const;
+  const char *get_client_sec_protocol() const;
+  const char *get_client_cipher_suite() const;
+  const char *get_client_curve() const;
+  const char *get_client_security_group() const;
+  int         get_client_alpn_id() const;
 
   // ===== SNI =====
-
-  virtual const char *
-  get_sni_server_name() const
-  {
-    return nullptr;
-  }
+  const char *get_sni_server_name() const;
 
   // ===== Connection flags =====
-
-  virtual bool
-  get_client_tcp_reused() const
-  {
-    return false;
-  }
-  virtual bool
-  get_client_connection_is_ssl() const
-  {
-    return false;
-  }
-  virtual bool
-  get_client_ssl_reused() const
-  {
-    return false;
-  }
-  virtual bool
-  get_is_internal() const
-  {
-    return false;
-  }
-  virtual bool
-  get_server_connection_is_ssl() const
-  {
-    return false;
-  }
-  virtual bool
-  get_server_ssl_reused() const
-  {
-    return false;
-  }
-  virtual int
-  get_server_connection_provided_cert() const
-  {
-    return 0;
-  }
-  virtual int
-  get_client_provided_cert() const
-  {
-    return 0;
-  }
+  bool get_client_tcp_reused() const;
+  bool get_client_connection_is_ssl() const;
+  bool get_client_ssl_reused() const;
+  bool get_is_internal() const;
+  bool get_server_connection_is_ssl() const;
+  bool get_server_ssl_reused() const;
+  int  get_server_connection_provided_cert() const;
+  int  get_client_provided_cert() const;
 
   // ===== Server transaction count =====
-
-  virtual int64_t
-  get_server_transact_count() const
-  {
-    return 0;
-  }
+  int64_t get_server_transact_count() const;
 
   // ===== Finish status =====
-
-  virtual int
-  get_client_finish_status_code() const
-  {
-    return 0;
-  }
-  virtual int
-  get_proxy_finish_status_code() const
-  {
-    return 0;
-  }
+  int get_client_finish_status_code() const;
+  int get_proxy_finish_status_code() const;
 
   // ===== Error codes =====
-
-  virtual const char *
-  get_client_rx_error_code() const
-  {
-    return "-";
-  }
-  virtual const char *
-  get_client_tx_error_code() const
-  {
-    return "-";
-  }
+  const char *get_client_rx_error_code() const;
+  const char *get_client_tx_error_code() const;
 
   // ===== MPTCP =====
-
-  virtual std::optional<bool>
-  get_mptcp_state() const
-  {
-    return std::nullopt;
-  }
+  std::optional<bool> get_mptcp_state() const;
 
   // ===== Misc transaction state =====
-
-  virtual in_port_t
-  get_incoming_port() const
-  {
-    return 0;
-  }
-  virtual int
-  get_orig_scheme() const
-  {
-    return -1;
-  }
-  virtual int64_t
-  get_congestion_control_crat() const
-  {
-    return 0;
-  }
+  in_port_t get_incoming_port() const;
+  int       get_orig_scheme() const;
+  int64_t   get_congestion_control_crat() const;
 
   // ===== Cache state =====
-
-  virtual int
-  get_cache_write_code() const
-  {
-    return 0;
-  }
-  virtual int
-  get_cache_transform_write_code() const
-  {
-    return 0;
-  }
-  virtual int
-  get_cache_open_read_tries() const
-  {
-    return 0;
-  }
-  virtual int
-  get_cache_open_write_tries() const
-  {
-    return 0;
-  }
-  virtual int
-  get_max_cache_open_write_retries() const
-  {
-    return -1;
-  }
+  int get_cache_write_code() const;
+  int get_cache_transform_write_code() const;
+  int get_cache_open_read_tries() const;
+  int get_cache_open_write_tries() const;
+  int get_max_cache_open_write_retries() const;
 
   // ===== Retry attempts =====
-
-  virtual int64_t
-  get_simple_retry_attempts() const
-  {
-    return 0;
-  }
-  virtual int64_t
-  get_unavailable_retry_attempts() const
-  {
-    return 0;
-  }
-  virtual int64_t
-  get_retry_attempts_saved() const
-  {
-    return 0;
-  }
+  int64_t get_simple_retry_attempts() const;
+  int64_t get_unavailable_retry_attempts() const;
+  int64_t get_retry_attempts_saved() const;
 
   // ===== Status plugin entry name =====
-
-  virtual std::string_view
-  get_http_return_code_setter_name() const
-  {
-    return {};
-  }
+  std::string_view get_http_return_code_setter_name() const;
 
   // ===== Proxy Protocol =====
-
-  virtual int
-  get_pp_version() const
-  {
-    return 0;
-  }
-  virtual sockaddr const *
-  get_pp_src_addr() const
-  {
-    return nullptr;
-  }
-  virtual sockaddr const *
-  get_pp_dst_addr() const
-  {
-    return nullptr;
-  }
-  virtual std::string_view
-  get_pp_authority() const
-  {
-    return {};
-  }
-  virtual std::string_view
-  get_pp_tls_cipher() const
-  {
-    return {};
-  }
-  virtual std::string_view
-  get_pp_tls_version() const
-  {
-    return {};
-  }
-  virtual std::string_view
-  get_pp_tls_group() const
-  {
-    return {};
-  }
+  int              get_pp_version() const;
+  sockaddr const  *get_pp_src_addr() const;
+  sockaddr const  *get_pp_dst_addr() const;
+  std::string_view get_pp_authority() const;
+  std::string_view get_pp_tls_cipher() const;
+  std::string_view get_pp_tls_version() const;
+  std::string_view get_pp_tls_group() const;
 
   // ===== Server response Transfer-Encoding =====
-
-  virtual std::string_view
-  get_server_response_transfer_encoding() const
-  {
-    return {};
-  }
+  std::string_view get_server_response_transfer_encoding() const;
 
   // ===== Fallback fields for pre-transaction logging =====
+  std::string_view get_method() const;
+  std::string_view get_scheme() const;
+  std::string_view get_client_protocol_str() const;
 
-  virtual std::string_view
-  get_method() const
-  {
-    return {};
-  }
-  virtual std::string_view
-  get_scheme() const
-  {
-    return {};
-  }
-  virtual std::string_view
-  get_client_protocol_str() const
-  {
-    return {};
-  }
-
-  // noncopyable
   TransactionLogData(const TransactionLogData &)            = delete;
   TransactionLogData &operator=(const TransactionLogData &) = delete;
 
-protected:
-  TransactionLogData() = default;
+private:
+  HttpSM                      *m_http_sm  = nullptr;
+  PreTransactionLogData const *m_pre_data = nullptr;
+
+  // Cached values for fields that require computation or string formatting.
+  mutable char m_client_rx_error_code[10] = {'-', '\0'};
+  mutable char m_client_tx_error_code[10] = {'-', '\0'};
+  mutable bool m_error_codes_formatted    = false;
+
+  // Cached URL string pointers (computed on first access).
+  mutable const char *m_client_req_url_str      = nullptr;
+  mutable int         m_client_req_url_len      = 0;
+  mutable const char *m_client_req_url_path_str = nullptr;
+  mutable int         m_client_req_url_path_len = 0;
+  mutable bool        m_url_cached              = false;
+
+  // Cached content-type pointers (computed on first access).
+  mutable char *m_proxy_resp_content_type_str  = nullptr;
+  mutable int   m_proxy_resp_content_type_len  = 0;
+  mutable char *m_proxy_resp_reason_phrase_str = nullptr;
+  mutable int   m_proxy_resp_reason_phrase_len = 0;
+  mutable bool  m_content_type_cached          = false;
+
+  void cache_url_strings() const;
+  void cache_content_type() const;
+  void format_error_codes() const;
 };
diff --git a/src/proxy/ProxyTransaction.cc b/src/proxy/ProxyTransaction.cc
index ee02429722..c2a5a4584e 100644
--- a/src/proxy/ProxyTransaction.cc
+++ b/src/proxy/ProxyTransaction.cc
@@ -25,6 +25,7 @@
 #include "proxy/Plugin.h"
 #include "proxy/PreTransactionLogData.h"
 #include "proxy/logging/LogAccess.h"
+#include "proxy/logging/TransactionLogData.h"
 #include "proxy/logging/Log.h"
 
 namespace
@@ -411,6 +412,7 @@ ProxyTransaction::log_pre_transaction_access(HTTPHdr const 
*request, const char
   data.owned_milestones[TS_MILESTONE_UA_READ_HEADER_DONE] = now;
   data.owned_milestones[TS_MILESTONE_SM_FINISH]           = now;
 
-  LogAccess access(data);
+  TransactionLogData log_data(data);
+  LogAccess          access(log_data);
   Log::access(&access);
 }
diff --git a/src/proxy/http/CMakeLists.txt b/src/proxy/http/CMakeLists.txt
index 94a3130803..55cf7ebd36 100644
--- a/src/proxy/http/CMakeLists.txt
+++ b/src/proxy/http/CMakeLists.txt
@@ -27,7 +27,6 @@ add_library(
   HttpDebugNames.cc
   HttpProxyServerMain.cc
   HttpSM.cc
-  CompletedTransactionLogData.cc
   Http1ServerSession.cc
   HttpSessionManager.cc
   HttpTransact.cc
diff --git a/src/proxy/http/CompletedTransactionLogData.cc 
b/src/proxy/http/CompletedTransactionLogData.cc
deleted file mode 100644
index 39d62a7517..0000000000
--- a/src/proxy/http/CompletedTransactionLogData.cc
+++ /dev/null
@@ -1,837 +0,0 @@
-/** @file
-
-  CompletedTransactionLogData implementation: read TransactionLogData from a
-  live HttpSM.
-
-  @section license License
-
-  Licensed to the Apache Software Foundation (ASF) under one
-  or more contributor license agreements.  See the NOTICE file
-  distributed with this work for additional information
-  regarding copyright ownership.  The ASF licenses this file
-  to you 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
-
-      http://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 "proxy/http/CompletedTransactionLogData.h"
-#include "proxy/http/HttpSM.h"
-#include "proxy/logging/LogAccess.h"
-#include "proxy/hdrs/MIME.h"
-#include "../private/SSLProxySession.h"
-
-namespace
-{
-/** Map HttpTransact::CacheWriteStatus_t to LogCacheWriteCodeType. */
-int
-convert_cache_write_code(HttpTransact::CacheWriteStatus_t t)
-{
-  switch (t) {
-  case HttpTransact::CacheWriteStatus_t::NO_WRITE:
-    return LOG_CACHE_WRITE_NONE;
-  case HttpTransact::CacheWriteStatus_t::LOCK_MISS:
-    return LOG_CACHE_WRITE_LOCK_MISSED;
-  case HttpTransact::CacheWriteStatus_t::IN_PROGRESS:
-    return LOG_CACHE_WRITE_LOCK_ABORTED;
-  case HttpTransact::CacheWriteStatus_t::ERROR:
-    return LOG_CACHE_WRITE_ERROR;
-  case HttpTransact::CacheWriteStatus_t::COMPLETE:
-    return LOG_CACHE_WRITE_COMPLETE;
-  default:
-    ink_assert(!"bad cache write code");
-    return LOG_CACHE_WRITE_NONE;
-  }
-}
-
-int
-compute_client_finish_status(HttpSM *sm)
-{
-  HttpTransact::AbortState_t cl_abort_state = sm->t_state.client_info.abort;
-  if (cl_abort_state == HttpTransact::ABORTED) {
-    if (sm->t_state.client_info.state == HttpTransact::ACTIVE_TIMEOUT ||
-        sm->t_state.client_info.state == HttpTransact::INACTIVE_TIMEOUT) {
-      return LOG_FINISH_TIMEOUT;
-    }
-    return LOG_FINISH_INTR;
-  }
-  return LOG_FINISH_FIN;
-}
-
-int
-compute_proxy_finish_status(HttpSM *sm)
-{
-  if (sm->t_state.current.server) {
-    switch (sm->t_state.current.server->state) {
-    case HttpTransact::ACTIVE_TIMEOUT:
-    case HttpTransact::INACTIVE_TIMEOUT:
-      return LOG_FINISH_TIMEOUT;
-    case HttpTransact::CONNECTION_ERROR:
-      return LOG_FINISH_INTR;
-    default:
-      if (sm->t_state.current.server->abort == HttpTransact::ABORTED) {
-        return LOG_FINISH_INTR;
-      }
-      break;
-    }
-  }
-  return LOG_FINISH_FIN;
-}
-} // end anonymous namespace
-
-CompletedTransactionLogData::CompletedTransactionLogData(HttpSM *sm) : 
m_http_sm(sm)
-{
-  ink_assert(sm != nullptr);
-}
-
-void *
-CompletedTransactionLogData::http_sm_for_plugins() const
-{
-  return m_http_sm;
-}
-
-// ===== Milestones =====
-
-TransactionMilestones const *
-CompletedTransactionLogData::get_milestones() const
-{
-  return &m_http_sm->milestones;
-}
-
-// ===== Headers =====
-
-HTTPHdr *
-CompletedTransactionLogData::get_client_request() const
-{
-  HttpTransact::HeaderInfo *hdr = &m_http_sm->t_state.hdr_info;
-  if (hdr->client_request.valid()) {
-    return &hdr->client_request;
-  }
-  return nullptr;
-}
-
-HTTPHdr *
-CompletedTransactionLogData::get_proxy_response() const
-{
-  HttpTransact::HeaderInfo *hdr = &m_http_sm->t_state.hdr_info;
-  if (hdr->client_response.valid()) {
-    return &hdr->client_response;
-  }
-  return nullptr;
-}
-
-HTTPHdr *
-CompletedTransactionLogData::get_proxy_request() const
-{
-  HttpTransact::HeaderInfo *hdr = &m_http_sm->t_state.hdr_info;
-  if (hdr->server_request.valid()) {
-    return &hdr->server_request;
-  }
-  return nullptr;
-}
-
-HTTPHdr *
-CompletedTransactionLogData::get_server_response() const
-{
-  HttpTransact::HeaderInfo *hdr = &m_http_sm->t_state.hdr_info;
-  if (hdr->server_response.valid()) {
-    return &hdr->server_response;
-  }
-  return nullptr;
-}
-
-HTTPHdr *
-CompletedTransactionLogData::get_cache_response() const
-{
-  HttpTransact::HeaderInfo *hdr = &m_http_sm->t_state.hdr_info;
-  if (hdr->cache_response.valid()) {
-    return &hdr->cache_response;
-  }
-  return nullptr;
-}
-
-// ===== Client request URL / path =====
-
-void
-CompletedTransactionLogData::cache_url_strings() const
-{
-  if (m_url_cached) {
-    return;
-  }
-  m_url_cached = true;
-
-  HTTPHdr *client_request = get_client_request();
-  if (client_request) {
-    m_client_req_url_str      = 
client_request->url_string_get_ref(&m_client_req_url_len);
-    auto path_sv              = client_request->path_get();
-    m_client_req_url_path_str = path_sv.data();
-    m_client_req_url_path_len = static_cast<int>(path_sv.length());
-  }
-}
-
-const char *
-CompletedTransactionLogData::get_client_req_url_str() const
-{
-  cache_url_strings();
-  return m_client_req_url_str;
-}
-
-int
-CompletedTransactionLogData::get_client_req_url_len() const
-{
-  cache_url_strings();
-  return m_client_req_url_len;
-}
-
-const char *
-CompletedTransactionLogData::get_client_req_url_path_str() const
-{
-  cache_url_strings();
-  return m_client_req_url_path_str;
-}
-
-int
-CompletedTransactionLogData::get_client_req_url_path_len() const
-{
-  cache_url_strings();
-  return m_client_req_url_path_len;
-}
-
-// ===== Proxy response content-type / reason =====
-
-void
-CompletedTransactionLogData::cache_content_type() const
-{
-  if (m_content_type_cached) {
-    return;
-  }
-  m_content_type_cached = true;
-
-  HTTPHdr *proxy_response = get_proxy_response();
-  if (proxy_response) {
-    MIMEField *field = 
proxy_response->field_find(static_cast<std::string_view>(MIME_FIELD_CONTENT_TYPE));
-    if (field) {
-      auto ct                       = field->value_get();
-      m_proxy_resp_content_type_str = const_cast<char *>(ct.data());
-      m_proxy_resp_content_type_len = ct.length();
-    } else {
-      constexpr std::string_view hidden_ct{"@Content-Type"};
-      field = proxy_response->field_find(hidden_ct);
-      if (field) {
-        auto ct                       = field->value_get();
-        m_proxy_resp_content_type_str = const_cast<char *>(ct.data());
-        m_proxy_resp_content_type_len = ct.length();
-      }
-    }
-    auto reason                    = proxy_response->reason_get();
-    m_proxy_resp_reason_phrase_str = const_cast<char *>(reason.data());
-    m_proxy_resp_reason_phrase_len = static_cast<int>(reason.length());
-  }
-}
-
-char *
-CompletedTransactionLogData::get_proxy_resp_content_type_str() const
-{
-  cache_content_type();
-  return m_proxy_resp_content_type_str;
-}
-
-int
-CompletedTransactionLogData::get_proxy_resp_content_type_len() const
-{
-  cache_content_type();
-  return m_proxy_resp_content_type_len;
-}
-
-char *
-CompletedTransactionLogData::get_proxy_resp_reason_phrase_str() const
-{
-  cache_content_type();
-  return m_proxy_resp_reason_phrase_str;
-}
-
-int
-CompletedTransactionLogData::get_proxy_resp_reason_phrase_len() const
-{
-  cache_content_type();
-  return m_proxy_resp_reason_phrase_len;
-}
-
-// ===== Unmapped URL =====
-
-char *
-CompletedTransactionLogData::get_unmapped_url_str() const
-{
-  if (m_http_sm->t_state.unmapped_url.valid()) {
-    int len = 0;
-    return m_http_sm->t_state.unmapped_url.string_get_ref(&len);
-  }
-  return nullptr;
-}
-
-int
-CompletedTransactionLogData::get_unmapped_url_len() const
-{
-  if (m_http_sm->t_state.unmapped_url.valid()) {
-    int len = 0;
-    m_http_sm->t_state.unmapped_url.string_get_ref(&len);
-    return len;
-  }
-  return 0;
-}
-
-// ===== Cache lookup URL =====
-
-char *
-CompletedTransactionLogData::get_cache_lookup_url_str() const
-{
-  if (m_http_sm->t_state.cache_info.lookup_url_storage.valid()) {
-    int len = 0;
-    return 
m_http_sm->t_state.cache_info.lookup_url_storage.string_get_ref(&len);
-  }
-  return nullptr;
-}
-
-int
-CompletedTransactionLogData::get_cache_lookup_url_len() const
-{
-  if (m_http_sm->t_state.cache_info.lookup_url_storage.valid()) {
-    int len = 0;
-    m_http_sm->t_state.cache_info.lookup_url_storage.string_get_ref(&len);
-    return len;
-  }
-  return 0;
-}
-
-// ===== Client addressing =====
-
-sockaddr const *
-CompletedTransactionLogData::get_client_addr() const
-{
-  return &m_http_sm->t_state.effective_client_addr.sa;
-}
-
-sockaddr const *
-CompletedTransactionLogData::get_client_src_addr() const
-{
-  return &m_http_sm->t_state.client_info.src_addr.sa;
-}
-
-sockaddr const *
-CompletedTransactionLogData::get_client_dst_addr() const
-{
-  return &m_http_sm->t_state.client_info.dst_addr.sa;
-}
-
-sockaddr const *
-CompletedTransactionLogData::get_verified_client_addr() const
-{
-  if (auto txn = m_http_sm->get_ua_txn(); txn) {
-    sockaddr const *vaddr = txn->get_verified_client_addr();
-    if (vaddr && ats_is_ip(vaddr)) {
-      return vaddr;
-    }
-  }
-  return nullptr;
-}
-
-uint16_t
-CompletedTransactionLogData::get_client_port() const
-{
-  if (auto txn = m_http_sm->get_ua_txn(); txn) {
-    return txn->get_client_port();
-  }
-  return 0;
-}
-
-// ===== Server addressing =====
-
-sockaddr const *
-CompletedTransactionLogData::get_server_src_addr() const
-{
-  if (m_http_sm->t_state.current.server) {
-    return &m_http_sm->t_state.current.server->src_addr.sa;
-  }
-  return nullptr;
-}
-
-sockaddr const *
-CompletedTransactionLogData::get_server_dst_addr() const
-{
-  if (m_http_sm->t_state.current.server) {
-    return &m_http_sm->t_state.current.server->dst_addr.sa;
-  }
-  return nullptr;
-}
-
-sockaddr const *
-CompletedTransactionLogData::get_server_info_dst_addr() const
-{
-  return &m_http_sm->t_state.server_info.dst_addr.sa;
-}
-
-const char *
-CompletedTransactionLogData::get_server_name() const
-{
-  if (m_http_sm->t_state.current.server) {
-    return m_http_sm->t_state.current.server->name;
-  }
-  return nullptr;
-}
-
-// ===== Squid codes =====
-
-SquidLogCode
-CompletedTransactionLogData::get_log_code() const
-{
-  return m_http_sm->t_state.squid_codes.log_code;
-}
-
-SquidSubcode
-CompletedTransactionLogData::get_subcode() const
-{
-  return m_http_sm->t_state.squid_codes.subcode;
-}
-
-SquidHitMissCode
-CompletedTransactionLogData::get_hit_miss_code() const
-{
-  return m_http_sm->t_state.squid_codes.hit_miss_code;
-}
-
-SquidHierarchyCode
-CompletedTransactionLogData::get_hier_code() const
-{
-  return m_http_sm->t_state.squid_codes.hier_code;
-}
-
-// ===== Byte counters =====
-
-int64_t
-CompletedTransactionLogData::get_client_request_body_bytes() const
-{
-  return m_http_sm->client_request_body_bytes;
-}
-
-int64_t
-CompletedTransactionLogData::get_client_response_hdr_bytes() const
-{
-  return m_http_sm->client_response_hdr_bytes;
-}
-
-int64_t
-CompletedTransactionLogData::get_client_response_body_bytes() const
-{
-  return m_http_sm->client_response_body_bytes;
-}
-
-int64_t
-CompletedTransactionLogData::get_server_request_body_bytes() const
-{
-  return m_http_sm->server_request_body_bytes;
-}
-
-int64_t
-CompletedTransactionLogData::get_server_response_body_bytes() const
-{
-  return m_http_sm->server_response_body_bytes;
-}
-
-int64_t
-CompletedTransactionLogData::get_cache_response_body_bytes() const
-{
-  return m_http_sm->cache_response_body_bytes;
-}
-
-int64_t
-CompletedTransactionLogData::get_cache_response_hdr_bytes() const
-{
-  return m_http_sm->cache_response_hdr_bytes;
-}
-
-// ===== Transaction identifiers =====
-
-int64_t
-CompletedTransactionLogData::get_sm_id() const
-{
-  return m_http_sm->sm_id;
-}
-
-int64_t
-CompletedTransactionLogData::get_connection_id() const
-{
-  return m_http_sm->client_connection_id();
-}
-
-int
-CompletedTransactionLogData::get_transaction_id() const
-{
-  return m_http_sm->client_transaction_id();
-}
-
-int
-CompletedTransactionLogData::get_transaction_priority_weight() const
-{
-  return m_http_sm->client_transaction_priority_weight();
-}
-
-int
-CompletedTransactionLogData::get_transaction_priority_dependence() const
-{
-  return m_http_sm->client_transaction_priority_dependence();
-}
-
-// ===== Plugin info =====
-
-int64_t
-CompletedTransactionLogData::get_plugin_id() const
-{
-  return m_http_sm->plugin_id;
-}
-
-const char *
-CompletedTransactionLogData::get_plugin_tag() const
-{
-  return m_http_sm->plugin_tag;
-}
-
-// ===== Protocol info =====
-
-const char *
-CompletedTransactionLogData::get_client_protocol() const
-{
-  return m_http_sm->get_user_agent().get_client_protocol();
-}
-
-const char *
-CompletedTransactionLogData::get_server_protocol() const
-{
-  return m_http_sm->server_protocol;
-}
-
-const char *
-CompletedTransactionLogData::get_client_sec_protocol() const
-{
-  return m_http_sm->get_user_agent().get_client_sec_protocol();
-}
-
-const char *
-CompletedTransactionLogData::get_client_cipher_suite() const
-{
-  return m_http_sm->get_user_agent().get_client_cipher_suite();
-}
-
-const char *
-CompletedTransactionLogData::get_client_curve() const
-{
-  return m_http_sm->get_user_agent().get_client_curve();
-}
-
-const char *
-CompletedTransactionLogData::get_client_security_group() const
-{
-  return m_http_sm->get_user_agent().get_client_security_group();
-}
-
-int
-CompletedTransactionLogData::get_client_alpn_id() const
-{
-  return m_http_sm->get_user_agent().get_client_alpn_id();
-}
-
-// ===== SNI =====
-
-const char *
-CompletedTransactionLogData::get_sni_server_name() const
-{
-  if (auto txn = m_http_sm->get_ua_txn(); txn) {
-    if (auto ssn = txn->get_proxy_ssn(); ssn) {
-      if (auto ssl = ssn->ssl(); ssl) {
-        return ssl->client_sni_server_name();
-      }
-    }
-  }
-  return nullptr;
-}
-
-// ===== Connection flags =====
-
-bool
-CompletedTransactionLogData::get_client_tcp_reused() const
-{
-  return m_http_sm->get_user_agent().get_client_tcp_reused();
-}
-
-bool
-CompletedTransactionLogData::get_client_connection_is_ssl() const
-{
-  return m_http_sm->get_user_agent().get_client_connection_is_ssl();
-}
-
-bool
-CompletedTransactionLogData::get_client_ssl_reused() const
-{
-  return m_http_sm->get_user_agent().get_client_ssl_reused();
-}
-
-bool
-CompletedTransactionLogData::get_is_internal() const
-{
-  return m_http_sm->is_internal;
-}
-
-bool
-CompletedTransactionLogData::get_server_connection_is_ssl() const
-{
-  return m_http_sm->server_connection_is_ssl;
-}
-
-bool
-CompletedTransactionLogData::get_server_ssl_reused() const
-{
-  return m_http_sm->server_ssl_reused;
-}
-
-int
-CompletedTransactionLogData::get_server_connection_provided_cert() const
-{
-  return m_http_sm->server_connection_provided_cert;
-}
-
-int
-CompletedTransactionLogData::get_client_provided_cert() const
-{
-  if (auto txn = m_http_sm->get_ua_txn(); txn) {
-    if (auto ssn = txn->get_proxy_ssn(); ssn) {
-      if (auto ssl = ssn->ssl(); ssl) {
-        return ssl->client_provided_certificate();
-      }
-    }
-  }
-  return 0;
-}
-
-// ===== Server transaction count =====
-
-int64_t
-CompletedTransactionLogData::get_server_transact_count() const
-{
-  return m_http_sm->server_transact_count;
-}
-
-// ===== Finish status =====
-
-int
-CompletedTransactionLogData::get_client_finish_status_code() const
-{
-  return compute_client_finish_status(m_http_sm);
-}
-
-int
-CompletedTransactionLogData::get_proxy_finish_status_code() const
-{
-  return compute_proxy_finish_status(m_http_sm);
-}
-
-// ===== Error codes =====
-
-void
-CompletedTransactionLogData::format_error_codes() const
-{
-  if (m_error_codes_formatted) {
-    return;
-  }
-  m_error_codes_formatted = true;
-  m_http_sm->t_state.client_info.rx_error_code.str(m_client_rx_error_code, 
sizeof(m_client_rx_error_code));
-  m_http_sm->t_state.client_info.tx_error_code.str(m_client_tx_error_code, 
sizeof(m_client_tx_error_code));
-}
-
-const char *
-CompletedTransactionLogData::get_client_rx_error_code() const
-{
-  format_error_codes();
-  return m_client_rx_error_code;
-}
-
-const char *
-CompletedTransactionLogData::get_client_tx_error_code() const
-{
-  format_error_codes();
-  return m_client_tx_error_code;
-}
-
-// ===== MPTCP =====
-
-std::optional<bool>
-CompletedTransactionLogData::get_mptcp_state() const
-{
-  return m_http_sm->mptcp_state;
-}
-
-// ===== Misc transaction state =====
-
-in_port_t
-CompletedTransactionLogData::get_incoming_port() const
-{
-  return m_http_sm->t_state.request_data.incoming_port;
-}
-
-int
-CompletedTransactionLogData::get_orig_scheme() const
-{
-  return m_http_sm->t_state.orig_scheme;
-}
-
-int64_t
-CompletedTransactionLogData::get_congestion_control_crat() const
-{
-  return m_http_sm->t_state.congestion_control_crat;
-}
-
-// ===== Cache state =====
-
-int
-CompletedTransactionLogData::get_cache_write_code() const
-{
-  return convert_cache_write_code(m_http_sm->t_state.cache_info.write_status);
-}
-
-int
-CompletedTransactionLogData::get_cache_transform_write_code() const
-{
-  return 
convert_cache_write_code(m_http_sm->t_state.cache_info.transform_write_status);
-}
-
-int
-CompletedTransactionLogData::get_cache_open_read_tries() const
-{
-  return m_http_sm->get_cache_sm().get_open_read_tries();
-}
-
-int
-CompletedTransactionLogData::get_cache_open_write_tries() const
-{
-  return m_http_sm->get_cache_sm().get_open_write_tries();
-}
-
-int
-CompletedTransactionLogData::get_max_cache_open_write_retries() const
-{
-  return m_http_sm->t_state.txn_conf->max_cache_open_write_retries;
-}
-
-// ===== Retry attempts =====
-
-int64_t
-CompletedTransactionLogData::get_simple_retry_attempts() const
-{
-  return m_http_sm->t_state.current.simple_retry_attempts;
-}
-
-int64_t
-CompletedTransactionLogData::get_unavailable_retry_attempts() const
-{
-  return m_http_sm->t_state.current.unavailable_server_retry_attempts;
-}
-
-int64_t
-CompletedTransactionLogData::get_retry_attempts_saved() const
-{
-  return m_http_sm->t_state.current.retry_attempts.saved();
-}
-
-// ===== Status plugin entry name =====
-
-std::string_view
-CompletedTransactionLogData::get_http_return_code_setter_name() const
-{
-  return m_http_sm->t_state.http_return_code_setter_name;
-}
-
-// ===== Proxy Protocol =====
-
-int
-CompletedTransactionLogData::get_pp_version() const
-{
-  if (m_http_sm->t_state.pp_info.version != ProxyProtocolVersion::UNDEFINED) {
-    return static_cast<int>(m_http_sm->t_state.pp_info.version);
-  }
-  return 0;
-}
-
-sockaddr const *
-CompletedTransactionLogData::get_pp_src_addr() const
-{
-  if (m_http_sm->t_state.pp_info.version != ProxyProtocolVersion::UNDEFINED) {
-    return &m_http_sm->t_state.pp_info.src_addr.sa;
-  }
-  return nullptr;
-}
-
-sockaddr const *
-CompletedTransactionLogData::get_pp_dst_addr() const
-{
-  if (m_http_sm->t_state.pp_info.version != ProxyProtocolVersion::UNDEFINED) {
-    return &m_http_sm->t_state.pp_info.dst_addr.sa;
-  }
-  return nullptr;
-}
-
-std::string_view
-CompletedTransactionLogData::get_pp_authority() const
-{
-  if (m_http_sm->t_state.pp_info.version != ProxyProtocolVersion::UNDEFINED) {
-    if (auto authority_opt = 
m_http_sm->t_state.pp_info.get_tlv(PP2_TYPE_AUTHORITY); authority_opt) {
-      return *authority_opt;
-    }
-  }
-  return {};
-}
-
-std::string_view
-CompletedTransactionLogData::get_pp_tls_cipher() const
-{
-  if (m_http_sm->t_state.pp_info.version != ProxyProtocolVersion::UNDEFINED) {
-    if (auto cipher = m_http_sm->t_state.pp_info.get_tlv_ssl_cipher(); cipher) 
{
-      return *cipher;
-    }
-  }
-  return {};
-}
-
-std::string_view
-CompletedTransactionLogData::get_pp_tls_version() const
-{
-  if (m_http_sm->t_state.pp_info.version != ProxyProtocolVersion::UNDEFINED) {
-    if (auto version = m_http_sm->t_state.pp_info.get_tlv_ssl_version(); 
version) {
-      return *version;
-    }
-  }
-  return {};
-}
-
-std::string_view
-CompletedTransactionLogData::get_pp_tls_group() const
-{
-  if (m_http_sm->t_state.pp_info.version != ProxyProtocolVersion::UNDEFINED) {
-    if (auto group = m_http_sm->t_state.pp_info.get_tlv_ssl_group(); group) {
-      return *group;
-    }
-  }
-  return {};
-}
-
-// ===== Server response Transfer-Encoding =====
-
-std::string_view
-CompletedTransactionLogData::get_server_response_transfer_encoding() const
-{
-  return m_http_sm->t_state.hdr_info.server_response_transfer_encoding;
-}
diff --git a/src/proxy/http/HttpBodyFactory.cc 
b/src/proxy/http/HttpBodyFactory.cc
index 05849b781e..cdaaac582d 100644
--- a/src/proxy/http/HttpBodyFactory.cc
+++ b/src/proxy/http/HttpBodyFactory.cc
@@ -40,7 +40,7 @@
 #include "proxy/hdrs/URL.h"
 #include "proxy/logging/Log.h"
 #include "proxy/logging/LogAccess.h"
-#include "proxy/http/CompletedTransactionLogData.h"
+#include "proxy/logging/TransactionLogData.h"
 #include "proxy/hdrs/HttpCompat.h"
 #include "tscore/Layout.h"
 
@@ -1158,8 +1158,8 @@ 
HttpBodyTemplate::build_instantiated_buffer(HttpTransact::State *context, int64_
 
   Dbg(dbg_ctl_body_factory_instantiation, "    before instantiation: [%s]", 
template_buffer);
 
-  CompletedTransactionLogData log_data(context->state_machine);
-  LogAccess                   la(log_data);
+  TransactionLogData log_data(context->state_machine);
+  LogAccess          la(log_data);
 
   buffer = resolve_logfield_string(&la, template_buffer);
 
diff --git a/src/proxy/http/HttpSM.cc b/src/proxy/http/HttpSM.cc
index 524a140574..c75f69ec9d 100644
--- a/src/proxy/http/HttpSM.cc
+++ b/src/proxy/http/HttpSM.cc
@@ -45,7 +45,7 @@
 #include "proxy/http/PreWarmConfig.h"
 #include "proxy/logging/Log.h"
 #include "proxy/logging/LogAccess.h"
-#include "proxy/http/CompletedTransactionLogData.h"
+#include "proxy/logging/TransactionLogData.h"
 #include "proxy/PluginVC.h"
 #include "proxy/ReverseProxy.h"
 #include "proxy/http/remap/RemapProcessor.h"
@@ -7723,8 +7723,8 @@ HttpSM::kill_this()
     //////////////
     SMDbg(dbg_ctl_http_seq, "Logging transaction");
     if (Log::transaction_logging_enabled() && 
t_state.api_info.logging_enabled) {
-      CompletedTransactionLogData log_data(this);
-      LogAccess                   accessor(log_data);
+      TransactionLogData log_data(this);
+      LogAccess          accessor(log_data);
 
       int ret = Log::access(&accessor);
 
@@ -8454,8 +8454,8 @@ HttpSM::do_redirect()
     if (redirect_url != nullptr ||
         
t_state.hdr_info.client_response.field_find(static_cast<std::string_view>(MIME_FIELD_LOCATION)))
 {
       if (Log::transaction_logging_enabled() && 
t_state.api_info.logging_enabled) {
-        CompletedTransactionLogData log_data(this);
-        LogAccess                   accessor(log_data);
+        TransactionLogData log_data(this);
+        LogAccess          accessor(log_data);
         if (redirect_url == nullptr) {
           if (t_state.squid_codes.log_code == SquidLogCode::TCP_HIT) {
             t_state.squid_codes.log_code = SquidLogCode::TCP_HIT_REDIRECT;
diff --git a/src/proxy/logging/CMakeLists.txt b/src/proxy/logging/CMakeLists.txt
index bf109bad66..f897952ff2 100644
--- a/src/proxy/logging/CMakeLists.txt
+++ b/src/proxy/logging/CMakeLists.txt
@@ -28,6 +28,7 @@ add_library(
   LogFormat.cc
   LogFieldFallback.cc
   LogObject.cc
+  TransactionLogData.cc
   LogUtils.cc
   RolledLogDeleter.cc
   YamlLogConfig.cc
@@ -37,7 +38,16 @@ add_library(ts::logging ALIAS logging)
 
 target_include_directories(logging PRIVATE ${SWOC_INCLUDE_DIR})
 
-target_link_libraries(logging PUBLIC ts::inkevent ts::inkutils ts::hdrs 
ts::tscore ts::configmanager yaml-cpp::yaml-cpp)
+target_link_libraries(
+  logging
+  PUBLIC ts::inkevent
+         ts::inkutils
+         ts::http
+         ts::hdrs
+         ts::tscore
+         ts::configmanager
+         yaml-cpp::yaml-cpp
+)
 
 if(BUILD_TESTING)
   add_executable(test_LogFieldFallback unit-tests/test_LogFieldFallback.cc)
diff --git a/src/proxy/logging/TransactionLogData.cc 
b/src/proxy/logging/TransactionLogData.cc
new file mode 100644
index 0000000000..af44b5e374
--- /dev/null
+++ b/src/proxy/logging/TransactionLogData.cc
@@ -0,0 +1,1117 @@
+/** @file
+
+  TransactionLogData implementation.
+
+  @section license License
+
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you 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
+
+      http://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 "proxy/logging/TransactionLogData.h"
+#include "proxy/PreTransactionLogData.h"
+#include "proxy/http/HttpSM.h"
+#include "proxy/logging/LogAccess.h"
+#include "proxy/hdrs/MIME.h"
+#include "tscore/ink_defs.h"
+#include "../private/SSLProxySession.h"
+
+namespace
+{
+/** Map HttpTransact::CacheWriteStatus_t to LogCacheWriteCodeType. */
+int
+convert_cache_write_code(HttpTransact::CacheWriteStatus_t t)
+{
+  switch (t) {
+  case HttpTransact::CacheWriteStatus_t::NO_WRITE:
+    return LOG_CACHE_WRITE_NONE;
+  case HttpTransact::CacheWriteStatus_t::LOCK_MISS:
+    return LOG_CACHE_WRITE_LOCK_MISSED;
+  case HttpTransact::CacheWriteStatus_t::IN_PROGRESS:
+    return LOG_CACHE_WRITE_LOCK_ABORTED;
+  case HttpTransact::CacheWriteStatus_t::ERROR:
+    return LOG_CACHE_WRITE_ERROR;
+  case HttpTransact::CacheWriteStatus_t::COMPLETE:
+    return LOG_CACHE_WRITE_COMPLETE;
+  default:
+    ink_assert(!"bad cache write code");
+    return LOG_CACHE_WRITE_NONE;
+  }
+}
+
+int
+compute_client_finish_status(HttpSM *sm)
+{
+  HttpTransact::AbortState_t cl_abort_state = sm->t_state.client_info.abort;
+  if (cl_abort_state == HttpTransact::ABORTED) {
+    if (sm->t_state.client_info.state == HttpTransact::ACTIVE_TIMEOUT ||
+        sm->t_state.client_info.state == HttpTransact::INACTIVE_TIMEOUT) {
+      return LOG_FINISH_TIMEOUT;
+    }
+    return LOG_FINISH_INTR;
+  }
+  return LOG_FINISH_FIN;
+}
+
+int
+compute_proxy_finish_status(HttpSM *sm)
+{
+  if (sm->t_state.current.server) {
+    switch (sm->t_state.current.server->state) {
+    case HttpTransact::ACTIVE_TIMEOUT:
+    case HttpTransact::INACTIVE_TIMEOUT:
+      return LOG_FINISH_TIMEOUT;
+    case HttpTransact::CONNECTION_ERROR:
+      return LOG_FINISH_INTR;
+    default:
+      if (sm->t_state.current.server->abort == HttpTransact::ABORTED) {
+        return LOG_FINISH_INTR;
+      }
+      break;
+    }
+  }
+  return LOG_FINISH_FIN;
+}
+} // end anonymous namespace
+
+TransactionLogData::TransactionLogData(HttpSM *sm) : m_http_sm(sm)
+{
+  ink_assert(sm != nullptr);
+}
+
+TransactionLogData::TransactionLogData(PreTransactionLogData const &pre_data) 
: m_pre_data(&pre_data) {}
+
+void *
+TransactionLogData::http_sm_for_plugins() const
+{
+  if (likely(m_http_sm != nullptr)) {
+    return m_http_sm;
+  }
+  return nullptr;
+}
+
+// ===== Milestones =====
+
+TransactionMilestones const *
+TransactionLogData::get_milestones() const
+{
+  if (likely(m_http_sm != nullptr)) {
+    return &m_http_sm->milestones;
+  }
+  return &m_pre_data->owned_milestones;
+}
+
+// ===== Headers =====
+
+HTTPHdr *
+TransactionLogData::get_client_request() const
+{
+  if (likely(m_http_sm != nullptr)) {
+    HttpTransact::HeaderInfo *hdr = &m_http_sm->t_state.hdr_info;
+    if (hdr->client_request.valid()) {
+      return &hdr->client_request;
+    }
+    return nullptr;
+  }
+
+  if (m_pre_data->owned_client_request.valid()) {
+    return const_cast<HTTPHdr *>(&m_pre_data->owned_client_request);
+  }
+  return nullptr;
+}
+
+HTTPHdr *
+TransactionLogData::get_proxy_response() const
+{
+  if (likely(m_http_sm != nullptr)) {
+    HttpTransact::HeaderInfo *hdr = &m_http_sm->t_state.hdr_info;
+    if (hdr->client_response.valid()) {
+      return &hdr->client_response;
+    }
+  }
+  return nullptr;
+}
+
+HTTPHdr *
+TransactionLogData::get_proxy_request() const
+{
+  if (likely(m_http_sm != nullptr)) {
+    HttpTransact::HeaderInfo *hdr = &m_http_sm->t_state.hdr_info;
+    if (hdr->server_request.valid()) {
+      return &hdr->server_request;
+    }
+  }
+  return nullptr;
+}
+
+HTTPHdr *
+TransactionLogData::get_server_response() const
+{
+  if (likely(m_http_sm != nullptr)) {
+    HttpTransact::HeaderInfo *hdr = &m_http_sm->t_state.hdr_info;
+    if (hdr->server_response.valid()) {
+      return &hdr->server_response;
+    }
+  }
+  return nullptr;
+}
+
+HTTPHdr *
+TransactionLogData::get_cache_response() const
+{
+  if (likely(m_http_sm != nullptr)) {
+    HttpTransact::HeaderInfo *hdr = &m_http_sm->t_state.hdr_info;
+    if (hdr->cache_response.valid()) {
+      return &hdr->cache_response;
+    }
+  }
+  return nullptr;
+}
+
+// ===== Client request URL / path =====
+
+void
+TransactionLogData::cache_url_strings() const
+{
+  if (m_url_cached) {
+    return;
+  }
+  m_url_cached = true;
+
+  HTTPHdr *client_request = get_client_request();
+  if (client_request) {
+    m_client_req_url_str      = 
client_request->url_string_get_ref(&m_client_req_url_len);
+    auto path_sv              = client_request->path_get();
+    m_client_req_url_path_str = path_sv.data();
+    m_client_req_url_path_len = static_cast<int>(path_sv.length());
+  }
+}
+
+const char *
+TransactionLogData::get_client_req_url_str() const
+{
+  if (likely(m_http_sm != nullptr)) {
+    cache_url_strings();
+    return m_client_req_url_str;
+  }
+  return m_pre_data->owned_url.empty() ? nullptr : 
m_pre_data->owned_url.c_str();
+}
+
+int
+TransactionLogData::get_client_req_url_len() const
+{
+  if (likely(m_http_sm != nullptr)) {
+    cache_url_strings();
+    return m_client_req_url_len;
+  }
+  return static_cast<int>(m_pre_data->owned_url.size());
+}
+
+const char *
+TransactionLogData::get_client_req_url_path_str() const
+{
+  if (likely(m_http_sm != nullptr)) {
+    cache_url_strings();
+    return m_client_req_url_path_str;
+  }
+  return m_pre_data->owned_path.empty() ? nullptr : 
m_pre_data->owned_path.c_str();
+}
+
+int
+TransactionLogData::get_client_req_url_path_len() const
+{
+  if (likely(m_http_sm != nullptr)) {
+    cache_url_strings();
+    return m_client_req_url_path_len;
+  }
+  return static_cast<int>(m_pre_data->owned_path.size());
+}
+
+// ===== Proxy response content-type / reason =====
+
+void
+TransactionLogData::cache_content_type() const
+{
+  if (m_content_type_cached) {
+    return;
+  }
+  m_content_type_cached = true;
+
+  HTTPHdr *proxy_response = get_proxy_response();
+  if (proxy_response) {
+    MIMEField *field = 
proxy_response->field_find(static_cast<std::string_view>(MIME_FIELD_CONTENT_TYPE));
+    if (field) {
+      auto ct                       = field->value_get();
+      m_proxy_resp_content_type_str = const_cast<char *>(ct.data());
+      m_proxy_resp_content_type_len = ct.length();
+    } else {
+      constexpr std::string_view hidden_ct{"@Content-Type"};
+      field = proxy_response->field_find(hidden_ct);
+      if (field) {
+        auto ct                       = field->value_get();
+        m_proxy_resp_content_type_str = const_cast<char *>(ct.data());
+        m_proxy_resp_content_type_len = ct.length();
+      }
+    }
+    auto reason                    = proxy_response->reason_get();
+    m_proxy_resp_reason_phrase_str = const_cast<char *>(reason.data());
+    m_proxy_resp_reason_phrase_len = static_cast<int>(reason.length());
+  }
+}
+
+char *
+TransactionLogData::get_proxy_resp_content_type_str() const
+{
+  if (likely(m_http_sm != nullptr)) {
+    cache_content_type();
+    return m_proxy_resp_content_type_str;
+  }
+  return nullptr;
+}
+
+int
+TransactionLogData::get_proxy_resp_content_type_len() const
+{
+  if (likely(m_http_sm != nullptr)) {
+    cache_content_type();
+    return m_proxy_resp_content_type_len;
+  }
+  return 0;
+}
+
+char *
+TransactionLogData::get_proxy_resp_reason_phrase_str() const
+{
+  if (likely(m_http_sm != nullptr)) {
+    cache_content_type();
+    return m_proxy_resp_reason_phrase_str;
+  }
+  return nullptr;
+}
+
+int
+TransactionLogData::get_proxy_resp_reason_phrase_len() const
+{
+  if (likely(m_http_sm != nullptr)) {
+    cache_content_type();
+    return m_proxy_resp_reason_phrase_len;
+  }
+  return 0;
+}
+
+// ===== Unmapped URL =====
+
+char *
+TransactionLogData::get_unmapped_url_str() const
+{
+  if (likely(m_http_sm != nullptr)) {
+    if (m_http_sm->t_state.unmapped_url.valid()) {
+      int len = 0;
+      return m_http_sm->t_state.unmapped_url.string_get_ref(&len);
+    }
+  }
+  return nullptr;
+}
+
+int
+TransactionLogData::get_unmapped_url_len() const
+{
+  if (likely(m_http_sm != nullptr)) {
+    if (m_http_sm->t_state.unmapped_url.valid()) {
+      int len = 0;
+      m_http_sm->t_state.unmapped_url.string_get_ref(&len);
+      return len;
+    }
+  }
+  return 0;
+}
+
+// ===== Cache lookup URL =====
+
+char *
+TransactionLogData::get_cache_lookup_url_str() const
+{
+  if (likely(m_http_sm != nullptr)) {
+    if (m_http_sm->t_state.cache_info.lookup_url_storage.valid()) {
+      int len = 0;
+      return 
m_http_sm->t_state.cache_info.lookup_url_storage.string_get_ref(&len);
+    }
+  }
+  return nullptr;
+}
+
+int
+TransactionLogData::get_cache_lookup_url_len() const
+{
+  if (likely(m_http_sm != nullptr)) {
+    if (m_http_sm->t_state.cache_info.lookup_url_storage.valid()) {
+      int len = 0;
+      m_http_sm->t_state.cache_info.lookup_url_storage.string_get_ref(&len);
+      return len;
+    }
+  }
+  return 0;
+}
+
+// ===== Client addressing =====
+
+sockaddr const *
+TransactionLogData::get_client_addr() const
+{
+  if (likely(m_http_sm != nullptr)) {
+    return &m_http_sm->t_state.effective_client_addr.sa;
+  }
+  return &m_pre_data->owned_client_addr.sa;
+}
+
+sockaddr const *
+TransactionLogData::get_client_src_addr() const
+{
+  if (likely(m_http_sm != nullptr)) {
+    return &m_http_sm->t_state.client_info.src_addr.sa;
+  }
+  return &m_pre_data->owned_client_src_addr.sa;
+}
+
+sockaddr const *
+TransactionLogData::get_client_dst_addr() const
+{
+  if (likely(m_http_sm != nullptr)) {
+    return &m_http_sm->t_state.client_info.dst_addr.sa;
+  }
+  return &m_pre_data->owned_client_dst_addr.sa;
+}
+
+sockaddr const *
+TransactionLogData::get_verified_client_addr() const
+{
+  if (likely(m_http_sm != nullptr)) {
+    if (auto txn = m_http_sm->get_ua_txn(); txn) {
+      sockaddr const *vaddr = txn->get_verified_client_addr();
+      if (vaddr && ats_is_ip(vaddr)) {
+        return vaddr;
+      }
+    }
+  }
+  return nullptr;
+}
+
+uint16_t
+TransactionLogData::get_client_port() const
+{
+  if (likely(m_http_sm != nullptr)) {
+    if (auto txn = m_http_sm->get_ua_txn(); txn) {
+      return txn->get_client_port();
+    }
+    return 0;
+  }
+  return m_pre_data->m_client_port;
+}
+
+// ===== Server addressing =====
+
+sockaddr const *
+TransactionLogData::get_server_src_addr() const
+{
+  if (likely(m_http_sm != nullptr)) {
+    if (m_http_sm->t_state.current.server) {
+      return &m_http_sm->t_state.current.server->src_addr.sa;
+    }
+  }
+  return nullptr;
+}
+
+sockaddr const *
+TransactionLogData::get_server_dst_addr() const
+{
+  if (likely(m_http_sm != nullptr)) {
+    if (m_http_sm->t_state.current.server) {
+      return &m_http_sm->t_state.current.server->dst_addr.sa;
+    }
+  }
+  return nullptr;
+}
+
+sockaddr const *
+TransactionLogData::get_server_info_dst_addr() const
+{
+  if (likely(m_http_sm != nullptr)) {
+    return &m_http_sm->t_state.server_info.dst_addr.sa;
+  }
+  return nullptr;
+}
+
+const char *
+TransactionLogData::get_server_name() const
+{
+  if (likely(m_http_sm != nullptr)) {
+    if (m_http_sm->t_state.current.server) {
+      return m_http_sm->t_state.current.server->name;
+    }
+  }
+  return nullptr;
+}
+
+// ===== Squid codes =====
+
+SquidLogCode
+TransactionLogData::get_log_code() const
+{
+  if (likely(m_http_sm != nullptr)) {
+    return m_http_sm->t_state.squid_codes.log_code;
+  }
+  return m_pre_data->m_log_code;
+}
+
+SquidSubcode
+TransactionLogData::get_subcode() const
+{
+  if (likely(m_http_sm != nullptr)) {
+    return m_http_sm->t_state.squid_codes.subcode;
+  }
+  return SquidSubcode::EMPTY;
+}
+
+SquidHitMissCode
+TransactionLogData::get_hit_miss_code() const
+{
+  if (likely(m_http_sm != nullptr)) {
+    return m_http_sm->t_state.squid_codes.hit_miss_code;
+  }
+  return m_pre_data->m_hit_miss_code;
+}
+
+SquidHierarchyCode
+TransactionLogData::get_hier_code() const
+{
+  if (likely(m_http_sm != nullptr)) {
+    return m_http_sm->t_state.squid_codes.hier_code;
+  }
+  return m_pre_data->m_hier_code;
+}
+
+// ===== Byte counters =====
+
+int64_t
+TransactionLogData::get_client_request_body_bytes() const
+{
+  if (likely(m_http_sm != nullptr)) {
+    return m_http_sm->client_request_body_bytes;
+  }
+  return 0;
+}
+
+int64_t
+TransactionLogData::get_client_response_hdr_bytes() const
+{
+  if (likely(m_http_sm != nullptr)) {
+    return m_http_sm->client_response_hdr_bytes;
+  }
+  return 0;
+}
+
+int64_t
+TransactionLogData::get_client_response_body_bytes() const
+{
+  if (likely(m_http_sm != nullptr)) {
+    return m_http_sm->client_response_body_bytes;
+  }
+  return 0;
+}
+
+int64_t
+TransactionLogData::get_server_request_body_bytes() const
+{
+  if (likely(m_http_sm != nullptr)) {
+    return m_http_sm->server_request_body_bytes;
+  }
+  return 0;
+}
+
+int64_t
+TransactionLogData::get_server_response_body_bytes() const
+{
+  if (likely(m_http_sm != nullptr)) {
+    return m_http_sm->server_response_body_bytes;
+  }
+  return 0;
+}
+
+int64_t
+TransactionLogData::get_cache_response_body_bytes() const
+{
+  if (likely(m_http_sm != nullptr)) {
+    return m_http_sm->cache_response_body_bytes;
+  }
+  return 0;
+}
+
+int64_t
+TransactionLogData::get_cache_response_hdr_bytes() const
+{
+  if (likely(m_http_sm != nullptr)) {
+    return m_http_sm->cache_response_hdr_bytes;
+  }
+  return 0;
+}
+
+// ===== Transaction identifiers =====
+
+int64_t
+TransactionLogData::get_sm_id() const
+{
+  if (likely(m_http_sm != nullptr)) {
+    return m_http_sm->sm_id;
+  }
+  return 0;
+}
+
+int64_t
+TransactionLogData::get_connection_id() const
+{
+  if (likely(m_http_sm != nullptr)) {
+    return m_http_sm->client_connection_id();
+  }
+  return m_pre_data->m_connection_id;
+}
+
+int
+TransactionLogData::get_transaction_id() const
+{
+  if (likely(m_http_sm != nullptr)) {
+    return m_http_sm->client_transaction_id();
+  }
+  return m_pre_data->m_transaction_id;
+}
+
+int
+TransactionLogData::get_transaction_priority_weight() const
+{
+  if (likely(m_http_sm != nullptr)) {
+    return m_http_sm->client_transaction_priority_weight();
+  }
+  return 0;
+}
+
+int
+TransactionLogData::get_transaction_priority_dependence() const
+{
+  if (likely(m_http_sm != nullptr)) {
+    return m_http_sm->client_transaction_priority_dependence();
+  }
+  return 0;
+}
+
+// ===== Plugin info =====
+
+int64_t
+TransactionLogData::get_plugin_id() const
+{
+  if (likely(m_http_sm != nullptr)) {
+    return m_http_sm->plugin_id;
+  }
+  return 0;
+}
+
+const char *
+TransactionLogData::get_plugin_tag() const
+{
+  if (likely(m_http_sm != nullptr)) {
+    return m_http_sm->plugin_tag;
+  }
+  return nullptr;
+}
+
+// ===== Protocol info =====
+
+const char *
+TransactionLogData::get_client_protocol() const
+{
+  if (likely(m_http_sm != nullptr)) {
+    return m_http_sm->get_user_agent().get_client_protocol();
+  }
+  return m_pre_data->owned_client_protocol_str.empty() ? nullptr : 
m_pre_data->owned_client_protocol_str.c_str();
+}
+
+const char *
+TransactionLogData::get_server_protocol() const
+{
+  if (likely(m_http_sm != nullptr)) {
+    return m_http_sm->server_protocol;
+  }
+  return nullptr;
+}
+
+const char *
+TransactionLogData::get_client_sec_protocol() const
+{
+  if (likely(m_http_sm != nullptr)) {
+    return m_http_sm->get_user_agent().get_client_sec_protocol();
+  }
+  return nullptr;
+}
+
+const char *
+TransactionLogData::get_client_cipher_suite() const
+{
+  if (likely(m_http_sm != nullptr)) {
+    return m_http_sm->get_user_agent().get_client_cipher_suite();
+  }
+  return nullptr;
+}
+
+const char *
+TransactionLogData::get_client_curve() const
+{
+  if (likely(m_http_sm != nullptr)) {
+    return m_http_sm->get_user_agent().get_client_curve();
+  }
+  return nullptr;
+}
+
+const char *
+TransactionLogData::get_client_security_group() const
+{
+  if (likely(m_http_sm != nullptr)) {
+    return m_http_sm->get_user_agent().get_client_security_group();
+  }
+  return nullptr;
+}
+
+int
+TransactionLogData::get_client_alpn_id() const
+{
+  if (likely(m_http_sm != nullptr)) {
+    return m_http_sm->get_user_agent().get_client_alpn_id();
+  }
+  return -1;
+}
+
+// ===== SNI =====
+
+const char *
+TransactionLogData::get_sni_server_name() const
+{
+  if (likely(m_http_sm != nullptr)) {
+    if (auto txn = m_http_sm->get_ua_txn(); txn) {
+      if (auto ssn = txn->get_proxy_ssn(); ssn) {
+        if (auto ssl = ssn->ssl(); ssl) {
+          return ssl->client_sni_server_name();
+        }
+      }
+    }
+  }
+  return nullptr;
+}
+
+// ===== Connection flags =====
+
+bool
+TransactionLogData::get_client_tcp_reused() const
+{
+  if (likely(m_http_sm != nullptr)) {
+    return m_http_sm->get_user_agent().get_client_tcp_reused();
+  }
+  return false;
+}
+
+bool
+TransactionLogData::get_client_connection_is_ssl() const
+{
+  if (likely(m_http_sm != nullptr)) {
+    return m_http_sm->get_user_agent().get_client_connection_is_ssl();
+  }
+  return m_pre_data->m_client_connection_is_ssl;
+}
+
+bool
+TransactionLogData::get_client_ssl_reused() const
+{
+  if (likely(m_http_sm != nullptr)) {
+    return m_http_sm->get_user_agent().get_client_ssl_reused();
+  }
+  return false;
+}
+
+bool
+TransactionLogData::get_is_internal() const
+{
+  if (likely(m_http_sm != nullptr)) {
+    return m_http_sm->is_internal;
+  }
+  return false;
+}
+
+bool
+TransactionLogData::get_server_connection_is_ssl() const
+{
+  if (likely(m_http_sm != nullptr)) {
+    return m_http_sm->server_connection_is_ssl;
+  }
+  return false;
+}
+
+bool
+TransactionLogData::get_server_ssl_reused() const
+{
+  if (likely(m_http_sm != nullptr)) {
+    return m_http_sm->server_ssl_reused;
+  }
+  return false;
+}
+
+int
+TransactionLogData::get_server_connection_provided_cert() const
+{
+  if (likely(m_http_sm != nullptr)) {
+    return m_http_sm->server_connection_provided_cert;
+  }
+  return 0;
+}
+
+int
+TransactionLogData::get_client_provided_cert() const
+{
+  if (likely(m_http_sm != nullptr)) {
+    if (auto txn = m_http_sm->get_ua_txn(); txn) {
+      if (auto ssn = txn->get_proxy_ssn(); ssn) {
+        if (auto ssl = ssn->ssl(); ssl) {
+          return ssl->client_provided_certificate();
+        }
+      }
+    }
+  }
+  return 0;
+}
+
+// ===== Server transaction count =====
+
+int64_t
+TransactionLogData::get_server_transact_count() const
+{
+  if (likely(m_http_sm != nullptr)) {
+    return m_http_sm->server_transact_count;
+  }
+  return m_pre_data->m_server_transact_count;
+}
+
+// ===== Finish status =====
+
+int
+TransactionLogData::get_client_finish_status_code() const
+{
+  if (likely(m_http_sm != nullptr)) {
+    return compute_client_finish_status(m_http_sm);
+  }
+  return 0;
+}
+
+int
+TransactionLogData::get_proxy_finish_status_code() const
+{
+  if (likely(m_http_sm != nullptr)) {
+    return compute_proxy_finish_status(m_http_sm);
+  }
+  return 0;
+}
+
+// ===== Error codes =====
+
+void
+TransactionLogData::format_error_codes() const
+{
+  if (m_error_codes_formatted) {
+    return;
+  }
+  m_error_codes_formatted = true;
+  ink_assert(m_http_sm != nullptr);
+  m_http_sm->t_state.client_info.rx_error_code.str(m_client_rx_error_code, 
sizeof(m_client_rx_error_code));
+  m_http_sm->t_state.client_info.tx_error_code.str(m_client_tx_error_code, 
sizeof(m_client_tx_error_code));
+}
+
+const char *
+TransactionLogData::get_client_rx_error_code() const
+{
+  if (likely(m_http_sm != nullptr)) {
+    format_error_codes();
+    return m_client_rx_error_code;
+  }
+  return "-";
+}
+
+const char *
+TransactionLogData::get_client_tx_error_code() const
+{
+  if (likely(m_http_sm != nullptr)) {
+    format_error_codes();
+    return m_client_tx_error_code;
+  }
+  return "-";
+}
+
+// ===== MPTCP =====
+
+std::optional<bool>
+TransactionLogData::get_mptcp_state() const
+{
+  if (likely(m_http_sm != nullptr)) {
+    return m_http_sm->mptcp_state;
+  }
+  return std::nullopt;
+}
+
+// ===== Misc transaction state =====
+
+in_port_t
+TransactionLogData::get_incoming_port() const
+{
+  if (likely(m_http_sm != nullptr)) {
+    return m_http_sm->t_state.request_data.incoming_port;
+  }
+  return 0;
+}
+
+int
+TransactionLogData::get_orig_scheme() const
+{
+  if (likely(m_http_sm != nullptr)) {
+    return m_http_sm->t_state.orig_scheme;
+  }
+  return -1;
+}
+
+int64_t
+TransactionLogData::get_congestion_control_crat() const
+{
+  if (likely(m_http_sm != nullptr)) {
+    return m_http_sm->t_state.congestion_control_crat;
+  }
+  return 0;
+}
+
+// ===== Cache state =====
+
+int
+TransactionLogData::get_cache_write_code() const
+{
+  if (likely(m_http_sm != nullptr)) {
+    return 
convert_cache_write_code(m_http_sm->t_state.cache_info.write_status);
+  }
+  return 0;
+}
+
+int
+TransactionLogData::get_cache_transform_write_code() const
+{
+  if (likely(m_http_sm != nullptr)) {
+    return 
convert_cache_write_code(m_http_sm->t_state.cache_info.transform_write_status);
+  }
+  return 0;
+}
+
+int
+TransactionLogData::get_cache_open_read_tries() const
+{
+  if (likely(m_http_sm != nullptr)) {
+    return m_http_sm->get_cache_sm().get_open_read_tries();
+  }
+  return 0;
+}
+
+int
+TransactionLogData::get_cache_open_write_tries() const
+{
+  if (likely(m_http_sm != nullptr)) {
+    return m_http_sm->get_cache_sm().get_open_write_tries();
+  }
+  return 0;
+}
+
+int
+TransactionLogData::get_max_cache_open_write_retries() const
+{
+  if (likely(m_http_sm != nullptr)) {
+    return m_http_sm->t_state.txn_conf->max_cache_open_write_retries;
+  }
+  return -1;
+}
+
+// ===== Retry attempts =====
+
+int64_t
+TransactionLogData::get_simple_retry_attempts() const
+{
+  if (likely(m_http_sm != nullptr)) {
+    return m_http_sm->t_state.current.simple_retry_attempts;
+  }
+  return 0;
+}
+
+int64_t
+TransactionLogData::get_unavailable_retry_attempts() const
+{
+  if (likely(m_http_sm != nullptr)) {
+    return m_http_sm->t_state.current.unavailable_server_retry_attempts;
+  }
+  return 0;
+}
+
+int64_t
+TransactionLogData::get_retry_attempts_saved() const
+{
+  if (likely(m_http_sm != nullptr)) {
+    return m_http_sm->t_state.current.retry_attempts.saved();
+  }
+  return 0;
+}
+
+// ===== Status plugin entry name =====
+
+std::string_view
+TransactionLogData::get_http_return_code_setter_name() const
+{
+  if (likely(m_http_sm != nullptr)) {
+    return m_http_sm->t_state.http_return_code_setter_name;
+  }
+  return {};
+}
+
+// ===== Proxy Protocol =====
+
+int
+TransactionLogData::get_pp_version() const
+{
+  if (likely(m_http_sm != nullptr)) {
+    if (m_http_sm->t_state.pp_info.version != ProxyProtocolVersion::UNDEFINED) 
{
+      return static_cast<int>(m_http_sm->t_state.pp_info.version);
+    }
+  }
+  return 0;
+}
+
+sockaddr const *
+TransactionLogData::get_pp_src_addr() const
+{
+  if (likely(m_http_sm != nullptr)) {
+    if (m_http_sm->t_state.pp_info.version != ProxyProtocolVersion::UNDEFINED) 
{
+      return &m_http_sm->t_state.pp_info.src_addr.sa;
+    }
+  }
+  return nullptr;
+}
+
+sockaddr const *
+TransactionLogData::get_pp_dst_addr() const
+{
+  if (likely(m_http_sm != nullptr)) {
+    if (m_http_sm->t_state.pp_info.version != ProxyProtocolVersion::UNDEFINED) 
{
+      return &m_http_sm->t_state.pp_info.dst_addr.sa;
+    }
+  }
+  return nullptr;
+}
+
+std::string_view
+TransactionLogData::get_pp_authority() const
+{
+  if (likely(m_http_sm != nullptr)) {
+    if (m_http_sm->t_state.pp_info.version != ProxyProtocolVersion::UNDEFINED) 
{
+      if (auto authority_opt = 
m_http_sm->t_state.pp_info.get_tlv(PP2_TYPE_AUTHORITY); authority_opt) {
+        return *authority_opt;
+      }
+    }
+  }
+  return {};
+}
+
+std::string_view
+TransactionLogData::get_pp_tls_cipher() const
+{
+  if (likely(m_http_sm != nullptr)) {
+    if (m_http_sm->t_state.pp_info.version != ProxyProtocolVersion::UNDEFINED) 
{
+      if (auto cipher = m_http_sm->t_state.pp_info.get_tlv_ssl_cipher(); 
cipher) {
+        return *cipher;
+      }
+    }
+  }
+  return {};
+}
+
+std::string_view
+TransactionLogData::get_pp_tls_version() const
+{
+  if (likely(m_http_sm != nullptr)) {
+    if (m_http_sm->t_state.pp_info.version != ProxyProtocolVersion::UNDEFINED) 
{
+      if (auto version = m_http_sm->t_state.pp_info.get_tlv_ssl_version(); 
version) {
+        return *version;
+      }
+    }
+  }
+  return {};
+}
+
+std::string_view
+TransactionLogData::get_pp_tls_group() const
+{
+  if (likely(m_http_sm != nullptr)) {
+    if (m_http_sm->t_state.pp_info.version != ProxyProtocolVersion::UNDEFINED) 
{
+      if (auto group = m_http_sm->t_state.pp_info.get_tlv_ssl_group(); group) {
+        return *group;
+      }
+    }
+  }
+  return {};
+}
+
+// ===== Server response Transfer-Encoding =====
+
+std::string_view
+TransactionLogData::get_server_response_transfer_encoding() const
+{
+  if (likely(m_http_sm != nullptr)) {
+    return m_http_sm->t_state.hdr_info.server_response_transfer_encoding;
+  }
+  return {};
+}
+
+// ===== Fallback fields for pre-transaction logging =====
+
+std::string_view
+TransactionLogData::get_method() const
+{
+  if (likely(m_http_sm != nullptr)) {
+    return {};
+  }
+  return m_pre_data->owned_method;
+}
+
+std::string_view
+TransactionLogData::get_scheme() const
+{
+  if (likely(m_http_sm != nullptr)) {
+    return {};
+  }
+  return m_pre_data->owned_scheme;
+}
+
+std::string_view
+TransactionLogData::get_client_protocol_str() const
+{
+  if (likely(m_http_sm != nullptr)) {
+    return {};
+  }
+  return m_pre_data->owned_client_protocol_str;
+}
diff --git a/src/proxy/logging/unit-tests/test_LogAccess.cc 
b/src/proxy/logging/unit-tests/test_LogAccess.cc
index e567a2889c..aee2bd6728 100644
--- a/src/proxy/logging/unit-tests/test_LogAccess.cc
+++ b/src/proxy/logging/unit-tests/test_LogAccess.cc
@@ -25,6 +25,7 @@
 
 #include "proxy/PreTransactionLogData.h"
 #include "proxy/logging/LogAccess.h"
+#include "proxy/logging/TransactionLogData.h"
 #include "tscore/ink_inet.h"
 
 #include <string_view>
@@ -167,7 +168,8 @@ TEST_CASE("LogAccess pre-transaction CONNECT fields", 
"[LogAccess]")
 {
   PreTransactionLogData data;
   populate_pre_transaction_data(data, "CONNECT", ""sv, "example.com:443", 
""sv);
-  LogAccess access(data);
+  TransactionLogData log_data(data);
+  LogAccess          access(log_data);
 
   access.init();
 
@@ -187,7 +189,8 @@ TEST_CASE("LogAccess malformed CONNECT without authority 
falls back to path", "[
 {
   PreTransactionLogData data;
   populate_pre_transaction_data(data, "CONNECT", "https"sv, ""sv, "/"sv);
-  LogAccess access(data);
+  TransactionLogData log_data(data);
+  LogAccess          access(log_data);
 
   access.init();
 
@@ -202,7 +205,8 @@ TEST_CASE("LogAccess pre-transaction client host port is 
null-safe", "[LogAccess
 {
   PreTransactionLogData data;
   populate_pre_transaction_data(data, "GET", "https"sv, "example.com", 
"/client-port"sv);
-  LogAccess access(data);
+  TransactionLogData log_data(data);
+  LogAccess          access(log_data);
 
   access.init();
 

Reply via email to