Dobby  3.0
Dobby “Docker based Thingy” is a tool for managing and running OCI containers using crun
Classes | Public Member Functions | Static Public Attributes | Private Member Functions | Static Private Member Functions | Private Attributes | Static Private Attributes | List of all members
EthanLogClient Class Reference
Collaboration diagram for EthanLogClient:
Collaboration graph
[legend]

Classes

struct  TokenBucket
 

Public Member Functions

 EthanLogClient (sd_event *loop, ContainerId &&id, std::string &&name, int fd, unsigned allowedLevels, unsigned rate, unsigned burstSize, const std::string &memCgroupMountPoint)
 Constructs a logging client which represents one pipe. More...
 
 EthanLogClient (const EthanLogClient &other)=delete
 
 EthanLogClient (EthanLogClient &&other)=delete
 
EthanLogClientoperator= (const EthanLogClient &other)=delete
 
EthanLogClientoperator= (EthanLogClient &&other)=delete
 
void setContainerPid (pid_t pid)
 Sets the base pid for the client's container. More...
 
bool closed () const
 
ContainerId id () const
 

Static Public Attributes

static constexpr unsigned LOG_LEVEL_FATAL = (0x1 << 0)
 
static constexpr unsigned LOG_LEVEL_ERROR = (0x1 << 1)
 
static constexpr unsigned LOG_LEVEL_WARNING = (0x1 << 2)
 
static constexpr unsigned LOG_LEVEL_MILESTONE = (0x1 << 3)
 
static constexpr unsigned LOG_LEVEL_INFO = (0x1 << 4)
 
static constexpr unsigned LOG_LEVEL_DEBUG = (0x1 << 5)
 

Private Member Functions

int pipeFdHandler (uint32_t revents)
 Callback called when data to read on the logging pipe. More...
 
void processLogData ()
 Process some log data from a client pipe. More...
 
int processLogLevel (const char *field, ssize_t len, struct iovec *iov) const
 Process the log level field. More...
 
int processPid (const char *field, ssize_t len, struct iovec *iov) const
 Process the pid field. More...
 
int processTimestamp (const char *field, ssize_t len, struct iovec *iov) const
 Process the timestamp field. More...
 
int processThreadName (const char *field, ssize_t len, struct iovec *iov) const
 Process the thread name field. More...
 
int processCodeFile (const char *field, ssize_t len, struct iovec *iov) const
 Process the function name field. More...
 
int processCodeFunction (const char *field, ssize_t len, struct iovec *iov) const
 Process the function name field. More...
 
int processCodeLine (const char *field, ssize_t len, struct iovec *iov) const
 Process the line number field. More...
 
int processMessage (const char *field, ssize_t len, struct iovec *iov) const
 Process the message field. More...
 
bool shouldDrop ()
 Returns true if the message should be dropped due to rate limiting. More...
 
pid_t findRealPid (pid_t nsPid) const
 Attempts to find the pid number in the root pid namespace from a pid in the containers namespace. More...
 
std::set< pid_t > getAllContainerPids () const
 Reads the set of all pids within the client's container. More...
 
pid_t readNsPidFromProc (pid_t pid) const
 Given a pid (in global namespace) tries to find what it's namespace pid is. More...
 

Static Private Member Functions

static int pipeFdHandler (sd_event_source *source, int fd, uint32_t revents, void *userData)
 Callback called when data to read on the logging pipe. More...
 

Private Attributes

const ContainerId mContainerId
 
const std::string mName
 
const int mPipeFd
 
const unsigned mAllowedLevels
 
sd_event_source * mSource
 
std::string mIdentifier
 
char mMsgBuf [(MAX_LOG_MSG_LENGTH *2)]
 
size_t mMsgLen
 
bool mRateLimitingEnabled
 
struct EthanLogClient::TokenBucket mTokenBucket
 
unsigned int mDropped
 
std::chrono::steady_clock::time_point mFirstDropped
 
