Consider the following C code:
void openFile(const char *mode, char *filename, FILE **fileptr)
{
...
*fileptr = fopen(filename, mode);
...
}
FILE *logstream;
if (LOG_FILE_ENABLED)
{
openFile("w", "mylogfile.txt", logstream);
}
else
{
logstream = stderr;
}
fprintf(logstream, "[DEBUG] Some debug message...\n");
fclose(logstream);
I am attempting to translate this to idiomatic C++. How can I overload openFile() such that it takes a std::ofstream, but keep logstream stream-agnostic? I was assuming it would be something like this:
void openFile(const char *mode, char *filename, std::ofstream &ofs)
{
...
ofs.open(filename);
...
}
std::ostream logstream;
if (LOG_FILE_ENABLED)
{
logstream = std::ofstream();
openFile("w", "mylogfile.txt", logstream);
}
else
{
logstream = std::cerr;
}
logstream << "[DEBUG] Some debug message..." << std::endl;
logstream.close();
However this is apparently wildly incorrect - you can't even initialize a plain std::ostream like that. How should I handle this - preferably while avoiding the use of raw pointers?
I would move the actual work to a separate function or lambda that takes a std::ostream as input. The caller can then decide which type of std::ostream to pass in, eg:
void doRealWork(std::ostream &log)
{
...
log << "[DEBUG] Some debug message..." << std::endl;
...
}
if (LOG_FILE_ENABLED)
{
std::ofstream log("mylogfile.txt");
doRealWork(log);
}
else
{
doRealWork(std::cerr);
}
Or:
auto theRealWork = [&](std::ostream &&log)
{
...
log << "[DEBUG] Some debug message..." << std::endl;
...
}
if (LOG_FILE_ENABLED) {
theRealWork(std::ofstream{"mylogfile.txt"});
} else {
theRealWork(static_cast<std::ostream&&>(std::cerr));
}
UPDATE: Otherwise, you can do something more like this instead:
using unique_ostream_ptr = std::unique_ptr<std::ostream, void(*)(std::ostream*)>;
unique_ostream_ptr logstream;
if (LOG_FILE_ENABLED) {
logstream = unique_ostream_ptr(new std::ofstream("mylogfile.txt"), [](std::ostream *strm){ delete strm; });
} else {
logstream = unique_ostream_ptr(&std::cerr, [](std::ostream *){});
}
*logstream << "[DEBUG] Some debug message...\n";
Or:
using shared_ostream_ptr = std::shared_ptr<std::ostream>;
shared_ostream_ptr logstream;
if (LOG_FILE_ENABLED) {
logstream = std::make_shared<std::ofstream>("mylogfile.txt");
} else {
logstream = shared_ostream_ptr(&std::cerr, [](std::ostream*){});
}
*logstream << "[DEBUG] Some debug message...\n";
C++ stream library has pretty ancient design.
Nevertheless - its basic idea is that ostream or istream are just wrapper objects over stream-buffers.
So you might try something like in this code:
std::ostream get_log(bool str) {
if (str) return std::ostream(new std::stringbuf());
// else
std::filebuf* f = new std::filebuf();
f->open("log", std::ios_base::out);
return std::ostream(f);
}
But, as I mentioned, this is very ancient design - so no RAII - this buffer is not owned by stream - you would need to delete it by yourself:
int main() {
std::ostream log = get_log(true);
log << "aaa";
std::cout << static_cast<std::stringbuf&>(*log.rdbuf()).str();
delete log.rdbuf(); // (!)
}
So this is not very usable.
So my final advice - use smart pointer over ostream - like this:
std::unique_ptr<std::ostream> get_log(bool str) {
if (str) return new std::ostringstream();
std::ofstream* f = new std::ofstream();
f->open("log", std::ios_base::out);
return f;
}
int main() {
auto log = get_log(true);
*log << "aaa";
}
You were nearly there; you just need to be mindful of scoping rules, and of the fact that there is no such thing as an std::ostream other than as an abstract base class.
So:
std::ostream* logstreamPtr = nullptr;
std::ofstream ofs;
if (LOG_FILE_ENABLED)
{
logstreamPtr = &ofs;
openFile("w", "mylogfile.txt", ofs);
}
else
{
logstreamPtr = &std::cerr;
}
std::ostream& logstream = *logstreamPtr;
logstream << "[DEBUG] Some debug message..." << std::endl;
logstream.close();
You don't need the reference logstream, but it saves you from having to repeatedly reference logstreamPtr later, which would be boring.
Don't be afraid of this raw pointer. This is the purest application of pointers there is. You can go down the smart pointer route if you like, but you gain nothing and lose readability (and, in some cases, performance).
By the way, if you're worried about performance, don't open and close the log file for every single message; that's extremely wasteful.
Related
I want to create an opentelemetry tracing configuration that writes the exporter logs to a file. For that, I used the OStreamSpanExporter class that takes a ref to a std::ostream object (by default, the ctor argument is std::cout). So here is what I did:
#include <fstream>
namespace trace_sdk = opentelemetry::sdk::trace;
std::ofstream file_handle(log_trace_output_file_.c_str());
auto exporter = std::unique_ptr<trace_sdk::SpanExporter>(new opentelemetry::exporter::trace::OStreamSpanExporter(file_handle));
auto processor = std::unique_ptr<trace_sdk::SpanProcessor>(
new trace_sdk::SimpleSpanProcessor(std::move(exporter)));
auto provider = nostd::shared_ptr<opentelemetry::trace::TracerProvider>(
new trace_sdk::TracerProvider(std::move(processor)));
// Set the global trace provider
opentelemetry::trace::Provider::SetTracerProvider(provider);
This compiles nicely. Before you ask, we checked that log_trace_output_file_.c_str() is not empty. However I encounter segmentation fault as soon as I start creating spans... Do you know what I might have been doing wrong here ? Thank you.
Ok, I realised that because of the std::move when declaring the processor, we were giving away the ownership thus we were trying to access a stream that was nullptr...
Here is what I ended up doing:
main.cpp
auto trace_provider = new TraceProvider(vm["trace-provider"].as<std::string>(), vm["trace-output-log-file"].as<std::string>());
trace_provider->InitTracer();
TraceProvider.hpp
class TraceProvider {
public:
TraceProvider(std::string exporter_backend_str, std::string log_trace_output_file = std::string());
~TraceProvider();
void InitTracer();
private:
std::string exporter_backend_str_;
std::string log_trace_output_file_;
std::shared_ptr<std::ofstream> log_trace_output_file_handle_ = nullptr;
void initSimpleTracer();
};
TraceProvider.cpp
TraceProvider::TraceProvider(std::string exporter_backend_str, std::string log_trace_output_file) {
exporter_backend_str_ = exporter_backend_str;
exporter_backend_ = string_to_trace_exporter(exporter_backend_str);
log_trace_output_file_ = log_trace_output_file;
if (exporter_backend_ == Exporter::SIMPLE) {
try {
if (log_trace_output_file_.compare("") != 0) {
log_trace_output_file_handle_ = std::make_shared<std::ofstream>(std::ofstream(log_trace_output_file.c_str()));
} else {
throw std::runtime_error("You chose the Simple trace exporter but you specified an empty log file.");
}
} catch(std::exception const& e) {
std::cout << "Exception: " << e.what() << "\n";
}
}
}
TraceProvider::~TraceProvider() {
// If it exists, close the file stream and delete the ptr
if (log_trace_output_file_handle_ != nullptr) {
std::cout << "Closing tracing log file at: " << log_trace_output_file_ << std::endl;
log_trace_output_file_handle_.get()->close();
log_trace_output_file_handle_.reset();
log_trace_output_file_handle_ = nullptr;
}
}
void TraceProvider::InitTracer() {
switch (exporter_backend_) {
case Exporter::SIMPLE:
initSimpleTracer();
break;
case Exporter::JAEGER:
initJaegerTracer();
break;
default:
std::stringstream err_msg_stream;
err_msg_stream << "Invalid tracing backend: " << exporter_backend_str_
<< "\n";
throw po::validation_error(po::validation_error::invalid_option_value,
err_msg_stream.str());
}
}
void TraceProvider::initSimpleTracer() {
std::unique_ptr<trace_sdk::SpanExporter> exporter;
if (log_trace_output_file_.compare("") != 0)
exporter = std::unique_ptr<trace_sdk::SpanExporter>(new opentelemetry::exporter::trace::OStreamSpanExporter(*log_trace_output_file_handle_.get()));
} else {
exporter = std::unique_ptr<trace_sdk::SpanExporter>(new opentelemetry::exporter::trace::OStreamSpanExporter);
}
auto processor = std::unique_ptr<trace_sdk::SpanProcessor>(
new trace_sdk::SimpleSpanProcessor(std::move(exporter)));
auto provider = nostd::shared_ptr<opentelemetry::trace::TracerProvider>(
new trace_sdk::TracerProvider(std::move(processor), resources));
// Set the global trace provider
opentelemetry::trace::Provider::SetTracerProvider(provider);
// Set global propagator
context::propagation::GlobalTextMapPropagator::SetGlobalPropagator(
nostd::shared_ptr<context::propagation::TextMapPropagator>(
new opentelemetry::trace::propagation::HttpTraceContext()));
std::cout << "Simple (log stream) exporter successfully initialized!"
<< std::endl;
}
I am trying to use an ostream object to write to either to a filestream of stringstream based user input (similar to fmemopen in Linux).
I realized that ostream doesnt take stringstream or fstream objects but instead takes stringbug or filebuf.
I tried the following code:
char content[] = "This is a test";
if (isFile)
{
filebuf fp;
fp.open(filename, ios::out);
ostream os(&fp);
os << content;
fp.close();
}
else
{
stringbuf str;
ostream os(&str);
os << content;
}
This works fine, in the if else condition but I would like to use the ostream os, as os << content, outside the if else condition. The issue is however I am unable to globally define ostream os since there is no such constructor for ostream.
Is there a way to get around this?
This can be handled a couple of different ways.
Using a helper function:
void write(ostream &os, const char *content)
{
os << content
}
...
char content[] = "This is a test";
if (isFile)
{
ofstream ofs(filename);
write(ofs, content);
}
else
{
ostringstream oss;
write(oss, content);
string s = oss.str();
// use s as needed...
}
Alternatively, using a lambda:
char content[] = "This is a test";
auto write = [](ostream &os, const char *content){ os << content; }
if (isFile)
{
ofstream ofs(filename);
write(ofs, content);
}
else
{
ostringstream oss;
write(oss, content);
string s = oss.str();
// use s as needed...
}
Using a pointer instead:
char content[] = "This is a test";
std::unique_ptr<ostream> os;
if (isFile)
os = std::make_unique<ofstream>(filename);
else
os = std::make_unique<ostringstream>();
*os << content;
if (!isFile)
{
string s = static_cast<ostringstream*>(os.get())->str(); // or: static_cast<ostringstream&>(*os).str()
// use s as needed...
}
Without using a full-blown logging library (or IF statements) - is there a way in C++ to sometimes print out messages to the console and sometimes not?
I am using std::cerr, is there a way to control when this outputs or not?
Ideally I could have:
std::cerr << "Constructor called" << endl;
and have a way to enable/disable this line of code?
I am not sure what you mean by "without if", but you can write code without using the if yourself. A macro can check a flag for you.
#define CERR if (cerr_disabled) {} else std::cerr
bool cerr_disabled = false;
Then, in your code:
CERR << "error message" << std::endl;
If cerr_disabled is true, then nothing is printed.
The advantage of this macro approach is that none of the print arguments get evaluated if the err logging is disabled. For instance, if you needed to call a function to create a more complicated log string:
std::string fancy_log_message () {
//...
}
CERR << fancy_log_message();
If cerr_disabled is true, fancy_log_message() is not called. This is something that can't be achieved by just suppressing the stream object itself.
The simple approach is to set/clear std::ios_base::failbit on the stream: while std::ios_base::failbit is set, the streams won't do any work [unless the output operators are written incorrectly]:
std::cerr.setstate(std::ios_base::failbit);
std::cerr << "this won't show\n";
std::cerr.clear();
std::cerr << "this will show!\n";
To make these operations easier to use you can create manipulators, e.g.:
std::ostream& stream_on(std::ostream& out) {
out.clear();
return out;
}
std::ostream& stream_off(std::ostream& out) {
out.setstate(std::ios_base::failbit);
return out;
}
std::cerr << stream_off << "not printed\n" << stream_on << "printed\n";
If you really want to disable the stream even if the output operators are badly implemented, you can save the current rdbuf() (e.g., in a suitable std::ostream::pword()) and set the stream buffer to nullptr:
static int stream_off_index() { static int rc = std::ios_base::xalloc(); return rc; }
std::ostream& stream_on(std::ostream& out) {
out.pword(stream_off_index) = out.rdbuf(nullptr);
return out;
}
std::ostream& stream_off(std::ostream& out) {
if (!out.rdbuf()) {
out.rdbuf(out.pword(stream_off_index);
}
return out;
}
Here is a solution without macros:
#include <iostream>
void toggle_cerr()
{
static std::streambuf* p = std::cerr.rdbuf();
std::cerr.rdbuf(std::cerr.rdbuf() ? nullptr : p);
}
int main()
{
toggle_cerr();
std::cerr << "str";
}
I've created an ofstream and there is a point in which I need to check if it's empty or has had things streamed into it.
Any ideas how I would go about doing this?
The std::ofstream files don't support this directly. What you can do if this is an important requirement is to create a filtering stream buffer which internally used std::filebuf but also records if there was any output being done. This could look look as simple as this:
struct statusbuf:
std::streambuf {
statusbuf(std::streambuf* buf): buf_(buf), had_output_(false) {}
bool had_output() const { return this->had_output_; }
private:
int overflow(int c) {
if (!traits_type::eq_int_type(c, traits_type::eof())) {
this->had_output_ = true;
}
return this->buf_->overflow(c);
}
std::streambuf* buf_;
bool had_output_;
};
You can initialize an std::ostream with this and query the stream buffer as needed:
std::ofstream out("some file");
statusbuf buf(out.rdbuf());
std::ostream sout(&buf);
std::cout << "had_output: " << buf.had_output() << "\n";
sout << "Hello, world!\n";
std::cout << "had_ouptut: " << buf.had_output() << "\n";
you could use ofstream.rdbuff to get the file buffer and than use streambuf::sgetn to read it. I believe that should work.
I saw a useful start here:
http://www.cs.technion.ac.il/~imaman/programs/teestream.html
And it works great to make a new stream which goes to both clog and a log file.
However, if I try to redefine clog to be the new stream it does not work because the new stream has the same rdbuf() as clog so the following has no effect:
clog.rdbuf(myTee.rdbuf());
So how can I modify the tee class to have its own rdbuf() which can then be the target of clog?
Thanks.
-William
If you really want to keep using std::clog for the tee instead of sending output to a different stream, you need to work one level lower: Instead of deriving from ostream, derive from streambuf. Then you can do this:
fstream logFile(...);
TeeBuf tbuf(logFile.rdbuf(), clog.rdbuf());
clog.rdbuf(&tbuf);
For more information on how to derive your own streambuf class, see here.
You don't want to do what your've trying to do because the 'tee' is not working at the rdbuf level. So setting the rdbuf to something else will not work, the output will only go to one stream.
You need to follow there example:
e.g.
fstream clog_file(...);
xstream clog_x(...);
TeeStream clog(clog_file, clog_x);
then use clog everywhere instead of your original clog.
Here is the class I created that seems to do the job, thanks to all who helped out!
-William
class TeeStream : public std::basic_filebuf<char, std::char_traits<char> >
{
private:
class FileStream : public std::ofstream {
public:
FileStream()
: logFileName("/my/log/file/location.log") {
open(logFileName.c_str(), ios::out | ios::trunc);
if (fail()) {
cerr << "Error: failed to open log file: " << logFileName << endl;
exit(1);
}
}
~FileStream() {
close();
}
const char *getLogFileName() const {
return logFileName.c_str();
}
private:
const string logFileName;
};
public:
typedef std::char_traits<char> traits;
typedef std::basic_filebuf<char, traits> baseClass;
TeeStream()
: baseClass(),
_logOutputStream(),
_clogBuf(clog.rdbuf()),
_fileBuf(_logOutputStream.rdbuf()) {
clog.rdbuf(this);
_logOutputStream << "Log file starts here:" << endl;
}
~TeeStream() {
clog.rdbuf(_clogBuf);
}
int_type overflow(char_type additionalChar =traits::eof()) {
const int_type eof = traits::eof();
const char_type additionalCharacter = traits::to_char_type(additionalChar);
const int_type result1 = _clogBuf->sputc(additionalCharacter);
const int_type result2 = _fileBuf->sputc(additionalCharacter);
if (traits::eq_int_type(eof, result1)) {
return eof;
} else {
return result2;
}
}
int sync() {
const int result1 = _clogBuf->pubsync();
const int result2 = _fileBuf->pubsync();
if (result1 == -1) {
return -1;
} else {
return result2;
}
}
private:
FileStream _logOutputStream;
streambuf * const _clogBuf;
streambuf * const _fileBuf;
};
I would just use the Boost iostreams stuff to do it.
#include <iostream>
#include <fstream>
#include <boost/iostreams/tee.hpp>
#include <boost/iostreams/stream.hpp>
int main(const int a_argc, const char *a_args[])
{
namespace io = boost::iostreams;
typedef io::tee_device<std::ofstream, std::ostream> TeeDevice;
typedef io::stream<TeeDevice> TeeStream;
std::ofstream flog("logFile.txt");
//We need to copy clog, otherwise we get infinite recursion
//later on when we reassign clog's rdbuf.
std::ostream clogCopy(std::clog.rdbuf());
TeeDevice logTee(flog, clogCopy);
TeeStream logTeeStream(logTee);
logTeeStream << "This text gets clogged and flogged." << std::endl;
//Modify clog to automatically go through the tee.
std::streambuf *originalRdBuf = std::clog.rdbuf(logTeeStream.rdbuf());
std::clog << "This text doesn't only get clogged, it's flogged too." << std::endl;
std::clog.rdbuf(originalRdBuf);
std::clog << "This text avoids flogging." << std::endl;
}