8286180: Enable construction of LogStreamImpl from LogMessageImpl

Reviewed-by: dholmes, stefank
This commit is contained in:
Johan Sjölén 2022-05-30 16:53:35 +00:00 committed by Stefan Karlsson
parent 0c420e03ae
commit b2ba9fc9f6
6 changed files with 234 additions and 113 deletions

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2016, 2019, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2016, 2022, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -32,35 +32,35 @@
// polluting the surrounding API with template functions.
class LogHandle {
private:
LogTagSet* const _tagset;
LogTagSet& _tagset;
public:
template <LogTagType T0, LogTagType T1, LogTagType T2, LogTagType T3, LogTagType T4, LogTagType GuardTag>
LogHandle(const LogImpl<T0, T1, T2, T3, T4, GuardTag>& type_carrier) :
_tagset(&LogTagSetMapping<T0, T1, T2, T3, T4>::tagset()) {}
LogHandle(const LogImpl<T0, T1, T2, T3, T4, GuardTag>& type_carrier)
: _tagset(LogTagSetMapping<T0, T1, T2, T3, T4>::tagset()) {}
bool is_level(LogLevelType level) {
return _tagset->is_level(level);
return _tagset.is_level(level);
}
LogTagSet* const tagset() const {
LogTagSet& tagset() const {
return _tagset;
}
#define LOG_LEVEL(level, name) ATTRIBUTE_PRINTF(2, 0) \
LogHandle& v##name(const char* fmt, va_list args) { \
_tagset->vwrite(LogLevel::level, fmt, args); \
_tagset.vwrite(LogLevel::level, fmt, args); \
return *this; \
} \
LogHandle& name(const char* fmt, ...) ATTRIBUTE_PRINTF(2, 3) { \
va_list args; \
va_start(args, fmt); \
_tagset->vwrite(LogLevel::level, fmt, args); \
_tagset.vwrite(LogLevel::level, fmt, args); \
va_end(args); \
return *this; \
} \
bool is_##name() { \
return _tagset->is_level(LogLevel::level); \
return _tagset.is_level(LogLevel::level); \
}
LOG_LEVEL_LIST
#undef LOG_LEVEL
@ -73,15 +73,17 @@ public:
class LogTargetHandle {
private:
const LogLevelType _level;
LogTagSet* const _tagset;
LogTagSet& _tagset;
public:
LogTargetHandle(LogLevelType level, LogTagSet* const tagset) : _level(level), _tagset(tagset) {}
LogTargetHandle(LogLevelType level, LogTagSet& tagset)
: _level(level),
_tagset(tagset) {}
template <LogLevelType level, LogTagType T0, LogTagType T1, LogTagType T2, LogTagType T3, LogTagType T4, LogTagType GuardTag>
LogTargetHandle(const LogTargetImpl<level, T0, T1, T2, T3, T4, GuardTag>& type_carrier) :
_level(level),
_tagset(&LogTagSetMapping<T0, T1, T2, T3, T4>::tagset()) {}
LogTargetHandle(const LogTargetImpl<level, T0, T1, T2, T3, T4, GuardTag>& type_carrier)
: _level(level),
_tagset(LogTagSetMapping<T0, T1, T2, T3, T4>::tagset()) {}
template <LogLevelType level, LogTagType T0, LogTagType T1, LogTagType T2, LogTagType T3, LogTagType T4, LogTagType GuardTag>
static LogTargetHandle create() {
@ -92,15 +94,14 @@ public:
va_list args;
va_start(args, fmt);
if (is_enabled()) {
_tagset->vwrite(_level, fmt, args);
_tagset.vwrite(_level, fmt, args);
}
va_end(args);
}
bool is_enabled() const {
return _tagset->is_level(_level);
return _tagset.is_level(_level);
}
};
#endif // SHARE_LOGGING_LOGHANDLE_HPP

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2016, 2019, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2016, 2022, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -57,19 +57,19 @@
// Log outputs on debug level will see the debug message,
// but not the trace message.
//
#define LogMessage(...) LogMessageImpl<LOG_TAGS(__VA_ARGS__)>
template <LogTagType T0, LogTagType T1 = LogTag::__NO_TAG, LogTagType T2 = LogTag::__NO_TAG,
LogTagType T3 = LogTag::__NO_TAG, LogTagType T4 = LogTag::__NO_TAG, LogTagType GuardTag = LogTag::__NO_TAG>
class LogMessageImpl : public LogMessageBuffer {
private:
bool _has_content;
LogTagSet& _tagset;
public:
LogMessageImpl() :
_has_content(false),
_tagset(LogTagSetMapping<T0, T1, T2, T3, T4, GuardTag>::tagset())
{}
#define LogMessage(...) LogMessageTemplate<LOG_TAGS(__VA_ARGS__)>
class LogMessageImpl : public LogMessageBuffer {
private:
LogTagSet& _tagset;
bool _has_content;
protected:
LogMessageImpl(LogTagSet& tagset)
: _tagset(tagset),
_has_content(false) {};
public:
~LogMessageImpl() {
if (_has_content) {
flush();
@ -90,11 +90,15 @@ class LogMessageImpl : public LogMessageBuffer {
void vwrite(LogLevelType level, const char* fmt, va_list args) {
if (!_has_content) {
_has_content = true;
set_prefix(LogPrefix<T0, T1, T2, T3, T4>::prefix);
set_prefix(_tagset.write_prefix());
}
LogMessageBuffer::vwrite(level, fmt, args);
}
bool is_level(LogLevelType level) const {
return _tagset.is_level(level);
}
#define LOG_LEVEL(level, name) \
bool is_##name() const { \
return _tagset.is_level(LogLevel::level); \
@ -103,4 +107,13 @@ class LogMessageImpl : public LogMessageBuffer {
#undef LOG_LEVEL
};
template <LogTagType T0, LogTagType T1 = LogTag::__NO_TAG, LogTagType T2 = LogTag::__NO_TAG, LogTagType T3 = LogTag::__NO_TAG,
LogTagType T4 = LogTag::__NO_TAG, LogTagType GuardTag = LogTag::__NO_TAG>
class LogMessageTemplate : public LogMessageImpl {
public:
LogMessageTemplate(LogMessageTemplate&&) = default;
LogMessageTemplate()
: LogMessageImpl(LogTagSetMapping<T0, T1, T2, T3, T4>::tagset()) {}
};
#endif // SHARE_LOGGING_LOGMESSAGE_HPP

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2016, 2021, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2016, 2022, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -23,18 +23,16 @@
*/
#include "precompiled.hpp"
#include "logging/log.hpp"
#include "logging/logStream.hpp"
#include "runtime/os.hpp"
#include "utilities/align.hpp"
LogStream::LineBuffer::LineBuffer()
: _buf(_smallbuf), _cap(sizeof(_smallbuf)), _pos(0)
{
LogStreamImplBase::LineBuffer::LineBuffer()
: _buf(_smallbuf), _cap(sizeof(_smallbuf)), _pos(0) {
_buf[0] = '\0';
}
LogStream::LineBuffer::~LineBuffer() {
LogStreamImplBase::LineBuffer::~LineBuffer() {
assert(_pos == 0, "still outstanding bytes in the line buffer");
if (_buf != _smallbuf) {
os::free(_buf);
@ -43,51 +41,56 @@ LogStream::LineBuffer::~LineBuffer() {
// try_ensure_cap tries to enlarge the capacity of the internal buffer
// to the given atleast value. May fail if either OOM happens or atleast
// is larger than a reasonable max of 1 M. Caller must not assume
// capacity without checking.
void LogStream::LineBuffer::try_ensure_cap(size_t atleast) {
// is larger than a reasonable max of 1 M.
// Returns whether the capacity is at least atleast bytes.
bool LogStreamImplBase::LineBuffer::try_ensure_cap(size_t atleast) {
// Cap out at a reasonable max to prevent runaway leaks.
const size_t reasonable_max = 1 * M;
assert(_cap >= sizeof(_smallbuf), "sanity");
if (_cap < atleast) {
// Cap out at a reasonable max to prevent runaway leaks.
const size_t reasonable_max = 1 * M;
assert(_cap <= reasonable_max, "sanity");
if (_cap == reasonable_max) {
return;
}
assert(_cap <= reasonable_max, "sanity");
const size_t additional_expansion = 256;
size_t newcap = align_up(atleast + additional_expansion, additional_expansion);
if (newcap > reasonable_max) {
log_info(logging)("Suspiciously long log line: \"%.100s%s",
_buf, (_pos >= 100 ? "..." : ""));
newcap = reasonable_max;
}
char* const newbuf = (char*) os::malloc(newcap, mtLogging);
if (newbuf == NULL) { // OOM. Leave object unchanged.
return;
}
if (_pos > 0) { // preserve old content
memcpy(newbuf, _buf, _pos + 1); // ..including trailing zero
}
if (_buf != _smallbuf) {
os::free(_buf);
}
_buf = newbuf;
_cap = newcap;
if (_cap >= atleast) {
return true;
}
assert(_cap >= atleast, "sanity");
if (_cap == reasonable_max) {
return false;
}
const size_t additional_expansion = 256;
size_t newcap = align_up(atleast + additional_expansion, additional_expansion);
if (newcap > reasonable_max) {
log_info(logging)("Suspiciously long log line: \"%.100s%s",
_buf, (_pos >= 100 ? "..." : ""));
newcap = reasonable_max;
}
char* const newbuf = (char*)os::malloc(newcap, mtLogging);
if (newbuf == NULL) { // OOM. Leave object unchanged.
return false;
}
if (_pos > 0) { // preserve old content
memcpy(newbuf, _buf, _pos + 1); // ..including trailing zero
}
if (_buf != _smallbuf) {
os::free(_buf);
}
_buf = newbuf;
_cap = newcap;
return _cap >= atleast;
}
void LogStream::LineBuffer::append(const char* s, size_t len) {
void LogStreamImplBase::LineBuffer::append(const char* s, size_t len) {
assert(_buf[_pos] == '\0', "sanity");
assert(_pos < _cap, "sanity");
const size_t minimum_capacity_needed = _pos + len + 1;
try_ensure_cap(minimum_capacity_needed);
const bool has_capacity = try_ensure_cap(minimum_capacity_needed);
// try_ensure_cap may not have enlarged the capacity to the full requested
// extend or may have not worked at all. In that case, just gracefully work
// extent or may have not worked at all. In that case, just gracefully work
// with what we have already; just truncate if necessary.
if (_cap < minimum_capacity_needed) {
if (!has_capacity) {
len = _cap - _pos - 1;
if (len == 0) {
return;
@ -98,15 +101,24 @@ void LogStream::LineBuffer::append(const char* s, size_t len) {
_buf[_pos] = '\0';
}
void LogStream::LineBuffer::reset() {
void LogStreamImplBase::LineBuffer::reset() {
_pos = 0;
_buf[_pos] = '\0';
}
void LogStream::write(const char* s, size_t len) {
template <typename BackingLog>
LogStreamImpl<BackingLog>::~LogStreamImpl() {
if (!_current_line.is_empty()) {
_backing_log.print("%s", _current_line.buffer());
_current_line.reset();
}
}
template <typename BackingLog>
void LogStreamImpl<BackingLog>::write(const char* s, size_t len) {
if (len > 0 && s[len - 1] == '\n') {
_current_line.append(s, len - 1); // omit the newline.
_log_handle.print("%s", _current_line.buffer());
_backing_log.print("%s", _current_line.buffer());
_current_line.reset();
} else {
_current_line.append(s, len);
@ -114,12 +126,5 @@ void LogStream::write(const char* s, size_t len) {
update_position(s, len);
}
// Destructor writes any unfinished output left in the line buffer.
LogStream::~LogStream() {
if (_current_line.is_empty() == false) {
_log_handle.print("%s", _current_line.buffer());
_current_line.reset();
}
}
template class LogStreamImpl<LogTargetHandle>;
template class LogStreamImpl<LogMessageHandle>;

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2016, 2020, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2016, 2022, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -27,14 +27,17 @@
#include "logging/log.hpp"
#include "logging/logHandle.hpp"
#include "logging/logMessage.hpp"
#include "utilities/ostream.hpp"
class LogStream : public outputStream {
// see test/hotspot/gtest/logging/test_logStream.cpp
class LogStreamImplBase : public outputStream {
friend class LogStreamTest_TestLineBufferAllocation_vm_Test;
friend class LogStreamTest_TestLineBufferAllocationCap_vm_Test;
// No heap allocation of LogStream.
static void* operator new (size_t) = delete;
static void* operator new[] (size_t) = delete;
// Helper class, maintains the line buffer. For small line lengths,
// we avoid malloc and use a fixed sized member char array. If LogStream
// is allocated on the stack, this means small lines are assembled
@ -44,21 +47,49 @@ class LogStream : public outputStream {
char* _buf;
size_t _cap;
size_t _pos;
void try_ensure_cap(size_t cap);
bool try_ensure_cap(size_t cap);
public:
LineBuffer();
~LineBuffer();
bool is_empty() const { return _pos == 0; }
const char* buffer() const { return _buf; }
bool is_empty() const {
return _pos == 0;
}
const char* buffer() const {
return _buf;
}
void append(const char* s, size_t len);
void reset();
};
LineBuffer _current_line;
LogTargetHandle _log_handle;
// Prevent operator new for LogStream.
static void* operator new (size_t);
static void* operator new[] (size_t);
protected:
LineBuffer _current_line;
};
template <typename BackingLog>
class LogStreamImpl : public LogStreamImplBase {
private:
BackingLog _backing_log;
public:
explicit LogStreamImpl(BackingLog bl)
: _backing_log(bl) {};
~LogStreamImpl() override;
bool is_enabled() {
return _backing_log.is_enabled();
}
void write(const char* s, size_t len) override;
};
class LogStream : public LogStreamImpl<LogTargetHandle> {
// see test/hotspot/gtest/logging/test_logStream.cpp
friend class LogStreamTest_TestLineBufferAllocation_vm_Test;
friend class LogStreamTest_TestLineBufferAllocationCap_vm_Test;
NONCOPYABLE(LogStream);
public:
// Constructor to support creation from a LogTarget instance.
@ -66,8 +97,8 @@ public:
// LogTarget(Debug, gc) log;
// LogStream(log) stream;
template <LogLevelType level, LogTagType T0, LogTagType T1, LogTagType T2, LogTagType T3, LogTagType T4, LogTagType GuardTag>
LogStream(const LogTargetImpl<level, T0, T1, T2, T3, T4, GuardTag>& type_carrier) :
_log_handle(level, &LogTagSetMapping<T0, T1, T2, T3, T4>::tagset()) {}
LogStream(const LogTargetImpl<level, T0, T1, T2, T3, T4, GuardTag>& type_carrier)
: LogStreamImpl(LogTargetHandle(level, LogTagSetMapping<T0, T1, T2, T3, T4>::tagset())) {}
// Constructor to support creation from typed (likely NULL) pointer. Mostly used by the logging framework.
//
@ -75,29 +106,24 @@ public:
// or
// LogStream stream((LogTargetImpl<level, T0, T1, T2, T3, T4, GuardTag>*)NULL);
template <LogLevelType level, LogTagType T0, LogTagType T1, LogTagType T2, LogTagType T3, LogTagType T4, LogTagType GuardTag>
LogStream(const LogTargetImpl<level, T0, T1, T2, T3, T4, GuardTag>* type_carrier) :
_log_handle(level, &LogTagSetMapping<T0, T1, T2, T3, T4>::tagset()) {}
// Destructor writes any unfinished output left in the line buffer.
~LogStream();
LogStream(const LogTargetImpl<level, T0, T1, T2, T3, T4, GuardTag>* type_carrier)
: LogStreamImpl(LogTargetHandle(level, LogTagSetMapping<T0, T1, T2, T3, T4>::tagset())) {}
// Constructor to support creation from a LogTargetHandle.
//
// LogTarget(Debug, gc) log;
// LogTargetHandle(log) handle;
// LogStream stream(handle);
LogStream(LogTargetHandle handle) : _log_handle(handle) {}
LogStream(LogTargetHandle handle)
: LogStreamImpl(handle) {}
// Constructor to support creation from a log level and tagset.
//
// LogStream(level, tageset);
LogStream(LogLevelType level, LogTagSet* const tagset) : _log_handle(level, tagset) {}
LogStream(LogLevelType level, LogTagSet& tagset)
: LogStreamImpl(LogTargetHandle(level, tagset)) {}
bool is_enabled() const {
return _log_handle.is_enabled();
}
void write(const char* s, size_t len);
// Destructor writes any unfinished output left in the line buffer.
};
// Support creation of a LogStream without having to provide a LogTarget pointer.
@ -106,7 +132,36 @@ public:
template <LogLevelType level, LogTagType T0, LogTagType T1, LogTagType T2, LogTagType T3, LogTagType T4, LogTagType GuardTag>
class LogStreamTemplate : public LogStream {
public:
LogStreamTemplate() : LogStream((LogTargetImpl<level, T0, T1, T2, T3, T4, GuardTag>*)NULL) {}
LogStreamTemplate()
: LogStream((LogTargetImpl<level, T0, T1, T2, T3, T4, GuardTag>*)NULL) {}
};
class LogMessageHandle {
const LogLevelType _level;
LogMessageImpl& _lm;
public:
LogMessageHandle(const LogLevelType level, LogMessageImpl& lm)
: _level(level), _lm(lm) {}
bool is_enabled() {
return _lm.is_level(_level);
}
void print(const char* fmt, ...) ATTRIBUTE_PRINTF(2, 3) {
va_list args;
va_start(args, fmt);
if (is_enabled()) {
_lm.vwrite(_level, fmt, args);
}
va_end(args);
}
};
class NonInterleavingLogStream : public LogStreamImpl<LogMessageHandle> {
public:
NonInterleavingLogStream(LogLevelType level, LogMessageImpl& lm)
: LogStreamImpl(LogMessageHandle(level, lm)) {}
};
#endif // SHARE_LOGGING_LOGSTREAM_HPP

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2015, 2019, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2015, 2022, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -108,6 +108,10 @@ class LogTagSet {
_output_list.clear();
}
PrefixWriter write_prefix() {
return _write_prefix;
}
void set_output_level(LogOutput* output, LogLevelType level) {
_output_list.set_output_level(output, level);
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2016, 2021, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2016, 2022, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -26,7 +26,9 @@
#include "logTestFixture.hpp"
#include "logTestUtils.inline.hpp"
#include "logging/log.hpp"
#include "logging/logMessage.hpp"
#include "logging/logStream.hpp"
#include "memory/resourceArea.hpp"
#include "unittest.hpp"
class LogStreamTest : public LogTestFixture {
@ -90,6 +92,47 @@ TEST_VM_F(LogStreamTest, TestLineBufferAllocation) {
}
}
// LogStream allows interleaving of other messages.
// Compare this to NonInterLeavingLogStreamTest_NonInterleavingStream
TEST_VM_F(LogStreamTest, InterleavingStream) {
set_log_config(TestLogFileName, "gc=info");
const char* message_order[] = {"1", "I am one line", "2", "but", "3", "I am not", NULL};
{
LogStream foo(Log(gc)::info());
if (foo.is_enabled()) {
foo.print("I am");
log_info(gc)("1");
foo.print_cr(" one line");
log_info(gc)("2");
foo.print_cr("but");
log_info(gc)("3");
foo.print_cr("I am not");
}
}
EXPECT_TRUE(file_contains_substrings_in_order(TestLogFileName, message_order));
}
// NonInterleavingLogStream does not allow interleaving of other messages.
// Compare this to LogStreamTest_InterleavingStream
TEST_VM_F(LogStreamTest, NonInterleavingStream) {
set_log_config(TestLogFileName, "gc=info");
const char* message_order[] = {"1", "2" , "3", "I am one line", "but", "I am not", NULL};
{
LogMessage(gc) lm ;
NonInterleavingLogStream foo{LogLevelType::Info, lm};
if (foo.is_enabled()) {
foo.print("I am");
log_info(gc)("1");
foo.print_cr(" one line");
log_info(gc)("2");
foo.print_cr("but");
log_info(gc)("3");
foo.print_cr("I am not");
}
}
EXPECT_TRUE(file_contains_substrings_in_order(TestLogFileName, message_order));
}
// Test, in release build, that the internal line buffer of a LogStream
// object caps out at 1M.
TEST_VM_F(LogStreamTest, TestLineBufferAllocationCap) {