Commit 7beb29a3 authored by Martin Marinov's avatar Martin Marinov Committed by GitHub Enterprise

REFORM-838 Add and generalize Journal code refactored from ReForm (#2)

* Add the Journal sources
* Remove ReForm specific code in Journal and allow various configuration options
* Fix a macro that was incorrect if JOURNAL_ON was undefined
* Journal can work either with Boost or the std::experimental filesystem implementations
* OStringStream using std::ostringstream now and sets the locale to "C"
* Debug::FIle now uses OStringStream
* Add IOutputStream:Base::print(float)
* Fix initialization bugs
* Allow comment streaming, allow existing journal entries to be output as comments, improve code consistency
* Document the Journal functions
* Add Journal::output_path()
parent 99f338eb
......@@ -32,10 +32,11 @@ endfunction(base_add_subdir)
base_add_subdir(Code)
base_add_subdir(Config)
base_add_subdir(Debug)
base_add_subdir(Utils)
base_add_subdir(Journal)
base_add_subdir(Progress)
base_add_subdir(Security)
base_add_subdir(Test)
base_add_subdir(Utils)
target_include_directories(${output_lib} PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/..")
......
......@@ -5,6 +5,7 @@
#include "DebCallStack.hh"
#include "Base/Utils/Environment.hh"
#include "Base/Utils/OStringStream.hh"
#include <string>
#include <fstream>
......@@ -42,8 +43,8 @@ void File::line_break(const bool _cnsl) { print('\n', _cnsl); }
void File::print(const std::string& _s)
{
for (size_t i = 0, n = _s.size(); i < n; ++i)
print(_s[i]);
for (const auto c : _s)
print(c);
}
void File::print(const char* const _s)
......@@ -57,28 +58,30 @@ void File::print(const char* const _s)
void File::print(const size_t _i)
{
char buffer[128];
#if defined(_MSC_VER) && _MSC_VER < 1900 // MSVC versions older than VC2015
sprintf_s(buffer, sizeof(buffer), "%Iu", _i);
#else // MSVC 2015 and everything else
sprintf_s(buffer, sizeof(buffer), "%zu", _i);
#endif
print(buffer);
Base::OStringStream strm;
strm.print(_i);
print(strm.str);
}
void File::print(const int _i)
{
char buffer[64];
sprintf_s(buffer, sizeof(buffer), "%i", _i);
print(buffer);
Base::OStringStream strm;
strm.print(_i);
print(strm.str);
}
void File::print(float _f)
{
Base::OStringStream strm;
strm.print(_f);
print(strm.str);
}
void File::print(double _d)
{
char buffer[64];
sprintf_s(buffer, sizeof(buffer), double_format, _d);
print(buffer);
Base::OStringStream strm;
strm.print(_d);
print(strm.str);
}
void File::print(const Base::Command& _co)
......
......@@ -36,6 +36,7 @@ public:
void print(const std::string& _s);
void print(const size_t _i);
void print(const int _i);
void print(float _f);
void print(double _d);
void print(const Base::Command& _co);
......
......@@ -62,12 +62,13 @@ class BASEDLLEXPORT Stream : public Base::IOutputStream
public:
Stream(File& _file) : file_(_file) {}
Base::IOutputStream& print(const int);
Base::IOutputStream& print(const size_t);
Base::IOutputStream& print(const double);
Base::IOutputStream& print(const char* const);
Base::IOutputStream& print(const char);
Base::IOutputStream& print(const Base::Command&);
Base::IOutputStream& print(const int) override;
Base::IOutputStream& print(const size_t) override;
Base::IOutputStream& print(const float) override;
Base::IOutputStream& print(const double) override;
Base::IOutputStream& print(const char* const) override;
Base::IOutputStream& print(const char) override;
Base::IOutputStream& print(const Base::Command&) override;
private:
File& file_;
......
......@@ -74,6 +74,12 @@ Base::IOutputStream& Stream::print(const size_t _i)
return *this;
};
Base::IOutputStream& Stream::print(const float _f)
{
file_.print(_f);
return *this;
};
Base::IOutputStream& Stream::print(const double _d)
{
file_.print(_d);
......
set(my_headers
${CMAKE_CURRENT_SOURCE_DIR}/JournalStream.hh
PARENT_SCOPE
)
set(my_sources
${CMAKE_CURRENT_SOURCE_DIR}/JournalStream.cc
PARENT_SCOPE
)
This diff is collapsed.
// Copyright 2019 Autodesk, Inc. All rights reserved.
//
// This computer source code and related instructions and comments are the
// unpublished confidential and proprietary information of Autodesk, Inc.
// and are protected under applicable copyright and trade secret law. They
// may not be disclosed to, copied or used by any third party without the
// prior written consent of Autodesk, Inc.
#ifndef BASE_JOURNAL_STREAM_HH_INCLUDED
#define BASE_JOURNAL_STREAM_HH_INCLUDED
#ifdef JOURNAL_ON
#include <Base/Utils/IOutputStream.hh>
#ifdef JOURNAL_USE_BOOST
#include <boost/core/typeinfo.hpp>
#endif // JOURNAL_USE_BOOST
#include <string>
#include <memory>
#include <vector>
namespace Journal {
typedef std::string String;
typedef size_t Key;
const Key INVALID_KEY = 0;
template <class T> Key key(const T* const _pntr) { return (Key)_pntr; }
// Wrapper for data as text from any type, returned from the Journal::define()
struct Data
{
Data(const String& _str, const Key _key = INVALID_KEY)
: str(_str), key(_key)
{}
String str; // reference string
const Key key; // pointer to the variable data, if not a plain type
};
typedef std::vector<Data> DataVector;
const Data NULL_PTR("nullptr");
// If Boost is available for use in Journal, we provide a default type_name()
// implementation based on RTTI and boost demangling. Otherwise type_info()
// has to be specialized for any type that is used as an output argument.
#ifdef JOURNAL_USE_BOOST
//! Correct some known issues with automatic type_name()
void filter_type_name(String& _type_name);
// Automatic type-name extraction using boost
template <class T> String type_name()
{
auto str = boost::core::demangled_name(BOOST_CORE_TYPEID(T));
#ifdef _MSC_VER
// on MSVC, pointer types contain __ptr64 which is MSVC only, so filter it out
filter_type_name(str);
#endif // _MSC_VER
return str;
}
#else // JOURNAL_USE_BOOST
//! Override this for any type that is used as output data for C/C++ journals
template <class T> String type_name();
#endif // JOURNAL_USE_BOOST
//! the journal stream class, used when streaming from define() specializations
class Stream
{
public:
// do not call constructor/destructor directly, use set_on(true/false) instead
Stream(const char* const _cmpn_name, const char* const _base_path);
~Stream();
// disable copy & assignment
Stream(const Stream&) = delete;
Stream& operator=(const Stream&) = delete;
//! Get the journal output path
String output_path() const;
//! Get the next available filename based on _name to use by the journal
String next_filename(const char* const _name);
//! Put the "end line" combination for the journal, e.g., ";\n" for C/C++
void end_line();
//! Make an unique journal name which can be linked to a pointer/key later
String make_unique_name(const char* const _name);
//! Book an unique journal name which can be linked to a pointer/key later
const char* book_unique_name(const char* const _name);
//! Link to a pointer/key to a pre-booked unique journal name
void link_unique_name(const Key _key, const char* const _name);
//! Find the unique name corresponding to the key
const char* find_unique_name(const Key _key);
//! Journal a constructor
template <class ObjectT, typename... ArgT>
void constructor(const char* const _fnct, const ObjectT* _obj,
const ArgT&... _args)
{
emit_constructor(_fnct, object(_obj), arguments(_args...));
}
//! Journal a destructor
template <class ObjectT>
void destructor(const char* const _fnct, const ObjectT* _obj)
{
emit_destructor(_fnct, object(_obj));
}
//! Journal a method returning outcome
template <class ObjectT, typename... ArgT>
void method_outcome(const char* const _fnct, const ObjectT* _obj,
const ArgT&... _args)
{
emit_method_outcome(_fnct, object(_obj), arguments(_args...));
}
//! Journal a void method
template <class ObjectT, typename... ArgT>
void method_void(const char* const _fnct, const ObjectT* _obj,
const ArgT&... _args)
{
emit_method_void(_fnct, object(_obj), arguments(_args...));
}
//! Journal a method returning any type
template <typename ReturnT, class ObjectT, typename... ArgT>
void method(const char* const _fnct, const ReturnT& _rtrn,
const ObjectT* _obj, const ArgT&... _args)
{
emit_method(_fnct, ::Journal::define(*this, _rtrn), object(_obj),
arguments(_args...));
}
//! Journal a function returning outcome
template <typename... ArgT>
void function_outcome(const char* const _fnct, const ArgT&... _args)
{
emit_function_outcome(_fnct, arguments(_args...));
}
//! Journal a void function
template <typename... ArgT>
void function_void(const char* const _fnct, const ArgT&... _args)
{
emit_function_void(_fnct, arguments(_args...));
}
//! Journal a function returning any type
template <typename ReturnT, typename... ArgT>
void function(
const char* const _fnct, const ReturnT& _rtrn, const ArgT&... _args)
{
emit_function(_fnct, ::Journal::define(*this, _rtrn), arguments(_args...));
}
//! Retrieve the key for any data
template <class ObjectT>
Key object(const ObjectT* _obj) const
{// don't need a type name here as we can extract this from the key
return key(_obj);
}
//! Journal a comment start, and return os() to complete it
Base::IOutputStream& comment_os();
//! Journal an include file
void include(const char* const _flnm);
//! Journal a blank line
void blank_line();
/*!
Journal a path transform function which allows all filenames to be
transformed before passed as arguments to journaled functions.
This allows journal data stored as files to be loaded from other locations.
In C/C++ journals the transform is #define PATH(FLNM) _path_trns.
If no path transform is journaled, the transform is identity.
*/
void set_path_transform(const char* const _trns);
/*!
Transforms the path with a previously set path transform, or leaves it as
it is if no transform is set.
*/
void transform_path(String& _path) const;
/*
Journal the start of the main function. The signature can be customized or if
left blank is set to int main().
*/
void main(const char* const _main_fnct = nullptr);
//! Journal a custom code line.
void code_line(const char* const _line);
//! Direct access to the underlying stream, use with caution
Base::IOutputStream& os();
private:
class Impl;
Impl* impl_;
private:
template <typename... ArgT>
DataVector arguments(const ArgT&... _args)
{
return { ::Journal::define(*this, _args)... };
}
void emit_constructor(
const char* const _fnct, const Key& _obj, const DataVector& _args);
void emit_destructor(const char* const _fnct, const Key& _obj);
void emit_method_outcome(
const char* const _fnct, const Key& _obj, const DataVector& _args);
void emit_method_void(
const char* const _fnct, const Key& _obj, const DataVector& _args);
void emit_method(const char* const _fnct, const Data& _rtrn, const Key& _obj,
const DataVector& _args);
void emit_function_outcome(const char* const _fnct, const DataVector& _args);
void emit_function_void(const char* const _fnct, const DataVector& _args);
void emit_function(
const char* const _fnct, const Data& _rtrn, const DataVector& _args);
};
typedef std::unique_ptr<Stream> StreamPtr;
// the global stream pointer is exposed for efficiency only, do *not* use it
extern StreamPtr strm_ptr;
/*!
Turn on/off the journal, set the base path optionally. If no base path is
provided, the base path will be composed from the component name as follows:
* from the environment variable toupper(cmpn_name)_JOURNAL_BASE if defined,
* from the local home data directory of the current user/cmpn_name, e.g.,
%LOCALAPP_DATA%/cmpn_name on Windows, or $HOME/.cmpn_name on Linux and OS X,
* the current folder sub-folder ./cmpn_name.
\note If _on == true and for some reason the journal output folder and files
cannot be made, the journaling is disabled and this function returns false.
*/
bool set_on(const bool _on, //!< turn on or off
const char* const _cmpn_name, //!< component name in desired capitalization
const char* const _base_path = nullptr //!< optional base path
);
/*!
Check if the journal is on or off, as efficient as possible as it is the only
function which is called when the journal is turned off!
*/
inline bool on() { return strm_ptr != nullptr; }
//! Access the stream directly, this is not recommended in general
inline Stream& stream() { return *strm_ptr; }
//! Get the journal output path, or an empty string if the journal is off
inline String output_path() { return on() ? stream().output_path() : String(); }
// Basic types define()
inline Data define(Stream& /*_strm*/, const bool _b)
{
return Data(_b ? "true" : "false");
}
inline Data define(Stream& /*_strm*/, const short _i)
{
return Data(std::to_string(_i));
}
inline Data define(Stream& /*_strm*/, const unsigned short _u)
{
return Data(std::to_string(_u));
}
inline Data define(Stream& /*_strm*/, const int _i)
{
return Data(std::to_string(_i));
}
inline Data define(Stream& /*_strm*/, const unsigned int _u)
{
return Data(std::to_string(_u));
}
Data define(Stream& _strm, const float _f);
Data define(Stream& _strm, const double _d);
inline Data define(Stream& /*_strm*/, const char* const _str)
{
return Data(String("\"") + _str + String("\""));
}
inline Data define(Stream& _strm, const String& _str)
{
return define(_strm, _str.c_str());
}
struct Name
{
Name(const char* const _name) : name(_name) {}
const char* const name;
};
// Wrapper for an C-style array of known length
template <typename T> class CArrayT : public Name
{
public:
const size_t size;
const T* const pntr;
CArrayT(const char* const _name, const size_t _size, const T* const _pntr)
: Name(_name), size(_size), pntr(_pntr)
{
}
};
template <typename T>
CArrayT<T> c_array(const char* const _name,
const size_t _size, const T* const _pntr)
{
return CArrayT<T>(_name, _size, _pntr);
}
template <typename T>
inline Data define(Stream& _strm, const CArrayT<T>& _c_arr)
{
if (_c_arr.pntr == nullptr) // nullptr if the argument is optional
return NULL_PTR;
std::vector<Data> dfns;
dfns.reserve(_c_arr.size);
for (size_t i = 0; i < _c_arr.size; ++i)
dfns.push_back(define(_strm, _c_arr.pntr[i]));
// Make a unique name for this array variable and skip the leading _
const auto name = _strm.make_unique_name(_c_arr.name + 1);
auto& os = _strm.os();
os << "const " << type_name<T>() << " " << name <<
"[" << _c_arr.size << "] = {";
for (size_t i = 0; i < _c_arr.size; ++i)
{
if (i > 0)
os << ", ";
os << dfns[i].str;
}
os << "}";
_strm.end_line();
return Data(name);
}
template <typename T>
struct OutputT : public Name
{
typedef OutputT<T> Self;
OutputT(const char* const _name, T& _vrbl)
: Name(on() ? stream().book_unique_name(_name) : _name), vrbl(_vrbl)
{}
T& vrbl;
T& operator=(const T& _val)
{
vrbl = _val;
if (on()) // register the value to the variable name
stream().link_unique_name((Key)_val, name);
return vrbl;
}
Self& operator=(const Self&) = delete;
};
template <typename T>
OutputT<T> output(const char* const _name, T& _vrbl)
{
return OutputT<T>(_name, _vrbl);
}
template <class T>
inline Data define(Stream& _strm, const OutputT<T>& _otpt)
{
_strm.os() << type_name<T>() << " " << _otpt.name /*<< " = " << _otpt.vrbl*/;
_strm.end_line();
return Data(_otpt.name);
}
//! Contains a plain filename w/o a path to be loaded during replay
struct Filename
{
Filename(const String& _str) : str(_str) {}
String str;
};
Data define(Stream& _strm, const Filename& _flnm);
template <const size_t _bffr_size>
inline Data define(Stream& _strm, const Base::FormatT<_bffr_size>& _frmt)
{
return define(_strm, _frmt.buffer());
}
}// namespace Journal
#if defined(_MSC_VER)
#define __JOURNAL_FUNCTION__ __FUNCTION__ // works in VC well
#else
#define __JOURNAL_FUNCTION__ __PRETTY_FUNCTION__ // needed for gcc & xcode
#endif// _MSC_VER
#define JOURNAL_CHECK if (::Journal::on())
#define JOURNAL_CALL(CALL, ...) \
::Journal::stream().CALL(__JOURNAL_FUNCTION__, ##__VA_ARGS__)
#define JOURNAL_INCLUDE_THIS ::Journal::stream().include(__FILE__)
#define JOURNAL_INCLUDE(FILE) \
{ JOURNAL_CHECK ::Journal::stream().include(FILE); }
#define JOURNAL_COMMENT(CMNT) \
{ JOURNAL_CHECK { ::Journal::stream().comment_os() << CMNT << Base::ENDL; } }
#define JOURNAL_AS_COMMENT(EXPR) \
{ JOURNAL_CHECK{ ::Journal::stream().comment_os(); EXPR; } }
#define JOURNAL(CALL, ...) { JOURNAL_CHECK JOURNAL_CALL(CALL, ##__VA_ARGS__); }
#define JOURNAL_INC(CALL, ...) { JOURNAL_CHECK \
{ JOURNAL_INCLUDE_THIS; JOURNAL_CALL(CALL, ##__VA_ARGS__); } }
#define JOURNAL_CONSTRUCTOR(...) { JOURNAL_CHECK \
{ JOURNAL_INCLUDE_THIS; JOURNAL_CALL(constructor, this, ##__VA_ARGS__); } }
#define JOURNAL_DESTRUCTOR JOURNAL(destructor, this)
#define JOURNAL_METHOD(VRBL, ...) JOURNAL(method, VRBL, this, ##__VA_ARGS__)
#define JOURNAL_METHOD_OUTCOME(...) JOURNAL(method_outcome, this, ##__VA_ARGS__)
#define JOURNAL_METHOD_VOID(...) JOURNAL(method_void, this, ##__VA_ARGS__)
#define JOURNAL_FUNCTION(VRBL, ...) JOURNAL_INC(function, VRBL, ##__VA_ARGS__)
#define JOURNAL_FUNCTION_VOID(...) JOURNAL_INC(function_void, ##__VA_ARGS__)
#define JOURNAL_FUNCTION_OUTCOME(...) \
JOURNAL_INC(function_outcome, this, ##__VA_ARGS__)
#define JOURNAL_C_ARRAY(SIZE, PNTR) ::Journal::c_array(#PNTR, SIZE, PNTR)
#define JOURNAL_OUTPUT(VRBL) jrnl##VRBL
#define JOURNAL_OUTPUT_WRAP(VRBL) \
auto JOURNAL_OUTPUT(VRBL) = ::Journal::output(#VRBL, VRBL)
#define JOURNAL_PATH_TRANSFORM(TRNS) \
::Journal::stream().set_path_transform(TRNS)
#define JOURNAL_MAIN(MAIN) ::Journal::stream().main(MAIN)
#define JOURNAL_CODE_LINE(CODE) ::Journal::stream().code_line(CODE)
#define JOURNAL_BLANK_LINE ::Journal::stream().blank_line()
#define JOURNAL_ERROR(MSG) JOURNAL_COMMENT("Journal ERROR: " << MSG)
#define JOURNAL_ERROR_if(CND, MSG) if (CND) JOURNAL_ERROR(MSG)
#define JOURNAL_ERROR_if_do(CND, MSG, ACTN) \
if (CND) { JOURNAL_ERROR(MSG); ACTN; }
#else
#define JOURNAL_INCLUDE_THIS
#define JOURNAL_INCLUDE(FILE)
#define JOURNAL_COMMENT(CMNT)
#define JOURNAL(CMNT, ...)
#define JOURNAL_CONSTRUCTOR(...)
#define JOURNAL_DESTRUCTOR
#define JOURNAL_METHOD(VRBL, ...)
#define JOURNAL_METHOD_OUTCOME(...)
#define JOURNAL_METHOD_VOID(...)
#define JOURNAL_FUNCTION(VRBL, ...)
#define JOURNAL_FUNCTION_VOID(...)
#define JOURNAL_FUNCTION_OUTCOME(...)