Misc

Logging

PVXS internally logs warning/status messages using errlog.h from EPICS Base. User applications may control which messages are printed, and may add output.

#include <pvxs/log.h>
namespace pvxs { ... }

Control of log message output is available through named loggers. All internal logger names begin with the prefix “pvxs.”.

In addition to a name, each logger has an associated integer logging level. A message will be logged if the level of the message is less than or equal to the level of the associated logger.

To enable all logging at full detail.

export PVXS_LOG="*=DEBUG"
enum class pvxs::Level

Importance of message.

Values:

enumerator Debug
enumerator Info
enumerator Warn
enumerator Err
enumerator Crit

Controlling Logging

By default, all loggers have level Err. It is recommended that user applications prefer configuration through the environment variable $PVXS_LOG by calling pvxs::logger_config_env().

void pvxs::logger_config_env()

Configure logging from environment variable $PVXS_LOG

Value of the form “key=VAL,…”

Keys may be literal logger names, or may include ‘*’ wildcards to match multiple loggers. eg. “pvxs.*=DEBUG” will enable all internal log messages.

VAL may be one of “CRIT”, “ERR”, “WARN”, “INFO”, or “DEBUG”

If this is undesirable, logger levels may be (reset) manually.

inline void pvxs::logger_level_set(const char *name, Level lvl)
void pvxs::logger_level_clear()

Remove any previously logger configurations. Does not change any logger::lvl Use prior to re-applying new configuration.

Logging from User applications

To emit log messages from user code, a new logger should be defined with DEFINE_LOGGER which will be usable within the current translation unit. It is allowable for multiple loggers to have the same name.

Logger names beginning with “pvxs.*” is reserved for internal usage, and must not be present in user code.

DEFINE_LOGGER(VAR, NAME)

Define a new logger global.

Parameters:
  • VAR – The (static) variable name passed to log_printf() and friends.

  • NAME – A name string in “A.B.C” form.

log_crit_printf(LOGGER, FMT, ...)
log_err_printf(LOGGER, FMT, ...)
log_warn_printf(LOGGER, FMT, ...)
log_info_printf(LOGGER, FMT, ...)
log_debug_printf(LOGGER, FMT, ...)
log_printf(LOGGER, LVL, FMT, ...)

Try to log a message at the defined level.

Due to portability issues with MSVC, log formats must have at least one argument.