std::chrono::steady_clock::time_point mLastDropped
 
std::string mDefaultObjectPid
 
std::string mDefaultSyslogPid
 
std::string mCgroupPidsPath
 
std::map< pid_t, pid_t > mNsToRealPidMapping
 

Static Private Attributes

static constexpr ssize_t MAX_LOG_MSG_LENGTH = 512
 
static constexpr char RECORD_DELIM = '\x1e'
 
static constexpr char FIELD_DELIM = '\x1f'
 

Constructor & Destructor Documentation

◆ EthanLogClient()

EthanLogClient::EthanLogClient ( sd_event *  loop,
ContainerId &&  id,
std::string &&  name,
int  fd,
unsigned  allowedLevels,
unsigned  rate,
unsigned  burstSize,
const std::string &  memCgrpMountPoint 
)

Constructs a logging client which represents one pipe.

Parameters
[in]loopThe systemd event loop the plugin is running
[in]nameThe name of the container, used to tag all log messages
[in]fdThe pipe fd, this class takes ownership of the pipe and closes it in the destructor
[in]allowedLevelsBitmask of the allowed log levels.
[in]rateThe number of log messages allowed per second.
[in]burstThe maximum number of messages allowed in a burst.
[in]memCgrpMountPointUsed to lookup the pids within a container for mapping namespaced pids to real pids.

Member Function Documentation

◆ findRealPid()

pid_t EthanLogClient::findRealPid ( pid_t  nsPid) const
private

Attempts to find the pid number in the root pid namespace from a pid in the containers namespace.

It's tricky getting the real pid of a process in a namespace, see the below link for more information: https://blogs.oracle.com/linux/translating-process-id-between-namespaces

However we can make some assumptions about our containers which make it slightly easier; the first is that we always have a memory cgroup setup for them, and secondly there aren't typically going to be lots of processes in our containers. So what we do is read the /sys/fs/cgroup/memory/<id>/cgroup.procs file to get all the processes within the container, then we read each of their /proc/<pid>/status files to extract the NSpid fields and then match them up.

To speed up the process, everytime this method is called and we don't have an existing mapping, then we re-create the full mapping. This helps flush out dead processes from the cache and also speed up subsequent lookups. However this could result in a bit of load in Dobby, if the client constantly sent invalid pid numbers to us ... or more likely there are lots of transient processes that log just a single line, like a shell script or something ... not sure what the solution for that is.

Parameters
[in]nsPidThe pid in the namespace of the container.
Returns
The 'real' pid in the global namespace.

◆ getAllContainerPids()

std::set< pid_t > EthanLogClient::getAllContainerPids ( ) const
private

Reads the set of all pids within the client's container.

This reads the cgroup.pids file from the memory cgroup for the container.

Returns
Set of all the real pids within the container.

◆ pipeFdHandler() [1/2]

int EthanLogClient::pipeFdHandler ( sd_event_source *  source,
int  fd,
uint32_t  revents,
void *  userData 
)
staticprivate

Callback called when data to read on the logging pipe.

Checks if the pipe is closed, if not then reads a block from the pipe.

◆ pipeFdHandler() [2/2]

int EthanLogClient::pipeFdHandler ( uint32_t  revents)
private

Callback called when data to read on the logging pipe.

Checks if the pipe is closed, if not then reads a block from the pipe.

◆ processCodeFile()

int EthanLogClient::processCodeFile ( const char *  field,
ssize_t  len,
struct iovec *  iov 
) const
private

Process the function name field.

Parameters
[in]tokThe field minus the leading 'S' character
[out]funcUpon return will point to the function name string
Returns
The number of fields added to iov on success, -1 on failure

◆ processCodeFunction()

int EthanLogClient::processCodeFunction ( const char *  field,
ssize_t  len,
struct iovec *  iov 
) const
private

Process the function name field.

Parameters
[in]tokThe field minus the leading 'F' character
[out]funcUpon return will point to the function name string
Returns
The number of fields added to iov on success, -1 on failure

