blob: f7e223a0802ef79fb8975114581ab08fc7a367ff [file]
/** @file
A brief file description
@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 "tscore/ink_platform.h"
#include "tscore/ink_atomic.h"
#include "tscore/Diags.h"
#include "proxy/logging/LogFormat.h"
#include "proxy/logging/LogLimits.h"
#include "proxy/logging/LogAccess.h"
#include <cstddef>
class LogObject;
class LogConfig;
class LogBufferIterator;
#define LOG_SEGMENT_COOKIE 0xaceface
#define LOG_SEGMENT_VERSION 3 ///< Current default version.
#define LOG_SEGMENT_VERSION_MIN_SUPPORTED 2 ///< Oldest version this build can still read.
#define LOG_SEGMENT_VERSION_FIELDTYPES 3 ///< First version that carries the field-type schema (self-describing).
#if defined(__linux__)
#define LB_DEFAULT_ALIGN 512
#else
#define LB_DEFAULT_ALIGN 8
#endif
/*-------------------------------------------------------------------------
LogEntryHeader
This struct is automatically laid down at the head of each entry in the
buffer.
-------------------------------------------------------------------------*/
struct LogEntryHeader {
int64_t timestamp; // the seconds portion of the timestamp
int32_t timestamp_usec; // the microseconds portion of the timestamp
uint32_t entry_len;
};
/*-------------------------------------------------------------------------
LogFieldTypeSchema
Self-describing field-type table for v3 segments, written once per segment at
LogBufferHeader::fmt_fieldtypes_offset (alongside fmt_fieldlist). It lets a
generic reader decode every field from the file alone, dispatching on the
LogField::Type codes with no embedded ATS symbol->type table.
On-wire layout:
uint16_t field_count; // == number of symbols in fmt_fieldlist
uint8_t type_code[field_count]; // LogField::Type, in fieldlist order
No independent schema version: the segment's LOG_SEGMENT_VERSION governs this
layout. All integers are host byte order, like the rest of LogBufferHeader;
the blob is padded with the header to 8-byte alignment.
-------------------------------------------------------------------------*/
struct LogFieldTypeSchema {
uint16_t field_count;
// Immediately followed by uint8_t type_code[field_count].
const uint8_t *
type_codes() const
{
return reinterpret_cast<const uint8_t *>(this) + sizeof(LogFieldTypeSchema);
}
};
/*-------------------------------------------------------------------------
LogBufferHeader
This struct is automatically laid down at the head of each buffer.
-------------------------------------------------------------------------*/
struct LogBufferHeader {
uint32_t cookie; // so we can find it on disk
uint32_t version; // in case we want to change it later
uint32_t format_type; // SQUID_LOG, COMMON_LOG, ...
uint32_t byte_count; // actual # of bytes for the segment
uint32_t entry_count; // actual number of entries stored
uint32_t low_timestamp; // lowest timestamp value of entries
uint32_t high_timestamp; // highest timestamp value of entries
uint32_t log_object_flags; // log object flags
uint64_t log_object_signature; // log object signature
#if defined(LOG_BUFFER_TRACKING)
uint32_t id;
#endif // defined(LOG_BUFFER_TRACKING)
// all offsets are computed from the start of the buffer (ie, "this"),
// and so any valid offset will be at least sizeof(LogBufferHeader).
uint32_t fmt_name_offset; // offset to format name string
uint32_t fmt_fieldlist_offset; // offset to format fieldlist string
uint32_t fmt_printf_offset; // offset to format printf string
uint32_t src_hostname_offset; // offset to source (client) hostname
uint32_t log_filename_offset; // offset to log filename
uint32_t data_offset; // offset to start of data entry
// section
// NEW in v3: offset to the LogFieldTypeSchema blob, or 0 if absent. Appended
// after data_offset so the layout through data_offset is byte-identical to
// v2; v2 readers ignore it and v3 readers tolerate v2 segments lacking it.
uint32_t fmt_fieldtypes_offset;
// some helper functions to return the header strings
char *fmt_fieldlist();
char *fmt_printf();
char *fmt_fieldtypes(); // v3 field-type schema blob; nullptr for v2 segments
char *src_hostname();
char *log_filename();
};
/** Whether this build can read a segment of the given @a version.
The single source of truth for the supported range: readers accept the
inclusive range [LOG_SEGMENT_VERSION_MIN_SUPPORTED, LOG_SEGMENT_VERSION] so
a new build keeps decoding logs written by an older one.
*/
inline bool
log_segment_version_supported(unsigned version)
{
return LOG_SEGMENT_VERSION_MIN_SUPPORTED <= version && version <= LOG_SEGMENT_VERSION;
}
/** On-disk size of LogBufferHeader for a given segment version.
Raw readers that read the header by size (rather than via the data_offset
field) must size the read to the version on disk: v3 appended
fmt_fieldtypes_offset after data_offset, so a v2 segment's header is
shorter. Reading a v2 segment with the (larger) v3 struct size would
consume bytes belonging to the data section.
@return the header size in bytes, or 0 if @a version is unsupported.
*/
inline size_t
log_buffer_header_size(unsigned version)
{
if (!log_segment_version_supported(version)) {
return 0;
}
// v2 stops at data_offset; v3 and later include fmt_fieldtypes_offset.
return version >= LOG_SEGMENT_VERSION_FIELDTYPES ? sizeof(LogBufferHeader) : offsetof(LogBufferHeader, fmt_fieldtypes_offset);
}
union LB_State {
LB_State() : ival(0) {}
LB_State(LB_State &vs) { ival = vs.ival; }
LB_State &
operator=(LB_State &vs)
{
ival = vs.ival;
return *this;
}
int64_t ival; // ival is used to help do an atomic CAS for struct s
struct {
uint32_t offset; // buffer offset(bytes in buffer)
uint16_t num_entries; // number of entries in buffer
uint16_t full : 1; // not accepting more checkouts
uint16_t num_writers : 15; // number of writers
} s;
};
/*-------------------------------------------------------------------------
LogBuffer
-------------------------------------------------------------------------*/
class LogBuffer
{
public:
SLINK(LogBuffer, write_link);
enum LB_ResultCode {
LB_OK = 0,
LB_FULL_NO_WRITERS,
LB_FULL_ACTIVE_WRITERS,
LB_RETRY,
LB_ALL_WRITERS_DONE,
LB_BUSY,
LB_BUFFER_TOO_SMALL
};
LogBuffer(const LogConfig *cfg, LogObject *owner, size_t size, size_t buf_align = LB_DEFAULT_ALIGN,
size_t write_align = INK_MIN_ALIGN);
LogBuffer(LogObject *owner, LogBufferHeader *header);
~LogBuffer();
char &
operator[](int idx)
{
ink_assert(idx >= 0);
ink_assert((size_t)idx < m_size);
return m_buffer[idx];
}
int
switch_state(LB_State &old_state, LB_State &new_state)
{
INK_WRITE_MEMORY_BARRIER;
return (ink_atomic_cas(&m_state.ival, old_state.ival, new_state.ival));
}
// 'fast' mode only, not thread-safe
LB_ResultCode fast_write(size_t *write_offset, size_t write_size);
LB_ResultCode checkout_write(size_t *write_offset, size_t write_size);
LB_ResultCode checkin_write(size_t write_offset);
void force_full();
LogBufferHeader *
header() const
{
return m_header;
}
long
expiration_time() const
{
return m_expiration_time;
}
// this should only be called when buffer is ready to be flushed
void update_header_data();
uint32_t
get_id() const
{
return m_id;
}
LogObject *
get_owner() const
{
return m_owner;
}
LINK(LogBuffer, link);
// static variables
static int32_t M_ID;
// static functions
static size_t max_entry_bytes();
static int to_ascii(LogEntryHeader *entry, LogFormatType type, char *buf, int max_len, const char *symbol_str, char *printf_str,
unsigned buffer_version, const char *alt_format = nullptr, LogEscapeType escape_type = LOG_ESCAPE_NONE);
static int resolve_custom_entry(LogFieldList *fieldlist, char *printf_str, char *read_from, char *write_to, int write_to_len,
long timestamp, long timestamp_us, unsigned buffer_version, LogFieldList *alt_fieldlist = nullptr,
char *alt_printf_str = nullptr, LogEscapeType escape_type = LOG_ESCAPE_NONE);
static void
destroy(LogBuffer *&lb)
{
// ink_atomic_increment() returns the previous value, so when it was 1, we are
// the thread that decremented to zero and should delete ...
int refcnt = ink_atomic_increment(&lb->m_references, -1);
if (refcnt == 1) {
delete lb;
lb = nullptr;
}
ink_release_assert(refcnt >= 0);
}
private:
char *m_unaligned_buffer; // the unaligned buffer
char *m_buffer; // the buffer
size_t m_size; // the buffer size
size_t m_buf_align; // the buffer alignment
size_t m_write_align; // the write alignment mask
int m_buffer_fast_allocator_size; // indicates whether the logbuffer is allocated from ioBuf
long m_expiration_time; // buffer expiration time
LogObject *m_owner; // the LogObject that owns this buf.
LogBufferHeader *m_header;
uint32_t m_id; // unique buffer id (for debugging)
public:
LB_State m_state; // buffer state
int m_references; // outstanding checkout_write references.
// noncopyable
// -- member functions that are not allowed --
LogBuffer(const LogBuffer &rhs) = delete;
LogBuffer &operator=(const LogBuffer &rhs) = delete;
private:
// private functions
size_t _add_buffer_header(const LogConfig *cfg);
unsigned add_header_str(const char *str, char *buf_ptr, unsigned buf_len);
unsigned add_field_type_schema(const LogFieldList *fieldlist, char *buf_ptr, unsigned buf_len);
void freeLogBuffer();
// -- member functions that are not allowed --
LogBuffer();
friend class LogBufferIterator;
};
class LogFile;
/*-------------------------------------------------------------------------
LogBufferList
Support atomic operations on a list of LogBuffer objects.
-------------------------------------------------------------------------*/
class LogBufferList
{
private:
Queue<LogBuffer> m_buffer_list;
ink_mutex m_mutex;
int m_size;
public:
LogBufferList();
~LogBufferList();
void add(LogBuffer *lb);
LogBuffer *get();
int
get_size()
{
return m_size;
}
};
/*-------------------------------------------------------------------------
LogBufferIterator
This class will iterate over the entries in a LogBuffer.
-------------------------------------------------------------------------*/
class LogBufferIterator
{
public:
LogBufferIterator(LogBufferHeader *header);
~LogBufferIterator();
LogEntryHeader *next();
// noncopyable
// -- member functions not allowed --
LogBufferIterator(const LogBufferIterator &) = delete;
LogBufferIterator &operator=(const LogBufferIterator &) = delete;
private:
char *m_next;
char *m_buffer_end; // one past the last readable byte of the segment
unsigned m_iter_entry_count;
unsigned m_buffer_entry_count;
// -- member functions not allowed --
LogBufferIterator();
};
/*-------------------------------------------------------------------------
LogBufferIterator
This class provides the ability to iterate over the LogEntries stored
within a given LogBuffer.
-------------------------------------------------------------------------*/
inline LogBufferIterator::LogBufferIterator(LogBufferHeader *header)
: m_next(nullptr), m_buffer_end(nullptr), m_iter_entry_count(0), m_buffer_entry_count(0)
{
ink_assert(header);
// Entry iteration is identical across v2/v3 (v3 only appended a header field
// after data_offset). Accept the whole supported range.
if (log_segment_version_supported(header->version)) {
// Bound the data section against byte_count so a corrupt/hostile data_offset
// (a .blog read by logcat/logstats may be untrusted) can't make next()
// dereference outside the buffer. data_offset sits past the header, and must
// be 8-byte aligned so the int64 reads in each entry are well-aligned.
size_t header_size = log_buffer_header_size(header->version);
if (header->data_offset >= header_size && header->data_offset <= header->byte_count &&
header->data_offset % INK_MIN_ALIGN == 0) {
m_next = reinterpret_cast<char *>(header) + header->data_offset;
m_buffer_end = reinterpret_cast<char *>(header) + header->byte_count;
m_buffer_entry_count = header->entry_count;
}
// else: bad data_offset -- leave m_next null (next() yields nothing). The
// file readers report it; the iterator stays silent to avoid log spam.
} else {
Note("Invalid LogBuffer version %d in LogBufferIterator; "
"supported versions are %d-%d",
header->version, LOG_SEGMENT_VERSION_MIN_SUPPORTED, LOG_SEGMENT_VERSION);
}
}
/*-------------------------------------------------------------------------
-------------------------------------------------------------------------*/
inline LogBufferIterator::~LogBufferIterator() {}