DEFINE_LOGGER(blah, "myapp.blah");
void blahfn(int x) {
    log_info_printf(blah, "blah happened with %d\n", x);

struct logger

A logger.

Public Functions

inline bool test(Level lvl)
Returns:

true if the logger currently allows a message at level LVL.

Public Members

const char *const name

global name of this logger. Need not be unique

std::atomic<Level> lvl

Current logging level. See logger_level_set().

Identification

Compile time access to PVXS library version information.

#include <pvxs/util.h>
namespace pvxs { ... }
PVXS_VERSION

Current library version.

PVXS_ABI_VERSION

Current library ABI version

Since

0.1.1

VERSION_INT(V, R, M, P)

Construct version number constant.

eg. to conditionally compile based on library version.

#if PVXS_VERSION < VERSION_INT(1,2,3,4)
// enable some compatibility code
#endif
unsigned long pvxs::version_int()
Returns:

PVXS_VERSION captured at library compile time

const char *pvxs::version_str()

Library version as a string. eg. “PVXS 1.2.3”.

unsigned long pvxs::version_abi_int()

Since

0.1.1

Returns:

PVXS_ABI_VERSION captured at library compile time

static inline bool pvxs::version_abi_check()

Runtime ABI check.

This test is only meaningful if it is performed prior to any other library calls.

It is guaranteed that the library has no global constructors.

Since

0.1.1

Returns:

true if the header and library ABI versions match, and if the header version is not newer than the library version.

Unit-test helpers

Extensions to epicsUnitTest.h

#include <pvxs/unittest.h>
namespace pvxs { ... }
testTrue(EXPR)

Macro which assert that an expression evaluate to ‘true’. Evaluates to a pvxs::testCase

testFalse(EXPR)

Macro which assert that an expression evaluate to ‘true’. Evaluates to a pvxs::testCase

testEq(LHS, RHS)

Macro which asserts equality between LHS and RHS. Evaluates to a pvxs::testCase Roughly equivalent to

testOk((LHS)==(RHS), "..."); 

testNotEq(LHS, RHS)

Macro which asserts in-equality between LHS and RHS. Evaluates to a pvxs::testCase Roughly equivalent to

testOk((LHS)!=(RHS), "..."); 

testStrEq(LHS, RHS)

Macro which asserts equality between LHS and RHS. Evaluates to a pvxs::testCase Functionally equivalent to testEq() with two std::string instances. Prints diff-like output which is friendlier to multi-line strings.

testStrMatch(EXPR, STR)

Macro which asserts that STR matches the regular expression EXPR Evaluates to a pvxs::testCase

Since

0.2.1 Expression syntax is POSIX extended.

Since

0.1.1

testArrEq(LHS, RHS)

Macro which asserts equality between LHS and RHS. Evaluates to a pvxs::testCase Functionally equivalent to testEq() for objects with .size() and operator[]. Prints element by element differences

testShow()

Macro which prints diagnostic (non-test) lines. Evaluates to a pvxs::testCase Roughly equivalent to

testDiag("..."); 

The testEq() macro and friends expand to a function which returns a pvxs::testCase instance which may be used as a std::ostream to append text describing a test. eg.

testEq(1, 1)<<"We really hope this is true.";
if(testNotEq(1, 2)<<"shouldn't be true") {
    // further conditional tests if 1!=2
}
template<class Exception, typename FN>
testCase pvxs::testThrows(FN fn)

Assert that an exception is thrown.

testThrows<std::runtime_error>([]() {
     testShow()<<"Now you see me";
     throw std::runtime_error("I happened");
     testShow()<<"Now you don't";
})<<"some message";

Template Parameters:

Exception – The exception type which should be thrown

Parameters:

fn – A callable

Returns:

A testCase which passes if an Exception instance was caught, and false otherwise (wrong type, or no exception).

template<class Exception, typename FN>
testCase pvxs::testThrowsMatch(const std::string &expr, FN fn)

Assert that an exception is throw with a certain message.

testThrowsMatch<std::runtime_error>("happened", []() {
     testShow()<<"Now you see me";
     throw std::runtime_error("I happened");
     testShow()<<"Now you don't";
})<<"some message";

Since

0.1.1

Template Parameters:

Exception – The exception type which should be thrown

Parameters:
  • expr – A regular expression

  • fn – A callable

Returns:

A testCase which passes if an Exception instance was caught and std::exception::what() matched the provided regular expression.

class testCase

A single test case (or diagnostic line).

Acts as an output string to accumulate test comment. Multi-line output results in one test line, and subsequent diagnostic lines.

Test line is printed when an active (non-moved) testCase is destroyed.

Public Functions

testCase()

new diag message

explicit testCase(bool result)

new test case

inline explicit operator bool() const

true when passing

inline testCase &setPass(bool v)

Override current pass/fail result.

testCase &setPassMatch(const std::string &expr, const std::string &inp)

Override current pass/fail result if input matches a regular expression

Since

0.2.1 Expression syntax is POSIX extended.

Since

0.1.1 Added

template<typename T>
inline testCase &operator<<(const T &v)

Append to message.

inline std::ostream &stream()

Access to underlying std::ostream used to accumulate notes. When our operator<< isn’t enough.

Since

1.1.4

IOC Testing

Additional helpers for testing IOC applications.

#include <pvxs/iochooks.h>
namespace pvxs { namespace ioc { ... } }

When possible, use of the TestIOC class is recommended for both forward and backward compatibility with EPICS Base (>= 3.15.0.1).

class TestIOC

Manage Test IOC life-cycle calls.

Makes necessary calls to dbUnitTest.h API as well as any added calls needed by PVXS components.

MAIN(mytest) {
    testPlan(0); // TODO: Set actual number of tests
    pvxs::testSetup();
    pvxs::logger_config_env(); // (optional)
    {
        TestIOC ioc; // testdbPrepare()

        // mytestioc.dbd must include pvxsIoc.dbd
        testdbReadDatabase("mytestioc.dbd", NULL, NULL);
        mytestioc_registerRecordDeviceDriver(pdbbase);
        testdbReadDatabase("sometest.db", NULL, NULL);

        // tests before iocInit()

        ioc.init();

        // tests after iocInit()

        ioc.shutdown(); // (optional) in ~TestIOC if omitted
    }
    {
        ... repeat ...
    }
    epicsExitCallAtExits();
    pvxs::cleanup_for_valgrind();
}

Since

1.3.0

Utilities

Misc. utility code.

#include <pvxs/util.h>
namespace pvxs { ... }
inline detail::Escaper pvxs::escape(const std::string &s)

Print string to output stream with non-printable characters escaped.

Outputs (almost) C-style escapes. Prefers short escapes for newline, tab, quote, etc (”\n”). Falls back to hex escape (eg. “\xab”).

Unlike C, hex escapes are always 2 chars. eg. the output “\xabcase” would need to be manually changed to “\xab””case” to be used as C source.

std::string blah("this \"is a test\"");
std::cout<<pvxs::escape(blah);

inline detail::Escaper pvxs::escape(const char *s)

Print nil terminated char array to output stream with non-printable characters escaped.

std::cout<<pvxs::escape("this \"is a test\"");

inline detail::Escaper pvxs::escape(const char *s, size_t n)

Print fixed length char array to output stream with non-printable characters escaped.

std::cout<<pvxs::escape("this \"is a test\"", 6);
// prints 'this \"'

void pvxs::cleanup_for_valgrind()

Free some internal global allocations to avoid false positives in valgrind (or similar) tools looking for memory leaks.

Calls libevent_global_shutdown() when available (libevent >=2.1).

Warning

This function is optional. If you don’t understand the intended use case, then do not call it!

Pre:

Caller must release all resources explicitly allocated through PVXS (on all threads).

Post:

Invalidates internal state. Use of any API functions afterwards is undefined!

class SigInt

Portable process signal handling in CLI tools.

epicsEvent evt;
SigInt handle([&evt]() {
     evt.trigger();
});
... setup network operations
evt.wait();
// completion, or SIGINT

Saves existing handler, which are restored by dtor.

Since

1.1.0 “handler” action runs in thread context. Safe to take locks etc. Previously handler action ran in signal handler context.

std::ostream &pvxs::target_information(std::ostream&)

Describe build and runtime configuration of current system.

Print information which may be using for when troubleshooting, or creating a bug report.

Printed by CLI “pvxinfo -D” and iocsh “pvxs_target_information”.

Returns:

The same ostream passed as argument.

template<typename T>
class MPMCFIFO

Thread-safe, bounded, multi-producer, multi-consumer FIFO queue.

Queue value_type must be movable. If T is also copy constructable, then push(const T&) may be used.

As an exception, the destructor is not re-entrant. Concurrent calls to methods during destruction will result in undefined behavior.

MPMCFIFO<std::function<void()>> Q;
...
while(auto work = Q.pop()) { // Q.push(nullptr) to break loop
    work();
}

Since

0.2.0

Public Types

typedef T value_type

Template parameter.

Public Functions

inline explicit MPMCFIFO(size_t limit = 0u)

Construct a new queue

Parameters:

limit – If non-zero, then emplace()/push() will block while while queue size is greater than or equal to this limit.

inline ~MPMCFIFO()

Destructor is not re-entrant.

inline size_t size() const

Poll number of elements in the work queue at this moment.

template<typename ...Args>
inline void emplace(Args&&... args)

Construct a new element into the queue.

Will block while full.

inline void push(T &&ent)

Move a new element to the queue.

inline void push(const T &ent)

Copy a new element to the queue.

inline T pop()

Remove an element from the queue.

Blocks while queue is empty.