◆ processCodeLine()

int EthanLogClient::processCodeLine ( const char *  field,
ssize_t  len,
struct iovec *  iov 
) const
private

Process the line number field.

Parameters
[in]tokThe field minus the leading 'N' character
[out]linenoUpon return will container the line number
Returns
The number of fields added to iov on success, -1 on failure

◆ processLogData()

void EthanLogClient::processLogData ( )
private

Process some log data from a client pipe.

Parameters
appThe app/client that has generated the data
dataThe string (or part of a string) sent by the client
datalenThe number of characters received

The log string(s) sent by the client library are formatted using ASCII separators, the format is (temporarily) described on the following confluence page: https://www.stb.bskyb.com/confluence/display/~grayb/Unified+Logging+on+the+STB

But for those that can't be bothered opening a browser, the following are the cliff notes:

 \x1e  - Character used to start and terminate a log message
 \x1f  - Character used to delimit fields within the message string

 Each field within the message is prefixed with one of the following
 upper case characters that define the field type.

 L   - Log level
 P   - PID of app in hexadecimal (without 0x prefix)
 T   - Timestamp from monotonic clock in hexadecimal (without 0x prefix)
 R   - Name of the thread
 S   - Name of the source file containing the log message
 F   - Name of the function producing the log message
 N   - The line number of the log producer
 M   - The log message (mandatory but can be empty)

◆ processLogLevel()

int EthanLogClient::processLogLevel ( const char *  field,
ssize_t  len,
struct iovec *  iov 
) const
private

Process the log level field.

Parameters
[in]fieldThe field minus the leading 'L' character
[out]iovThe formatted io vector for journald
Returns
the number of fields added to iov on success, -1 on failure

◆ processMessage()

int EthanLogClient::processMessage ( const char *  field,
ssize_t  len,
struct iovec *  iov 
) const
private

Process the message field.

Parameters
[in]tokThe field minus the leading 'F' character
[out]messageUpon return will point to the message string
Returns
the number of fields added to iov on success, -1 on failure

◆ processPid()

int EthanLogClient::processPid ( const char *  field,
ssize_t  len,
struct iovec *  iov 
) const
private

Process the pid field.

Parameters
[in]fieldThe field minus the leading 'P' character
[in]lenThe length of the field.
Returns
The number of fields added to iov on success, -1 on failure

◆ processThreadName()

int EthanLogClient::processThreadName ( const char *  field,
ssize_t  len,
struct iovec *  iov 
) const
private

Process the thread name field.

Parameters
[in]tokThe field minus the leading 'R' character
[out]threadUpon return will point to the thread name
Returns
The number of fields added to iov on success, -1 on failure

◆ processTimestamp()

int EthanLogClient::processTimestamp ( const char *  field,
ssize_t  len,
struct iovec *  iov 
) const
private

Process the timestamp field.

Parameters
[in]tokThe field minus the leading 'T' character
[out]tsThe calculated timestamp
Returns
the number of fields added to iov on success, -1 on failure

◆ readNsPidFromProc()

pid_t EthanLogClient::readNsPidFromProc ( pid_t  pid) const
private

Given a pid (in global namespace) tries to find what it's namespace pid is.

This reads the /proc/<pid>/status file, line NStgid.

Parameters
[in]pidThe real pid of the process to lookup.
Returns
Set of all the real pids within the container.

◆ setContainerPid()

void EthanLogClient::setContainerPid ( pid_t  pid)

Sets the base pid for the client's container.

This is used as the default pid to put in the log message if the client hasn't supplied a pid, or we couldn't resolve their pid in the global pid namespace.

Parameters
pidThe base pid of the container.

◆ shouldDrop()

bool EthanLogClient::shouldDrop ( )
private

Returns true if the message should be dropped due to rate limiting.

The rate limits are set in the constructor and the algorithm used is a token bucket type setup.


The documentation for this class was generated from the following files: